//////////////////////////////////////////////////////////////////////
//	Chase Cam Routines
//
//	mainly for debugging creature AI
//
//
//	Camera Modes:
//
//  mode 0
//		camera always faces player's back
//		distance from player and angle are affected by pitch
//
//		client still controls facing yaw and pitch of player entity
//		client still controls movement of player entity
//
//	mode 1
//		camera chooses a best angle to view the entity from
//		camera moves from old best angle to a new best angle
//		while locked on camera->goalentity
//
//		client still controls movement of player entity
//		client still controls facing yaw and pitch of player entity
//			even though the client view is facing a different direction
//
//	mode 2
//		camera views from view entity and turns to track target
//
//	mode 3
//		camera always faces target's back
//		distance from target is affected by obstructions
//
//////////////////////////////////////////////////////////////////////
#include	<math.h>

#include	"world.h"

#include	"ai.h"
#include	"ai_move.h"

#include	"chasecam.h"
#include	"nodelist.h"

//////////////////////////////////////////////////////////////////////
//	defines
//////////////////////////////////////////////////////////////////////

#define		NUMDIR				16
#define		NUMPITCH			2
#define		CAMERA_PITCH		15
#define		CAMERA_MAXSPEED		320.0
#define		CAMERA_CATCHMULT	4.0
#define		CAMERA_ACCELERATE	1.25 
#define		CAMERA_DECELERATE	0.75
#define		CAMERA_MODES		4

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

userEntity_t	*camera;

userEntity_t	*cam_debug_target;		// for toggling back and forth between player and an entity

static	vec3_t	cam_mins = {-8, -8, -8};
static	vec3_t	cam_maxs = {8, 8, 8};

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

//////////////////////
//	camera_set_yaw
//
//	sets client's view angle to ang
//////////////////////

void	camera_set_yaw (userEntity_t *client, Vector &ang)
{
	Vector	angles;

	angles = ang;
	angles.x = client->client->v_angle [0];
	gstate->SetClientAngles (client, angles.vec ());
}

//////////////////////
//	camera_set_viewangle
//
//	sets client's view angle to ang
//////////////////////

void	camera_set_viewangle (userEntity_t *client, Vector &ang)
{
	ang.x = ang.x;
	gstate->SetClientAngles (client, ang.vec ());
}

//////////////////////
//	camera_track_client
//
//	turns the camera to face target
//////////////////////

void	camera_track_client (userEntity_t *camera)
{
	Vector		ang, dir;

	dir = (Vector (camera->goalentity->s.origin) + Vector (0, 0, camera->goalentity->viewheight)) - Vector (camera->s.origin);
	dir.Normalize ();

	// get the angle from camera to self, forward is already the vector
	com->VecToAngles (ang, dir);
	camera_set_viewangle (camera->owner, ang);
}

//////////////////////
//	camera_track
//
//	turns the camera to face target
//////////////////////

void	camera_track (userEntity_t *camera)
{
	Vector		ang, dir;

	dir = Vector (camera->goalentity->s.origin) - Vector (camera->s.origin);
	dir.Normalize ();

	// get the angle from camera to self, forward is already the vector
	com->VecToAngles (ang, dir);
	camera_set_viewangle (camera->owner, ang);
}

//////////////////////
//	camera_track_yaw
//
//	turns the camera to face target
//////////////////////

void	camera_track_yaw (userEntity_t *camera)
{
	Vector		ang, dir;

	dir = Vector (camera->goalentity->s.origin) - Vector (camera->s.origin);
	dir.Normalize ();

	// get the angle from camera to self, forward is already the vector
	com->VecToAngles (ang, dir);
	camera_set_yaw (camera->owner, ang);
}

//////////////////////
//	camera_ignore_input
//
//	sets client's view angle to ang
//////////////////////

void	camera_ignore_input (userEntity_t *client)
{
	//	Q2FIXME:	change to Q2 network functions
	//	gstate->WriteByte (client, MSG_ONE, SVC_IGNOREINPUT);
}

//////////////////////
//	camera_restore_input
//
//	sets client's view angle to ang
//////////////////////

void	camera_restore_input (userEntity_t *client)
{
	//	Q2FIXME:	change to Q2 network functions
	//	gstate->WriteByte (client, MSG_ONE, SVC_RESTOREINPUT);
}

//////////////////////
//	camera_best_angle
//
//	finds the best (least obstructed) direction
//	to view an entity from
//
//	traces lines out CAMERA_PITCH degrees above horizontal
//
//////////////////////

void	camera_best_angle (userEntity_t *camera, Vector &best_org)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	float			dist	[NUMDIR][NUMPITCH];
	float			best	[NUMDIR][NUMPITCH];
	Vector			end		[NUMDIR][NUMPITCH];
	float			yaw_add, yaw, yaw_start;
	float			best_yaw, best_pitch;
	int				y, i, lo, hi, best_num, best_y;
	Vector			org, temp, goal_org;
	vec3_t			mins, maxs;

	mins [0] = mins [1] = mins [2] = -8;
	maxs [0] = maxs [1] = maxs [2] = 8;

	// find the angle opposite of the angle the target is facing and start there
	yaw = yaw_start = fmod (camera->goalentity->s.angles [1] + 180.0, 360);

	yaw_add = 360.0 / NUMDIR;
	org = camera->goalentity->s.origin;
	org.z += camera->goalentity->viewheight;

	// trace lines out in NUMDIR directions, looking for most open view
	for (y = 0; y < NUMPITCH; y++)
	{
		for (i = 0; i < NUMDIR; i++)
		{
			temp = Vector (-(CAMERA_PITCH * y), yaw, 0.0);
			temp.AngleVectors (forward, right, up);
	
			end [i][y] = org + forward * chook->follow_dist;

			//	Q2KILL
			//	gstate->TraceBox (org.vec (), end [i][y].vec (), mins, maxs, FALSE, camera->goalentity, &trace);
			tr = gstate->trace_q2 (org.vec (), mins, maxs, end [i][y].vec (), camera->goalentity, MASK_OPAQUE);
			
			dist [i][y] = tr.fraction * chook->follow_dist;
			if (y == 1) dist [i][y] *= 1.25;	// weight overhead view higher than others

//			gstate->particle (tr.endpos, mins, gstate->global_white, 1);
//			com->SpawnMarker (gcamera, Vector (tr.endpos), "models/debug/marker.mdl", 0.1);

			yaw += fmod (yaw_add, 360);
		}
	}

	// get the "best" total for each direction by adding the distance
	// each trace went to the distance its two immediate neighbors went

	// doesn't consider how open the traces above/below were, but it probably should
	for (y = 0; y < NUMPITCH; y++)
	{
		for (i = 0; i < NUMDIR; i++)
		{
			lo = i - 1;
			if (lo < 0) lo = NUMDIR - 1;

			hi = i + 1;
			if (hi > NUMDIR - 1) hi = 0;

			best [i][y] = dist [lo][y] + dist [i][y] + dist [hi][y];
	
			// weight the total if it is visible from current location
			// this will cut down on quick camera switches
			
			//	Q2KILL
			//	gstate->TraceBox (camera->s.origin, end [i][y].vec (), mins, maxs, FALSE, camera, &trace);
			tr = gstate->trace_q2 (camera->s.origin, mins, maxs, end [i][y].vec (), camera, MASK_OPAQUE);

			if (tr.fraction == 1.0)
				best [i][y] *= 2.0;
		}
	}

	hi = 0;
	// now find the best with the highest value
	for (y = 0; y < NUMPITCH; y++)
	{
		for (i = 0; i < NUMDIR; i++)
		{
			if (best [i][y] > hi)
			{
				best_num = i;
				best_y = y;
				hi = best [i][y];
			}
		}
	}

	best_yaw = fmod ((float) best_num * yaw_add + yaw_start, 360);
	best_pitch = -(CAMERA_PITCH * best_y);
	
	temp = Vector (best_pitch, best_yaw, 0.0);
	temp.AngleVectors (forward, right, up);

	best_org = org + forward * (dist [best_num][best_y] - 32.0);
}

//************************* Camera Mode 0 *******************************//
//  mode 0
//		camera always faces player's back
//		distance from player and angle are affected by pitch
//
//		client still controls facing yaw and pitch of player entity
//		client still controls movement of player entity

///////////////////////////////////////////////////////////////////////////////
//	camera_point_behind
//
//	mode 0 placement
//
//	finds optimal point behind camera target based on target's view angles
//	takes obstructions into account
///////////////////////////////////////////////////////////////////////////////

void	camera_point_behind (userEntity_t *cameral, Vector &point)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	float			dist_mult, pitch_scale, back_off, dist;
	Vector			org, end;
	userEntity_t	*target;

	target = camera->goalentity;
	dist = chook->follow_dist;

	if (chook->mode < 128)
	{
		// get multipliers for forward and up
		dist_mult = dist * 0.7;

		// find forward, right and up vectors
		end = Vector (target->client->v_angle);
		end.x *= 0.33;
		end = end + chook->view_angle;
		end.AngleVectors (forward, right, up);

		org = Vector (target->s.origin);
		org.z += target->viewheight;

		pitch_scale = target->client->v_angle [0] / 240.0; // (80 * 3) or v_angle / 3.0
		end = org + forward * -(dist_mult * (1.0 - fabs (pitch_scale))) + up * (dist_mult * pitch_scale);

		tr = gstate->trace_q2 (org.vec (), cam_mins, cam_maxs, end.vec (), camera->goalentity, MASK_OPAQUE);

		if (tr.fraction < 1.0)
		{
			end = Vector (tr.endpos);

			org = org - end;
			back_off = org.vlen ();

			if (back_off < 8)
				back_off = back_off * 0.5;
			else
				back_off = 8;

			// back off a bit from the endpoint, toward camera
			//end = end + forward * (back_off * (1.0 - pitch_scale)) + up * (back_off * pitch_scale);
			end = end + forward * back_off;
		}

		point = end;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	camera_place_behind
//
//	Original mode 0 placement with Quake 1 engine.  Doesn't look very good in 
//	Q2 because camera origin is set directly, and that screws with interpolation 
//	a bit
///////////////////////////////////////////////////////////////////////////////

void	camera_place_behind (userEntity_t *camera)
{
	Vector	point;

	camera_point_behind (camera, point);
	
	point.set (camera->s.origin);
	gstate->LinkEntity (camera);

	camera_track_yaw (camera);
}

///////////////////////////////////////////////////////////////////////////////
//	camera_chase_behind
//
//	rotates the camera around the target
//	while turning to face it
///////////////////////////////////////////////////////////////////////////////

void	camera_chase_behind (userEntity_t *camera)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	float			dist, wish_speed;
	float			pred_dist, time_to_target, dist_per_frame;
	Vector			vel, pred_org, temp;

	if (chook->mode < 128)
	{
		chook->target_dist = qv_dist (camera->s.origin, camera->goalentity->s.origin);

		camera_point_behind (camera, chook->best_org);
		chook->next_change = gstate->time + chook->min_change_time;

		chook->best_dir = chook->best_org - Vector (camera->s.origin);
		dist = chook->best_dir.vlen ();
		chook->best_dir.Normalize ();

		// can we reach the new best_org?
		tr = gstate->trace_q2 (chook->best_org.vec (), cam_mins, cam_maxs, camera->s.origin, camera->goalentity, MASK_OPAQUE);
		
		if (tr.fraction != 1.0)
		{
			chook->best_org.set (camera->s.origin);
			gstate->LinkEntity (camera);
			chook->speed = 0;
		}
		else if (Vector (camera->s.origin) != Vector (chook->best_org))
		{
/*
			vel = Vector (camera->velocity);
			time_to_target = dist / vel.vlen ();
			dist_per_frame = vel.vlen () * gstate->frametime;
			pred_org = Vector (camera->s.origin) + vel * gstate->frametime;

			temp = chook->best_org - pred_org;
			pred_dist = temp.vlen ();

			if (chook->speed == 0.0)
				chook->speed = 32.0;

			if (dist <= 8.0 || pred_dist < 8.0)
			{
				if (dist < dist_per_frame || pred_dist < dist_per_frame || time_to_target <= gstate->frametime)
				{
					com->ZeroVector (camera->velocity);
					chook->best_org.set (camera->s.origin);
					chook->speed = 0;
					gstate->LinkEntity (camera);
				}
				else
				{
					chook->speed *= CAMERA_DECELERATE;
					if (chook->speed < 32)  chook->speed = 0;

					temp = chook->best_dir * chook->speed;
					temp.set (camera->velocity);
				}
			}
			else if (pred_dist > 8.0)
			{
				chook->speed *= CAMERA_ACCELERATE;
				
				if (chook->speed > chook->max_speed)
					chook->speed = chook->max_speed;

				temp = chook->best_dir * chook->speed;
				temp.set (camera->velocity);
			}
*/
			vel = Vector (camera->velocity);
			time_to_target = dist / vel.vlen ();
			pred_org = Vector (camera->s.origin) + vel * gstate->frametime;

			temp = chook->best_org - pred_org;
			pred_dist = temp.vlen ();

			if (dist < 8 || pred_dist < 8 || time_to_target <= gstate->frametime)
			{
				com->ZeroVector (camera->velocity);
				chook->best_org.set (camera->s.origin);
				gstate->LinkEntity (camera);
			}
			else
			{
				wish_speed = 640 * ((pred_dist + 40) / 160);
				if (wish_speed > 640)
					wish_speed = 640;
				vel = chook->best_dir * wish_speed;
				vel.set (camera->velocity);
			} 
		}
	}

	chook->target_angles = Vector (camera->goalentity->s.angles);

//	camera_track_client (camera);
	camera_track_yaw (camera);
}

//************************* Camera Mode 1 *******************************//
//
//	mode 1
//		camera chooses a best angle to view the entity from
//		camera moves from old best angle to a new best angle
//		while locked on camera->goalentity
//
//		client still controls movement of player entity
//		client still controls facing yaw and pitch of player entity
//			even though the client view is facing a different direction

///////////////////////////////////////////////////////////////////////////////
//	camera_auto_chase
//
//	rotates the camera around the target
//	while turning to face it
///////////////////////////////////////////////////////////////////////////////

void	camera_auto_chase (userEntity_t *camera)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	Vector			ang, dir, next_cam, next_targ;
	float			dist, wish_speed;

	float			pred_dist, time_to_target, dist_per_frame;
	Vector			vel, pred_org, temp;

	if (chook->mode < 128)
	{
		chook->best_dir = chook->best_org - Vector (camera->s.origin);
		dist = chook->best_dir.vlen ();
		chook->best_dir.Normalize ();

		chook->target_dist = qv_dist (camera->s.origin, camera->goalentity->s.origin);

		// determine if we can reach the best_org by moving, otherwise
		// place us there...
		gstate->TraceLine (chook->best_org.vec (), camera->s.origin, TRUE, camera, &trace);
		
		if (trace.fraction != 1.0)
		{
			chook->best_org.set (camera->s.origin);
			gstate->SetOrigin2 (camera, camera->s.origin);
		}
		else if (trace.fraction == 1.0)
			gstate->TraceLine (camera->goalentity->s.origin, camera->s.origin, TRUE, camera->goalentity, &trace);	

		// is it time to look for a better view?
		if (gstate->time >= chook->next_change || trace.fraction != 1.0 ||
			chook->target_dist > chook->follow_dist)
		{
			temp = chook->best_org;

			camera_best_angle (camera, chook->best_org);
			chook->next_change = gstate->time + chook->min_change_time;

			chook->best_dir = chook->best_org - Vector (camera->s.origin);
			dist = chook->best_dir.vlen ();
			chook->best_dir.Normalize ();

			// can we reach the new best_org?
			gstate->TraceLine (chook->best_org.vec (), camera->s.origin, TRUE, camera->goalentity, &trace);
			if (trace.fraction != 1.0)
				gstate->SetOrigin2 (camera, chook->best_org.vec ());

			if (chook->speed < 32.0 && chook->best_org != temp)
				chook->speed = 32.0;
			
			if (chook->best_org != temp)
			{
				chook->max_speed = CAMERA_MAXSPEED;
				// get distance to target
				if (chook->target_dist > chook->follow_dist * 2.0)
					chook->max_speed *= CAMERA_CATCHMULT;
			}
		}

/*		
		if (dist < 64.0)
		{
			chook->speed *= CAMERA_DECELERATE;
			if (chook->speed < 32)  chook->speed = 0;
		}
		else
			chook->speed *= CAMERA_ACCELERATE;

		if (chook->speed > chook->max_speed)
			chook->speed = chook->max_speed;

		temp = chook->best_dir * chook->speed;

		//	camera_accelerate (camera, temp, wish_speed);
		temp.set (camera->velocity);
*/
		vel = Vector (camera->velocity);
		time_to_target = dist / vel.vlen ();
		dist_per_frame = vel.vlen () * gstate->frametime;
		pred_org = Vector (camera->s.origin) + vel * gstate->frametime;

		temp = chook->best_org - pred_org;
		pred_dist = temp.vlen ();

		if (dist <= 64.0 || pred_dist < 64.0)
		{
			if (dist < dist_per_frame || pred_dist < dist_per_frame || time_to_target <= gstate->frametime)
			{
				com->ZeroVector (camera->velocity);
				chook->best_org.set (camera->s.origin);
				gstate->LinkEntity (camera);
			}
			else
			{
				chook->speed *= CAMERA_DECELERATE;
				if (chook->speed < 32)  chook->speed = 0;

				temp = chook->best_dir * chook->speed;
				temp.set (camera->velocity);
			}
		}
		else if (pred_dist > 64.0)
		{
			chook->speed *= CAMERA_ACCELERATE;
			
			if (chook->speed > chook->max_speed)
				chook->speed = chook->max_speed;

			temp = chook->best_dir * chook->speed;
			temp.set (camera->velocity);
		}
	}

	camera_track_client (camera);
}

//************************* Camera Mode 2 *******************************//

//************************* Camera Mode 3 *******************************//

//////////////////////
//	camera_follow
//
//	mode 0 placement
//
//  places the camera dist units from self->s.origin
//	at a 45 degree angle up and -forward
//  or at the furthest unobstructed distance
//	from self->s.origin at a 45 degree angle
//////////////////////

void	camera_follow (userEntity_t *camera)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	Vector			end, ang;

	if (chook->mode < 128)
	{
		ang = Vector (camera->goalentity->s.angles);
		ang.AngleVectors (forward, right, up);
		forward = forward * -1.0;

		end = Vector (camera->goalentity->s.origin) + forward * chook->follow_dist;

		gstate->TraceLine (camera->goalentity->s.origin, end.vec (), TRUE, camera->goalentity, &trace);
		gstate->SetOrigin2 (camera, trace.endpos);
	}
}

//************************* Camera Mode 4 *******************************//

//////////////////////
//	camera_rotate
//
//	rotates the camera around the target
//	while turning to face it
//////////////////////

void	camera_rotate (userEntity_t *camera)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	Vector			ang, temp, org, dir;
	vec3_t			mins, maxs;
	float			dist;

	if (chook->mode < 128)
	{
		mins [0] = mins [1] = mins [2] = -8;
		maxs [0] = maxs [1] = maxs [2] = 8;

		if (gstate->time >= chook->next_change)
		{
			camera_best_angle (camera, chook->best_org);
			chook->next_change = gstate->time + chook->min_change_time;
		}
		
		temp = Vector (camera->goalentity->s.origin) - Vector (camera->s.origin);
		com->VecToAngles (ang, temp);
		ang.AngleVectors (forward, right, up);
		dist = temp.vlen ();

		org = Vector (camera->s.origin) + right * (CAMERA_MAXSPEED * gstate->frametime + 8.0);
		
		//	Q2KILL
		//	gstate->TraceBox (camera->s.origin, org.vec (), mins, maxs, TRUE, camera, &trace);
		tr = gstate->trace_q2 (camera->s.origin, mins, maxs, org.vec (), camera, MASK_OPAQUE);

		if (tr.fraction < 1.0)
		{
			gstate->bprint ("going forward\n");
			// go forward to avoid a collision with world
			temp = forward * CAMERA_MAXSPEED;
		}
		else if (chook->follow_dist - dist > 16.0)
		{
			gstate->bprint ("going diagonally backwards\n");

			// try going back to reach max distance
			dist = (CAMERA_MAXSPEED * gstate->frametime + 8.0);
			org = Vector (camera->s.origin) + right * dist - forward * dist;
			
			//	Q2KILL
			//	gstate->TraceBox (camera->s.origin, org.vec (), mins, maxs, TRUE, camera, &trace);
			tr = gstate->trace_q2 (camera->s.origin, mins, maxs, org.vec (), camera, MASK_OPAQUE);

			if (tr.fraction == 1.0)
				temp = right * CAMERA_MAXSPEED - forward * CAMERA_MAXSPEED;
			else
				temp = -forward * CAMERA_MAXSPEED;
		}
		else if (dist - chook->follow_dist > 16.0)
		{
			gstate->bprint ("going diagonally forward\n");

			// try going back to reach max distance
			dist = (CAMERA_MAXSPEED * gstate->frametime + 8.0);
			org = Vector (camera->s.origin) + right * dist + forward * dist;
			
			//	Q2KILL
			//	gstate->TraceBox (camera->s.origin, org.vec (), mins, maxs, TRUE, camera, &trace);
			tr = gstate->trace_q2 (camera->s.origin, mins, maxs, org.vec (), camera, MASK_OPAQUE);

			if (tr.fraction == 1.0)
				temp = right * CAMERA_MAXSPEED + forward * CAMERA_MAXSPEED;
			else
				temp = forward * CAMERA_MAXSPEED;
		}
		else
		{
			gstate->bprint ("going right\n");
			temp = right * CAMERA_MAXSPEED;
		}

		temp.set (camera->velocity);

		camera_set_viewangle (camera->owner, ang);
	}
}

//************************* Camera Mode 5 *******************************//

//////////////////////
//	camera_place_dist
//
//	mode 4 placement
//
//	places the camera dist units in yaw direction
//	from self->s.origin in direction yaw
//////////////////////

void	camera_place_dist (userEntity_t *camera, userEntity_t *target, float dist)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	Vector			temp, org, end;
	float			back_off, dist_mult;

	// get multipliers for forward and up
	dist_mult = sqrt (dist * dist / 2.0);

	temp = chook->view_angle;
	temp.AngleVectors (forward, right, up);

	org = Vector (target->s.origin);
	end = org + forward * dist + up * dist;

	gstate->TraceLine (org.vec (), end.vec (), TRUE, camera->goalentity, &trace);
	end = Vector (trace.endpos);

	temp = org - end;
	back_off = temp.vlen ();

	if (back_off > 16.0)
		back_off = 16.0;
	// back off a bit from the endpoint, toward camera
	end = end - forward * back_off - up * back_off;

	gstate->SetOrigin2 (camera, end.vec ());
}

//************************* Camera Mode 6 *******************************//

//////////////////////
//	camera_best_view
//
//	rotates the camera around the target
//	while turning to face it
//////////////////////

void	camera_best_view (userEntity_t *camera)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;	
	Vector			ang, dir, temp, next_cam, next_targ;
	float			dist, wish_speed;

	// get the speed before the origin is set
	temp = Vector (camera->velocity);
	chook->speed = temp.vlen ();

	// determine if we can reach the best_org by moving, otherwise
	// place us there...
	gstate->TraceLine (chook->best_org.vec (), camera->s.origin, TRUE, camera, &trace);
	if (trace.fraction == 1.0)
		gstate->TraceLine (camera->goalentity->s.origin, camera->s.origin, TRUE, camera->goalentity, &trace);	
	// is it time to look for a better view?
	if (gstate->time >= chook->next_change || trace.fraction != 1.0)
	{
		camera_best_angle (camera, chook->best_org);
		chook->next_change = gstate->time + chook->min_change_time;

		chook->best_dir = chook->best_org - Vector (camera->s.origin);
		chook->best_dir.Normalize ();

		// can we reach the new best_org?
		gstate->TraceLine (chook->best_org.vec (), camera->s.origin, TRUE, camera->goalentity, &trace);
		if (trace.fraction != 1.0)
			gstate->SetOrigin2 (camera, chook->best_org.vec ());

		if (chook->speed < 32.0)
			chook->speed = 32.0;
	}

	temp = Vector (camera->s.origin) - chook->best_org;
	dist = temp.vlen ();

	if (dist < 16.0)
	// if we're close to the best position, slow down
		temp = Vector (0, 0, 0);
	else if (dist < 64.0)
	// if we're close to the best position, slow down
		temp = chook->best_dir * chook->speed * CAMERA_DECELERATE;
	else
	{
		// otherwise, go right for the best viewpoint
		temp = chook->best_dir * chook->speed * CAMERA_ACCELERATE;
		if (temp.vlen () > CAMERA_MAXSPEED)
			temp = chook->best_dir * 160.0;
	}

	// add the view object's velocity to ours
	temp = temp + Vector (camera->goalentity->velocity);
	temp.set (camera->velocity);

	camera_track_client (camera);
}

////////////////////////////////////////////////////////////////////////////
//	end of camera think functions
////////////////////////////////////////////////////////////////////////////

//////////////////////
//	camera_cast_out
//
//	returns control to camera->goalentity from camera->owner
//////////////////////

void	camera_cast_out (userEntity_t *camera)
{
	playerHook_t	*mhook = (playerHook_t *) camera->goalentity->userHook;
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;

	// you can't cast yourself out, imbecile!!
	if (camera->goalentity == camera->owner || !chook->demonic)
		return;

	// clear possession flag and pointer
	camera->goalentity->flags -= (camera->goalentity->flags & FL_POSSESSED);
	mhook->demon = NULL;

	// give them back their brains
	camera->goalentity->think = chook->target_think;
	camera->goalentity->nextthink = gstate->time + 0.1;

	mhook->ai_flags = chook->target_ai_flags;
	mhook->th_state = chook->target_th_state;

	// restore the old movetype
	camera->goalentity->movetype = chook->target_movetype;

	// if another client, then restore their pre and post thinks
	if (camera->goalentity->flags & FL_CLIENT)
	{
		camera->goalentity->prethink = chook->target_prethink;
		camera->goalentity->prethink = chook->target_postthink;
		
		chook->target_prethink = NULL;
		chook->target_postthink = NULL;

		gstate->SetClientInputEntity (camera->owner, camera->owner);
	}

	// now give them back their body!
	gstate->SetClientInputEntity (camera->owner, camera->owner);
}

//////////////////////
//	camera_possess
//
//	gives control of the camera->goalentity to camera->owner
//////////////////////

void	camera_possess (userEntity_t *camera)
{
	playerHook_t	*mhook = (playerHook_t *) camera->goalentity->userHook;
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;

	// you can't possess yourself, moron!!
	if (camera->goalentity == camera->owner || !chook->demonic)
		return;

	// set the new entity's possesser 
	mhook->demon = camera->owner;
	camera->goalentity->flags |= FL_POSSESSED;

	// make them stupid
	chook->target_think = camera->goalentity->think;
	camera->goalentity->think = NULL;

	chook->target_ai_flags = mhook->ai_flags;
	chook->target_th_state = mhook->th_state;
	mhook->ai_flags = 4;
	camera->goalentity->max_speed = mhook->run_speed / 320.0;

	// make sure we have a valid movetype
	chook->target_movetype = camera->goalentity->movetype;
	camera->goalentity->movetype = MOVETYPE_WALK;

	//	FIXME:	doesn't work
	// if another client, then take away their pre and post thinks
	if (camera->goalentity->flags & FL_CLIENT)
	{
		chook->target_prethink = camera->goalentity->prethink;
		chook->target_postthink = camera->goalentity->postthink;
		
		camera->goalentity->prethink = NULL;
		camera->goalentity->postthink = NULL;
	}

	//	set the input entity back to the camera's target, monsters only?
	gstate->SetClientInputEntity (camera->owner, camera->goalentity);
}

//////////////////////
//	camera_set_target
//
//	sets the camera's target to a specific entity
//////////////////////

void	camera_set_target (userEntity_t *camera, userEntity_t *target)
{
	cameraHook_t	*chook = (cameraHook_t *) camera->userHook;
	playerHook_t	*phook;

	if (camera->goalentity != target)
	{
		camera_cast_out (camera);

		if (camera->owner->flags & (FL_CLIENT + FL_MONSTER + FL_BOT))
		{
			phook = (playerHook_t *) camera->owner->userHook;
			phook->camera = camera;
		}
		else
		{
			gstate->bprint ("camera target has no playerHook\n");
			return;
		}

		camera->goalentity = target;
		chook->next_change = -1;

		camera_possess (camera);
	}
}

//////////////////////
//	camera_next_monster
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_next_monster (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	playerHook_t	*mhook;
	cameraHook_t	*chook;	
	userEntity_t	*ent, *start_ent;
	int				looped;

	// make sure client is actually connected
	if (!phook)
		return;

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

		camera_cast_out (camera);

		if (phook->camera->owner != camera->goalentity)
		{
			mhook = (playerHook_t *) camera->goalentity->userHook;
			mhook->camera = NULL;
		}

		ent = gstate->NextEntity (phook->camera->goalentity);
		if (!ent) ent = gstate->FirstEntity ();
	
		start_ent = ent;

		while (1)
		{
			if (ent->flags & FL_MONSTER || ent->flags & FL_BOT || ent == camera->owner || (ent->flags & FL_CLIENT && ent != camera))
				break;
			
			ent = gstate->NextEntity (ent);
			if (!ent) ent = gstate->FirstEntity ();
	
			if (start_ent == ent)
				// looped through once and didn't find a monster
				return;
		}
	
		if (ent->netname)
			gstate->sprint (self, "AmoebaCam: following %s\n", ent->netname);
		else
			gstate->sprint (self, "AmoebaCam: following %s\n", ent->className);

		mhook = (playerHook_t *) ent->userHook;
		mhook->camera = camera;

		camera->goalentity = ent;
		chook->next_change = -1;

		camera_possess (camera);
	}

}

//////////////////////
//	camera_prev_monster
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_prev_monster (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	playerHook_t	*mhook;
	cameraHook_t	*chook;	
	userEntity_t	*last_ent, *ent;
	int				looped;

	// make sure client is actually connected
	if (!phook)
		return;

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

		camera_cast_out (camera);		

		if (phook->camera->owner != camera->goalentity)
		{
			mhook = (playerHook_t *) camera->goalentity->userHook;
			mhook->camera = NULL;
		}

		last_ent = camera->goalentity;
		ent = gstate->NextEntity (camera->goalentity);
	
		if (!ent) ent = gstate->FirstEntity ();

		while (1)
		{
			if (ent->flags & FL_MONSTER || ent->flags & FL_BOT || ent == camera->owner || (ent->flags & FL_CLIENT && ent != camera))
				last_ent = ent;

			ent = gstate->NextEntity (ent);
			if (!ent) ent = gstate->FirstEntity ();
		
			if (ent == camera->goalentity)
				// back to where we started
				break;
		}
	
		if (last_ent == camera->goalentity)
			return;

		ent = last_ent;
		
		if (ent->netname)
			gstate->sprint (self, "AmoebaCam: following %s\n", ent->netname);
		else
			gstate->sprint (self, "AmoebaCam: following %s\n", ent->className);

		mhook = (playerHook_t *) ent->userHook;
		mhook->camera = camera;

		camera->goalentity = ent;
		chook->next_change = -1;

		camera_possess (camera);
	}
}

//////////////////////
//	camera_debug
//
//	toggles FL_DEBUG on the camera's target
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_debug (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	Vector			org;
	userEntity_t	*goal, *target;
	userEntity_t	*owner;

	// make sure client is actually connected
	if (!phook)
		return;
	
	if (phook->camera)
	{
		target = camera->goalentity;
		goal = target->goalentity;

		if (!(target->flags & FL_CLIENT))
		{
			org = Vector (goal->s.origin);
			owner = goal->owner;
		}
		
		if (target->flags & FL_DEBUG)
		{
			target->flags -= FL_DEBUG;
			gstate->Con_Printf ("FL_DEBUG info OFF\n");

			if (!(target->flags & FL_CLIENT))
			{
				//	if they don't have a fake goal, remove the real entity and
				//	replace it with a fake one
				if (!(goal->flags & FL_FAKEENTITY))
				{
					gstate->RemoveEntity (goal);

					goal = ai_spawn_goal (target);
					org.set (goal->s.origin);
				}
			}

			node_set_viewer (camera->owner);
		}
		else
		{
			target->flags |= FL_DEBUG;
			gstate->Con_Printf ("FL_DEBUG info ON\n");

			if (!(target->flags & FL_CLIENT))
			{
				//	if they have a fake goal, remove it and replace it with
				//	a goalentity that we can see
				if (goal->flags & FL_FAKEENTITY)
				{
					gstate->bprint ("setting up visible goalentity.\n");
					com->free (goal);

					// set up the bot's roam_goal which is his carrot-on-a-string
					goal = gstate->SpawnEntity ();
					goal->solid = SOLID_NOT;
					goal->movetype = MOVETYPE_NONE;
					goal->flags = 0;
					goal->s.modelindex = gstate->ModelIndex ("models/debug/goal/goal.md2");
					VectorCopy (zero_vector, goal->s.mins);
					VectorCopy (zero_vector, goal->s.maxs);
					VectorCopy (org.vec (), goal->s.origin);
					gstate->LinkEntity (goal);
				}
			}

			node_set_viewer (target);
		}

		//	clients don't have a goal!
		if (!(target->flags & FL_CLIENT))
		{
			goal->owner = owner;
			target->goalentity = goal;
		}
	}
}

//////////////////////
//	camera_toggle_possession
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_toggle_possession (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;

	// make sure client is actually connected
	if (!phook)
		return;
	
	if (phook->camera)
	{
		cameraHook_t	*chook = (cameraHook_t *) phook->camera->userHook;

		if (chook->demonic)
		{
			camera_cast_out (camera);
			chook->demonic = FALSE;

			gstate->sprint (self, "AmoebaCam: possession disabled.\n");
		}
		else
		{
			chook->demonic = TRUE;
			camera_possess (camera);

			gstate->sprint (self, "AmoebaCam: possession enabled.\n");
		}
	}
}

//////////////////////
//	camera_mode
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_mode (userEntity_t *self, int cmode)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	
	if (phook->camera)
	{
		userEntity_t	*camera = phook->camera;
		cameraHook_t	*chook = (cameraHook_t *) phook->camera->userHook;

		//	can't mode switch when locked
		if (cmode >= 128)
			return;

		if (cmode == chook->mode && chook->mode != CAMERA_MODES)
			return;

		////////////////////////////////////////////////////////////////////
		//	if CAMERA_MODES is passed, then the mode is just incremented 
		////////////////////////////////////////////////////////////////////

		if (cmode == CAMERA_MODES)
		{
			chook->mode++;

			if (chook->mode > CAMERA_MODES)
				chook->mode = 0;
		}
		else
			chook->mode = cmode;

		switch (chook->mode)
		{
			case	1:
				//	lock all angles to camera
				gstate->SetCameraState (self, true, CAMFL_LOCK_X + CAMFL_LOCK_Y + CAMFL_LOCK_Z);

				chook->ideal_angle = Vector (0, 90, 0);
				chook->view_angle = Vector (0, 90, 0);
				chook->next_change = -1;
				chook->min_change_time = 1.0;
				chook->follow_dist = 80.0;

				chook->pre_think = NULL;
				chook->post_think = camera_auto_chase;

				// reset angles so target doesn't get stuck if possessed
				camera->goalentity->s.angles [0] = 0.0;
				camera->goalentity->s.angles [2] = 0.0;

				break;
			case	2:
				//	lock all angles to camera
				gstate->SetCameraState (self, true, CAMFL_LOCK_X + CAMFL_LOCK_Y + CAMFL_LOCK_Z);

				chook->pre_think = NULL;
				chook->post_think = camera_track_client;
				break;

			case	3:
				//	lock all angles to camera
				gstate->SetCameraState (self, true, CAMFL_LOCK_X + CAMFL_LOCK_Y + CAMFL_LOCK_Z);

				chook->view_angle = Vector (0, 0, 0);				
				chook->follow_dist = 128.0;

				chook->pre_think = camera_follow;
				chook->post_think = camera_track_client;
				break;

			case	4:
				//	lock all angles to camera
				gstate->SetCameraState (self, true, CAMFL_LOCK_X + CAMFL_LOCK_Y + CAMFL_LOCK_Z);

				chook->follow_dist = 128.0;

				chook->pre_think = NULL;
				chook->post_think = camera_rotate;
				break;

			case	99:
				//	gib cam
				//	lock all angles to camera
				gstate->SetCameraState (self, true, CAMFL_LOCK_X + CAMFL_LOCK_Y + CAMFL_LOCK_Z);

				chook->ideal_angle = Vector (15, 90, 0);
				chook->view_angle = Vector (15, 90, 0);
				chook->next_change = -1;
				chook->min_change_time = 1.0;
				chook->follow_dist = 156.0;

				chook->pre_think = NULL;
				chook->post_think = camera_auto_chase;

				// reset angles so target doesn't get stuck if possessed
				camera->goalentity->s.angles [0] = 0.0;
				camera->goalentity->s.angles [2] = 0.0;

				break;

			default:
				//	MODE 0

				//	only let pitch movement translate
				gstate->SetCameraState (self, true, CAMFL_LOCK_Y + CAMFL_LOCK_Z);

				chook->ideal_angle = Vector (0, 0, 0);
				chook->view_angle = Vector (15, 0, 0);
				chook->next_change = -1;
				chook->min_change_time = 0.5;
				chook->follow_dist = 80.0;
				chook->max_speed = CAMERA_MAXSPEED * 2.0;

				chook->pre_think = NULL;
				chook->post_think = camera_place_behind;

				/*
				chook->view_angle = Vector (15, 0, 0);				
				chook->follow_dist = 128.0;					

				chook->pre_think = camera_place_behind;
				chook->post_think = camera_track_yaw;
				*/
				break;
		}

		gstate->Con_Printf ("camera mode %i\n", chook->mode);
	}
}

//////////////////////
//	camera_start
//
//  creates a view entity
//	behind self
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_start (userEntity_t *target)
{
	Vector			org, temp;
	cameraHook_t	*chook;
	playerHook_t	*phook = (playerHook_t *) target->userHook;

	camera = gstate->SpawnEntity ();

	camera->solid = SOLID_NOT;
	camera->movetype = MOVETYPE_FLY;
	camera->className = "camera";
	//camera->flags = FL_CLIENT;
	
	camera->owner = target;	// who started the camera
	
	camera->goalentity = target;	// entity the camera is following
	camera->s.effects = 0;

	com->ZeroVector (camera->view_ofs);

	camera->s.modelindex = gstate->ModelIndex ("models/debug/lastnode/lastnode.md2");
	camera->s.renderfx |= RF_NODRAW;
//	camera->modelName = NULL;
	gstate->SetSize (camera, 0, 0, 0, 0, 0, 0);
	gstate->SetOrigin2 (camera, target->s.origin);

	camera->userHook = com->malloc_hook (sizeof (cameraHook_t), MEM_MALLOC);
	chook = (cameraHook_t *) camera->userHook;

	chook->mode = CAMERA_MODES;
	chook->demonic = FALSE;

	// give the client a point to the camera
	phook->camera = camera;

	gstate->SetClientViewEntity (target, camera);
	
	if (chook->demonic)
		gstate->SetClientInputEntity (target, camera->goalentity);
	else
		gstate->SetClientInputEntity (target, camera->owner);

	camera_mode (target, CAMERA_MODES);

	camera_place_behind (camera);

	gstate->sprint (target, "AmoebaCam activated\n");
}

//////////////////////
//	camera_stop
//
//	sets view ent back to camera->userHook->master
//	and removes camera
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_stop (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;

	if (phook->camera)
	{
		userEntity_t	*camera = phook->camera;
		cameraHook_t	*chook = (cameraHook_t *) camera->userHook;
		playerHook_t	*ghook = (playerHook_t *) phook->camera->goalentity->userHook;
		Vector			temp;

		if (camera->goalentity->flags & FL_POSSESSED)
			camera_cast_out (camera);

		//	turn off the camera
		gstate->SetCameraState (self, false, 0);

		// move view back to default entity
		gstate->SetClientViewEntity (self, camera->owner);
		
		// set the input entity back to the client
		gstate->SetClientInputEntity (self, camera->owner);

		temp = Vector (self->s.angles);
		temp.z = 0;
		camera_set_viewangle (self, temp);

		com->free (chook);
		gstate->RemoveEntity (camera);

		//	remove the client's camera
		phook->camera = NULL;
		//	remove the target's camera
		if (ghook && camera->goalentity->flags & (FL_CLIENT | FL_MONSTER | FL_BOT))
			ghook->camera = NULL;

		gstate->sprint (self, "AmoebaCam deactivated\n");

		self->s.renderfx = RF_NODRAW;
	}
}

//////////////////////
//	camera_toggle_gravity
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_toggle_gravity (userEntity_t *self)
{
	gstate->gravity_dir [2] *= -1;
}

//////////////////////
//	camera_toggle
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_toggle (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	cameraHook_t	*chook;

	// make sure client is actually connected
	if (!phook || self->deadflag != DEAD_NO)
		return;

	if (phook->camera)
	{
		chook = (cameraHook_t *) phook->camera->userHook;
		
		if (chook->mode < 1)
			camera_mode (self, CAMERA_MODES);
		else
			camera_stop (self);
	}
	else
		camera_start (self);
}

//////////////////////
//	camera_set_debug_target
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_set_debug_target (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;

	// make sure client is actually connected
	if (!phook)
		return;

	if (phook->camera)
	{
		cam_debug_target = camera->goalentity;
		gstate->sprint (self, "Debug target set to %s\n", cam_debug_target->className);
	}
}

//////////////////////
//	camera_toggle_debug_target
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_toggle_debug_target (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	playerHook_t	*ghook;

	// make sure client is actually connected
	if (!phook)
		return;

	if (phook->camera && cam_debug_target)
	{
		if (camera->goalentity == camera->owner)
			camera->goalentity = cam_debug_target;
		else
		{
			ghook = (playerHook_t *) phook->camera->goalentity->userHook;
			//	remove the target's camera
			ghook->camera = NULL;
	
			camera->goalentity = camera->owner;
		}
	}
}

//////////////////////
//	camera_kill_target
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_kill_target (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;

	// make sure client is actually connected
	if (!phook)
		return;

	if (phook->camera)
	{
		if (camera->goalentity != camera->owner)
		{
			if (camera->goalentity->flags &  (FL_MONSTER + FL_BOT))
			{
				camera->goalentity->die (camera->goalentity, self, self, 32768, camera->goalentity->s.origin);
			}
			else
			{
				gstate->RemoveEntity (camera->goalentity);
				camera->goalentity = camera->owner;
			}
		}
	}
}

//////////////////////
//	camera_lock
//
//	ONLY CALLED WITH SELF == CLIENT
//////////////////////

void	camera_lock (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	cameraHook_t	*chook;

	// make sure client is actually connected
	if (!phook)
		return;

	if (phook->camera)
	{
		chook = (cameraHook_t *) phook->camera->userHook;
		
		if (chook->mode >= 128)
			chook->mode -= 128;
		else
			chook->mode += 128;
	}
}

//////////////////////
//	camera_force_enemy
//
//	ONLY CALLED WITH SELF == CLIENT
//
//	forces the camera's target (if a monster or bot) to consider
//	the camera's owner its enemy., even if the target can't see
//	the camera's owner
//////////////////////

void	camera_force_enemy (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	playerHook_t	*mhook;

	// make sure client is actually connected
	if (!phook)
		return;

	if (phook->camera)
	{
		if (camera->goalentity != camera->owner)
		{
			if (camera->goalentity->flags &  (FL_MONSTER + FL_BOT))
			{
				mhook = (playerHook_t *) camera->goalentity->userHook;
				if (mhook->sight_sound) 
					mhook->sight_sound (camera->goalentity);

				camera->goalentity->enemy = camera->owner;
				ai_set_goal (camera->goalentity, camera->goalentity->enemy, camera->goalentity->enemy->s.origin, GOAL_ENEMY);

				gstate->Con_Printf ("Forcing %s's enemy to %s.\n", camera->goalentity->className, camera->owner->className);

				if (!ai_go_to_enemy (camera->goalentity))
					//	can't go for some reason
					return;
				else if (mhook->sight)
					mhook->sight (camera->goalentity);
			}
		}
	}
}