/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "quakedef.h"

float	pm_stepheight = 18;
#ifdef Q2BSPS

#define	Q2PMF_DUCKED			1
#define	Q2PMF_JUMP_HELD		2
#define	Q2PMF_ON_GROUND		4
#define	Q2PMF_TIME_WATERJUMP	8	// pm_time is waterjump
#define	Q2PMF_TIME_LAND		16	// pm_time is time before rejump
#define	Q2PMF_TIME_TELEPORT	32	// pm_time is non-moving time
#define Q2PMF_NO_PREDICTION	64	// temporarily disables prediction (used for grappling hook)

// all of the locals will be zeroed before each
// pmove, just to make damn sure we don't have
// any differences when running on client or server

typedef struct
{
	vec3_t		origin;			// full float precision
	vec3_t		velocity;		// full float precision

	vec3_t		forward, right, up;
	float		frametime;


	q2csurface_t	*groundsurface;
	cplane_t	groundplane;
	int			groundcontents;

	vec3_t		previous_origin;
	qboolean	ladder;
} q2pml_t;

q2pmove_t		*q2pm;
static q2pml_t	q2pml;


// movement parameters
float	pm_stopspeed = 100;
float	pm_maxspeed = 300;
float	pm_duckspeed = 100;
float	pm_accelerate = 10;
float	pm_airaccelerate = 0;
float	pm_wateraccelerate = 10;
float	pm_friction = 6;
float	pm_waterfriction = 1;
float	pm_waterspeed = 400;
//float	pm_stepheight;

/*

  walking up a step should kill some velocity

*/


/*
==================
PMQ2_ClipVelocity

Slide off of the impacting object
returns the blocked flags (1 = floor, 2 = step / wall)
==================
*/
#define	STOP_EPSILON	0.1

void PMQ2_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
{
	float	backoff;
	float	change;
	int		i;
	
	backoff = DotProduct (in, normal) * overbounce;

	for (i=0 ; i<3 ; i++)
	{
		change = normal[i]*backoff;
		out[i] = in[i] - change;
		if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
			out[i] = 0;
	}
}




/*
==================
PMQ2_StepSlideMove

Each intersection will try to step over the obstruction instead of
sliding along it.

Returns a new origin, velocity, and contact entity
Does not modify any world state?
==================
*/
#define	MIN_STEP_NORMAL	0.7		// can't step up onto very steep slopes
#define	MAX_CLIP_PLANES	5
void PMQ2_StepSlideMove_ (void)
{
	int			bumpcount, numbumps;
	vec3_t		dir;
	float		d;
	int			numplanes;
	vec3_t		planes[MAX_CLIP_PLANES];
	vec3_t		primal_velocity;
	int			i, j;
	q2trace_t	trace;
	vec3_t		end;
	float		time_left;
	
	numbumps = 4;
	
	VectorCopy (q2pml.velocity, primal_velocity);
	numplanes = 0;
	
	time_left = q2pml.frametime;

	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
	{
		for (i=0 ; i<3 ; i++)
			end[i] = q2pml.origin[i] + time_left * q2pml.velocity[i];

		trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, end);

		if (trace.allsolid)
		{	// entity is trapped in another solid
			q2pml.velocity[2] = 0;	// don't build up falling damage
			return;
		}

		if (trace.fraction > 0)
		{	// actually covered some distance
			VectorCopy (trace.endpos, q2pml.origin);
			numplanes = 0;
		}

		if (trace.fraction == 1)
			 break;		// moved the entire distance

		// save entity for contact
		if (q2pm->numtouch < MAXTOUCH && trace.ent)
		{
			q2pm->touchents[q2pm->numtouch] = trace.ent;
			q2pm->numtouch++;
		}
		
		time_left -= time_left * trace.fraction;

		// slide along this plane
		if (numplanes >= MAX_CLIP_PLANES)
		{	// this shouldn't really happen
			VectorCopy (vec3_origin, q2pml.velocity);
			break;
		}

		VectorCopy (trace.plane.normal, planes[numplanes]);
		numplanes++;

#if 0
	float		rub;

		//
		// modify velocity so it parallels all of the clip planes
		//
		if (numplanes == 1)
		{	// go along this plane
			VectorCopy (q2pml.velocity, dir);
			VectorNormalize (dir);
			rub = 1.0 + 0.5 * DotProduct (dir, planes[0]);

			// slide along the plane
			PMQ2_ClipVelocity (q2pml.velocity, planes[0], q2pml.velocity, 1.01);
			// rub some extra speed off on xy axis
			// not on Z, or you can scrub down walls
			q2pml.velocity[0] *= rub;
			q2pml.velocity[1] *= rub;
			q2pml.velocity[2] *= rub;
		}
		else if (numplanes == 2)
		{	// go along the crease
			VectorCopy (q2pml.velocity, dir);
			VectorNormalize (dir);
			rub = 1.0 + 0.5 * DotProduct (dir, planes[0]);

			// slide along the plane
			CrossProduct (planes[0], planes[1], dir);
			d = DotProduct (dir, q2pml.velocity);
			VectorScale (dir, d, q2pml.velocity);

			// rub some extra speed off
			VectorScale (q2pml.velocity, rub, q2pml.velocity);
		}
		else
		{
//			Con_Printf ("clip velocity, numplanes == %i\n",numplanes);
			VectorCopy (vec3_origin, q2pml.velocity);
			break;
		}

#else
//
// modify original_velocity so it parallels all of the clip planes
//
		for (i=0 ; i<numplanes ; i++)
		{
			PMQ2_ClipVelocity (q2pml.velocity, planes[i], q2pml.velocity, 1.01);
			for (j=0 ; j<numplanes ; j++)
				if (j != i)
				{
					if (DotProduct (q2pml.velocity, planes[j]) < 0)
						break;	// not ok
				}
			if (j == numplanes)
				break;
		}
		
		if (i != numplanes)
		{	// go along this plane
		}
		else
		{	// go along the crease
			if (numplanes != 2)
			{
//				Con_Printf ("clip velocity, numplanes == %i\n",numplanes);
				VectorCopy (vec3_origin, q2pml.velocity);
				break;
			}
			CrossProduct (planes[0], planes[1], dir);
			d = DotProduct (dir, q2pml.velocity);
			VectorScale (dir, d, q2pml.velocity);
		}
#endif
		//
		// if velocity is against the original velocity, stop dead
		// to avoid tiny occilations in sloping corners
		//
		if (DotProduct (q2pml.velocity, primal_velocity) <= 0)
		{
			VectorCopy (vec3_origin, q2pml.velocity);
			break;
		}
	}

	if (q2pm->s.pm_time)
	{
		VectorCopy (primal_velocity, q2pml.velocity);
	}
}

/*
==================
PMQ2_StepSlideMove

==================
*/
void PMQ2_StepSlideMove (void)
{
	vec3_t		start_o, start_v;
	vec3_t		down_o, down_v;
	q2trace_t		trace;
	float		down_dist, up_dist;
//	vec3_t		delta;
	vec3_t		up, down;

	VectorCopy (q2pml.origin, start_o);
	VectorCopy (q2pml.velocity, start_v);

	PMQ2_StepSlideMove_ ();

	VectorCopy (q2pml.origin, down_o);
	VectorCopy (q2pml.velocity, down_v);

	VectorCopy (start_o, up);
	up[2] += pm_stepheight;

	trace = q2pm->trace (up, q2pm->mins, q2pm->maxs, up);
	if (trace.allsolid)
		return;		// can't step up

	// try sliding above
	VectorCopy (up, q2pml.origin);
	VectorCopy (start_v, q2pml.velocity);

	PMQ2_StepSlideMove_ ();

	// push down the final amount
	VectorCopy (q2pml.origin, down);
	down[2] -= pm_stepheight;
	trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, down);
	if (!trace.allsolid)
	{
		VectorCopy (trace.endpos, q2pml.origin);
	}

#if 0
	VectorSubtract (q2pml.origin, up, delta);
	up_dist = DotProduct (delta, start_v);

	VectorSubtract (down_o, start_o, delta);
	down_dist = DotProduct (delta, start_v);
#else
	VectorCopy(q2pml.origin, up);

	// decide which one went farther
    down_dist = (down_o[0] - start_o[0])*(down_o[0] - start_o[0])
        + (down_o[1] - start_o[1])*(down_o[1] - start_o[1]);
    up_dist = (up[0] - start_o[0])*(up[0] - start_o[0])
        + (up[1] - start_o[1])*(up[1] - start_o[1]);
#endif

	if (down_dist > up_dist || trace.plane.normal[2] < MIN_STEP_NORMAL)
	{
		VectorCopy (down_o, q2pml.origin);
		VectorCopy (down_v, q2pml.velocity);
		return;
	}
	//!! Special case
	// if we were walking along a plane, then we need to copy the Z over
	q2pml.velocity[2] = down_v[2];
}


/*
==================
PMQ2_Friction

Handles both ground friction and water friction
==================
*/
void PMQ2_Friction (void)
{
	float	*vel;
	float	speed, newspeed, control;
	float	friction;
	float	drop;
	
	vel = q2pml.velocity;
	
	speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]);
	if (speed < 1)
	{
		vel[0] = 0;
		vel[1] = 0;
		return;
	}

	drop = 0;

// apply ground friction
	if ((q2pm->groundentity && q2pml.groundsurface && !(q2pml.groundsurface->flags & SURF_SLICK) ) || (q2pml.ladder) )
	{
		friction = pm_friction;
		control = speed < pm_stopspeed ? pm_stopspeed : speed;
		drop += control*friction*q2pml.frametime;
	}

// apply water friction
	if (q2pm->waterlevel && !q2pml.ladder)
		drop += speed*pm_waterfriction*q2pm->waterlevel*q2pml.frametime;

// scale the velocity
	newspeed = speed - drop;
	if (newspeed < 0)
	{
		newspeed = 0;
	}
	newspeed /= speed;

	vel[0] = vel[0] * newspeed;
	vel[1] = vel[1] * newspeed;
	vel[2] = vel[2] * newspeed;
}


/*
==============
PMQ2_Accelerate

Handles user intended acceleration
==============
*/
void PMQ2_Accelerate (vec3_t wishdir, float wishspeed, float accel)
{
	int			i;
	float		addspeed, accelspeed, currentspeed;

	currentspeed = DotProduct (q2pml.velocity, wishdir);
	addspeed = wishspeed - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = accel*q2pml.frametime*wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;
	
	for (i=0 ; i<3 ; i++)
		q2pml.velocity[i] += accelspeed*wishdir[i];	
}

void PMQ2_AirAccelerate (vec3_t wishdir, float wishspeed, float accel)
{
	int			i;
	float		addspeed, accelspeed, currentspeed, wishspd = wishspeed;
		
	if (wishspd > 30)
		wishspd = 30;
	currentspeed = DotProduct (q2pml.velocity, wishdir);
	addspeed = wishspd - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = accel * wishspeed * q2pml.frametime;
	if (accelspeed > addspeed)
		accelspeed = addspeed;
	
	for (i=0 ; i<3 ; i++)
		q2pml.velocity[i] += accelspeed*wishdir[i];	
}

/*
=============
PMQ2_AddCurrents
=============
*/
void PMQ2_AddCurrents (vec3_t	wishvel)
{
	vec3_t	v;
	float	s;

	//
	// account for ladders
	//

	if (q2pml.ladder && fabs(q2pml.velocity[2]) <= 200)
	{
		if ((q2pm->viewangles[PITCH] <= -15) && (q2pm->cmd.forwardmove > 0))
			wishvel[2] = 200;
		else if ((q2pm->viewangles[PITCH] >= 15) && (q2pm->cmd.forwardmove > 0))
			wishvel[2] = -200;
		else if (q2pm->cmd.upmove > 0)
			wishvel[2] = 200;
		else if (q2pm->cmd.upmove < 0)
			wishvel[2] = -200;
		else
			wishvel[2] = 0;

		// limit horizontal speed when on a ladder
		if (wishvel[0] < -25)
			wishvel[0] = -25;
		else if (wishvel[0] > 25)
			wishvel[0] = 25;

		if (wishvel[1] < -25)
			wishvel[1] = -25;
		else if (wishvel[1] > 25)
			wishvel[1] = 25;
	}


	//
	// add water currents
	//

	if (q2pm->watertype & MASK_CURRENT)
	{
		memset(v, 0, sizeof(vec3_t));

		if (q2pm->watertype & Q2CONTENTS_CURRENT_0)
			v[0] += 1;
		if (q2pm->watertype & Q2CONTENTS_CURRENT_90)
			v[1] += 1;
		if (q2pm->watertype & Q2CONTENTS_CURRENT_180)
			v[0] -= 1;
		if (q2pm->watertype & Q2CONTENTS_CURRENT_270)
			v[1] -= 1;
		if (q2pm->watertype & Q2CONTENTS_CURRENT_UP)
			v[2] += 1;
		if (q2pm->watertype & Q2CONTENTS_CURRENT_DOWN)
			v[2] -= 1;

		s = pm_waterspeed;
		if ((q2pm->waterlevel == 1) && (q2pm->groundentity))
			s /= 2;

		VectorMA (wishvel, s, v, wishvel);
	}

	//
	// add conveyor belt velocities
	//

	if (q2pm->groundentity)
	{
		memset(v, 0, sizeof(vec3_t));

		if (q2pml.groundcontents & Q2CONTENTS_CURRENT_0)
			v[0] += 1;
		if (q2pml.groundcontents & Q2CONTENTS_CURRENT_90)
			v[1] += 1;
		if (q2pml.groundcontents & Q2CONTENTS_CURRENT_180)
			v[0] -= 1;
		if (q2pml.groundcontents & Q2CONTENTS_CURRENT_270)
			v[1] -= 1;
		if (q2pml.groundcontents & Q2CONTENTS_CURRENT_UP)
			v[2] += 1;
		if (q2pml.groundcontents & Q2CONTENTS_CURRENT_DOWN)
			v[2] -= 1;

		VectorMA (wishvel, 100 /* q2pm->groundentity->speed */, v, wishvel);
	}
}


/*
===================
PMQ2_WaterMove

===================
*/
void PMQ2_WaterMove (void)
{
	int		i;
	vec3_t	wishvel;
	float	wishspeed;
	vec3_t	wishdir;

//
// user intentions
//
	for (i=0 ; i<3 ; i++)
		wishvel[i] = q2pml.forward[i]*q2pm->cmd.forwardmove + q2pml.right[i]*q2pm->cmd.sidemove;

	if (!q2pm->cmd.forwardmove && !q2pm->cmd.sidemove && !q2pm->cmd.upmove)
		wishvel[2] -= 60;		// drift towards bottom
	else
		wishvel[2] += q2pm->cmd.upmove;

	PMQ2_AddCurrents (wishvel);

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize(wishdir);

	if (wishspeed > pm_maxspeed)
	{
		VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel);
		wishspeed = pm_maxspeed;
	}
	wishspeed *= 0.5;

	PMQ2_Accelerate (wishdir, wishspeed, pm_wateraccelerate);

	PMQ2_StepSlideMove ();
}


/*
===================
PMQ2_AirMove

===================
*/
void PMQ2_AirMove (void)
{
	int			i;
	vec3_t		wishvel;
	float		fmove, smove;
	vec3_t		wishdir;
	float		wishspeed;
	float		maxspeed;

	fmove = q2pm->cmd.forwardmove;
	smove = q2pm->cmd.sidemove;
	
//!!!!! pitch should be 1/3 so this isn't needed??!
#if 0
	q2pml.forward[2] = 0;
	q2pml.right[2] = 0;
	VectorNormalize (q2pml.forward);
	VectorNormalize (q2pml.right);
#endif

	for (i=0 ; i<2 ; i++)
		wishvel[i] = q2pml.forward[i]*fmove + q2pml.right[i]*smove;
	wishvel[2] = 0;

	PMQ2_AddCurrents (wishvel);

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize(wishdir);

//
// clamp to server defined max speed
//
	maxspeed = (q2pm->s.pm_flags & Q2PMF_DUCKED) ? pm_duckspeed : pm_maxspeed;

	if (wishspeed > maxspeed)
	{
		VectorScale (wishvel, maxspeed/wishspeed, wishvel);
		wishspeed = maxspeed;
	}
	
	if ( q2pml.ladder )
	{
		PMQ2_Accelerate (wishdir, wishspeed, pm_accelerate);
		if (!wishvel[2])
		{
			if (q2pml.velocity[2] > 0)
			{
				q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
				if (q2pml.velocity[2] < 0)
					q2pml.velocity[2]  = 0;
			}
			else
			{
				q2pml.velocity[2] += q2pm->s.gravity * q2pml.frametime;
				if (q2pml.velocity[2] > 0)
					q2pml.velocity[2]  = 0;
			}
		}
		PMQ2_StepSlideMove ();
	}
	else if ( q2pm->groundentity )
	{	// walking on ground
		q2pml.velocity[2] = 0; //!!! this is before the accel
		PMQ2_Accelerate (wishdir, wishspeed, pm_accelerate);

// PGM	-- fix for negative trigger_gravity fields
//		q2pml.velocity[2] = 0;
		if(q2pm->s.gravity > 0)
			q2pml.velocity[2] = 0;
		else
			q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
// PGM

		if (!q2pml.velocity[0] && !q2pml.velocity[1])
			return;
		PMQ2_StepSlideMove ();
	}
	else
	{	// not on ground, so little effect on velocity
		if (pm_airaccelerate)
			PMQ2_AirAccelerate (wishdir, wishspeed, pm_accelerate);
		else
			PMQ2_Accelerate (wishdir, wishspeed, 1);
		// add gravity
		q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
		PMQ2_StepSlideMove ();
	}
}



/*
=============
PMQ2_CatagorizePosition
=============
*/
void PMQ2_CatagorizePosition (void)
{
	vec3_t		point;
	int			cont;
	q2trace_t		trace;
	int			sample1;
	int			sample2;

// if the player hull point one unit down is solid, the player
// is on ground

// see if standing on something solid	
	point[0] = q2pml.origin[0];
	point[1] = q2pml.origin[1];
	point[2] = q2pml.origin[2] - 0.25;
	if (q2pml.velocity[2] > 180) //!!ZOID changed from 100 to 180 (ramp accel)
	{
		q2pm->s.pm_flags &= ~Q2PMF_ON_GROUND;
		q2pm->groundentity = NULL;
	}
	else
	{
		trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, point);
		q2pml.groundplane = trace.plane;
		q2pml.groundsurface = trace.surface;
		q2pml.groundcontents = trace.contents;

		if (!trace.ent || (trace.plane.normal[2] < 0.7 && !trace.startsolid) )
		{
			q2pm->groundentity = NULL;
			q2pm->s.pm_flags &= ~Q2PMF_ON_GROUND;
		}
		else
		{
			q2pm->groundentity = trace.ent;

			// hitting solid ground will end a waterjump
			if (q2pm->s.pm_flags & Q2PMF_TIME_WATERJUMP)
			{
				q2pm->s.pm_flags &= ~(Q2PMF_TIME_WATERJUMP | Q2PMF_TIME_LAND | Q2PMF_TIME_TELEPORT);
				q2pm->s.pm_time = 0;
			}

			if (! (q2pm->s.pm_flags & Q2PMF_ON_GROUND) )
			{	// just hit the ground
				q2pm->s.pm_flags |= Q2PMF_ON_GROUND;
				// don't do landing time if we were just going down a slope
				if (q2pml.velocity[2] < -200)
				{
					q2pm->s.pm_flags |= Q2PMF_TIME_LAND;
					// don't allow another jump for a little while
					if (q2pml.velocity[2] < -400)
						q2pm->s.pm_time = 25;	
					else
						q2pm->s.pm_time = 18;
				}
			}
		}

#if 0
		if (trace.fraction < 1.0 && trace.ent && q2pml.velocity[2] < 0)
			q2pml.velocity[2] = 0;
#endif

		if (q2pm->numtouch < MAXTOUCH && trace.ent)
		{
			q2pm->touchents[q2pm->numtouch] = trace.ent;
			q2pm->numtouch++;
		}
	}

//
// get waterlevel, accounting for ducking
//
	q2pm->waterlevel = 0;
	q2pm->watertype = 0;

	sample2 = q2pm->viewheight - q2pm->mins[2];
	sample1 = sample2 / 2;

	point[2] = q2pml.origin[2] + q2pm->mins[2] + 1;	
	cont = q2pm->pointcontents (point);

	if (cont & MASK_WATER)
	{
		q2pm->watertype = cont;
		q2pm->waterlevel = 1;
		point[2] = q2pml.origin[2] + q2pm->mins[2] + sample1;
		cont = q2pm->pointcontents (point);
		if (cont & MASK_WATER)
		{
			q2pm->waterlevel = 2;
			point[2] = q2pml.origin[2] + q2pm->mins[2] + sample2;
			cont = q2pm->pointcontents (point);
			if (cont & MASK_WATER)
				q2pm->waterlevel = 3;
		}
	}

}


/*
=============
PMQ2_CheckJump
=============
*/
void PMQ2_CheckJump (void)
{
	if (q2pm->s.pm_flags & Q2PMF_TIME_LAND)
	{	// hasn't been long enough since landing to jump again
		return;
	}

	if (q2pm->cmd.upmove < 10)
	{	// not holding jump
		q2pm->s.pm_flags &= ~Q2PMF_JUMP_HELD;
		return;
	}

	// must wait for jump to be released
	if (q2pm->s.pm_flags & Q2PMF_JUMP_HELD)
		return;

	if (q2pm->s.pm_type == Q2PM_DEAD)
		return;

	if (q2pm->waterlevel >= 2)
	{	// swimming, not jumping
		q2pm->groundentity = NULL;

		if (q2pml.velocity[2] <= -300)
			return;

		if (q2pm->watertype == Q2CONTENTS_WATER)
			q2pml.velocity[2] = 100;
		else if (q2pm->watertype == Q2CONTENTS_SLIME)
			q2pml.velocity[2] = 80;
		else
			q2pml.velocity[2] = 50;
		return;
	}

	if (q2pm->groundentity == NULL)
		return;		// in air, so no effect

	q2pm->s.pm_flags |= Q2PMF_JUMP_HELD;

	q2pm->groundentity = NULL;
	q2pml.velocity[2] += 270;
	if (q2pml.velocity[2] < 270)
		q2pml.velocity[2] = 270;
}


/*
=============
PMQ2_CheckSpecialMovement
=============
*/
void PMQ2_CheckSpecialMovement (void)
{
	vec3_t	spot;
	int		cont;
	vec3_t	flatforward;
	q2trace_t	trace;

	if (q2pm->s.pm_time)
		return;

	q2pml.ladder = false;

	// check for ladder
	flatforward[0] = q2pml.forward[0];
	flatforward[1] = q2pml.forward[1];
	flatforward[2] = 0;
	VectorNormalize (flatforward);

	VectorMA (q2pml.origin, 1, flatforward, spot);
	trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, spot);
	if ((trace.fraction < 1) && (trace.contents & Q2CONTENTS_LADDER))
		q2pml.ladder = true;

	// check for water jump
	if (q2pm->waterlevel != 2)
		return;

	VectorMA (q2pml.origin, 30, flatforward, spot);
	spot[2] += 4;
	cont = q2pm->pointcontents (spot);
	if (!(cont & Q2CONTENTS_SOLID))
		return;

	spot[2] += 16;
	cont = q2pm->pointcontents (spot);
	if (cont)
		return;
	// jump out of water
	VectorScale (flatforward, 50, q2pml.velocity);
	q2pml.velocity[2] = 350;

	q2pm->s.pm_flags |= Q2PMF_TIME_WATERJUMP;
	q2pm->s.pm_time = 255;
}


/*
===============
PMQ2_FlyMove
===============
*/
void PMQ2_FlyMove (qboolean doclip)
{
	float	speed, drop, friction, control, newspeed;
	float	currentspeed, addspeed, accelspeed;
	int			i;
	vec3_t		wishvel;
	float		fmove, smove;
	vec3_t		wishdir;
	float		wishspeed;
	vec3_t		end;
	q2trace_t	trace;

	q2pm->viewheight = DEFAULT_VIEWHEIGHT;

	// friction

	speed = Length (q2pml.velocity);
	if (speed < 1)
	{
		VectorCopy (vec3_origin, q2pml.velocity);
	}
	else
	{
		drop = 0;

		friction = pm_friction*1.5;	// extra friction
		control = speed < pm_stopspeed ? pm_stopspeed : speed;
		drop += control*friction*q2pml.frametime;

		// scale the velocity
		newspeed = speed - drop;
		if (newspeed < 0)
			newspeed = 0;
		newspeed /= speed;

		VectorScale (q2pml.velocity, newspeed, q2pml.velocity);
	}

	// accelerate
	fmove = q2pm->cmd.forwardmove;
	smove = q2pm->cmd.sidemove;
	
	VectorNormalize (q2pml.forward);
	VectorNormalize (q2pml.right);

	for (i=0 ; i<3 ; i++)
		wishvel[i] = q2pml.forward[i]*fmove + q2pml.right[i]*smove;
	wishvel[2] += q2pm->cmd.upmove;

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize(wishdir);

	//
	// clamp to server defined max speed
	//
	if (wishspeed > pm_maxspeed)
	{
		VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel);
		wishspeed = pm_maxspeed;
	}


	currentspeed = DotProduct(q2pml.velocity, wishdir);
	addspeed = wishspeed - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = pm_accelerate*q2pml.frametime*wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;
	
	for (i=0 ; i<3 ; i++)
		q2pml.velocity[i] += accelspeed*wishdir[i];	

	if (doclip) {
		for (i=0 ; i<3 ; i++)
			end[i] = q2pml.origin[i] + q2pml.frametime * q2pml.velocity[i];

		trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, end);

		VectorCopy (trace.endpos, q2pml.origin);
	} else {
		// move
		VectorMA (q2pml.origin, q2pml.frametime, q2pml.velocity, q2pml.origin);
	}
}


/*
==============
PMQ2_CheckDuck

Sets mins, maxs, and q2pm->viewheight
==============
*/
void PMQ2_CheckDuck (void)
{
	q2trace_t	trace;

	q2pm->mins[0] = -16;
	q2pm->mins[1] = -16;

	q2pm->maxs[0] = 16;
	q2pm->maxs[1] = 16;

	if (q2pm->s.pm_type == Q2PM_GIB)
	{
		q2pm->mins[2] = 0;
		q2pm->maxs[2] = 16;
		q2pm->viewheight = 8;
		return;
	}

	q2pm->mins[2] = -24;

	if (q2pm->s.pm_type == Q2PM_DEAD)
	{
		q2pm->s.pm_flags |= Q2PMF_DUCKED;
	}
	else if (q2pm->cmd.upmove < 0 && (q2pm->s.pm_flags & Q2PMF_ON_GROUND) )
	{	// duck
		q2pm->s.pm_flags |= Q2PMF_DUCKED;
	}
	else
	{	// stand up if possible
		if (q2pm->s.pm_flags & Q2PMF_DUCKED)
		{
			// try to stand up
			q2pm->maxs[2] = 32;
			trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, q2pml.origin);
			if (!trace.allsolid)
				q2pm->s.pm_flags &= ~Q2PMF_DUCKED;
		}
	}

	if (q2pm->s.pm_flags & Q2PMF_DUCKED)
	{
		q2pm->maxs[2] = 4;
		q2pm->viewheight = -2;
	}
	else
	{
		q2pm->maxs[2] = 32;
		q2pm->viewheight = DEFAULT_VIEWHEIGHT;
	}
}


/*
==============
PMQ2_DeadMove
==============
*/
void PMQ2_DeadMove (void)
{
	float	forward;

	if (!q2pm->groundentity)
		return;

	// extra friction

	forward = Length (q2pml.velocity);
	forward -= 20;
	if (forward <= 0)
	{
		memset(q2pml.velocity, 0, sizeof(vec3_t));
	}
	else
	{
		VectorNormalize (q2pml.velocity);
		VectorScale (q2pml.velocity, forward, q2pml.velocity);
	}
}


qboolean	PMQ2_GoodPosition (void)
{
	q2trace_t	trace;
	vec3_t	origin, end;
	int		i;

	if (q2pm->s.pm_type == Q2PM_SPECTATOR)
		return true;

	for (i=0 ; i<3 ; i++)
		origin[i] = end[i] = q2pm->s.origin[i]*0.125;
	trace = q2pm->trace (origin, q2pm->mins, q2pm->maxs, end);

	return !trace.allsolid;
}

/*
================
PMQ2_SnapPosition

On exit, the origin will have a value that is pre-quantized to the 0.125
precision of the network channel and in a valid position.
================
*/
void PMQ2_SnapPosition (void)
{
	int		sign[3];
	int		i, j, bits;
	short	base[3];
	// try all single bits first
	static int jitterbits[8] = {0,4,1,2,3,5,6,7};

	// snap velocity to eigths
	for (i=0 ; i<3 ; i++)
		q2pm->s.velocity[i] = (int)(q2pml.velocity[i]*8);

	for (i=0 ; i<3 ; i++)
	{
		if (q2pml.origin[i] >= 0)
			sign[i] = 1;
		else 
			sign[i] = -1;
		q2pm->s.origin[i] = (int)(q2pml.origin[i]*8);
		if (q2pm->s.origin[i]*0.125 == q2pml.origin[i])
			sign[i] = 0;
	}
	VectorCopy (q2pm->s.origin, base);

	// try all combinations
	for (j=0 ; j<8 ; j++)
	{
		bits = jitterbits[j];
		VectorCopy (base, q2pm->s.origin);
		for (i=0 ; i<3 ; i++)
			if (bits & (1<<i) )
				q2pm->s.origin[i] += sign[i];

		if (PMQ2_GoodPosition ())
			return;
	}

	// go back to the last position
	VectorCopy (q2pml.previous_origin, q2pm->s.origin);
//	Con_DPrintf ("using previous_origin\n");
}

#if 0
//NO LONGER USED
/*
================
PMQ2_InitialSnapPosition

================
*/
void PMQ2_InitialSnapPosition (void)
{
	int		x, y, z;
	short	base[3];

	VectorCopy (q2pm->s.origin, base);

	for (z=1 ; z>=-1 ; z--)
	{
		q2pm->s.origin[2] = base[2] + z;
		for (y=1 ; y>=-1 ; y--)
		{
			q2pm->s.origin[1] = base[1] + y;
			for (x=1 ; x>=-1 ; x--)
			{
				q2pm->s.origin[0] = base[0] + x;
				if (PMQ2_GoodPosition ())
				{
					q2pml.origin[0] = q2pm->s.origin[0]*0.125;
					q2pml.origin[1] = q2pm->s.origin[1]*0.125;
					q2pml.origin[2] = q2pm->s.origin[2]*0.125;
					VectorCopy (q2pm->s.origin, q2pml.previous_origin);
					return;
				}
			}
		}
	}

	Con_DPrintf ("Bad InitialSnapPosition\n");
}
#else
/*
================
PMQ2_InitialSnapPosition

================
*/
void PMQ2_InitialSnapPosition(void)
{
	int        x, y, z;
	short      base[3];
	static int offset[3] = { 0, -1, 1 };

	VectorCopy (q2pm->s.origin, base);

	for ( z = 0; z < 3; z++ ) {
		q2pm->s.origin[2] = base[2] + offset[ z ];
		for ( y = 0; y < 3; y++ ) {
			q2pm->s.origin[1] = base[1] + offset[ y ];
			for ( x = 0; x < 3; x++ ) {
				q2pm->s.origin[0] = base[0] + offset[ x ];
				if (PMQ2_GoodPosition ()) {
					q2pml.origin[0] = q2pm->s.origin[0]*0.125;
					q2pml.origin[1] = q2pm->s.origin[1]*0.125;
					q2pml.origin[2] = q2pm->s.origin[2]*0.125;
					VectorCopy (q2pm->s.origin, q2pml.previous_origin);
					return;
				}
			}
		}
	}

	Con_DPrintf ("Bad InitialSnapPosition\n");
}

#endif

/*
================
PMQ2_ClampAngles

================
*/
void PMQ2_ClampAngles (void)
{
	short	temp;
	int		i;

	if (q2pm->s.pm_flags & Q2PMF_TIME_TELEPORT)
	{
		q2pm->viewangles[YAW] = SHORT2ANGLE(q2pm->cmd.angles[YAW] + q2pm->s.delta_angles[YAW]);
		q2pm->viewangles[PITCH] = 0;
		q2pm->viewangles[ROLL] = 0;
	}
	else
	{
		// circularly clamp the angles with deltas
		for (i=0 ; i<3 ; i++)
		{
			temp = q2pm->cmd.angles[i] + q2pm->s.delta_angles[i];
			q2pm->viewangles[i] = SHORT2ANGLE(temp);
		}

		// don't let the player look up or down more than 90 degrees
		if (q2pm->viewangles[PITCH] > 89 && q2pm->viewangles[PITCH] < 180)
			q2pm->viewangles[PITCH] = 89;
		else if (q2pm->viewangles[PITCH] < 271 && q2pm->viewangles[PITCH] >= 180)
			q2pm->viewangles[PITCH] = 271;
	}
	AngleVectors (q2pm->viewangles, q2pml.forward, q2pml.right, q2pml.up);
}

/*
================
Pmove

Can be called by either the server or the client
================
*/
void VARGS Q2_Pmove (q2pmove_t *pmove)
{
	q2pm = pmove;

	// clear results
	q2pm->numtouch = 0;
	memset (q2pm->viewangles, 0, sizeof(vec3_t));
	q2pm->viewheight = 0;
	q2pm->groundentity = 0;
	q2pm->watertype = 0;
	q2pm->waterlevel = 0;

	// clear all pmove local vars
	memset (&q2pml, 0, sizeof(q2pml));

	// convert origin and velocity to float values
	q2pml.origin[0] = q2pm->s.origin[0]*0.125;
	q2pml.origin[1] = q2pm->s.origin[1]*0.125;
	q2pml.origin[2] = q2pm->s.origin[2]*0.125;

	q2pml.velocity[0] = q2pm->s.velocity[0]*0.125;
	q2pml.velocity[1] = q2pm->s.velocity[1]*0.125;
	q2pml.velocity[2] = q2pm->s.velocity[2]*0.125;

	// save old org in case we get stuck
	VectorCopy (q2pm->s.origin, q2pml.previous_origin);

	q2pml.frametime = q2pm->cmd.msec * 0.001;

	PMQ2_ClampAngles ();

	if (q2pm->s.pm_type == Q2PM_SPECTATOR)
	{
		PMQ2_FlyMove (false);
		PMQ2_SnapPosition ();
		return;
	}

	if (q2pm->s.pm_type >= Q2PM_DEAD)
	{
		q2pm->cmd.forwardmove = 0;
		q2pm->cmd.sidemove = 0;
		q2pm->cmd.upmove = 0;
	}

	if (q2pm->s.pm_type == Q2PM_FREEZE)
		return;		// no movement at all

	// set mins, maxs, and viewheight
	PMQ2_CheckDuck ();

	if (q2pm->snapinitial)
		PMQ2_InitialSnapPosition ();

	// set groundentity, watertype, and waterlevel
	PMQ2_CatagorizePosition ();

	if (q2pm->s.pm_type == Q2PM_DEAD)
		PMQ2_DeadMove ();

	PMQ2_CheckSpecialMovement ();

	// drop timing counter
	if (q2pm->s.pm_time)
	{
		int		msec;

		msec = q2pm->cmd.msec >> 3;
		if (!msec)
			msec = 1;
		if ( msec >= q2pm->s.pm_time) 
		{
			q2pm->s.pm_flags &= ~(Q2PMF_TIME_WATERJUMP | Q2PMF_TIME_LAND | Q2PMF_TIME_TELEPORT);
			q2pm->s.pm_time = 0;
		}
		else
			q2pm->s.pm_time -= msec;
	}

	if (q2pm->s.pm_flags & Q2PMF_TIME_TELEPORT)
	{	// teleport pause stays exactly in place
	}
	else if (q2pm->s.pm_flags & Q2PMF_TIME_WATERJUMP)
	{	// waterjump has no control, but falls
		q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
		if (q2pml.velocity[2] < 0)
		{	// cancel as soon as we are falling down again
			q2pm->s.pm_flags &= ~(Q2PMF_TIME_WATERJUMP | Q2PMF_TIME_LAND | Q2PMF_TIME_TELEPORT);
			q2pm->s.pm_time = 0;
		}

		PMQ2_StepSlideMove ();
	}
	else
	{
		PMQ2_CheckJump ();

		PMQ2_Friction ();

		if (q2pm->waterlevel >= 2)
			PMQ2_WaterMove ();
		else {
			vec3_t	angles;

			VectorCopy(q2pm->viewangles, angles);
			if (angles[PITCH] > 180)
				angles[PITCH] = angles[PITCH] - 360;
			angles[PITCH] /= 3;

			AngleVectors (angles, q2pml.forward, q2pml.right, q2pml.up);

			PMQ2_AirMove ();
		}
	}

	// set groundentity, watertype, and waterlevel for final spot
	PMQ2_CatagorizePosition ();

	PMQ2_SnapPosition ();
}


#endif

