#define	nE3DEMO

#include	"world.h"

#include	"ai.h"
//#include "ai_move.h"
#include	"ai_frames.h"

#include	"skat.h"
#include	"nodelist.h"
#include	"triggers.h"
#include	"client.h"
#include	"bodylist.h"
#include	"chasecam.h"
#include	"actorlist.h"
#include	"hierarchy.h"

void ClientEndServerFrame (edict_t *ent);

////////////////////////////////////////////////////////////////
//	exports
////////////////////////////////////////////////////////////////

//	client interface to server
DllExport	void	dll_PutClientInServer(userEntity_t *self);	//	Q2FIXME: still need to export???
DllExport	void	dll_ClientDisconnect(userEntity_t *self);

//	Q2 stuff
DllExport	void	dll_ClientBegin (userEntity_t *self, int loadgame);
DllExport	int		dll_ClientConnect (userEntity_t *self, void *userinfo, int loadgame);
DllExport	void	dll_ClientThink (userEntity_t *self, usercmd_t *ucmd, pmove_t *pm);
DllExport	void	dll_ClientUserinfoChanged(edict_t *ent, char *userinfo);
DllExport	void	dll_ClientCommand (edict_t *ent);
DllExport	void	dll_ClientBeginServerFrame (userEntity_t *self);
DllExport	void	dll_SetStats (userEntity_t *ent);

DllExport	void	info_player_deathmatch (userEntity_t *self);
DllExport	void	info_player_start (userEntity_t *self);
DllExport	void	info_player_coop (userEntity_t *self);

////////////////////////////////////////////////////////////////
//	globals
////////////////////////////////////////////////////////////////

int				cur_clients;
char			*client_deathtype;

userEntity_t	*pm_passent;


#define	FRAME_TIME	0.05
#define	FATIGUE_MAX		5000

////////////////////////////////////////////////////////////////
// structure for passing death info to weapons
////////////////////////////////////////////////////////////////

typedef	struct	killerInfo_s
{
	userEntity_t	*attacker;
	userEntity_t	*victim;
} killerInfo_t;

killerInfo_t	killerInfo;

/////////////////////////////////////////////////////////////////
//	prototypes
/////////////////////////////////////////////////////////////////

void	Client_Pain (userEntity_t *self, userEntity_t *other, float kick, int damage);
void	Client_Attack (userEntity_t *self);
void	client_begin_run (userEntity_t *self);
void	client_begin_stand (userEntity_t *self);

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

typedef	struct	laserHook_s
{
	Vector	end;
} laserHook_t;

void	laser_think (userEntity_t *self)
{
	userEntity_t	*ignore;
	Vector			start;
	Vector			end;
	trace_t			tr;
	Vector			point;
	Vector			last_movedir;
	Vector			temp;
	int				count;
	static			vec3_t	lmins = {-4, -4, -4};
	static			vec3_t	lmaxs = {4, 4, 4};
	laserHook_t		*hook = (laserHook_t *) self->userHook;

	if (self->spawnflags & 0x80000000)
		count = 8;
	else
		count = 4;

	if (self->enemy)
	{
		last_movedir = Vector (self->movedir);
		point = Vector (self->enemy->absmin) + 0.5 * Vector (self->enemy->size);
		temp = point - Vector (self->s.origin);
		temp.Normalize ();
		temp.set (self->movedir);

		if (temp != last_movedir)
			self->spawnflags |= 0x80000000;
	}
	else
		temp = Vector (self->movedir);

	ignore = self;

	start = Vector (self->s.origin);
//	end = start + temp * 64;
	hook->end.set (self->s.old_origin);

	if (gstate->time > self->delay)
		gstate->RemoveEntity (self);
	else
		self->nextthink = gstate->time + 0.1;
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void	com_Laser (userEntity_t *self, Vector org, Vector end, float lifetime)
{
	userEntity_t	*laser;
	Vector			temp;
	laserHook_t		*hook;

	laser = gstate->SpawnEntity ();

	laser->owner = self;
	laser->movetype = MOVETYPE_NONE;
	laser->solid = SOLID_NOT;
	laser->s.renderfx |= RF_BEAM;// | RF_TRANSLUCENT;
	laser->s.modelindex = 1;			// must be non-zero

	// set the beam diameter
	laser->s.frame = 4;

	// set the color
	laser->s.skinnum = 0xf2f2f0f0;

	VectorSet (laser->s.mins, -8, -8, -8);
	VectorSet (laser->s.maxs, 8, 8, 8);

	//	set the starting position
	org.set (laser->s.origin);
	gstate->LinkEntity (laser);

	//	find the direction to target
	temp = end - org;
	temp.Normalize ();
	temp.set (laser->movedir);

	laser->spawnflags |= 0x80000001;
	laser->svflags -= (laser->svflags & SVF_NOCLIENT);

	//	set the ending position
	end.set (laser->s.old_origin);

	laser->userHook = com->malloc_hook (sizeof (laserHook_t), MEM_MALLOC);
	hook = (laserHook_t *) laser->userHook;
	hook->end = end;

	laser->delay = gstate->time + 1.0;

	laser->think = laser_think;
	laser->nextthink = gstate->time + 0.1;
}

//////////////////////////////////
//	Client_Breathe
//
//	sets up idlescale according to the player's fatigue
//
//	FIXME: stop idle angle from being reset each time v_ipitch_cycle is sent
//////////////////////////////////

void	Client_Breathe (userEntity_t *self, userEntity_t *other, float kick, int damage)
{
	char			msg [24];
	float			pitch_rate;
	float			pitch_change;
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;

	// max pitch rate of 5
/*
	gstate->ClientCmd (self, "v_iyaw_level 0\n");

//	if (!(ihook->fatigue % 250))
//	{
		pitch_rate = (float) ihook->fatigue / (FATIGUE_MAX / 12.0) + 1;
		sprintf (msg, "v_ipitch_cycle %.2f\n", pitch_rate);
		gstate->ClientCmd (self, msg);
		gstate->Con_Dprintf ("%s\n", msg);
//	}

	// max pitch change of 2
	pitch_change = (float) ihook->fatigue / (FATIGUE_MAX / 4.0) + 1;
	sprintf (msg, "v_ipitch_level %.2f\n", pitch_change);
	gstate->ClientCmd (self, msg);
	gstate->Con_Dprintf ("%s\n", msg);
*/
}

////////////////////////////////////////////////////////////////////////
//	Client animation routines
////////////////////////////////////////////////////////////////////////

/////////////////////////////
//	client_stand_frames
/////////////////////////////

void	client_stand_frames (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *head_hook, *torso_hook, *gun_hook;

	if (hook->th_state == TH_STAND)
		return;


	leg_hook = (playerHook_t *) hook->legs->userHook;
	torso_hook = (playerHook_t *) hook->torso->userHook;
	head_hook = (playerHook_t *) hook->head->userHook;
	gun_hook = (playerHook_t *) hook->weapon->userHook;

	//	put legs in run
	ai_init_sequence (hook->legs, leg_hook->frames->run [0], SEQUENCE_RUN, 0);

	//	put torso in run
	ai_init_sequence (hook->torso, torso_hook->frames->run [0], SEQUENCE_RUN, 0);

	//	put head in run
	ai_init_sequence (hook->head, head_hook->frames->run [0], SEQUENCE_RUN, 0);

	//	put weapon in run
	ai_init_sequence (hook->weapon, gun_hook->frames->run [0], SEQUENCE_RUN, 0);

	leg_hook->frameInc = 1;
	torso_hook->frameInc = 1;
	head_hook->frameInc = 1;
	gun_hook->frameInc = 1;

	hook->th_state = TH_STAND;
}

///////////////////////////
//	client_death_frames
//
///////////////////////////

void	client_death_frames (userEntity_t *self)
{
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *head_hook, *torso_hook, *gun_hook;
	int				i;

	if (self->deadflag != DEAD_NO)
		return;

	if (self->input_entity != self)
	{
		i = (int) floor (rnd () * ihook->sequence_max [SEQUENCE_DEATH]);
		ai_init_sequence (self->input_entity, ihook->frames->death [i], SEQUENCE_DEATH, i);

		ihook->frame_flags = FRAME_ONCE;
		ihook->frameInc = 1;

		// player model just stands while possessed ent does stuff
		client_stand_frames (self);
	}
	else
	{
		i = (int) floor (rnd () * hook->sequence_max [SEQUENCE_DEATH]);

		leg_hook = (playerHook_t *) hook->legs->userHook;
		torso_hook = (playerHook_t *) hook->torso->userHook;
		head_hook = (playerHook_t *) hook->head->userHook;
		gun_hook = (playerHook_t *) hook->weapon->userHook;

		//	put legs in run
		ai_init_sequence (hook->legs, leg_hook->frames->run [i], SEQUENCE_DEATH, i);
		leg_hook->frame_flags = FRAME_ONCE;

		//	put torso in run
		ai_init_sequence (hook->torso, torso_hook->frames->run [i], SEQUENCE_DEATH, i);
		torso_hook->frame_flags = FRAME_ONCE;

		//	put head in run
		ai_init_sequence (hook->head, head_hook->frames->run [i], SEQUENCE_DEATH, i);
		head_hook->frame_flags = FRAME_ONCE;

		//	put weapon in run
		ai_init_sequence (hook->weapon, gun_hook->frames->run [i], SEQUENCE_RUN, i);
		gun_hook->frame_flags = FRAME_ONCE;

		leg_hook->frameInc = 1;
		torso_hook->frameInc = 1;
		head_hook->frameInc = 1;
		gun_hook->frameInc = 1;
	}
}

//////////////////////////
//	client_begin_run
//////////////////////////

void	client_begin_run (userEntity_t *self)
{
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *head_hook, *torso_hook, *gun_hook;

	if (ihook->th_state == TH_FOLLOW)
		return;

	ihook->th_state = TH_FOLLOW;

	if (self->input_entity != self)
	{

		ai_init_sequence (self->input_entity, ihook->frames->run [0], SEQUENCE_RUN, 0);

		ihook->frame_flags = FRAME_LOOP;
		ihook->frameInc = 1;

		// player model just stands while possessed ent does stuff
		client_stand_frames (self);
	}
	else
	{
		leg_hook = (playerHook_t *) hook->legs->userHook;
		torso_hook = (playerHook_t *) hook->torso->userHook;
		head_hook = (playerHook_t *) hook->head->userHook;
		gun_hook = (playerHook_t *) hook->weapon->userHook;

		//	put legs in run
		ai_init_sequence (hook->legs, leg_hook->frames->run [0], SEQUENCE_RUN, 0);

		//	put torso in run
		ai_init_sequence (hook->torso, torso_hook->frames->run [0], SEQUENCE_RUN, 0);

		//	put head in run
		ai_init_sequence (hook->head, head_hook->frames->run [0], SEQUENCE_RUN, 0);

		//	put weapon in run
		ai_init_sequence (hook->weapon, gun_hook->frames->run [0], SEQUENCE_RUN, 0);

		leg_hook->frameInc = 2;
		torso_hook->frameInc = 2;
		head_hook->frameInc = 2;
		gun_hook->frameInc = 2;
	}
}

//////////////////////////
//	client_begin_stand
//////////////////////////

void	client_begin_stand (userEntity_t *self)
{
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *head_hook, *torso_hook, *gun_hook;

	ihook->th_state = TH_STAND;

	if (self->input_entity != self)
	{
		ai_init_sequence (self->input_entity, ihook->frames->stand [0], SEQUENCE_STAND, 0);

		ihook->frame_flags = FRAME_LOOP;
		ihook->frameInc = 1;

		// player model just stands while possessed ent does stuff
		client_stand_frames (self);
	}
	else
	{
		leg_hook = (playerHook_t *) hook->legs->userHook;
		torso_hook = (playerHook_t *) hook->torso->userHook;
		head_hook = (playerHook_t *) hook->head->userHook;
		gun_hook = (playerHook_t *) hook->weapon->userHook;

		//	put legs in stand
		ai_init_sequence (hook->legs, leg_hook->frames->stand [0], SEQUENCE_STAND, 0);

		//	put torso in stand
		ai_init_sequence (hook->torso, torso_hook->frames->stand [0], SEQUENCE_STAND, 0);

		//	put head in stand
		ai_init_sequence (hook->head, head_hook->frames->stand [0], SEQUENCE_STAND, 0);

		//	put weapon in stand
		ai_init_sequence (hook->weapon, gun_hook->frames->stand [0], SEQUENCE_STAND, 0);
	}
}

//////////////////////////
//	client_begin_attack
//////////////////////////

void	client_begin_attack (userEntity_t *self)
{
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *head_hook, *torso_hook, *gun_hook;

	if (self->deadflag != DEAD_NO)
		return;

	ihook->th_state = TH_ATTACK;
/*
	ai_init_sequence (self->input_entity, ihook->frames->atak [0], SEQUENCE_ATAK, 0);
	ihook->attack_finished = (ihook->endFrame - ihook->startFrame) * 0.05;

	ihook->frame_flags = FRAME_ONCE;
	ihook->frameInc = 1;
*/

	if (self->input_entity != self)
	{

		ai_init_sequence (self->input_entity, ihook->frames->atak [0], SEQUENCE_ATAK, 0);

		ihook->frame_flags = FRAME_ONCE;
		ihook->frameInc = 1;

		// player model just stands while possessed ent does stuff
		client_stand_frames (self);
	}
	else
	{
		leg_hook = (playerHook_t *) hook->legs->userHook;
		torso_hook = (playerHook_t *) hook->torso->userHook;
		head_hook = (playerHook_t *) hook->head->userHook;
		gun_hook = (playerHook_t *) hook->weapon->userHook;

		//	put legs in run
//		ai_init_sequence (hook->legs, leg_hook->frames->atak [0], SEQUENCE_ATAK, 0);

		//	put torso in run
		ai_init_sequence (hook->torso, torso_hook->frames->atak [0], SEQUENCE_ATAK, 0);

		//	put head in run
		ai_init_sequence (hook->head, head_hook->frames->atak [0], SEQUENCE_ATAK, 0);

		//	put weapon in run
		ai_init_sequence (hook->weapon, gun_hook->frames->atak [0], SEQUENCE_ATAK, 0);

		hook->torso->s.frame = torso_hook->frames->atak [0]->first + 25;
//		hook->torso->s.frame += 25;
		torso_hook->endFrame -= 20;
//		leg_hook->frameInc = 2;
		torso_hook->frameInc = 1;
		head_hook->frameInc = 1;
		gun_hook->frameInc = 1;

//		leg_hook->frame_flags = FRAME_ONCE;
		torso_hook->frame_flags = FRAME_ONCE;
		head_hook->frame_flags = FRAME_ONCE;
		gun_hook->frame_flags = FRAME_ONCE;
	}
}

//////////////////////////
//	client_begin_jump
//////////////////////////

void	client_begin_jump (userEntity_t *self)
{
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *head_hook, *torso_hook, *gun_hook;

	if (ihook->ai_flags & AI_JUMP)
		return;

	ihook->ai_flags |= AI_JUMP;

	if (self->input_entity != self)
	{

		ai_init_sequence (self->input_entity, ihook->frames->jump [0], SEQUENCE_JUMP, 0);

		ihook->frame_flags = FRAME_LOOP;
		ihook->frameInc = 1;

		// player model just stands while possessed ent does stuff
		client_stand_frames (self);
	}
	else
	{
		leg_hook = (playerHook_t *) hook->legs->userHook;
		torso_hook = (playerHook_t *) hook->torso->userHook;
		head_hook = (playerHook_t *) hook->head->userHook;
		gun_hook = (playerHook_t *) hook->weapon->userHook;

		//	put legs in JUMP
		ai_init_sequence (hook->legs, leg_hook->frames->jump [0], SEQUENCE_JUMP, 0);

		//	put torso in JUMP
		ai_init_sequence (hook->torso, torso_hook->frames->jump [0], SEQUENCE_JUMP, 0);

		//	put head in JUMP
		ai_init_sequence (hook->head, head_hook->frames->jump [0], SEQUENCE_JUMP, 0);

		//	put weapon in JUMP
		ai_init_sequence (hook->weapon, gun_hook->frames->jump [0], SEQUENCE_JUMP, 0);

	}
}

//////////////////////////
//	client_warp_appear
//
//	Makes client stretch out of nothing when appearing from
//	a teleporter
//
//	client is immobile until reaching full size
//////////////////////////

void	client_warp_appear (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	float			scale_up = 1.2, scale_down = 0.8;

	self->s.render_scale [0] *= scale_up;
	self->s.render_scale [1] *= scale_up;
	self->s.render_scale [2] *= scale_down;

	if (hook->legs)
	{
		hook->legs->s.render_scale [0] *= scale_up;
		hook->legs->s.render_scale [1] *= scale_up;
		hook->legs->s.render_scale [2] *= scale_down;
	}
	if (hook->torso)
	{
		hook->torso->s.render_scale [0] *= scale_up;
		hook->torso->s.render_scale [1] *= scale_up;
		hook->torso->s.render_scale [2] *= scale_down;
	}
	if (hook->head)
	{
		hook->head->s.render_scale [0] *= scale_up;
		hook->head->s.render_scale [1] *= scale_up;
		hook->head->s.render_scale [2] *= scale_down;
	}
	if (hook->weapon)
	{
		hook->weapon->s.render_scale [0] *= scale_up;
		hook->weapon->s.render_scale [1] *= scale_up;
		hook->weapon->s.render_scale [2] *= scale_down;
	}

	if (self->s.render_scale [0] > 1.0)
	{
		//	Restore the player's input now that they've reached full size
		//	Q2FIXME:	change to Q2 network functions
		//	gstate->WriteByte (self, MSG_ONE, SVC_RESTOREINPUT);

		com->SetVector (self->s.render_scale, 1.0, 1.0, 1.0);
		if (hook->legs)
			com->SetVector (hook->legs->s.render_scale, 1.0, 1.0, 1.0);
		if (hook->torso)
			com->SetVector (hook->torso->s.render_scale, 1.0, 1.0, 1.0);
		if (hook->head)
			com->SetVector (hook->head->s.render_scale, 1.0, 1.0, 1.0);
		if (hook->weapon)
			com->SetVector (hook->weapon->s.render_scale, 1.0, 1.0, 1.0);

		self->solid = SOLID_BBOX;
		self->movetype = MOVETYPE_WALK;

//		hr_init_model_offsets (self, hook->legs, hook->torso, hook->head, hook->weapon);

		self->think = NULL;
		self->nextthink = -1;

		return;
	}

	self->think = client_warp_appear;
	self->nextthink = gstate->time + 0.05;
}

//////////////////////////////
//	client_animation_think
//
//////////////////////////////

void	client_animation_think (userEntity_t *self)
{
	userEntity_t	*ie = self->input_entity;
	playerHook_t	*ihook = (playerHook_t *) ie->userHook;
	playerHook_t	*leg_hook, *weapon_hook, *torso_hook, *head_hook;
	float			cur_vel;

	///////////////////////////////////////////////////
	// client fatigue
	///////////////////////////////////////////////////
/*
	gstate->ClientCmd (self, "v_idlescale 1\n");

	// set idle scale for breathing
	cur_vel = qv_len (self->velocity);
	if (cur_vel == 0.0)
	{
		ihook->fatigue -= 10;
		if (ihook->fatigue < 1)
			ihook->fatigue = 1;

		Client_Breathe (self);
	}
	else
	{
		ihook->fatigue += (int) floor (cur_vel / 32.0);
		if (ihook->fatigue > FATIGUE_MAX)
			ihook->fatigue = FATIGUE_MAX;
	}
*/

	///////////////////////////////////////////////////
	// frame updates
	///////////////////////////////////////////////////

	if (ie->deadflag == DEAD_NO)
	{
		torso_hook = (playerHook_t *) ihook->torso->userHook;

		if (ihook->th_state == TH_FOLLOW)
		{
			if (ie->velocity [0] == 0.0 && ie->velocity [1] == 0.0)
				client_begin_stand (self);
//			else if (ihook->frame_flags & FRAME_LAST)
//				client_begin_run (self);
		}
		else if (ihook->th_state == TH_STAND)
		{
			if (ie->velocity [0] != 0.0 && ie->velocity [1] != 0.0)
				client_begin_run (self);
//			else if (ihook->frame_flags & FRAME_LAST)
//				client_begin_stand (self);
		}
		else if (ihook->ai_flags & AI_JUMP)
		{
			if (ie->velocity [2] == 0.0)
			{
				if (ie->velocity [0] == 0.0 && ie->velocity [1] == 0.0)
					client_begin_stand (self);
				else
					client_begin_run (self);
			}
		}
		else if (ihook->th_state == TH_ATTACK)
		{
			if (torso_hook->frame_flags & FRAME_STATIC)
			{
				if (ie->velocity [0] == 0.0 && ie->velocity [1] == 0.0)
					client_begin_stand (self);
				else
					client_begin_run (self);
			}
		}


		///////////////////////////////////////////////////////////////////
		//	if input_entity != client, then we are in possesion,
		//	so handle attacks a bit different... use monster weapon
		//	in correct frame, instead of right when attack is pressed
		///////////////////////////////////////////////////////////////////
		if (ie != self)
		{
			if (ihook->th_state == TH_ATTACK)
			{
				if (ihook->frame_flags & FRAME_PLAYSOUND1)
				{
					ihook->frame_flags -= FRAME_PLAYSOUND1;

					ie->curWeapon->use (ie->curWeapon, ie);
				}
				else if (ihook->frame_flags & FRAME_PLAYSOUND2)
				{
					ihook->frame_flags -= FRAME_PLAYSOUND2;

					ie->curWeapon->use (ie->curWeapon, ie);
				}
			}
			//	plays any sounds for current animation
			else
			{
				if (ihook->frame_flags & FRAME_PLAYSOUND1)
				{
					ihook->frame_flags -= FRAME_PLAYSOUND1;

					if (ihook->sound1)
						gstate->sound (ie, CHAN_BODY, ihook->sound1, 255, ATTN_NORM);
				}
				else if (ihook->frame_flags & FRAME_PLAYSOUND2)
				{
					ihook->frame_flags -= FRAME_PLAYSOUND2;

					if (ihook->sound2)
						gstate->sound (ie, CHAN_BODY, ihook->sound2, 255, ATTN_NORM);
				}
			}
		}
	}
	else if (ie->deadflag == DEAD_DYING)
	{
		ie->view_ofs [2] -= 2;
		if (ie->view_ofs [2] < -8)
			ie->view_ofs [2] = -8;

		//	FIXME: remove when death frames are present for character
		self->deadflag = DEAD_DEAD;

		if (ihook->legs)
			ihook = (playerHook_t *) ihook->legs->userHook;

		if (ihook->frame_flags & FRAME_STATIC)
		{
			ihook->frame_flags -= FRAME_STATIC;

			ie->deadflag == DEAD_DEAD;
		}
	}
}


////////////////////////////////////////////////////////////////
/// Client Cheat Codes
////////////////////////////////////////////////////////////////

// FIXME: make for deathmatch only!

//////////////////////////////////
//	Client_KillTheNorwegian
//
//	kill Sverre when he cheats in deathmatch
//////////////////////////////////

void	Client_KillTheNorwegian (userEntity_t *self)
{
	self->health = 0.0;
	self->flags = self->flags - (self->flags & FL_ONGROUND);
	com->SetVector (self->velocity, rnd () * 500 - 250, rnd () * 500 - 250, rnd () * 1000 + 500);
	com->SetVector (self->avelocity, rnd () * 500 - 250, rnd () * 500 - 250, rnd () * 500 - 250);

	gstate->bprint (self->netname);
	gstate->bprint (" is a cheating loser.\n");

	self->die (self, self, self, 32768, self->s.origin);

	return;
}

///////////////////////////////////////////////////////////////////////////////
//	Client_NoTarget
///////////////////////////////////////////////////////////////////////////////

void	Client_NoTarget (userEntity_t *self)
{
	if (self->flags & FL_NOTARGET)
	{
		self->flags -= FL_NOTARGET;
		gstate->Con_Printf ("Notarget OFF.\n");
	}
	else
	{
		self->flags |= FL_NOTARGET;
		gstate->Con_Printf ("Notarget ON.\n");
	}
}

//////////////////////////////////
//	Client_LlamaCheat
//
//	toggles invulnerability on and off
//////////////////////////////////

void	Client_LlamaCheat (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	// make sure the client has been connected to the server
	if (!self->className)
		return;

	if (gstate->deathmatch)
	{
		Client_KillTheNorwegian (self);
		return;
	}

	if (hook->dflags & DFL_LLAMA)
	{
		gstate->sprint (self, "Llama mode OFF.\n");
		hook->dflags = hook->dflags - DFL_LLAMA;
	}
	else
	{
		gstate->sprint (self, "Llama mode ON.\n");
		hook->dflags = hook->dflags | DFL_LLAMA;
	}
}

//////////////////////////////////
//	Client_RampageCheat
//
//	toggles Red Neck Rampage (R) mode
//////////////////////////////////

void	Client_RampageCheat (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	// make sure the client has been connected to the server
	if (!self->className)
		return;

	if (gstate->deathmatch)
	{
		Client_KillTheNorwegian (self);
		return;
	}

   	if (hook->dflags & DFL_RAMPAGE)
	{
		gstate->sprint (self, "Rampage mode OFF.\n");
		hook->dflags = hook->dflags - DFL_RAMPAGE;
	}
	else
	{
		gstate->sprint (self, "Rampage mode ON.\n");
		hook->dflags = hook->dflags | DFL_RAMPAGE;
	}
}

//////////////////////////////////
//	Client_HealthCheat
//
//	gives client 999 health
//////////////////////////////////

void	Client_HealthCheat (userEntity_t *self)
{
	// make sure the client has been connected to the server
	if (!self->className)
		return;

	if (gstate->deathmatch)
	{
		Client_KillTheNorwegian (self);
		return;
	}

	self->health = 999;
}

//////////////////////////////////
//	Client_ShowStats
//
//	toggles stats display on and off
//////////////////////////////////

void	Client_ShowStats (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	if (hook->dflags & DFL_SHOWSTATS)
		hook->dflags = hook->dflags - DFL_SHOWSTATS;
	else
		hook->dflags = hook->dflags | DFL_SHOWSTATS;
}

//////////////////////////////////
//	Client_Use
//
//	checks for a useable object in front of self and activates it's use function
//	if it has one and is close enough
//
//  FIXME: change to TraceBox when Q2 is merged
//////////////////////////////////

void	Client_Use (userEntity_t	*self)
{
	Vector			org, end, start;
	trace_t			tr;
	vec3_t			mins = {-8, -8, -8};
	vec3_t			maxs = {8, 8, 8};

	// make sure the client has been connected to the server
	if (!self->className)
		return;

	///////////////////////////////////////////////////////////////////////
	//	if in camera mode (looking at a monitor), go back to 1st person
	///////////////////////////////////////////////////////////////////////

	if (self->flags & FL_CAMERAMODE)
	{
		camera_stop (self);
		self->flags -= FL_CAMERAMODE;

		return;
	}

	end = Vector (self->input_entity->client->v_angle);
	end.AngleVectors (forward, right, up);

/*
	start = Vector (self->input_entity->s.origin) + Vector (0, 0, 40.0) - right * 16.0;

	for (int y = 0; y < 7; y++)
	{
		for (int x = 0; x < 6; x++)
		{
			org = start - (Vector (0, 0, 8.0) * y) + (right * 8.0 * x);
			end = org + forward * 64;

			gstate->TraceLine (org.vec (), end.vec (), TRUE, self->input_entity, &trace);
			//com->SpawnMarker (self, Vector (trace.endpos), "models/debug/marker.mdl", 0.2);
			com_Laser (self, org, Vector (trace.endpos), 1.0);

			if (trace.ent)
				if (trace.ent->use)
					break;
		}

		if (trace.ent)
			if (trace.ent->use)
				break;
	}

	if (trace.ent)
	{
		//	can't use anything that has a target name except func_buttons
		if ((trace.ent->use && !trace.ent->targetname) || (trace.ent->use && !stricmp (trace.ent->className, "func_button")))
		{
			gstate->activator = self->input_entity;
			if (trace.ent->className)
			{
				if (!strstr (trace.ent->className, "func_wall"))
					trace.ent->use (trace.ent, self, self);
			}
			else
				trace.ent->use (trace.ent, self, self);
		}
	}
*/

	start = Vector (self->input_entity->s.origin) + Vector (0, 0, 22.0);
	end = start + forward * 80;
	tr = gstate->trace_q2 (start.vec (), mins, maxs, end.vec (), self->input_entity, MASK_PLAYERSOLID);

	if (tr.ent)
	{
		//	can't use anything that has a target name except func_buttons
		if ((tr.ent->use && !tr.ent->targetname) || (tr.ent->use && !stricmp (tr.ent->className, "func_button")))
		{
			gstate->activator = self->input_entity;
			if (tr.ent->className)
			{
				if (!strstr (tr.ent->className, "func_wall"))
					tr.ent->use (tr.ent, self, self);
			}
			else
				tr.ent->use (tr.ent, self, self);
		}
	}
}

//////////////////////////////////
//	Client_Attack
//
//	called to fire weapons
//////////////////////////////////

void Client_Attack (userEntity_t *self)
{
	playerHook_t		*hook = (playerHook_t *) self->userHook;
	playerHook_t		*ihook = (playerHook_t *) self->input_entity->userHook;
	cameraHook_t		*chook;
	userInventory_t		*invItem;
	ammo_t				*ammo;
	weapon_t			*weapon;

	//	Q2 stuff
	gclient_t			*client;

	client = self->client;

	//	exit if button not pressed
	if (!client || !(client->latched_buttons & BUTTON_ATTACK) || self->deadflag != DEAD_NO)
		return;

/*
	if (self->flags & FL_ATTACKHOLD)
	{
		if ((!self->button0 && !hook->last_button0) || (ihook->attack_finished > gstate->time))
		{
			ihook->last_button0 = self->button0;
			return;
		}
	}
	else
	{
		if (!self->button0)
		{
			hook->last_button0 = self->button0;
			return;
		}
		if (ihook->attack_finished > gstate->time)
		{
			hook->last_button0 = self->button0;
			return;
		}
	}
*/

	if (ihook->attack_finished > gstate->time)
		return;

	if (self->input_entity->curWeapon)
	{
		// put player model/possed monster in attack frames
		client_begin_attack (self);

		//	if self == input_entity (not possessing) then fire immediately
		//	otherwise, monster weapon is fired in client_animation_think
		//	on correct frames
		if (self->input_entity == self)
		{
			hook->weapon_fired = self->input_entity->curWeapon;
			self->input_entity->curWeapon->use (self->input_entity->curWeapon, self->input_entity);

			weapon = (weapon_t *) self->input_entity->curWeapon;
			// send down ammo count to client for display on status bar
			if (weapon->ammo)
			{
				ammo = (ammo_t *) weapon->ammo;
				//	Q2FIXME:	change to Q2 network functions
				/*
				gstate->WriteByte (self, MSG_ONE, SVC_AMMOCOUNT);
				gstate->WriteByte (self, MSG_ONE, (int)ammo->count);
				*/
			}
		}
	}

	hook->last_button0 = self->button0;
}

//////////////////////////////////
//	Client_CheckWaterJump
//
//called when self->waterlevel == 2 (head out of water) to check
//if there is a ledge in front of the player.  If so, upward and
//forward velocity given to self
//
//	FIXME:	make to work with input_entity
//////////////////////////////////

void	Client_CheckWaterJump (userEntity_t *self)
{
	Vector		mod_org, mod_end, temp;
//	Vector		forward, up, right;
	Vector		max_size, min_size;
//	userTrace_t	trace;
/*
	com->CopyVector (min_size, self->absmin);
	com->SubVector (min_size, self->s.origin);
	com->CopyVector (max_size, self->absmax);
	com->SubVector (max_size, self->s.origin);
*/
	temp = Vector (self->input_entity->s.angles);
	temp.AngleVectors (forward, right, up);

	// mod_org = self->s.origin + '0 0 8';
	mod_org = Vector (self->input_entity->s.origin);

	// add 8 to height for trace start
	mod_org.z = mod_org.z + 8.0;

	// set forward z to 0
	forward.z = 0.0;

	mod_end = forward * 24 + mod_org;

	gstate->TraceLine (mod_org.vec (), mod_end.vec (), FALSE, self->input_entity, &trace);

	// did we find an obstruction?
	if (trace.fraction < 1.0)
	{
		mod_org.z = mod_org.z + (self->input_entity->s.maxs [2] - 8.0);

		// mod_end = start + forward * 24.0;
		mod_end = mod_org + forward * 24.0;

		// self.movedir = traceplanenormal * -50;
		temp = Vector (trace.planeNormal) * -50;
		temp.set (self->input_entity->movedir);

		gstate->TraceLine (mod_org.vec (), mod_end.vec (), FALSE, self->input_entity, &trace);
		if (trace.fraction == 1.0)
		{
			self->input_entity->flags = self->input_entity->flags | FL_WATERJUMP;

			// FIXME: maybe the jump should always come from -up of the player?
			// I think so, except that this would screw up rocket jumping and jumping
			// while looking down/up...
			self->input_entity->velocity [2] = 0.0;
			temp = Vector (self->input_entity->velocity) + Vector (gstate->gravity_dir) * -225.0;
			temp.set (self->input_entity->velocity);
//			self->input_entity->velocity [2] = 225.0;

			if ((int)self->input_entity->flags & FL_JUMPRELEASED)
				self->input_entity->flags = self->input_entity->flags - FL_JUMPRELEASED;
			self->input_entity->teleport_time = gstate->time + 2.0;
			return;
		}
	}
};

//////////////////////////////////
//	Client_CheckLedgeGrab
//
//	called when self is in the air (z velocity != 0 and FL_ONGROUND not set)
//
//	checks for a ledge in front of the player.  If found, it gives self
//	upward and forward velocity to get over the ledge
//////////////////////////////////

void	Client_CheckLedgeGrab (userEntity_t *self)
{
	Vector			mod_org, mod_end, temp;
//	userTrace_t		trace;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	float			normal_diff;

	temp = Vector (self->s.angles);
	temp.AngleVectors (forward, right, up);

	// trace a line straight out in front looking for open space
	mod_org = Vector (self->s.origin);

/*
	if (hook->chinup_time > gstate->time)
		mod_org.z = mod_org.z + self->s.maxs [2];
	else
		mod_org.z = mod_org.z + self->s.maxs [2] * 2 - 8.0;
*/
	// set forward z to 0
	forward.z = 0.0;

	// mod_end = mod_org + forward * 24.0
	mod_end = mod_org + forward * 24.0;

	gstate->TraceLine (mod_org.vec (), mod_end.vec (), TRUE, self, &trace);
//	com->SpawnMarker (self, trace.endpos, "models/debug/marker.mdl", 0.2);

	if (trace.fraction == 1.0)
	{
		// trace a line down in front of the player looking for a solid object
		mod_org = Vector (self->s.origin) + forward * 24.0;

		// add z to height or trace start

		mod_end = mod_org;
		mod_org.z = mod_org.z + self->s.maxs [2] * 2 - 8.0;
		mod_end.z = mod_end.z + self->s.mins [2] - 4.0;

		gstate->TraceLine (mod_org.vec (), mod_end.vec (), TRUE, self, &trace);
//		com->SpawnMarker (self, Vector (trace.endpos), "models/debug/marker.mdl", 0.2);
		normal_diff = trace.planeNormal [2];
		normal_diff = 1.0 - fabs (normal_diff);

		// did we find an obstruction?
		if (((self->velocity [2] < 0.0 && trace.fraction < 0.1) ||
			(self->velocity [2] > 0.0 && trace.fraction < 1.0) || (trace.fraction < 0.3)) && (normal_diff < 0.05))
		{
			//com->PrintVector ("normal 2 = ", trace.planeNormal);

			// now trace a line from the player at the height where the obstruction
			// was detected
			mod_end = Vector (trace.endpos);
			mod_end.z = mod_end.z - 2.0;

			mod_org = Vector (self->s.origin);
			mod_org.z = mod_end.z;

			// self.movedir = traceplanenormal * -50;
			temp = Vector (trace.planeNormal) * -50.0;
			temp.set (self->movedir);

			gstate->TraceLine (mod_org.vec (), mod_end.vec (), TRUE, self, &trace);

			//com->PrintVector ("normal 3 = ", trace.planeNormal);

			// we want to find the normal for the x/y directions, so we
			// 0 out z in the normal and then subtract the magnitude of that
			// from 1.0
			trace.planeNormal [2] = 0.0;
			normal_diff = qv_len (trace.planeNormal);

			normal_diff = 1.0 - normal_diff;

			if (normal_diff < 0.02 && trace.fraction < 1.0)
			{
//				hook->chinup_time = gstate->time + 0.1;

				self->flags = self->flags | FL_WATERJUMP;
				self->velocity [2] = 80;

				if ((int)self->flags & FL_JUMPRELEASED)
					self->flags = (int)self->flags - FL_JUMPRELEASED;
				self->teleport_time = gstate->time + 2.0;
			}
		}
	}
};

//////////////////////////////////
//	Client_Jump
//
//	called when self->button2 is set
//
//	if the player has not released the jump button then this
//	function exits
//
//	if the player is already in the air and has a non-zero z
//	velocity (ie. moving up or down) then ledges are checked
//	for.
//
//	if the player is on the ground then z velocity is set
//
//	works with input_entity
//////////////////////////////////

void Client_Jump (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	float			r, max_jump;
	Vector			temp;

//	ledge grabbing stuff gone away, this code won't correctly work since
//	new jumping is now in place
/*
	// check jump_time so that the player can just tap jump without a
	// ledge grab happening
	if (hook->jump_time > gstate->time)
		return;
	// if the player has no z velocity then this is the first tap of the
	// jump button, so we set jump_time for the delay
	if (self->velocity [2] == 0.0)
		hook->jump_time = gstate->time + 0.2;

	// no more grabbing ledges
	if ((self->velocity [2] != 0.0) && (!((int)self->flags & FL_ONGROUND)))
		Client_CheckLedgeGrab (self);

	if (!(self->flags & FL_JUMPRELEASED))
		return;
	else
		self->flags = self->flags - FL_JUMPRELEASED;
*/
	//////////////////////////////////////////////////////////////////////
	//	make sure that the input entity can jump at all
	//////////////////////////////////////////////////////////////////////

	if (ihook->max_jump_dist <= 0.0)
		return;

	ihook->jumped = TRUE;

	#define JUMPSPEED	100
	// make sure we make the input entity jump!

	// if player is on the ground at all, then jump
	if ((self->input_entity->flags & FL_ONGROUND) || (self->input_entity->flags & FL_PARTIALGROUND))
	{
		ihook->jump_time = gstate->time;

		// FIXME: maybe the jump should always come from -up of the player?
		// I think so, except that this would screw up rocket jumping and jumping
		// while looking down/up...
		temp = Vector (self->input_entity->velocity) + Vector (gstate->gravity_dir) * -270.0;
		temp.set (self->input_entity->velocity);
//		self->input_entity->velocity [2] = 270.0;

		r = rnd ();
		if (r < 0.33)
			gstate->sound (self->input_entity, CHAN_BODY, "player/playerjump5.wav", 255, ATTN_NORM);
		else if (r < 0.66)
			gstate->sound (self->input_entity, CHAN_BODY, "player/playerjump6.wav", 255, ATTN_NORM);
		else
			gstate->sound (self->input_entity, CHAN_BODY, "player/playerjump7.wav", 255, ATTN_NORM);

		client_begin_jump (self);
	}
	else if (ihook->jump_time > gstate->time - (0.5 * ((float)hook->acro_boost / 10.0)))
	{
		// add to velocity of an already jumping player
		max_jump = 270.0 + ((JUMPSPEED / 10) * hook->acro_boost);
		if (self->velocity [2] < max_jump)
		{
			// FIXME: maybe the jump should always come from -up of the player?
			// I think so, except that this would screw up rocket jumping and jumping
			// while looking down/up...

			// FIXME: this won't be capped right for odd-directional gravity
			// need to isolate jump direction to check against max_jump
			r = gstate->GetCvar ("sv_gravity") * gstate->frametime + JUMPSPEED * gstate->frametime;
			temp = Vector (self->input_entity->velocity) + Vector (gstate->gravity_dir) * -r;
			//self->input_entity->velocity [2] += gstate->GetCvar ("sv_gravity") * gstate->frametime + JUMPSPEED * gstate->frametime;
			if (self->input_entity->velocity [2] > max_jump) self->input_entity->velocity [2] = max_jump;

			temp.set (self->input_entity->velocity);
		}

		// FIXME: make jump frames synch with a boosted jump
	}

}

//////////////////////////////////
//	Client_CheckInertialDamage
//
//	called each frame to check if the players velocity has decreased suddenly
//
//	treats xy velocity as separate from z velocity so that vertical velocity
//	is not compounded with horizontal velocity
//
//	if the client is possessing another input entity, then the client's current
//	powerup status overrides the input entity's
//
//	works with input_entity
//////////////////////////////////

void	Client_CheckInertialDamage (userEntity_t *self)
{
	float			cur_vel, v_diff, damage, r;
	int				took_damage, landed;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	Vector			v_temp;

	float			z_damage_vel = 550.0;
	float			xy_damage_vel = 400.0;
	float			damage_div = 50.0;

	landed = took_damage = FALSE;

	v_temp = Vector (self->input_entity->velocity);

	// check vertical velocity first

	// get the current z velocity
	cur_vel = fabs (self->input_entity->velocity [2]);
	// find the difference between current velocity and last velocity
	v_diff = ihook->last_zvel - cur_vel;

	if (v_diff > z_damage_vel)
	{
		// change in velocity is > z_damage_velocity

		// find amount of health to subtract
		damage = floor ((v_diff - z_damage_vel) / (damage_div * 0.15));

		// scale down damage based on acro skill
		damage = damage * ((10.0 - (float)hook->acro_boost) / 10.0);

		if (damage)
		{
			client_deathtype = "fell";
			com->Damage (self->input_entity, self->input_entity, self->input_entity, zero_vector, zero_vector, damage, DAMAGE_FALLING | DAMAGE_NO_BLOOD);
			took_damage = TRUE;
		}
		else landed = TRUE;
	}
	else if (v_diff > 300.0)
	landed = TRUE;

	// save current z velocity as last
	ihook->last_zvel = cur_vel;

	// now check horizontal velocity

	// get the current xy velocity
	v_temp.z = 0;
	cur_vel = v_temp.vlen ();

	// find the change in velocity from last check
	v_diff = ihook->last_xyvel - cur_vel;

	if (v_diff > xy_damage_vel)
	{
		// change in xy velocity is > xy_damage_vel

		// find amount of health to subtract
		damage = floor ((v_diff - xy_damage_vel) / damage_div);

		// scale down damage based on acro skill
		damage = damage * ((10.0 - (float)hook->acro_boost) / 10.0);

		if (damage)
		{
			client_deathtype = "fell";
			com->Damage (self->input_entity, self->input_entity, self->input_entity, zero_vector, zero_vector, damage, DAMAGE_FALLING | DAMAGE_NO_BLOOD);
			took_damage = TRUE;
		}
	}

	// if the player took any damage then play a hurt sound
	if (took_damage)
	{
		// FIXME: play sounds for input_entities other than client
		if (self->input_entity == self)
		{
			if (rnd () < 0.5)
				gstate->sound (self->input_entity, CHAN_BODY, "player/playerlandhurt2.wav", 255, ATTN_NORM);
			else
				gstate->sound (self->input_entity, CHAN_BODY, "player/playerlandhurt.wav", 255, ATTN_NORM);
		}
	}
	else if (landed)
	{
		// FIXME: play sounds for input_entities other than client
		if (self->input_entity == self)
		{
			r = rnd ();
			if (r < 0.25)
				gstate->sound (self->input_entity, CHAN_BODY, "player/playerland1.wav", 255, ATTN_NORM);
			else if (r < 0.5)
				gstate->sound (self->input_entity, CHAN_BODY, "player/playerland2.wav", 255, ATTN_NORM);
			else if (r < 0.75)
				gstate->sound (self->input_entity, CHAN_BODY, "player/playerland3.wav", 255, ATTN_NORM);
			else
				gstate->sound (self->input_entity, CHAN_BODY, "player/playerland4.wav", 255, ATTN_NORM);
		}
	}
/*
	else
	{
		if (rnd () < 0.5)
			gstate->sound (self, CHAN_BODY, "player/playerland1.wav", 255, ATTN_NORM);
		else
			gstate->sound (self, CHAN_BODY, "player/playerland2.wav", 255, ATTN_NORM);
	}
*/
	// save current xy velocity and last xy velocity
	ihook->last_xyvel = cur_vel;
}

//////////////////////////////////
//	Client_CheckWaterDamage
//
//	check's for water damage to input_entity
//	uses input_entity's envirosuit/etc info
//
//	FIXME:	Doesn't support input_entity correctly yet
//			need to make all entities get same self->waterlevel info
//////////////////////////////////

void	Client_CheckWaterDamage (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	float			time_under;
	Vector			temp;

	if (self->input_entity->health <= 0 || self->input_entity->movetype == MOVETYPE_NOCLIP)
		return;

	if (ihook->envirosuit_time < gstate->time)
	{
		if (self->input_entity->waterlevel < 3)
		{
			if (self == self->input_entity)
			{
				if (ihook->drown_time < gstate->time)
					gstate->sound (self->input_entity, CHAN_VOICE, "player/playerbreathe2.wav", 128, ATTN_NORM);
				else if (hook->drown_time < gstate->time + 9)
				{
					if (rnd () < 0.5)
						gstate->sound (self->input_entity, CHAN_VOICE, "player/playerbreathe2.wav", 128, ATTN_NORM);
					else
						gstate->sound (self->input_entity, CHAN_VOICE, "player/playerchoke1.wav", 128, ATTN_NORM);
				}
			}

			ihook->drown_time = gstate->time + 12;
		}
		else
		{
			if (ihook->drown_time <= gstate->time && ihook->pain_finished < gstate->time)
			{
				time_under = gstate->time - ihook->drown_time;

				client_deathtype = "drowned";
				com->Damage (self->input_entity, self->input_entity, self->input_entity, zero_vector, 
							 zero_vector, time_under * 0.75, DAMAGE_DROWN | DAMAGE_NO_BLOOD);
				ihook->pain_finished = gstate->time + 1;

				if (self->input_entity->watertype == CONTENTS_WATER)
				{
					if (self == self->input_entity)
					{
						if (time_under > 8)
							gstate->sound (self->input_entity, CHAN_VOICE, "player/playerchoke3.wav", 128, ATTN_NORM);
						else
							gstate->sound (self->input_entity, CHAN_VOICE, "player/playerwaterchoke1.wav", 128, ATTN_NORM);
					}
				}
			}
		}
	}

	if (!self->input_entity->waterlevel)
	{
		if (self->input_entity->flags & FL_INWATER)
		{
			if (self == self->input_entity)
				gstate->sound (self->input_entity, CHAN_BODY, "player/playerexitwater.wav", 128, ATTN_NORM);
			self->input_entity->flags = self->input_entity->flags - FL_INWATER;
		}

		return;
	}

	if (self->input_entity->watertype == CONTENTS_LAVA)
	{
		if (ihook->pain_finished < gstate->time)
		{
			com->Damage (self->input_entity, self->input_entity, self->input_entity, zero_vector, 
						 zero_vector, 10 * self->input_entity->waterlevel, DAMAGE_LAVA | DAMAGE_NO_BLOOD);

			if (ihook->sound_time <= gstate->time)
			{
//				self->input_entity->pain (self->input_entity, self->input_entity, 0, 10 * self->input_entity->waterlevel);
				ihook->sound_time = gstate->time + 1.0;
			}

			if (ihook->envirosuit_time > gstate->time && (ihook->items & IT_ENVIROSUIT))
				ihook->pain_finished = gstate->time + 1.0;
			else
				ihook->pain_finished = gstate->time + 0.2;

			client_deathtype = "lava";
		}
	}
	else if (self->input_entity->watertype == CONTENTS_SLIME)
	{
		com->Damage (self->input_entity, self->input_entity, self->input_entity, zero_vector, zero_vector, 
					 4 * self->input_entity->waterlevel, DAMAGE_SLIME | DAMAGE_NO_BLOOD);

		if (ihook->pain_finished < gstate->time && (ihook->envirosuit_time < gstate->time && (ihook->items & IT_ENVIROSUIT)))
		{
			if (ihook->sound_time <= gstate->time)
			{
//				self->input_entity->pain (self->input_entity);
				ihook->sound_time = gstate->time + 1.5;
			}

			ihook->pain_finished = gstate->time + 1;

			client_deathtype = "lava";
		}
	}

	if (!(self->input_entity->flags & FL_INWATER))
	{
		if (self == self->input_entity)
		{
			if (self->input_entity->watertype == CONTENTS_LAVA)
				gstate->sound (self->input_entity, CHAN_BODY, "player/inlava.wav", 128, ATTN_NORM);
			else if (self->input_entity->watertype == CONTENTS_WATER)
			{
				if (qv_len (self->velocity) > 500 || fabs (self->velocity [2]) > 300)
					gstate->sound (self->input_entity, CHAN_BODY, "player/playerlandinwater.wav", 128, ATTN_NORM);
				else
					gstate->sound (self->input_entity, CHAN_BODY, "player/playerswim1.wav", 128, ATTN_NORM);
			}
			else if (self->input_entity->watertype == CONTENTS_SLIME)
				gstate->sound (self->input_entity, CHAN_BODY, "player/slimbrn2.wav", 128, ATTN_NORM);
		}

		self->input_entity->flags = self->input_entity->flags | FL_INWATER;
	}

	if (!(self->input_entity->flags & FL_WATERJUMP))
	{
		temp = Vector (self->velocity) - (0.8 * self->waterlevel * gstate->frametime * Vector (self->velocity));
		temp.set (self->velocity);
	}
}

/////////////////////////////////////////
//	Client_GetSpawnPoint
//
/////////////////////////////////////////

userEntity_t *Client_GetSpawnPoint (userEntity_t *self)
{
	userEntity_t *spot;

	if (gstate->deathmatch)
		spot = com->DeathmatchSpawnPoint ("info_player_deathmatch");
	else
	{
		spot = com->SelectSpawnPoint ("info_player_start", global_spawntarget);
		com->free (global_spawntarget);
		global_spawntarget = NULL;
	}

	return	spot;
}

//////////////////////////////////
//	Client_Respawn
//
//	respawns the player after death
//	FIXME: sometimes player can't respawn... not fully dead?
//////////////////////////////////

int	Client_Respawn (userEntity_t *self)
{
	userEntity_t	*spot;
	Vector			temp;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	spot = Client_GetSpawnPoint (self);

	if (!spot)
	{
		gstate->Con_Printf ("No open spawn position for %s!\n", self->netname);
		return FALSE;
	}

	// make a copy of the body
	bodylist_add (self);

	temp = spot->s.origin;
	temp.set (self->s.origin);
	temp = spot->s.angles;
	temp.set (self->s.angles);

	temp = Vector (0.0, 0.0, 22.0);
	temp.set (self->view_ofs);

	self->fixangle = 1;
	self->movetype = MOVETYPE_WALK;
	self->solid    = SOLID_SLIDEBOX;
	self->flags    = FL_CLIENT;

	self->health = 100.0;
	self->takedamage = DAMAGE_AIM;
	self->deadflag = DEAD_NO;

	// initialize hook variables
	hook->jump_time = 0.0;
//	hook->chinup_time = 0.0;
	hook->attack_finished = 0.0;
	hook->dflags = 0;
	hook->pain_finished = 0;
	hook->drown_time = gstate->time + 12;
	hook->max_health = 100.0;

	com->ZeroVector (self->velocity);
	gstate->SetOrigin2 (self, self->s.origin);

	self->deadflag = DEAD_NO;

	spawn_tele_gib (Vector (self->s.origin), self);

	///////////////////////////////////////////////////////////////////////////
	//	re-initialize hierarchical models
	///////////////////////////////////////////////////////////////////////////

	hr_init_models (self);

	//	tell the client what models not to draw for this player
	com->SetClientModels (self, hook->head, hook->torso, hook->legs, hook->weapon);

	self->s.effects = EF_GIB;        // this makes server send client info to the clients
	self->s.renderfx = RF_NODRAW;
	gstate->LinkEntity (self);

	// start frames
	client_begin_stand (self);

	// setup inventory list for this player
	// inventory is stored in server code
    self->inventory = gstate->InventoryNew();

	return	TRUE;
}

//////////////////////////////////
//	Client_DeathThink
//
//	called each frame if the player is dead
//////////////////////////////////

void Client_DeathThink (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			ang, dir;
	char			command_str [128];

	if (self->client->latched_buttons && self->delay < gstate->time)
	{
		//	if in camera mode, turn it off
		if (hook->camera)
			camera_stop (self);

		if (gstate->deathmatch || gstate->coop)
		{
			//	Q2FIXME:	does this even work anymore??!?!?
			// set flag to keep trying respawns if necessary
			hook->dflags = hook->dflags | DFL_RESPAWN;
			Client_Respawn (self);

			return;
		}
		else
		{
			gstate->SetCvar ("sv_frametime", "1.0");

			// restart the entire server
			//	sprintf (command_str, "changelevel %s", gstate->mapName);
			sprintf (command_str, "map %s", gstate->mapName);
			gstate->ServerCmd (command_str);

			return;
		}
	}
	else if (hook->dflags & DFL_RESPAWN)
	{
		// failed to find an open respawn spot during last frame, so try again
		Client_Respawn (self);
	}

	/////////////////////////////////////////////////////////////
	//	follow client's killer if visible
	/////////////////////////////////////////////////////////////

	if (self->enemy)
	{
//		if (com->Visible (self, self->enemy))
//		{
			dir = Vector (self->enemy->s.origin) - Vector (self->s.origin);
			dir.Normalize ();
			com->VecToAngles (ang, dir);

			camera_set_viewangle (self, ang);
//		}
	}
}

//////////////////////////////////
//	Client_WarpThink
//
//	called when client is warping to move from node to node
//
//	self->enemy is node the client is heading for
//
//	FIXME: Move all this SETANGLE crap to client side!!
//////////////////////////////////

//	info_warp_destination spawnflags
#define		INFOWARP_SKIPTO		1
#define		INFOWARP_GLOW		2

void	Client_WarpThink (userEntity_t *self)
{
	float			dist, yaw_diff, pitch_diff, adj_pitch;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	trigHook_t		*thook = (trigHook_t *) self->enemy->userHook;
	userEntity_t	*target;
	Vector			temp, ang;

	dist = qv_dist (self->s.origin, self->enemy->s.origin);

	if (dist < 32.0 || self->teleport_time <= gstate->time)
	{
		if (self->enemy->target)
			target = com->FindTarget (self->enemy->target);

		if (!self->enemy->target || !target)
		{
			///////////////////////////////////////////////////////////////////
			//	End of warp path:
			//	if the goal has no target or the target entity is not found
			//	then it is the final destination so restore the player to normal
			///////////////////////////////////////////////////////////////////

			// have client ramp field of view back down to 90
			Client_SetFOVRamp (self, 120, 90);

			gstate->SetCameraState (self, false, 0);
			Client_ClearForceAngles (self);

			self->flags -= FL_INWARP;

			self->solid = SOLID_NOT;
			self->movetype = MOVETYPE_NONE;

			VectorCopy (self->enemy->s.origin, self->s.origin);
			gstate->LinkEntity (self);

			/////////////////////////////////////////////////////////
			//	start drawing models again
			//	set render_scale for all model parts to 0.1
			//	"unstick" all models from self, so stretching effect
			//	looks cooler
			/////////////////////////////////////////////////////////
			self->s.renderfx -= (self->s.renderfx & RF_NODRAW);
			com->SetVector (self->s.render_scale, 0.1, 0.1, 7.4);

			if (hook->dflags & DFL_HIERARCHICAL)
			{
				if (hook->legs)
				{
					hook->legs->s.renderfx -= (hook->legs->s.renderfx & RF_NODRAW);
					com->SetVector (hook->legs->s.render_scale, 0.1, 0.1, 7.4);
					com->SetVector (hook->legs->s.angles, 0, thook->mangle.y, 0.0);
//					gstate->hr_RemoveChild (self, hook->legs, false);

					if (hook->torso)
					{
						hook->torso->s.renderfx -= (hook->torso->s.renderfx & RF_NODRAW);
						com->SetVector (hook->torso->s.render_scale, 0.1, 0.1, 7.4);
						com->SetVector (hook->torso->s.angles, 0, thook->mangle.y, 0.0);
//						gstate->hr_RemoveChild (hook->legs, hook->torso, false);

						if (hook->head)
						{
							hook->head->s.renderfx -= (hook->head->s.renderfx & RF_NODRAW);
							com->SetVector (hook->head->s.render_scale, 0.1, 0.1, 7.4);
							com->SetVector (hook->head->s.angles, 0, thook->mangle.y, 0.0);
//							gstate->hr_RemoveChild (hook->torso, hook->head, false);
						}
						if (hook->weapon)
						{
							hook->weapon->s.renderfx -= (hook->weapon->s.renderfx & RF_NODRAW);
							com->SetVector (hook->weapon->s.render_scale, 0.1, 0.1, 7.4);
							com->SetVector (hook->weapon->s.angles, 0, thook->mangle.y, 0.0);
//							gstate->hr_RemoveChild (hook->torso, hook->weapon, false);
						}
					}
				}
			}

			//	set client's angles to direction of warp exit
			ang = Vector (0, thook->mangle.y, 0);
			self->flags |= FL_FIXANGLES;
			gstate->SetClientAngles (self, ang.vec ());			

			ang.AngleVectors (forward, right, up);

			temp = Vector (self->velocity);
			temp = temp.vlen () * -0.25 * forward;
			temp.z = 0;
			temp.set (self->velocity);
			self->viewheight = 22.0;

			//	turn of glow if glowing
			self->s.effects -= (self->s.effects & EF_DIMLIGHT);

			//	so exit node from teleporter/warp gets placed at actual exit!
			hook->last_origin = Vector (self->s.origin);

			com->CalcBoosts (self);

			gstate->sound (self, CHAN_BODY, "misc/warpend2.wav", 255, ATTN_NORM);

			////////////////////////////////////////////////////////////
			//	gib anything within clients bounding box
			////////////////////////////////////////////////////////////

			com->TeleFrag (self, 65535);

			////////////////////////////////////////////////////////////
			//	set up a think to handle rescaling of client
			////////////////////////////////////////////////////////////

			self->think = client_warp_appear;
			self->nextthink = gstate->time + 0.05;

			return;
		}
		else
		{
			///////////////////////////////////////////////////////////////////
			//	Go to a new path:
			//	reached a goal with a target, so go towards its target
			//	save the target for checking in the client's prethink
			///////////////////////////////////////////////////////////////////

			VectorCopy (self->enemy->s.origin, self->s.origin);
			gstate->LinkEntity (self);

			if (target->spawnflags & INFOWARP_GLOW)
				//	turn light on if spawnflag is set
				self->s.effects |= EF_DIMLIGHT;
			else
				//	turn light off if not set on this path corner
				self->s.effects -= (self->s.effects & EF_DIMLIGHT);

			//	if destination is not visible, then go straight to next path
			if (target->spawnflags & INFOWARP_SKIPTO)
			{
				// put player right on path corner
				VectorCopy (target->s.origin, self->s.origin);
				gstate->LinkEntity (self);

				// make player face target
				temp = Vector (self->s.origin) - Vector (target->s.origin);
				temp.vlen ();
				com->VecToAngles (ang, temp);

				gstate->SetClientAngles (self, ang.vec ());
				self->flags |= FL_FIXANGLES;

				self->teleport_time = gstate->time;
			}

			self->enemy = target;

			warp_setvalues (self, target);
		}
	}
	else
	{
		///////////////////////////////////////////////////////////////////////
		//	in transit to another info_warp_destination.  Compute view angles
		//	changes
		///////////////////////////////////////////////////////////////////////
		target = self->enemy;

		//	force player's velocity to max out
		temp = Vector (target->s.origin) - Vector (self->s.origin);
		temp.Normalize ();
		temp = temp * hook->run_speed;
		temp.set (self->velocity);
	}
}

/////////////////////////////////
//	Client_AnimateWeapon
//
//	animates the client's viewmodel each frame
//	skips frames if frame rate drops below 20
/////////////////////////////////

void	Client_AnimateWeapon (userEntity_t *self)
{
	userEntity_t *weapon=(userEntity_t *)self->client->ps.weapon;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	float			frame_remainder, temp;
	int curframe;
	int				add, new_frame;

	if (!weapon)
		return;

	// no frame changes if FRAME_STATIC is set
	if (!(hook->vm_frameFlags & FRAME_STATIC))
	{
		// increment frames according to length of last frame
		hook->vm_next_frameTime = hook->vm_next_frameTime + gstate->frametime;

		if (hook->vm_next_frameTime > hook->vm_frameTime)
		{
			temp = floor (hook->vm_next_frameTime / hook->vm_frameTime);
			add = (int)(hook->vm_frameInc * temp);
         curframe=weapon->s.frame;
         new_frame=curframe+add;

			///////////////////////////////////////////////////////////////////////
			//	set/clear FRAME_LAST flag if on last frame
			///////////////////////////////////////////////////////////////////////

            if (hook->vm_endFrame <= new_frame && hook->vm_endFrame > curframe)
				hook->vm_frameFlags |= FRAME_LAST;

			if (self->flags & FL_DEBUG)
			{
//				gstate->printxy (0, 80, "vm: sf=%i ef=%i wf=%i add=%i\n", hook->vm_startFrame, hook->vm_endFrame, self->weaponframe, add);
//				gstate->Con_Printf ("vm: ft=%.3f nft=%.3f\n", hook->vm_frameTime, hook->vm_next_frameTime);
//				gstate->Con_Printf ("vm: sf=%i ef=%i wf=%i add=%i\n", hook->vm_startFrame, hook->vm_endFrame, self->weaponframe, add);
			}

         // call special function on specific frame
         if ((hook->fxFrameFunc) && (weapon->s.frame<=hook->fxFrameNum) && (new_frame>=hook->fxFrameNum))
				hook->fxFrameFunc(self);

			///////////////////////////////////////////////////////////////////////
			//	set FRAME_PLAYSOUND flags if correct frame
			///////////////////////////////////////////////////////////////////////

            if (hook->sound1Frame >= curframe && hook->sound1Frame < new_frame)
				hook->vm_frameFlags = hook->vm_frameFlags | FRAME_PLAYSOUND1;
			if (hook->sound2Frame >= curframe &&  hook->sound2Frame < new_frame)
				hook->vm_frameFlags = hook->vm_frameFlags | FRAME_PLAYSOUND2;

			weapon->s.frame += add;
         curframe += add;

			// are we currently looping within a larger sequence?
			if (hook->vm_frameFlags & FRAME_REPEAT)
			{
				// determine if sequence needs to loop back to loopStart
				if (curframe > hook->vm_endLoop)
				{
					weapon->s.frame = hook->vm_startLoop;
					hook->vm_loopCount--;
					// if count is <= 0, stop the looping of this partial sequence
					if (hook->vm_loopCount <= 0)
					{
						hook->vm_frameFlags -= FRAME_REPEAT;
						hook->vm_startLoop = hook->vm_startFrame;
						hook->vm_endLoop = hook->vm_endFrame;
					}
				}
			}
			// going through an entire sequence
			else
			{
				// determine if sequence needs to loop back to start
				if (weapon->s.frame > hook->vm_endFrame)
				{
					if (hook->vm_frameFlags & FRAME_ONCE)
					{
						// we've gone through once, so set frames to static (no updates)
						hook->vm_frameFlags |= FRAME_STATIC;
						weapon->s.frame = hook->vm_endFrame;

						//	if a weapon switch, start bringing new weapon up
						if (hook->vm_frameFlags & FRAME_WEAPONSWITCH)
							hook->weapon_next (self);
					}
					else
						weapon->s.frame = hook->vm_startFrame;
				}
			}

			hook->vm_next_frameTime = hook->vm_next_frameTime - (temp * hook->vm_frameTime);
		}
	}
}

//////////////////////////////////
//	Client_PreThink
//
//	called each frame before the physics are run
//////////////////////////////////

void Client_PreThink (userEntity_t *self)
{
	userEntity_t	*went=(userEntity_t *)self->client->ps.weapon;
	float			cur_vel, v_diff, fog;
	int				pc1, hit_ratio, kill_ratio, ammo_count, i;
	ammo_t			*ammo;
	weapon_t		*weap = (weapon_t *) self->curWeapon;
	Vector			temp;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*ihook = (playerHook_t *) self->input_entity->userHook;
	mapNode_t		*node;
	char			fog_str [2];
	short			loopambient=false;

   if (went && (went->flags & FL_LOOPAMBIENT))
      loopambient=true;

	client_deathtype = NULL;

	/////////////////////////////////////////////////////////////////
	// handle ambient weapon anims
	/////////////////////////////////////////////////////////////////

   if ((hook->vm_frameFlags & FRAME_STATIC) && (gstate->time >= hook->attack_finished))
   {
      if ((self->velocity[0]==0 && self->velocity[1]==0 && self->velocity[2]==0) || loopambient)
      {
         if (gstate->time >= hook->ambient_time)
         {
            weap->command ((userInventory_t *)weap->ammo,"ambient",self);

            // if ambient_time wasn't changed, assume the command func doesn't handle
            // ambients ... so, don't call it anymore for this weapon
            if (gstate->time >= hook->ambient_time)
               hook->ambient_time=99999999;
         }
      }
      else if (gstate->time >= hook->ambient_time)          // this is so ambient doesn't always start
         hook->ambient_time=gstate->time+2.0+(rnd()*6);     // right when you stop moving
   }

	/////////////////////////////////////////////////////////////////
	// move camera
	/////////////////////////////////////////////////////////////////

	if (hook->camera)
	{
		cameraHook_t	*chook = (cameraHook_t *) hook->camera->userHook;

		if (chook->pre_think)
			chook->pre_think (hook->camera);
	}

	/////////////////////////////////////////////////////////////////
	//	check to see if client on a warp path
	//	frame updates don't happen in a warp
	/////////////////////////////////////////////////////////////////

	if (self->flags & FL_INWARP)
		return;

	/////////////////////////////////////////////////////////////////
	//	animate the client's player models and weapon
	///////////////////////////////////////////////////////////////

	if (self->input_entity != self)
	{
		//	animate the input entity's models
		if (ihook->dflags & DFL_HIERARCHICAL)
		{
			if (hook->head)
				ai_frame_update (ihook->head);
			if (hook->torso)
				ai_frame_update (ihook->torso);
			if (hook->legs)
				ai_frame_update (ihook->legs);
			if (hook->weapon)
				ai_frame_update (ihook->weapon);
		}
		else
			ai_frame_update (self);
	}
	else
	{
		if (hook->head)
			ai_frame_update (hook->head);
		if (hook->torso)
			ai_frame_update (hook->torso);
		if (hook->legs)
			ai_frame_update (hook->legs);
		if (hook->weapon)
			ai_frame_update (hook->weapon);
	}

	//////////////////////////////////////////////////////////////////
	//	animate the client's 1st person weapon
	//////////////////////////////////////////////////////////////////

	Client_AnimateWeapon (self);

	//////////////////////////////////////////////////////////////////
	//	choose correct animation based on movement
	//////////////////////////////////////////////////////////////////
	client_animation_think (self);

	//////////////////////////////////////////////////////////////////
	//	shake client view
	//////////////////////////////////////////////////////////////////

	if (hook->shake_time > gstate->time)
	{
		self->client->v_angle [0] += (int)(rnd ()* 20) - 10;
		self->client->v_angle [1] += (int)(rnd ()* 20) - 10;
		//	Q2FIXME:	change to Q2 network functions
		/*
		gstate->WriteByte (self, MSG_ONE, SVC_SETANGLE);
		gstate->WriteAngle (self, MSG_ONE, self->client->v_angle [0]);
		gstate->WriteAngle (self, MSG_ONE, self->client->v_angle [1]);
		gstate->WriteAngle (self, MSG_ONE, self->client->v_angle [2]);
		*/
	}

	//////////////////////////////////////////////////////////////////
	//	check for damage from contents
	//	check for smacking into ground/walls
	//////////////////////////////////////////////////////////////////

	Client_CheckInertialDamage (self);
	Client_CheckWaterDamage (self);
	
	//	drowning can kill player, so don't go on
	if (self->deadflag == DEAD_DEAD)
		return;

	//////////////////////////////////////////////////////////////////
	//	check for jumping out of water
	//////////////////////////////////////////////////////////////////

	if (self->waterlevel == 2)
		Client_CheckWaterJump (self);

	//////////////////////////////////////////////////////////////////
	//	fog stuff
	//	FIXME: someone broke this fog stuff
	//////////////////////////////////////////////////////////////////
	if (gstate->foglevel)
	{
		// check for water and turn fog off if we are in it
		// or on if we have just come out

		temp = Vector (self->s.origin) + Vector (self->view_ofs);
		temp.z += self->velocity [2] / 25;
		pc1 = gstate->PointContents (temp.vec ());

/*
// TODO: change this to use the new fog stuff
		if (pc1 != CONTENTS_EMPTY && hook->fog_on)
		{
			hook->fog_value = gstate->GetCvar ("r_fogvalue");
			gstate->SetCvar ("r_drawfog", "0");
			hook->fog_on = FALSE;
			gstate->foglevel = 1;
		}
		else if (!hook->fog_on && pc1 == CONTENTS_EMPTY)
		{
			hook->fog_on = TRUE;
			gstate->SetCvar ("r_drawfog", "1");
		}
*/
	}

	//////////////////////////////////////////////////////////////////
	//	check jump button
	//	Q2:	uses the pmove_t in the
	//////////////////////////////////////////////////////////////////

//	if (self->button2)
//		Client_Jump (self);

	//////////////////////////////////////////////////////////////////
	//	show debugging info
	//////////////////////////////////////////////////////////////////
	if (gstate->GetCvar ("vid_mode") > 3)
		pc1 = 461;
	else
		pc1 = 221;

//	if (hook->dflags & DFL_SHOWSTATS)
//	{

		hit_ratio = 0;
		if (hook->fired)
			hit_ratio = (int) floor ((hook->hit / hook->fired) * 100.0);
		kill_ratio = 0;
		if (hook->died)
			kill_ratio = (int)floor ((hook->killed / hook->died) * 100.0);

		weap = (weapon_t *) self->curWeapon;

		ammo_count = 0;
		if (weap)
		{
			if (weap->ammo)
			{
				ammo = weap->ammo;
				ammo_count = ammo->count;
			}
		}

//		gstate->printxy (0, pc1 - 18, "VEL %i MAXSPEED %.2f", (int)qv_len (self->velocity), self->max_speed);
//		gstate->printxy (0, pc1 - 9, "%i H/R %i K/R AMMO %i ", hit_ratio, kill_ratio, ammo_count);
//		gstate->printxy (0, pc1, "%i HEALTH %i ARMOR %i FRAGS", (int)self->health, (int) hook->armor_val, (int)self->frags);

//	}

	//////////////////////////////////////////////////////////////////
	//	node placement
	//////////////////////////////////////////////////////////////////

	node_place_node (self->input_entity, ihook->node_list, ihook->last_origin.vec ());
	ihook->last_origin = Vector (self->input_entity->s.origin);

	// determine if the player/bot has left the ground between nodes
	// used for determining when to link nodes back
	if (!(self->input_entity->flags & FL_ONGROUND) && !(self->input_entity->flags & FL_PARTIALGROUND))
		ihook->off_ground = TRUE;

	//////////////////////////////////////////////////////////////////
	//	manage hierarchical models
	//////////////////////////////////////////////////////////////////

	//	rotate visible head model to same yaw and pitch as self
	//	rotate head yaw to self's yaw
	hr_head_follow (self, hook->head);

	//	head/torso
	hr_yaw_follow (hook->head, hook->torso, MAX_HEAD_TWIST);

	//	torso/legs
	hr_yaw_follow (hook->torso, hook->legs, MAX_TORSO_TWIST);

	hr_torso_pitch (self, hook->torso, MAX_HEAD_TILT);

	//	find direction legs should face based on velocity
	hr_snap_legs (self, hook->legs);

//	ai_orient_to_gravity (self);
}

//////////////////////////////////
//	Client_CheckPowerUps
//
//	called each frame before the physics are run
//////////////////////////////////

void Client_CheckPowerUps (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	int				recalc = FALSE;

	// exp_flags - set for each item when picked up and cleared when the item's expire message is sent
	// items - set when an item is picked up and cleared when the item becomes inactive
	// xx_time - set when an item is picked up to number of seconds till item expires

	////////////////////////////////////////
	// poison
	////////////////////////////////////////

	if (hook->exp_flags & EXP_POISON && hook->poison_time <= gstate->time + 3.0)
	{
		//	poison expiration sound and message
		gstate->sound (self, CHAN_VOICE, "artifacts/poison/poisonfade.wav", 255.0, ATTN_NORM);
		client_deathtype = "poison";
		gstate->sprint (self, "You feel the poison leaving your system.\n");
		hook->exp_flags -= (hook->exp_flags & EXP_POISON);
	}
	if (hook->items & IT_POISON)
	{
		//	do damage
		if (hook->poison_next_damage_time <= gstate->time)
		{
			gstate->Con_Printf ("poisoning\n");
			com->Damage (self, self, self, zero_vector, zero_vector, hook->poison_damage, DAMAGE_POISON | DAMAGE_NO_BLOOD);
			hook->poison_next_damage_time = gstate->time + hook->poison_interval;
		}
		//	poison expire
		if (hook->poison_time < gstate->time)
			hook->items -= (hook->items & IT_POISON);
	}

	////////////////////////////////////////
	// oxylung
	////////////////////////////////////////

	if (hook->oxylung_time <= gstate->time + 3.0 && (hook->exp_flags & EXP_OXYLUNG))
	{
		//	oxylung expiration sound and message
		gstate->sound (self, CHAN_VOICE, "artifacts/oxylung/oxylungfade.wav", 255.0, ATTN_NORM);
		gstate->sprint (self, "Your oxylungs are almost used up.\n");
		hook->exp_flags -= (hook->exp_flags & EXP_OXYLUNG);
	}
	if (hook->items & IT_OXYLUNG)
	{
		//	oxylung expire
		if (hook->oxylung_time < gstate->time)
			hook->items -= (hook->items & IT_OXYLUNG);
	}

	////////////////////////////////////////
	// envirosuit
	////////////////////////////////////////

	if (hook->envirosuit_time <= gstate->time + 3.0 && (hook->exp_flags & EXP_ENVIROSUIT))
	{
		//	envirosuit expiration sound and message
		gstate->sound (self, CHAN_VOICE, "artifacts/envirosuit/envirosuitfade.wav", 255.0, ATTN_NORM);
		gstate->sprint (self, "Your envirosuit is expiring.\n");
		hook->exp_flags = hook->exp_flags - (hook->exp_flags & EXP_ENVIROSUIT);
	}
	if (hook->items & IT_ENVIROSUIT)
	{
		//	envirosuit expires
		if (hook->envirosuit_time < gstate->time)
			hook->items -= (hook->items & IT_ENVIROSUIT);
	}

	////////////////////////////////////////
	// megashield
	////////////////////////////////////////

	if (hook->megashield_time <= gstate->time + 3.0 && (hook->exp_flags & EXP_MEGASHIELD))
	{
		//	megashield expiration sound and message
		gstate->sprint (self, "Green Elf needs food badly.\n");
		gstate->sound (self, CHAN_AUTO, "artifacts/megashield/megafade.wav", 128, ATTN_NORM);
		hook->exp_flags = hook->exp_flags - (hook->exp_flags & EXP_MEGASHIELD);
	}
	if (hook->items & IT_MEGASHIELD)
	{
		//	megashield expires
		if (hook->envirosuit_time < gstate->time)
			hook->items -= (hook->items & IT_MEGASHIELD);
	}

	////////////////////////////////////////
	// wraithorb
	////////////////////////////////////////

	if (hook->items & IT_WRAITHORB)
	{
		if (hook->wraithorb_time >= gstate->time)
		{
			float	speed;

			speed = qv_len (self->velocity);

			// set translucency for player based on velocity
			// set to correct player model
			if (speed < 1.0)
				self->modelName = NULL;
			else
				self->modelName = "models/hiro.mdl";

			if (speed < 106.6)
				self->translucent = 1;
			else if (speed < 213.3)
				self->translucent = 2;
			else
				self->translucent = 3;
		}
		else
		{
			//	wraithorb expires
			hook->items = hook->items - (hook->items & IT_WRAITHORB);
			self->translucent = 0;
		}

		if (hook->wraithorb_time <= gstate->time + 3.0 && hook->exp_flags & IT_WRAITHORB)
		{
			//	wraithorb expiration sound and message
			gstate->sound (self, CHAN_AUTO, "artifacts/wraithorb/wraithorbfade.wav", 128, ATTN_NORM);
			gstate->sprint (self, "The Wraith Orb is waning.\n");
			hook->exp_flags -= (hook->exp_flags & IT_WRAITHORB);
		}
	}

	/////////////////////////////////////////////
	// stat boosters
	/////////////////////////////////////////////

	if (hook->items & IT_POWERBOOST)
	{
		if (hook->power_boost_time <= gstate->time + 3.0 && hook->exp_flags & EXP_POWERBOOST)
		{
			gstate->sprint (self, "You are becoming a girly man.\n");
			hook->exp_flags -= (hook->exp_flags & (EXP_POWERBOOST));
		}

		if (hook->power_boost_time <= gstate->time)
		{
			hook->items -= (hook->items & IT_POWERBOOST);
			hook->power_boost -= 3;
			recalc = TRUE;
		}
	}
	if (hook->items & IT_ATTACKBOOST)
	{
		if (hook->attack_boost_time <= gstate->time + 3.0 && hook->exp_flags & EXP_ATTACKBOOST)
		{
			gstate->sprint (self, "Your attack boost is running out.\n");
			hook->exp_flags -= (hook->exp_flags & (EXP_ATTACKBOOST));
		}

		if (hook->attack_boost_time <= gstate->time)
		{
			hook->items -= (hook->items & IT_ATTACKBOOST);
			hook->attack_boost -= 3;
			recalc = TRUE;
		}
	}
	if (hook->items & IT_SPEEDBOOST)
	{
		if (hook->speed_boost_time <= gstate->time + 3.0 && hook->exp_flags & EXP_SPEEDBOOST)
		{
			gstate->sprint (self, "Your speed boost is running out.\n");
			hook->exp_flags -= (hook->exp_flags & (EXP_SPEEDBOOST));
		}

		if (hook->speed_boost_time <= gstate->time)
		{
			hook->items -= (hook->items & IT_SPEEDBOOST);
			hook->speed_boost -= 3;
			recalc = TRUE;
		}
	}
	if (hook->items & IT_ACROBOOST)
	{
		if (hook->acro_boost_time <= gstate->time + 3.0 && hook->exp_flags & EXP_ACROBOOST)
		{
			gstate->sprint (self, "White men can't jump.\n");
			hook->exp_flags -= (hook->exp_flags & (EXP_ACROBOOST));
		}

		if (hook->acro_boost_time <= gstate->time)
		{
			hook->items -= (hook->items & IT_ACROBOOST);
			hook->acro_boost -= 3;
			recalc = TRUE;
		}
	}
	if (hook->items & IT_VITABOOST)
	{
		if (hook->vita_boost_time <= gstate->time + 3.0 && hook->exp_flags & EXP_VITABOOST)
		{
			gstate->sprint (self, "Your vita boost is running out.\n");
			hook->exp_flags -= (hook->exp_flags & (EXP_VITABOOST));
		}

		if (hook->vita_boost_time <= gstate->time)
		{
			hook->items -= (hook->items & IT_VITABOOST);
			hook->vita_boost -= 3;
			recalc = TRUE;
		}
	}

	if (recalc) com->CalcBoosts (self);
}

/*
==============
Client_PostThink

called each frame before the physics are run
===============
*/

void Client_PostThink (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	// camera posthink
	if (hook->camera)
	{
		cameraHook_t	*chook = (cameraHook_t *) hook->camera->userHook;

		if (chook->post_think)
			chook->post_think (hook->camera);
	}

	if (self->flags & FL_INWARP)
		return;

	if (self->deadflag != DEAD_NO)
		return;

	//	Q2:	now done in dll_ClientThink
//	Client_Attack (self);

	Client_CheckPowerUps (self);

/// moved to friction hook
//	hook->last_water_type = self->watertype;
//	hook->last_water_level = self->waterlevel;
}

/*
==============
Client_DeathMessage

called from com->Damage when a player is killed
===============
*/

void	Client_DeathMessage (userEntity_t *self)
{
	playerHook_t	*ahook, *hook;

	// set up killer info for passing to weapon command
	killerInfo.attacker = gstate->attacker;
	killerInfo.victim = self;

	if (gstate->attacker)
	{
		if (client_deathtype)
		{
			if (!stricmp (client_deathtype, "fell"))
				gstate->bprint ("%s cracked his skull.\n", self->netname);
			else if (!stricmp (client_deathtype, "drowned"))
				gstate->bprint ("%s sucked it down(TM).\n", self->netname);
			else if (!stricmp (client_deathtype, "lava"))
				gstate->bprint ("%s goes crispy.\n", self->netname);
			else if (!stricmp (client_deathtype, "slime"))
				gstate->bprint ("%s was slimed.\n", self->netname);
			else if (!stricmp (client_deathtype, "poison"))
				gstate->bprint ("%s got the rot.\n", self->netname);
		}
		// player killed himself
		else if (gstate->attacker == self && gstate->attacker->flags & FL_CLIENT)
		{
			hook = (playerHook_t *) self->userHook;
			ahook = (playerHook_t *) gstate->attacker->userHook;

			// lose a frag, stooopid
			self->frags--;
			hook->died++;

			if (ahook->weapon_fired)
			{
				if (ahook->weapon_fired->command)
					ahook->weapon_fired->command (ahook->weapon_fired, "suicide_msg", &killerInfo);
			}
		}
		// player was killed by another player
		else if (gstate->attacker != self && gstate->attacker->flags & FL_CLIENT)
		{
			hook = (playerHook_t *) self->userHook;
			ahook = (playerHook_t *) gstate->attacker->userHook;

			gstate->attacker->frags++;
			hook->died++;
			ahook->killed++;

			ahook = (playerHook_t *) gstate->attacker->userHook;
			if (ahook->weapon_fired)
			{
				if (ahook->weapon_fired->command)
					ahook->weapon_fired->command (ahook->weapon_fired, "death_msg", &killerInfo);
			}
		}
	}

}

/*
=============
Client_TouchBackpack

queries the model for start and end frames
=============
*/

static void Client_TouchBackpack (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	userInventory_t	*invItem, *dup;
	ammo_t			*ammo;

	if (!com->ValidTouch (self, other))
		return;

	// go through the backpack's inventory list and add each item in it to the user's inventory
	for (invItem = gstate->InventoryFirstItem (self->inventory); invItem != NULL; invItem = gstate->InventoryNextItem (self->inventory))
	{
		gstate->Con_Dprintf ("invItem is %s\n", invItem->name);
		// check for a duplicate of this item in toucher's pack
		dup = gstate->InventoryFindItem (other->inventory, invItem->name);

		if (!dup)
		{
			// put item in inventory of whoever touched
			if (!gstate->InventoryAddItem (other->inventory, invItem))
				gstate->Con_Printf ("Couldn't put backpack item in inventory\n");
			else if (other->flags & FL_CLIENT)
			{
				gstate->sprint (other, "You got a ");
				gstate->sprint (other, invItem->name);
				gstate->sprint (other, "\n");
			}
		}
		// if it is a duplicate and it is ammo just add the packs ammo count to it
		else if (strstr (dup->name, "ammo_"))
		{
			ammo = (ammo_t *) dup;
			// tell ammo item to add rounds to itself
			invItem->command (invItem, "give_ammo", &ammo->count);
		}

		// remove item from backpack's inventory list
		gstate->InventoryDeleteItem (self->inventory, invItem);
	}

	gstate->RemoveEntity (self);
}

/*
=============
Client_DropBackpack

queries the model for start and end frames
=============
*/

static void Client_DropBackpack (userEntity_t *self)
{
	userEntity_t	*pack;

	//	FIXME:	should player throw a backpack at all?
	return;

	if (!self->inventory)
		return;

	pack = gstate->SpawnEntity ();

	pack->movetype = MOVETYPE_TOSS;
	pack->solid = SOLID_TRIGGER;
	pack->owner = self;
	pack->touch = Client_TouchBackpack;

	gstate->SetModel (pack, "models/backpack.mdl");
	gstate->SetSize (pack, -16, -16, 0, 16, 16, 56);
	gstate->SetOrigin (pack, self->s.origin [0], self->s.origin [1], self->s.origin [2]);

	pack->velocity [2] = 200.0;
	pack->velocity [0] = rnd () * 400 - 200;
	pack->velocity [1] = rnd () * 400 - 200;

	pack->inventory = self->inventory;
	self->inventory = NULL;
}

/////////////////////////////
//	Client_Die
//
//	called from com->Damage when a player is killed
/////////////////////////////

#define	PLAYER_GIB_POINT	20
#define	BODY_GIB_POINT		40
#define	MAX_GIBS			8

void Client_Die (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *head_hook, *torso_hook, *weapon_hook;
	userEntity_t	*head;
	float			r;
	int				i;

	//////////////////////////////////////////////////////////
	//	drop weapon and print death message
	//////////////////////////////////////////////////////////

	if (self->deadflag == DEAD_NO)
	{
		Client_DropBackpack (self);
		Client_DeathMessage (self);
		client_death_frames (self);
	}

	self->solid = SOLID_NOT;
	self->flags = self->flags - (self->flags & FL_ONGROUND);
	self->movetype = MOVETYPE_BOUNCE;

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

	//////////////////////////////////////////////////////////
	// short delay before a player can respawn by jumping
	//////////////////////////////////////////////////////////

	hook->jump_time = gstate->time + 0.5;

	if (self->deadflag == DEAD_NO)
	{
		if (self->waterlevel > 2)
			gstate->sound (self, CHAN_VOICE, "player/playerwaterdeath.wav", 255, ATTN_NORM);
		else if (self->health < -40)
			gstate->sound (self, CHAN_VOICE, "player/udeath.wav", 255, ATTN_NORM);
		else
		{
			r = int(rnd () * 4) + 1;

			if (r == 1)
				gstate->sound (self, CHAN_VOICE, "player/playerdeath1.wav", 255, ATTN_NORM);
			else if (r == 2)
				gstate->sound (self, CHAN_VOICE, "player/playerdeath2.wav", 255, ATTN_NORM);
			else if (r == 3)
				gstate->sound (self, CHAN_VOICE, "player/playerdeath3.wav", 255, ATTN_NORM);
			else if (r == 4)
				gstate->sound (self, CHAN_VOICE, "player/playerdeath4.wav", 255, ATTN_NORM);
		}

		if (!hook->camera)
			camera_start (self);

		///////////////////////////////////////////////////////////////////////
		//	start death sequences for hierarchical models
		///////////////////////////////////////////////////////////////////////
		if (hook->dflags & DFL_HIERARCHICAL)
		{
			leg_hook = (playerHook_t *) hook->legs->userHook;
			head_hook = (playerHook_t *) hook->head->userHook;
			torso_hook = (playerHook_t *) hook->torso->userHook;
			weapon_hook = (playerHook_t *) hook->weapon->userHook;

			head = hook->head;			

			if (damage >= PLAYER_GIB_POINT)
			{
				head = hr_gib (self);
	
				self->deadflag = DEAD_DEAD;
			}
			else
			{
				i = (int) floor (rnd () * hook->sequence_max [SEQUENCE_DEATH]);

				if (hook->head)
				{
					ai_init_sequence (hook->head, head_hook->frames->death [i], SEQUENCE_DEATH, i);
					head_hook->frame_flags = FRAME_ONCE;
				}
				if (hook->legs)
				{
					ai_init_sequence (hook->legs, leg_hook->frames->death [i], SEQUENCE_DEATH, i);
					leg_hook->frame_flags = FRAME_ONCE;
				}
				if (hook->torso)
				{
					ai_init_sequence (hook->torso, torso_hook->frames->death [i], SEQUENCE_DEATH, i);
					torso_hook->frame_flags = FRAME_ONCE;
				}
				if (hook->weapon)
				{
					ai_init_sequence (hook->weapon, weapon_hook->frames->death [i], SEQUENCE_DEATH, i);
					weapon_hook->frame_flags = FRAME_ONCE;
				}

				self->deadflag = DEAD_DYING;
			}
		}
		///////////////////////////////////////////////////////////////////////
		//	start death sequences for non-hierarchical models
		///////////////////////////////////////////////////////////////////////
		else
		{
			head = self;

			if (damage >= PLAYER_GIB_POINT)
			{
				ai_throw_gib (self, gib_names [0], Vector (0, 0, self->s.maxs [2] * 0.5));

				for (i = 0; i < (int) (gstate->damage_inflicted / 10) && i < MAX_GIBS; i++)
				{
					if (rnd () < 0.5)
						ai_throw_gib (self, gib_names [4], Vector (rnd () * 16 - 8, rnd () * 16 - 8, rnd () * 16 - 8));
					else
						ai_throw_gib (self, gib_names [1], Vector (rnd () * 16 - 8, rnd () * 16 - 8, rnd () * 16 - 8));
				}
	
				self->deadflag = DEAD_DEAD;
			}
			else
			{
				i = (int) floor (rnd () * hook->sequence_max [SEQUENCE_DEATH]);

				ai_init_sequence (self, hook->frames->death [i], SEQUENCE_DEATH, i);
				hook->frame_flags = FRAME_ONCE;
			
				self->deadflag = DEAD_DYING;
			}
		}

		camera_set_target (hook->camera, head);
		camera_mode (self, 99);

		if (!gstate->deathmatch)
		{
			gstate->SetCvar ("sv_frametime", "0.25");
			//	don't allow restart for a few seconds
			self->delay = gstate->time + 4.0;
		}
		else
			//	don't allow restart for a bit
			self->delay = gstate->time + 1.0;
		self->client->latched_buttons = 0;
	}
	else if (self->deadflag == DEAD_DYING)
	{
		if (damage >= BODY_GIB_POINT)
		{
			if (hook->dflags & DFL_HIERARCHICAL)
				hr_gib (self);
			else
				for (i = 0; i < (int) (gstate->damage_inflicted / 10) && i < MAX_GIBS; i++)
				{
					ai_throw_gib (self, gib_names [0], Vector (rnd () * 16 - 8, rnd () * 16 - 8, rnd () * 16 - 8));
				}

			self->deadflag = DEAD_DEAD;
		}
		else
		{
			i = (int) floor (rnd () * hook->sequence_max [SEQUENCE_DEATH]);

			ai_init_sequence (self, hook->frames->death [i], SEQUENCE_DEATH, i);
			hook->frame_flags = FRAME_ONCE;

			self->deadflag = DEAD_DEAD;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
//	Client_Pain
//
//	initializes the client data structures
///////////////////////////////////////////////////////////////////////////////

void Client_Pain (userEntity_t *self, userEntity_t *other, float kick, int damage)
{
	float			r;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	if (hook->pain_finished > gstate->time)
		return;

	r = floor (rnd () * 4) + 1;

	if (r == 1)
	{
		gstate->sound (self, CHAN_VOICE, "player/playerpain1.wav", 255.0, ATTN_NORM);
		hook->pain_finished = gstate->time + 1.0;
	}
	else if (r == 2)
	{
		gstate->sound (self, CHAN_VOICE, "player/playerpain2.wav", 255.0, ATTN_NORM);
		hook->pain_finished = gstate->time + 1.0;
	}
	else if (r == 3)
	{
		gstate->sound (self, CHAN_VOICE, "player/playerpain3.wav", 255.0, ATTN_NORM);
		hook->pain_finished = gstate->time + 1.0;
	}
	else
	{
		gstate->sound (self, CHAN_VOICE, "player/playerpain4.wav", 255.0, ATTN_NORM);
		hook->pain_finished = gstate->time + 1.0;
	}
}

///////////////////////////////////////////////////////////////////////////////
//
//
///////////////////////////////////////////////////////////////////////////////

void	give_weapons (userEntity_t *self)
{
	userEntity_t	*head;
	int				i;

	//	Q2FIXME:	world doesn't have epairs set up right or something
//	return;

	if (strstr (gstate->mapName, "e1"))
	{
		gstate->sprint (self->owner, "Giving Disruptor Rod.\n");
		gstate->ServerCmd ("weapon_disruptor\n");

		gstate->sprint (self->owner, "Giving ION Blaster.\n");
		gstate->ServerCmd ("weapon_ionblaster\n");

		gstate->sprint (self->owner, "Giving Sidewinder.\n");
		gstate->ServerCmd ("weapon_sidewinder\n");

		gstate->sprint (self->owner, "Giving Shockwave.\n");
		gstate->ServerCmd ("weapon_shockwave\n");

		gstate->ServerCmd ("select_sidewinder\n");
	}
	else if (strstr (gstate->mapName, "e2"))
	{
		gstate->sprint (self->owner, "Giving Discus of Daedalus.\n");
		gstate->ServerCmd ("weapon_discus\n");

		gstate->sprint (self->owner, "Giving Venomous.\n");
		gstate->ServerCmd ("weapon_venomous\n");

		gstate->sprint (self->owner, "Giving Poseidon's Trident.\n");
		gstate->ServerCmd ("weapon_trident\n");

		gstate->ServerCmd ("select_discus\n");
	}
	else if (strstr (gstate->mapName, "e3"))
	{
		gstate->sprint (self->owner, "Giving Silver Claw.\n");
		gstate->ServerCmd ("weapon_silverclaw\n");

		gstate->sprint (self->owner, "Giving Bolter.\n");
		gstate->ServerCmd ("weapon_bolter\n");

		gstate->sprint (self->owner, "Giving Nharre's Nightmare.\n");
		gstate->ServerCmd ("weapon_nightmare\n");

		gstate->ServerCmd ("select_nightmare\n");
	}
	else if (strstr (gstate->mapName, "e4"))
	{
		gstate->sprint (self->owner, "Giving Glock.\n");
		gstate->ServerCmd ("weapon_glock\n");

		gstate->sprint (self->owner, "Giving Kineticore.\n");
		gstate->ServerCmd ("weapon_kcore\n");

		gstate->sprint (self->owner, "Giving Ripgun.\n");
		gstate->ServerCmd ("weapon_ripgun\n");

		gstate->sprint (self->owner, "Giving Novabeam.\n");
		gstate->ServerCmd ("weapon_novabeam\n");

		gstate->ServerCmd ("select_glock\n");
	}

	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	dll_PutClientInServer
//
//	initializes the client data structures
//	Q2FIXME:	clean this up
///////////////////////////////////////////////////////////////////////////////

void dll_PutClientInServer(userEntity_t *self)
{
	userEntity_t	*spot;
	playerHook_t	*hook;
	Vector			temp;
	char			fog [2];
	float			time_in_air;

	gclient_t		*client;
	int				i, index;

	/////////////////////////////////////////////////////////
	//	find the spawn point
	/////////////////////////////////////////////////////////

	spot = Client_GetSpawnPoint (self);

	if (!spot)
	{
		if (gstate->deathmatch)
			com->Error ("No info_player_deathmatch spawn points.");
		else
			com->Error ("No info_player_start spawn points.");
	}
	else
	{
		com->CopyVector (self->s.origin, spot->s.origin);
		com->CopyVector (self->s.angles, spot->s.angles);
	}

	///////////////////////////////////////////////////////////////////////////
	//	Q2 stuff
	///////////////////////////////////////////////////////////////////////////

	index = self - gstate->g_edicts - 1;
	client = self->client = &gstate->game->clients [index];

	//	Q2FIXME:	handle persistent data for client

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

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

	client->ps.fov = 90;
	//	Q2FIXME:	necessary??!?!
//	client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);

	self->s.effects = 0;
	//	Q2FIXME:	should base skin on entity number
	self->s.skinnum = 0;
//	self->s.modelindex = 255;			// will use the skin specified model
//	self->s.modelindex2 = 255;			// custom gun model
	self->s.frame = 0;
	self->s.origin[2] += 1;				// make sure off ground

	gstate->SetClientAngles (self, spot->s.angles);
	self->flags |= FL_FIXANGLES;
	self->clipmask = MASK_PLAYERSOLID;

	gstate->LinkEntity (self);

	///////////////////////////////////////////////////////////////////////////
	//	end Q2 stuff
	///////////////////////////////////////////////////////////////////////////

	self->className = "player";

	//	set the view offset
	//	Q2FIXME:	change to a vector
	self->viewheight = 22.0;

	gstate->SetSize (self, -16, -16, -24, 16, 16, 32);

	//	set up physics data
	self->movetype = MOVETYPE_WALK;
	self->solid    = SOLID_SLIDEBOX;
	self->flags    |= FL_CLIENT + FL_PUSHABLE;

	self->elasticity = 1.0;
	self->mass = 1.0;

	self->health = 100;
	self->takedamage = DAMAGE_AIM;
	self->deadflag = DEAD_NO;

	//	set up prethink procedure
	self->prethink = Client_PreThink;
	self->postthink = Client_PostThink;
	self->pain = Client_Pain;
	self->die = Client_Die;
	com->ZeroVector (self->client->v_angle);
	com->SetVector (self->ang_speed, 90, 90, 90);

	//	allocate hook structure
	self->userHook = com->malloc_hook (sizeof (playerHook_t), MEM_HUNK);
	hook = (playerHook_t *) self->userHook;

	//	initialize hook variables
	hook->jump_time = 0.0;
//	hook->chinup_time = 0.0;
	hook->attack_finished = 0.0;
	hook->dflags = DFL_GOOD;
	hook->max_health = 100;
	hook->type = TYPE_SUPERFLY;

	hook->fov_desired = 90.0;
	hook->fov_rate = 0.0;

	////////////////////////////////////////////////////////////////////
	//	create the "treasure" list for the player/bot that records
	//	locations containing valuable items
	////////////////////////////////////////////////////////////////////
	hook->skat_list = NULL;

	hook->node_list = node_create_nodelist (client_nodes);
	node_place_spawn_node (self, hook->node_list);
	gstate->Con_Dprintf ("player node_count = %i\n", client_nodes->node_count);

	//	clear any messages that might remain between level changes
	gstate->clearxy ();

	//	FIXME: when player is put in server restore his inventory
	self->curWeapon = NULL;

	////////////////////////////////////////////////////////////////////
	//	setup inventory list for this player
	//	inventory is stored in server code
	////////////////////////////////////////////////////////////////////
    self->inventory = gstate->InventoryNew();

	///////////////////////////////////////////////////////////////////
	// get frame info from model
	///////////////////////////////////////////////////////////////////

	// FIXME: change to ai_get_sequences when frame names are standardized

	hook->frameInc = 1;
	hook->frameTime = 0.05;
	hook->next_frameTime = gstate->time + hook->frameTime;

	hook->vm_frameInc = 1;
	hook->vm_frameTime = 0.05;
	hook->vm_next_frameTime = gstate->time + hook->vm_frameTime;

	hook->frames = (sequenceTable_t *) com->malloc_hook (sizeof (sequenceTable_t), MEM_MALLOC);

	ai_get_sequences (self);

/*
	if (gstate->foglevel)
	{
		hook->fog_on = TRUE;
		sprintf (fog, "%i", gstate->foglevel);
		gstate->SetCvar ("r_drawfog", "1");
		gstate->SetCvar ("r_fogvalue", fog);
	}
*/

	//////////////////////////////////////////////////////////////////////
	//	set up deathmatch statistics
	//////////////////////////////////////////////////////////////////////

	hook->killed = 0;
	hook->died = 0;
	hook->fired = 0;
	hook->hit = 0;
	hook->dflags = hook->dflags | DFL_SHOWSTATS;

	//////////////////////////////////////////////////////////////////////
	//	set up attribute modifiers
	//////////////////////////////////////////////////////////////////////

	hook->power_boost = 0;
	hook->attack_boost = 0;
	hook->speed_boost = 0;
	hook->acro_boost = 0;
	hook->vita_boost = 0;

	hook->power_boost_time = 0;
	hook->attack_boost_time = 0;
	hook->speed_boost_time = 0;
	hook->acro_boost_time = 0;
	hook->vita_boost_time = 0;
	hook->drown_time = gstate->time + 12.0;

	hook->fatigue = 0;

	////////////////////////////////////////////////////////////////////
	//	give player correct weapons for episode
	//
	//	sets up a think entity on the first frame to give weapons to this client
	//	FIXME: change to a thinkFunc instead of actual entity think
	////////////////////////////////////////////////////////////////////

	userEntity_t *th;
	th= gstate->SpawnEntity ();
	th->solid = SOLID_NOT;
	th->movetype = MOVETYPE_NONE;
	th->nextthink = gstate->time + 0.1;
	th->think = give_weapons;
	th->owner = self;

	////////////////////////////////////////////////////////////////////
	//	set running speed and max jump dist
	////////////////////////////////////////////////////////////////////

	self->max_speed = 1.0;//ATTR_RUNSPEED;
	hook->upward_vel = 270.0;
	hook->max_jump_dist = 216.0;
/*
	time_in_air = (2.0 * hook->upward_vel) / 800.0;
	hook->max_jump_dist = time_in_air * (self->max_speed * 320.0);
*/

	////////////////////////////////////////////////////////////////////
	//	set view entity and input entity for this client == self
	////////////////////////////////////////////////////////////////////

	gstate->SetClientViewEntity (self, self);
	gstate->SetClientInputEntity (self, self);

	////////////////////////////////////////////////////////////////////
	//	set up hierarchical models
	////////////////////////////////////////////////////////////////////

	hr_init_models (self);

	//	tell the client what models not to draw for this player
	com->SetClientModels (self, hook->head, hook->torso, hook->legs, hook->weapon);

	self->s.effects = EF_GIB;        // this makes server send client info to the clients
	self->s.renderfx = RF_NODRAW;
	gstate->LinkEntity (self);
	client_begin_stand (self);

	////////////////////////////////////////////////////////////////////
	//	add to actor_list client_bot_list, and client_list
	////////////////////////////////////////////////////////////////////

	alist_add (actor_list, self);
	alist_add (client_bot_list, self);
	alist_add (client_list, self);

	//	add one to running total of clients
	cur_clients++;

	hook->ID = com->GenID ();
}

///////////////////////////////////////////
//	dll_ClientDisconnect
//
//	called when a client disconnects from the server
///////////////////////////////////////////

void dll_ClientDisconnect(userEntity_t *self)
{
	////////////////////////////////////////////////////////////////////
	//	remove client from actor_list, client_bot_list, and client_list
	////////////////////////////////////////////////////////////////////

	alist_remove (actor_list, self);
	alist_remove (client_bot_list, self);
	alist_remove (client_list, self);

	gstate->UnlinkEntity (self);
	self->s.modelindex = 0;
	self->solid = SOLID_NOT;
	gstate->LinkEntity (self);

	self->className = "disconnected";
	self->inuse = FALSE;

	cur_clients--;
}

///////////////////////////////////////////////////////////////////////////////
//	dll_ClientUserInfoChanged
//
///////////////////////////////////////////////////////////////////////////////

void	dll_ClientUserinfoChanged(edict_t *ent, char *userinfo)
{
}

///////////////////////////////////////////////////////////////////////////////
//	dll_ClientConnect
//
///////////////////////////////////////////////////////////////////////////////

int		dll_ClientConnect (userEntity_t *self, void *userinfo, int loadgame)
{
	//	Q2FIXME:	check banned ip list and password in userinfo

	//	Q2FIXME:	check loadgame, init respawn and persistent data

	//	Q2FIXME:	call ClientUserinfoChanged

	//	Print connect message if there is already someone connected
	//	Q2FIXME:	init game sturcture
//	if (game.maxclients > 1)
		gstate->Con_Printf ("%s connected\n", self->netname);

	//	Q2FIXME:	were is level variable?  Make this part of serverState_t
	//	Q2FIXME:	is incrementing level.players really necessary?
//	level.players++;

	return	true;
}

///////////////////////////////////////////////////////////////////////////////
//	dll_ClientBegin
//
//	called when a client finishes connecting to the server
//	calls ClientEndServerFrame...  
///////////////////////////////////////////////////////////////////////////////

void	dll_ClientBegin (userEntity_t *self, int loadgame)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	//	Q2FIXME:	check deathmatch, call ClientBeginDeathmatch
	if (gstate->deathmatch)
	{
	}

	//	is a game loading from a save file?
	if (loadgame && self->className && !strcmp (self->className, "player"))
	{
		gstate->Con_Printf ("dll_ClientBegin: loading saved game...\n");
	}
	else
	{
		//	Q2FIXME:	do we really need to reinit entity here?
		//	Q2FIXME:	Initialize respawn data

		dll_PutClientInServer (self);
	}

	//	Q2FIXME:	set up level structure or make it part of serverState_t
//	if (level.intermission)
	{
		//	Q2FIXME:	call MoveClientToIntermission
	}
//	else
	{
		//	Q2FIXME:	spawn a muzzleflash for spawn

		//	send muzzle flash message
/*
		gstate->WriteByte (svc_muzzleflash);
		gstate->WriteShort (ent - gstate->g_edicts);
		gstate->WriteByte (MZ_LOGIN);
		gstate->MultiCast (self->s.origin, MULTICAST_PVS);
*/
	}

	//	Q2FIXME:	ClientEndServerFrame == PlayerPostThink?!?!?
	//	make sure all view stuff is valid

	//	Q2FIXME:	CALL THIS FROM PHYSICS.DLL!!!!!
	//	ClientEndServerFrame (self);
	//Client_PostThink (self);

	if (gstate->foglevel)
	{
		gstate->WriteByte (SVC_EFFECT_NOTIFY);				// wake up, here's a message!
		gstate->WriteLong (ET_FOG);							// its about the fog!
		gstate->WriteByte (1);								// fog on?
		gstate->WriteByte (255);							// red
		gstate->WriteByte (255);							// green
		gstate->WriteByte (255);							// blue
		gstate->WriteLong (4);								// start
		gstate->WriteLong (gstate->foglevel * 2048);		// end
		gstate->WriteByte (0);								// fade increment
		gstate->UniCast (self, true);						// send it dude!
	}
}

///////////////////////////////////////////////////////////////////////////////
//	dll_ClientBeginServerFrame
//
//	called when a client finishes connecting to the server
//	calls ClientEndServerFrame...  
///////////////////////////////////////////////////////////////////////////////

void	dll_ClientBeginServerFrame (userEntity_t *self)
{
	//////////////////////////////////////////////////////////////////
	//	if dead, do special stuff
	//////////////////////////////////////////////////////////////////

	if (self->deadflag == DEAD_DEAD)
	{
		Client_DeathThink (self);
		return;
	}
}

/* ------------------------------ ClientThink ------------------------------- */

///////////////////////////////////////////////////////////////////////////////
//	Client_SetFOVRamp
//
///////////////////////////////////////////////////////////////////////////////

void	Client_SetFOVRamp (userEntity_t *self, float rate, float desired)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	hook->fov_rate = rate;
	hook->fov_desired = desired;
	hook->fov_last_change = gstate->Sys_Milliseconds ();
}

///////////////////////////////////////////////////////////////////////////////
//	Client_FOVRamp
//
//	handles ramping of client FOV
///////////////////////////////////////////////////////////////////////////////

void	Client_FOVRamp (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	float			change, change_mult;
	int				ms, time;

	if (self->client->ps.fov != hook->fov_desired)
	{
		ms = gstate->Sys_Milliseconds ();
		time = ms - hook->fov_last_change;
		hook->fov_last_change = ms;

		change_mult = (float) time / 1000.0;

		if (self->client->ps.fov < hook->fov_desired)
		{
			change = fabs (hook->fov_rate) * change_mult;
			self->client->ps.fov += change;

			if (self->client->ps.fov > hook->fov_desired)
				self->client->ps.fov = hook->fov_desired;
		}
		else
		{
			change = - fabs (hook->fov_rate) * change_mult;
			self->client->ps.fov += change;

			if (self->client->ps.fov < hook->fov_desired)
				self->client->ps.fov = hook->fov_desired;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
//	Client_ClearForceAngles
//
///////////////////////////////////////////////////////////////////////////////

void	Client_ClearForceAngles (userEntity_t *self)
{
	self->client->ps.pmove.pm_type = PM_NORMAL;
	self->flags -= (self->flags & FL_FORCEANGLES);
}

///////////////////////////////////////////////////////////////////////////////
//	Client_SetForceAngles
//
///////////////////////////////////////////////////////////////////////////////

void	Client_SetForceAngles (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	hook->angles_last_change = gstate->Sys_Milliseconds ();

	self->flags |= FL_FORCEANGLES;
}

///////////////////////////////////////////////////////////////////////////////
//	Client_ChangeAngles
//
//	turns client to look at hook->final_view_angles at hook->final_view_rate
//	if self->flags & FL_FORCEANGLES
///////////////////////////////////////////////////////////////////////////////

void	Client_ChangeAngles (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	int				ms, time;
	Vector			angles;
	float			change_mult, rate, temp;

	if (!(self->flags & FL_FORCEANGLES))
		return;

	ms = gstate->Sys_Milliseconds ();
	time = ms - hook->angles_last_change;
	hook->angles_last_change = ms;

	change_mult = (float) time / 1000.0;

	angles = Vector (self->client->ps.viewangles);
	angles.x = com->AngleMod (angles.x);
	angles.y = com->AngleMod (angles.y);
	angles.z = com->AngleMod (angles.z);
	
	if (hook->force_rate.x == 0)
		angles.x = hook->force_angles.x;
	else if (angles.x != hook->force_angles.x)
	{
		rate = hook->force_rate.x * change_mult;
		if (rate > 0 && rate < 0.125)
			rate = 0.125;
		else if (rate < 0 && rate > -0.125)
			rate = -0.125;

		temp = angles.x + rate;

		if ((temp <= hook->force_angles.x && angles.x > hook->force_angles.x) ||
			(temp >= hook->force_angles.x && angles.x < hook->force_angles.x))
			angles.x = hook->force_angles.x;
		else 
			angles.x = temp;
	}

	if (hook->force_rate.y == 0)
		angles.y = hook->force_angles.y;
	else if (angles.y != hook->force_angles.y)
	{
		rate = hook->force_rate.y * change_mult;
		if (rate > 0 && rate < 0.125)
			rate = 0.125;
		else if (rate < 0 && rate > -0.125)
			rate = -0.125;

		temp = angles.y + rate;

		if ((temp <= hook->force_angles.y && angles.y > hook->force_angles.y) ||
			(temp >= hook->force_angles.y && angles.y < hook->force_angles.y))
			angles.y = hook->force_angles.y;
		else 
			angles.y = temp;
	}

	if (hook->force_rate.z == 0)
		angles.z = hook->force_angles.z;
	else if (self->flags & FL_INWARP)
		//	spin roll while in a warp
		angles.z += 180 * change_mult;
	else if (angles.z != hook->force_angles.z)
	{
		rate = hook->force_rate.z * change_mult;
		if (rate > 0 && rate < 0.125)
			rate = 0.125;
		else if (rate < 0 && rate > -0.125)
			rate = -0.125;

		temp = angles.z + rate;

		if ((temp <= hook->force_angles.z && angles.z > hook->force_angles.z) ||
			(temp >= hook->force_angles.z && angles.z < hook->force_angles.z))
			angles.z = hook->force_angles.z;
		else 
			angles.z = temp;
	}

	gstate->SetClientAngles (self, angles.vec ());
}

///////////////////////////////////////////////////////////////////////////////
//	dll_ClientThink
//
//	This will be called once for each client frame, which will
//	usually be a couple times for each server frame.
//
//	only used here to get player button input
///////////////////////////////////////////////////////////////////////////////

void	dll_ClientThink (userEntity_t *self, usercmd_t *ucmd, pmove_t *pm)
{
	gclient_t		*client;
	int				i, j;
	float			r;

	client = self->client;

	//	Q2FIXME:	handle intermissions
/*
	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;
	}
*/
	if (self->groundEntity && !pm->groundEntity && (pm->cmd.upmove >= 10) && (pm->waterlevel == 0))
	{
		//gstate->Con_Printf ("Jump\n");

		r = rnd ();
		if (r < 0.33)
			gstate->sound (self->input_entity, CHAN_BODY, "player/playerjump5.wav", 255, ATTN_NORM);
		else if (r < 0.66)
			gstate->sound (self->input_entity, CHAN_BODY, "player/playerjump6.wav", 255, ATTN_NORM);
		else
			gstate->sound (self->input_entity, CHAN_BODY, "player/playerjump7.wav", 255, ATTN_NORM);
	}

	if (self->flags & FL_INWARP)
		Client_WarpThink (self);

	Client_FOVRamp (self);

	Client_ChangeAngles (self);

	Client_Attack (self);
}

///////////////////////////////////////////////////////////////////////////////
//	dll_SetStats
//
//	sets stats to be set down for the passed client for display on status bar
//	menus, etc.
///////////////////////////////////////////////////////////////////////////////

void	dll_SetStats (userEntity_t *ent)
{
	playerHook_t	*hook = (playerHook_t *) ent->userHook;
	int lval;

	if (ent->curWeapon)
	{
		ent->curWeapon->command(ent->curWeapon,"ammo_count",&lval);
		ent->client->ps.stats[STAT_AMMO]=lval;
	}
	else
		ent->client->ps.stats[STAT_AMMO]=0;
	
	ent->client->ps.stats[STAT_ARMOR]=ent->armor_val;
	ent->client->ps.stats[STAT_HEALTH]=ent->health;
	ent->client->ps.stats[STAT_LEVEL]=ent->explevel;

	ent->client->ps.stats [STAT_JUMP] = hook->acro_boost;
	ent->client->ps.stats [STAT_SPEED] = hook->speed_boost;
	ent->client->ps.stats [STAT_ATTACK_SPEED] = hook->attack_boost;
	ent->client->ps.stats [STAT_ATTACK_POWER] = hook->power_boost;
	ent->client->ps.stats [STAT_MAX_HEALTH] = hook->max_health;

	//	FIXME:	display actual daikatan power level
	ent->client->ps.stats [STAT_SWORD_LEVEL] = 0;
}

/*------------------------------ spawn functions ----------------------------*/

///////////////////////////////////////////
//	info_player_start
//
//	single player spawn point
///////////////////////////////////////////

void info_player_start (userEntity_t *self)
{
	int	i;

	for (i = 0; self->epair [i].key; i++)
	{
		if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
	}

	gstate->Con_Printf ("info_player_start\n");
}

///////////////////////////////////////////
//	info_player_coop
//
//	co-op spawn point
///////////////////////////////////////////

void info_player_coop (userEntity_t *self)
{
	int	i;

	for (i = 0; self->epair [i].key; i++)
	{
		if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
	}
}

///////////////////////////////////////////
//	info_player_deathmatch
//
//	deathmatch spawn point
///////////////////////////////////////////

void info_player_deathmatch (userEntity_t *self)
{
	int	i;

	for (i = 0; self->epair [i].key; i++)
	{
		if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
	}
}

///////////////////////////////////////////
//
///////////////////////////////////////////

void	path_to_rocket (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	mapNode_t		*end;

	if (hook->skat_list)
	{
		end = skat_find_item (self, "weapon_rocket");
		if (end)
		{
			if (hook->path)
				hook->path = path_kill (hook->path);

			hook->path = path_shortest (self, hook->node_list, hook->node_list->cur_node, end, hook->path_length);
		}
		else
			gstate->Con_Dprintf ("no weapon_rocket in skat_list\n");
	}
	else
	{
		gstate->Con_Dprintf ("no skat logged\n");
	}
}
