//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/pmove.c                                                $
// $Revision:: 3                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/8/01 4:49 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
//

#include "qcommon.h"

#define	STEPSIZE	18

// 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;
	
	
	csurface_t *groundsurface;
	cplane_t groundplane;
	int groundcontents;
	
	vec3_t previous_origin;
	qboolean ladder;
	cplane_t plane;
	qboolean bounced;
	csurface_t *bouncesurface;
} pml_t;

pmove_t *pm;
pml_t pml;


// movement parameters

// FIXME
// these should be sent accross the network
#define CROUCH_HEIGHT			36
#define CROUCH_EYE_HEIGHT		30
#define STAND_HEIGHT			72
#define STAND_EYE_HEIGHT		66
// 2015 code
// for hoverbike
#define HOVERBIKE_HEIGHT		32
#define HOVERBIKE_EYE_HEIGHT	24

extern cvar_t *sv_stopspeed;
extern cvar_t *sv_waterfriction;
extern cvar_t *sv_waterspeed;
extern cvar_t *sv_wateraccelerate;
extern cvar_t *sv_maxwaterspeed;
extern cvar_t *sv_maxspeed;
extern cvar_t *sv_crouchspeed;
extern cvar_t *sv_accelerate;
extern cvar_t *sv_friction;
extern cvar_t *sv_edgefriction;
extern cvar_t *sv_airaccelerate;
extern cvar_t *sv_airmaxspeed;

qboolean using_conveyor = false;
qboolean bounced = false;
float movefactor=1.0f;

int MY_X = 0;
int MY_Y = 1;
int MY_Z = 2;
float SIGN = 1;

/*

  walking up a step should kill some velocity

*/


/*
==================
PM_ClipVelocity

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

static void PM_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;
	}
}

/*
==================
PM_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
static void PM_StepSlideMove_ (void)
{
	int			bumpcount, numbumps;
	vec3_t		dir;
   float       d;
//   float       rub;
	int			numplanes;
	vec3_t		planes[MAX_CLIP_PLANES];
	vec3_t		primal_velocity;
   int         i;
   int         j;
	trace_t	trace;
	vec3_t		end;
	float		time_left;

	numbumps = 4;

	VectorCopy (pml.velocity, primal_velocity);
	numplanes = 0;

	time_left = pml.frametime;

	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
	{
		for (i=0 ; i<3 ; i++)
			end[i] = pml.origin[i] + time_left * pml.velocity[i];
		trace = pm->trace (pml.origin, pm->mins, pm->maxs, end);

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

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

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

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

		time_left -= time_left * trace.fraction;

		// slide along this plane
		if (numplanes >= MAX_CLIP_PLANES)
		{	// this shouldn't really happen

			VectorCopy (vec3_origin, pml.velocity);
			break;
		}

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

//
// modify original_velocity so it parallels all of the clip planes
//
		for (i=0 ; i<numplanes ; i++)
		{
			// check for bounciness
			if ( trace.surface && ( trace.surface->frequency ) )
			{
				float restitution, amount;
				vec3_t bounce;
      
				bounced = true;
				pml.bounced = true;
				pml.bouncesurface = trace.surface;
				//
				// get our restitution
				//
				restitution = trace.surface->frequency;
				//
				// find out how much of our incoming velocity is along this plane
				//
				amount = DotProduct( planes[ 0 ], pml.velocity );
				//
				// we negate and scale because of the bounce
				//
				amount = (1.0f + restitution) * -amount;
				//
				// calculate bounce velocity
				//
				VectorScale( planes[ 0 ], amount, bounce );
				//
				// add it to our velocity
				//
				VectorAdd( pml.velocity, bounce, pml.velocity );
				//
				// check to see if our velocity is too low
				//
				for (i=0 ; i<3 ; i++)
				{
					if (pml.velocity[i] > -STOP_EPSILON && pml.velocity[i] < STOP_EPSILON)
						pml.velocity[i] = 0;
				}
			}
			else
			{
				PM_ClipVelocity (pml.velocity, planes[i], pml.velocity, 1.01);
			}
			for (j=0 ; j<numplanes ; j++)
				if (j != i)
				{
					if (DotProduct (pml.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, pml.velocity);
				break;
			}
			CrossProduct (planes[0], planes[1], dir);
			d = DotProduct (dir, pml.velocity);
			VectorScale (dir, d, pml.velocity);
		}
      //
		// if velocity is against the original velocity, stop dead
		// to avoid tiny occilations in sloping corners
		//
		if (!bounced && DotProduct (pml.velocity, primal_velocity) <= 0)
		{
			VectorCopy (vec3_origin, pml.velocity);
			break;
		}
	}

   if (pm->s.pm_time)
	{
		VectorCopy (primal_velocity, pml.velocity);
	}
}

/*
==================
PM_StepSlideMove

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

	bounced = false;

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

	PM_StepSlideMove_ ();

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

	VectorCopy (start_o, up);
	up[MY_Z] += STEPSIZE * SIGN;

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

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

	PM_StepSlideMove_ ();

	// push down the final amount
	VectorCopy (pml.origin, down);
	down[MY_Z] -= STEPSIZE * SIGN;
	trace = pm->trace (pml.origin, pm->mins, pm->maxs, down);
	if (!trace.allsolid)
	{
		VectorCopy (trace.endpos, pml.origin);
	}

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

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

	// decide which one went farther
    down_dist = (down_o[MY_X] - start_o[MY_X])*(down_o[MY_X] - start_o[MY_X])
        + (down_o[MY_Y] - start_o[MY_Y])*(down_o[MY_Y] - start_o[MY_Y]);
    up_dist = (up[MY_X] - start_o[MY_X])*(up[MY_X] - start_o[MY_X])
        + (up[MY_Y] - start_o[MY_Y])*(up[MY_Y] - start_o[MY_Y]);
#endif
   if (bounced || down_dist > up_dist || (trace.plane.normal[MY_Z]*SIGN) < MIN_STEP_NORMAL)
	{
 		VectorCopy (down_o, pml.origin);
		VectorCopy (down_v, pml.velocity);
		return;
	}
	//!! Special case
	// if we were walking along a plane, then we need to copy the Z over
	// MED took this out to make bouncing work
	// pml.velocity[MY_Z] = down_v[MY_Z];
}


/*
==================
PM_Friction

Handles both ground friction and water friction
==================
*/
static void PM_Friction (void)
{
	float	*vel;
	float	speed, newspeed, control;
	float	friction;
	float	drop;
	vec3_t	start, stop;
	trace_t	trace;

	vel = pml.velocity;

	speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]);
	if (speed < 1)
	{
		vel[MY_X] = 0;
		vel[MY_Y] = 0;
		return;
	}

	drop = 0;

// apply ground friction
	if ((pm->groundentity && pml.groundsurface ) || (pml.ladder) )
	{
		if (pml.groundsurface)
			friction = sv_friction->value * pml.groundsurface->magnitude;
		else
			friction = sv_friction->value;

		if (!pml.ladder)
		{
			// if the leading edge is over a dropoff, increase friction
			start[MY_X] = stop[MY_X] = pml.origin[MY_X] + vel[MY_X]/speed*16;
			start[MY_Y] = stop[MY_Y] = pml.origin[MY_Y] + vel[MY_Y]/speed*16;
			start[MY_Z] = pml.origin[MY_Z] + pm->mins[MY_Z];
			stop[MY_Z] = start[MY_Z] - 34*SIGN;

			trace = pm->trace( start, vec3_origin, vec3_origin, stop );

			if (trace.fraction == 1.0)
			{
				friction *= sv_edgefriction->value;
			}
		}

		control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
		drop += control*friction*pml.frametime;
	}

// apply water friction
	if (pm->waterlevel && !pml.ladder)
		drop += speed*sv_waterfriction->value*pm->waterlevel*pml.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;
}


/*
==============
PM_Accelerate

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

	currentspeed = DotProduct (pml.velocity, wishdir);
	addspeed = wishspeed - currentspeed;

	if (addspeed <= 0)
		return;
	accelspeed = accel*pml.frametime*wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	for (i=0 ; i<3 ; i++)
		pml.velocity[i] += accelspeed*wishdir[i];
}

static void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel)
{
	int			i;
	vec3_t		tempvel;
	float		addspeed, accelspeed, currentspeed, wishspd = wishspeed;

	if (wishspd > sv_airmaxspeed->value)
		wishspd = sv_airmaxspeed->value;

	currentspeed = DotProduct (pml.velocity, wishdir);

	addspeed = wishspd - currentspeed;
	if (addspeed <= 0)
		return;
	
	accelspeed = accel * wishspeed * pml.frametime;
	
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	for (i=0 ; i<3 ; i++)
		pml.velocity[i] += accelspeed*wishdir[i];

	// This works better for SIN
	if ( !( pm->s.pm_flags & ( PMF_NOAIRCLAMP ) ) )
	{
		VectorCopy( pml.velocity, tempvel );
		tempvel[ MY_Z ] = 0;
		addspeed = VectorLength( tempvel );
		if ( addspeed > sv_airmaxspeed->value )
		{
			float z_vel;

			z_vel = pml.velocity[ MY_Z ];
			VectorScale( pml.velocity, movefactor * ( sv_airmaxspeed->value / addspeed ), pml.velocity );
			pml.velocity[ MY_Z ] = z_vel;
		}
	}
}

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

	using_conveyor = false;

	//
	// add water currents
	//

	if (pm->watertype & MASK_CURRENT)
	{
		VectorClear (v);

		if (pm->watertype & CONTENTS_CURRENT_0)
			v[0] += 1;
		if (pm->watertype & CONTENTS_CURRENT_90)
			v[1] += 1;
		if (pm->watertype & CONTENTS_CURRENT_180)
			v[0] -= 1;
		if (pm->watertype & CONTENTS_CURRENT_270)
			v[1] -= 1;
		if (pm->watertype & CONTENTS_CURRENT_UP)
			v[2] += 1;
		if (pm->watertype & CONTENTS_CURRENT_DOWN)
			v[2] -= 1;

		s = sv_waterspeed->value;

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

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

	//
	// add conveyor belt velocities
	//

	if (pm->groundentity)
	{
		csurface_t *s = pml.groundsurface;
		VectorClear (v);

		if (pml.groundcontents & CONTENTS_CURRENT_0)
			v[0] += 1;
		if (pml.groundcontents & CONTENTS_CURRENT_90)
			v[1] += 1;
		if (pml.groundcontents & CONTENTS_CURRENT_180)
			v[0] -= 1;
		if (pml.groundcontents & CONTENTS_CURRENT_270)
			v[1] -= 1;
		if (pml.groundcontents & CONTENTS_CURRENT_UP)
			v[2] += 1;
		if (pml.groundcontents & CONTENTS_CURRENT_DOWN)
			v[2] -= 1;
		// Add velocity for the conveyor belt
		if (s)
		{
			if ((s->flags & SURF_CONVEYOR) && (s->flags & SURF_TRANSLATE))
			{
				using_conveyor = true;
				VectorMA (wishvel, s->trans_mag/(4*pml.frametime), s->transvec, wishvel);
				// VectorMA (wishvel, s->trans_mag, s->transvec, wishvel);
			}
		}
	}
}

/*
===================
PM_LadderMove

===================
*/
static void PM_LadderMove (vec3_t wishvel)
{
	float	fmove, smove;

	fmove = pm->cmd.forwardmove;
	smove = pm->cmd.sidemove;

// this shouldn't be needed because pitch is 1/3
#if 0
	pml.forward[2] = 0;
	pml.right[2] = 0;
	VectorNormalize (pml.forward);
	VectorNormalize (pml.right);
#endif

	//
	// account for ladders
	//
	if ( pml.ladder && fabs(pml.velocity[MY_Z]) <= 200)
	{
		vec3_t ladder_right;
		vec3_t zaxis;

		zaxis[ MY_X ] = 0;
		zaxis[ MY_Y ] = 0;
		zaxis[ MY_Z ] = SIGN;

		CrossProduct( zaxis, pml.plane.normal, ladder_right );

		if (pm->cmd.forwardmove > 0)
			wishvel[MY_Z] = 200*SIGN;
		else if (pm->cmd.forwardmove < 0)
			wishvel[MY_Z] = -200*SIGN;
		else if (pm->cmd.upmove < 0)
			wishvel[MY_Z] = -200*SIGN;
		else
			wishvel[MY_Z] = 0;

		// limit horizontal speed when on a ladder
		if (smove < -200)
			smove = -200;
		else if (smove > 200)
			smove = 200;

		if ( ( pm->groundentity && pml.groundsurface ) || ( pm->s.pm_flags & PMF_JUMP_HELD ) )
		{
			wishvel[MY_X] = pml.forward[MY_X]*fmove + ladder_right[MY_X]*smove;
			wishvel[MY_Y] = pml.forward[MY_Y]*fmove + ladder_right[MY_Y]*smove * SIGN;
		}
		else
		{
			wishvel[MY_X] = ladder_right[MY_X]*smove;
			wishvel[MY_Y] = ladder_right[MY_Y]*smove * SIGN;
		}
	}
	else
	{
		wishvel[MY_X] = pml.forward[MY_X]*fmove + pml.right[MY_X]*smove;
		wishvel[MY_Y] = pml.forward[MY_Y]*fmove + pml.right[MY_Y]*smove;
		wishvel[MY_Z] = 0;
	}
}

/*
===================
PM_WaterMove

===================
*/
static void PM_WaterMove (void)
{
	vec3_t	wishvel;
	float		wishspeed;
	vec3_t	wishdir;
	int		i;
   float    temp;

//
// user intentions
//
	if ( pml.ladder )
	{
   	wishvel[0] = wishvel[1] = wishvel[2] = 0;
		PM_LadderMove( wishvel );
	}
	else
	{
		for (i=0 ; i<3 ; i++)
			wishvel[i] = pml.forward[i]*pm->cmd.forwardmove + pml.right[i]*pm->cmd.sidemove;

		wishvel[MY_Z] += pm->cmd.upmove*SIGN;
	}
	PM_AddCurrents (wishvel);

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

	temp = sv_maxwaterspeed->value * movefactor * 2;
  	if (wishspeed > temp)
	{
		VectorScale (wishvel, temp/wishspeed, wishvel);
		wishspeed = temp;
	}
	wishspeed *= 0.5;

	PM_Accelerate (wishdir, wishspeed, sv_wateraccelerate->value );

	PM_StepSlideMove ();
}


/*
===================
PM_AirMove

===================
*/
static void PM_AirMove (void)
{
	vec3_t	wishvel;
	vec3_t	wishdir;
	float	wishspeed;
	float	maxspeed;
	float	accelerate1;
	float	accelerate2;

	wishvel[0] = wishvel[1] = wishvel[2] = 0;
	PM_LadderMove( wishvel );

	PM_AddCurrents (wishvel);

	VectorCopy (wishvel, wishdir);

	wishspeed = VectorNormalize(wishdir);

//
// clamp to server defined max speed
//
	if (pm->waterlevel && !pm->groundentity )
	{
		maxspeed = sv_maxwaterspeed->value * movefactor;
		accelerate1 = sv_wateraccelerate->value;
		accelerate2 = sv_wateraccelerate->value;      
	}
	else
	{
		accelerate1 = sv_accelerate->value;
		accelerate2 = sv_airaccelerate->value;
		maxspeed = ((pm->s.pm_flags & PMF_DUCKED) ? sv_crouchspeed->value : sv_maxspeed->value) * movefactor;
	}

	if (wishspeed > maxspeed)
	{
		VectorScale (wishvel, maxspeed/wishspeed, wishvel);
		wishspeed = maxspeed;
	}

	if (pm->waterlevel && !pm->groundentity )
	{
		wishspeed *= 0.5;
	}

	if ( pml.ladder )
	{
		PM_Accelerate (wishdir, wishspeed, accelerate1);

		if (!wishvel[MY_Z])
		{
			if (pml.velocity[MY_Z] > 0)
			{
				pml.velocity[MY_Z] -= pm->s.gravity * pml.frametime;
				if (pml.velocity[MY_Z] < 0)
					pml.velocity[MY_Z]  = 0;
			}
			else
			{
				pml.velocity[MY_Z] += pm->s.gravity * pml.frametime;
				if (pml.velocity[MY_Z] > 0)
					pml.velocity[MY_Z]  = 0;
			}
		}
		PM_StepSlideMove ();
	}
	else if ( pm->groundentity )
	{	// walking on ground
		pml.velocity[MY_Z] = 0; //!!! this is before the accel
		PM_Accelerate (wishdir, wishspeed, accelerate1);
		pml.velocity[MY_Z] -= pm->s.gravity * pml.frametime * SIGN;
		if (!pml.velocity[MY_X] && !pml.velocity[MY_Y])
			return;
		PM_StepSlideMove ();
	}
	else
	{	// not on ground, so little effect on velocity
		PM_AirAccelerate (wishdir, wishspeed, accelerate2);

		// add gravity
		pml.velocity[MY_Z] -= pm->s.gravity * pml.frametime * SIGN;
		PM_StepSlideMove ();
	}
}



/*
=============
PM_CatagorizePosition
=============
*/
static void PM_CatagorizePosition (void)
{
	vec3_t		point;
	int			cont;
	trace_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[MY_X] = pml.origin[MY_X];
	point[MY_Y] = pml.origin[MY_Y];
	point[MY_Z] = pml.origin[MY_Z] - 0.25*SIGN;
	if (pml.velocity[MY_Z]*SIGN > 180)
	{
		pm->s.pm_flags &= ~PMF_ON_GROUND;
		pm->groundentity = NULL;
		pm->groundsurface = NULL;
		pm->groundcontents = 0;
	}
	else
	{
		trace = pm->trace (pml.origin, pm->mins, pm->maxs, point);
		pml.groundplane = trace.plane;
		pml.groundsurface = trace.surface;
		pml.groundcontents = trace.contents;
		pm->groundsurface = pml.groundsurface;
		pm->groundplane = pml.groundplane;
		pm->groundcontents = pml.groundcontents;

		if (!trace.ent || (trace.plane.normal[MY_Z]*SIGN < 0.7 && !trace.startsolid) )
		{
			pm->groundentity = NULL;	// too steep
			pm->s.pm_flags &= ~PMF_ON_GROUND;
			pm->groundsurface = NULL;
		}
		else
		{
			pm->groundentity = trace.ent;
         // hitting solid ground will end a waterjump
			if (pm->s.pm_flags & PMF_TIME_WATERJUMP)
			{
				pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT);
				pm->s.pm_time = 0;
			}

			if (! (pm->s.pm_flags & PMF_ON_GROUND) )
			{	// just hit the ground
				pm->s.pm_flags |= PMF_ON_GROUND;
			}
		}

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

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

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

	point[MY_Z] = pml.origin[MY_Z] + pm->mins[MY_Z] + 1 * SIGN;
	cont = pm->pointcontents (point);

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

	// see if we bounced
	if ( pml.bounced )
	{
		pm->groundsurface = pml.bouncesurface;
	}
}


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

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

	// must wait for jump to be released
	if ( pm->s.pm_flags & PMF_JUMP_HELD )
	{
		if ( pml.ladder )
		{
			pm->cmd.upmove = 0;
		}
		return;
	}

	if (pm->s.pm_type == PM_DEAD)
		return;

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

		if (pml.velocity[MY_Z] * SIGN <= -300)
			return;

		if (pm->watertype == CONTENTS_WATER)
			pml.velocity[MY_Z] = 100 * SIGN;
		else
			pml.velocity[MY_Z] = 50 * SIGN;
		return;
	}

	if ((pm->groundentity == NULL) && !pml.ladder )
		return;		// in air, so no effect

	pm->s.pm_flags |= PMF_JUMP_HELD;

	pm->groundentity = NULL;

	pm->groundsurface = NULL;

	// don't add upward movement when jumping on a ladder
	if ( !pml.ladder )
	{
		// calculate xy velocity
		float length;
		vec3_t xyspeed;

		xyspeed[ 0 ] = pml.velocity[ MY_X ];
		xyspeed[ 1 ] = pml.velocity[ MY_Y ];
		xyspeed[ 2 ] = 0;
		length = VectorLength( xyspeed ) / 280;
		if ( length < 1 ) 
			length = 1;
		if ( length > 1.05f )
			length = 1.05f;
		length *= 270;

		if ( pm->s.pm_flags & (PMF_MUTANT|PMF_ADRENALINE) ) 
			length *= 2;

		pml.velocity[MY_Z] += length * SIGN;
		if (pml.velocity[MY_Z] * SIGN < length)
			pml.velocity[MY_Z] = length * SIGN;
	}

	if ( pml.ladder )
	{
		vec3_t vecadd;
		float speed;

		speed = 100;
		if ( pm->cmd.forwardmove > 0 )
		{
			speed += 100;//pm->cmd.forwardmove;
		}

		VectorScale( pml.plane.normal, speed, vecadd );
		VectorAdd( pml.velocity, vecadd, pml.velocity );
	}
}

/*
=============
PM_CheckSpecialMovement
=============
*/
static void PM_CheckSpecialMovement (void)
{
	vec3_t	spot;
	int		cont;
	vec3_t	flatforward;
	trace_t	trace;
	vec3_t	v;

   if (pm->s.pm_time)
		return;

	pml.ladder = false;
	pm->onladder = false;

// 2015 code
// hoverbike
// player can't use ladders while on a bike
	if(pm->s.pm_type == PM_ONBIKE)
		return;

	// check for ladder
	flatforward[MY_X] = pml.forward[MY_X];
	flatforward[MY_Y] = pml.forward[MY_Y];
	flatforward[MY_Z] = 0;
	VectorNormalize (flatforward);

	VectorMA (pml.origin, 1, flatforward, spot);
	trace = pm->trace (pml.origin, pm->mins, pm->maxs, spot);
	if ((trace.fraction < 1) && (trace.contents & CONTENTS_LADDER))
	{
		pml.plane = trace.plane;
		pml.ladder = true;
		pm->onladder = true;
	}
#if 0
	// Testing getting on ladders going down.
	else if ( !pm->groundentity )
	{
		vec3_t	d;
		float    f;

		f = 2 * VectorLength( pml.velocity ) * pml.frametime;
		VectorMA (pml.origin, f, flatforward, spot);
		spot[MY_Z]-=SIGN;
		VectorCopy( pml.origin, v );
		v[MY_Z]+=SIGN;
		trace = pm->trace (v, pm->mins, pm->maxs, spot);
		if ((trace.fraction < 1) && (trace.contents & CONTENTS_LADDER))
		{
			Com_DPrintf (DEVELOPER_MSG_PMOVE, "using ladder\n");
			pml.plane = trace.plane;
			pml.ladder = true;

			VectorSubtract( spot, v, d );
			d[ MY_Z ] -= 10 * SIGN;
			VectorScale( d, trace.fraction, pml.velocity );
			VectorScale( pml.velocity, 0.75/pml.frametime, pml.velocity );
		}
	}
#endif

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

	VectorCopy( pml.origin, v );
	v[ MY_Z ] += pm->maxs[ MY_Z ] - 28 * SIGN;
	VectorMA (v, 30, flatforward, spot);
	spot[MY_Z] += 4 * SIGN;
	cont = pm->pointcontents (spot);
	if (!(cont & CONTENTS_SOLID))
		return;

	spot[MY_Z] += 16 * SIGN;
	cont = pm->pointcontents (spot);
	if (cont)
		return;
	// jump out of water
	VectorScale (flatforward, 50, pml.velocity);
	pml.velocity[MY_Z] = 350 * SIGN;
	pm->s.pm_flags |= PMF_TIME_WATERJUMP;
	pm->s.pm_time = 255;
}


/*
===============
PM_FlyMove
===============
*/
static void PM_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;
   trace_t  trace;

	pm->viewheight = STAND_EYE_HEIGHT;

	// friction
	speed = VectorLength (pml.velocity);
	if (speed < 1)
	{
		VectorCopy (vec3_origin, pml.velocity);
	}
	else
	{
		drop = 0;

		friction = sv_friction->value*1.5;	// extra friction
		control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
		drop += control*friction*pml.frametime;

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

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

	// accelerate
	fmove = pm->cmd.forwardmove;
	smove = pm->cmd.sidemove;

    if ( !( pm->s.pm_flags & PMF_OLDNOCLIP ) )
		 pml.forward[MY_Z] = 0;

	VectorNormalize (pml.forward);
	VectorNormalize (pml.right);

    for (i=0 ; i<3 ; i++)
		wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
	wishvel[MY_Z] += pm->cmd.upmove * SIGN;

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

	//
	// clamp to server defined max speed
	//
	if (wishspeed > (sv_maxspeed->value * movefactor) )
	{
		VectorScale (wishvel, (sv_maxspeed->value * movefactor )/wishspeed, wishvel);
		wishspeed = sv_maxspeed->value * movefactor;
	}

	currentspeed = DotProduct(pml.velocity, wishdir);
	addspeed = wishspeed - currentspeed;
	if (addspeed <= 0)
		return;

	accelspeed = sv_accelerate->value*pml.frametime*wishspeed;

	if (accelspeed > addspeed)
		accelspeed = addspeed;

	for (i=0 ; i<3 ; i++)
		pml.velocity[i] += accelspeed*wishdir[i];

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

		trace = pm->trace (pml.origin, pm->mins, pm->maxs, end);

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


/*
==============
PM_CheckDuck

Sets mins, maxs, and pm->viewheight
==============
*/
static void PM_CheckDuck (void)
{
	trace_t	trace;

	// 2015 code 
	if ( pm->s.pm_type == PM_ONBIKE )
	{
		pm->s.pm_flags &= ~PMF_DUCKED;

		pm->mins[MY_X] = -18;
		pm->mins[MY_Y] = -18;

		pm->maxs[MY_X] = 18;
		pm->maxs[MY_Y] = 18;

		if(SIGN < 0)
		{
			pm->mins[MY_Z] = -HOVERBIKE_HEIGHT;
			pm->maxs[MY_Z] = 12;
		}
		else
		{
			pm->mins[MY_Z] = -12;
			pm->maxs[MY_Z] = HOVERBIKE_HEIGHT;
		}

		pm->viewheight = HOVERBIKE_EYE_HEIGHT;

		return;
	}

	pm->mins[MY_X] = -16;
	pm->mins[MY_Y] = -16;

	pm->maxs[MY_X] = 16;
	pm->maxs[MY_Y] = 16;

	if (pm->s.pm_type == PM_GIB)
	{
		if ( SIGN < 0 )
			{
			pm->mins[MY_Z] = -16;
			pm->maxs[MY_Z] = 0;
			pm->viewheight = 8;
			}
		else
			{
			pm->mins[MY_Z] = 0;
			pm->maxs[MY_Z] = 16;
			pm->viewheight = 8;
			}
		return;
	}

	pm->mins[MY_Z] = 0;
	pm->maxs[MY_Z] = 0;

	if ( (pm->s.pm_type == PM_DEAD) || (pm->s.pm_type == PM_INVEHICLE) || (pm->s.pm_type == PM_INVEHICLE_ZOOM) )
	{
		pm->s.pm_flags |= PMF_DUCKED;
	}
	// 2015 code 
	else if ((pm->cmd.upmove < 0) && (pm->s.pm_type != PM_MOVECAPTURED))
	{	// duck
		pm->s.pm_flags |= PMF_DUCKED;
	}
	else
	{	// stand up if possible
		if (pm->s.pm_flags & PMF_DUCKED)
		{
			// try to stand up
			if ( SIGN > 0 )
			{
				pm->maxs[MY_Z] = STAND_HEIGHT;
			}
			else
			{
				pm->mins[MY_Z] = -STAND_HEIGHT;
			}

			trace = pm->trace (pml.origin, pm->mins, pm->maxs, pml.origin);
			if (!trace.allsolid)
				pm->s.pm_flags &= ~PMF_DUCKED;
		}
	}

	if (pm->s.pm_flags & PMF_DUCKED)
	{
		if ( SIGN > 0 )
		{
			pm->maxs[MY_Z] = CROUCH_HEIGHT;
			pm->viewheight = CROUCH_EYE_HEIGHT;
		}
		else
		{
			pm->mins[MY_Z] = -CROUCH_HEIGHT;
			pm->viewheight = CROUCH_EYE_HEIGHT;
		}
	}
	else
	{
		if ( SIGN > 0 )
		{
			pm->maxs[MY_Z] = STAND_HEIGHT;
			pm->viewheight = STAND_EYE_HEIGHT;
		}
		else
		{
			pm->mins[MY_Z] = -STAND_HEIGHT;
			pm->viewheight = STAND_EYE_HEIGHT;
		}
	}
}


/*
==============
PM_DeadMove
==============
*/
static void PM_DeadMove (void)
{
	float	forward;

	if (!pm->groundentity)
		return;

	// extra friction

	forward = VectorLength (pml.velocity);
	forward -= 20;
	if (forward <= 0)
	{
		VectorClear (pml.velocity);
	}
	else
	{
		VectorNormalize (pml.velocity);
		VectorScale (pml.velocity, forward, pml.velocity);
	}
}


static qboolean	PM_GoodPosition (void)
{
	trace_t	trace;
	vec3_t	origin, end;
	int		i;

	if (pm->s.pm_type == PM_SPECTATOR)
		return true;

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

	return !trace.allsolid;
}

/*
================
PM_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.
================
*/
static void PM_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++)
		pm->s.velocity[i] = (int)(pml.velocity[i]*8);

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

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

		if (PM_GoodPosition ())
			return;
	}

	// go back to the last position
	VectorCopy (pml.previous_origin, pm->s.origin);

	Com_DPrintf (DEVELOPER_MSG_PMOVE_VERBOSE, "using previous_origin\n");
}

/*
================
PM_InitialSnapPosition

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

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

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

   Com_DPrintf (DEVELOPER_MSG_PMOVE, "Bad InitialSnapPosition\n");
}

/*
================
PM_ClampAngles

================
*/
static void PM_ClampAngles (void)
{
	short	temp;
	int		i;
	vec3_t t[ 3 ];

	// 2015 code 
	// hoverbike
	if(pm->s.pm_type == PM_ONBIKE)
	{
		// circularly clamp the angles with deltas
		for (i=0 ; i<3 ; i++)
		{
			temp = pm->cmd.angles[i] + pm->s.delta_angles[i];
			pm->viewangles[i] = SHORT2ANGLE(temp);
		}
		// don't let the player look up more than 90, or down more than 40
		if (pm->viewangles[PITCH] > 50 && pm->viewangles[PITCH] < 180)
			pm->viewangles[PITCH] = 50;
		else if (pm->viewangles[PITCH] < 271 && pm->viewangles[PITCH] >= 180)
			pm->viewangles[PITCH] = 271;
	}
	else
	{
		// circularly clamp the angles with deltas
		for (i=0 ; i<3 ; i++)
		{
			temp = pm->cmd.angles[i] + pm->s.delta_angles[i];
			pm->viewangles[i] = SHORT2ANGLE(temp);
		}

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

	AngleVectors (pm->viewangles, t[0], t[1], t[2]);

	pml.forward[ MY_X ] = t[ 0 ][ 0 ];
	pml.forward[ MY_Y ] = t[ 0 ][ 1 ] * SIGN;
	pml.forward[ MY_Z ] = t[ 0 ][ 2 ] * SIGN;
	pml.right[ MY_X ] = t[ 1 ][ 0 ];
	pml.right[ MY_Y ] = t[ 1 ][ 1 ] * SIGN;
	pml.right[ MY_Z ] = t[ 1 ][ 2 ] * SIGN;
	pml.up[ MY_X ] = t[ 2 ][ 0 ];
	pml.up[ MY_Y ] = t[ 2 ][ 1 ] * SIGN;
	pml.up[ MY_Z ] = t[ 2 ][ 2 ] * SIGN;
}

// 2015 code 
/*
================
PM_BikeMove

================
*/
static void PM_BikeMove(void)
{
	vec3_t pos;
	trace_t trace;
	int i;

	for (i=0 ; i<3 ; i++)
	{
		pos[i] = pml.origin[i] + pml.frametime*pml.velocity[i];
	}

	trace = pm->trace(pml.origin, pm->mins, pm->maxs, pos);

	VectorCopy (trace.endpos, pml.origin);
}

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

Can be called by either the server or the client
================
*/
void Pmove (pmove_t *pmove)
{
	vec3_t t[ 3 ];

	pm = pmove;

	MY_X = gravity_axis[ pm->s.gravity_axis ].x;
	MY_Y = gravity_axis[ pm->s.gravity_axis ].y;
	MY_Z = gravity_axis[ pm->s.gravity_axis ].z;
	SIGN = gravity_axis[ pm->s.gravity_axis ].sign;

	// clear results
	pm->numtouch = 0;
	VectorClear (pm->viewangles);
	pm->viewheight = 0;
	pm->groundentity = 0;
	pm->groundsurface = NULL;
	pm->groundcontents = 0;
	pm->onladder = false;
	pm->watertype = 0;
	pm->waterlevel = 0;

	movefactor = 1.0f;

	if ( pm->s.pm_flags & ( PMF_MUTANT | PMF_ADRENALINE ) )
	{
		movefactor = 2.1f;
	}

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

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

	pml.velocity[0] = pm->s.velocity[0]*0.125;
	pml.velocity[1] = pm->s.velocity[1]*0.125;
	pml.velocity[2] = pm->s.velocity[2]*0.125;

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

	pml.frametime = pm->cmd.msec * 0.001;

	PM_ClampAngles ();

	if (pm->s.pm_type == PM_SPECTATOR)
	{
		PM_FlyMove (false);
		PM_SnapPosition ();
		return;
	}

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

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

	if (pm->snapinitial)
		PM_InitialSnapPosition ();

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

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

	if (pm->s.pm_type == PM_DEAD)
		PM_DeadMove ();

	// 2015 code
	// movement captured
	if(pm->s.pm_type == PM_MOVECAPTURED)
	{
		// no control, but falls
		pml.velocity[MY_Z] -= pm->s.gravity * pml.frametime * SIGN;
		PM_StepSlideMove ();

		return;
	}

	PM_CheckSpecialMovement ();

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

		msec = pm->cmd.msec >> 3;
		if (!msec)
			msec = 1;
		if ( msec >= pm->s.pm_time)
		{
			pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT);
			pm->s.pm_time = 0;
		}
		else
			pm->s.pm_time -= msec;
	}

	if (pm->s.pm_flags & PMF_TIME_TELEPORT)
	{	// prevent player from backing up
		if ( pm->cmd.forwardmove < 0 )
		{
			pm->cmd.forwardmove = 0;
		}
	}
	if (pm->s.pm_flags & PMF_TIME_WATERJUMP)
	{	// waterjump has no control, but falls
		pml.velocity[MY_Z] -= pm->s.gravity * pml.frametime * SIGN;
		if (pml.velocity[MY_Z] * SIGN < 0)
		{	// cancel as soon as we are falling down again
			pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT);
			pm->s.pm_time = 0;
		}
      PM_StepSlideMove ();
	}
	// 2015 code 
	else if (pm->s.pm_type == PM_ONBIKE)
	{
		PM_BikeMove();
	}
	else
	{
		PM_CheckJump ();

		PM_Friction ();

		if (pm->waterlevel >= 2)
			PM_WaterMove ();
		else {
			vec3_t	angles;

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

			AngleVectors (angles, t[0], t[1], t[2]);

			pml.forward[ MY_X ] = t[ 0 ][ 0 ];
			pml.forward[ MY_Y ] = t[ 0 ][ 1 ] * SIGN;
			pml.forward[ MY_Z ] = t[ 0 ][ 2 ] * SIGN;
			pml.right[ MY_X ] = t[ 1 ][ 0 ];
			pml.right[ MY_Y ] = t[ 1 ][ 1 ] * SIGN;
			pml.right[ MY_Z ] = t[ 1 ][ 2 ] * SIGN;
			pml.up[ MY_X ] = t[ 2 ][ 0 ];
			pml.up[ MY_Y ] = t[ 2 ][ 1 ] * SIGN;
			pml.up[ MY_Z ] = t[ 2 ][ 2 ] * SIGN;

			// Scale for faster speed modes
			VectorScale( pml.forward, movefactor, pml.forward );
			VectorScale( pml.right, movefactor, pml.right );

			PM_AirMove ();
		}
	}

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

	PM_SnapPosition ();
}
