/*
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 "../game/q_shared.h"
#include "gs_public.h"

//===============================================================
//		WARSOW player AAboxes sizes

vec3_t playerbox_stand_mins = { -16, -16, -24 };
vec3_t playerbox_stand_maxs = { 16, 16, 40 };
int playerbox_stand_viewheight = 32;

vec3_t playerbox_crouch_mins = { -16, -16, -24 };
vec3_t playerbox_crouch_maxs = { 16, 16, 16 };
int playerbox_crouch_viewheight = 12;

vec3_t playerbox_gib_mins = { -16, -16, 0 };
vec3_t playerbox_gib_maxs = { 16, 16, 16 };
int playerbox_gib_viewheight = 8;

#define SPEEDKEY    500

#define PM_DASHJUMP_TIMEDELAY 1000 // delay in milliseconds
#define PM_WALLJUMP_TIMEDELAY	1200
#define PM_AIRCONTROL_BOUNCE_DELAY 200
#define PM_OVERBOUNCE		1.01f

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

// 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;
	vec3_t flatforward;     // normalized forward without z component, saved here because it needs
	// special handling for looking straight up or down
	float frametime;

	int groundsurfFlags;
	cplane_t groundplane;
	int groundcontents;

	vec3_t previous_origin;
	qboolean ladder;

	float forwardPush, sidePush, upPush;
} pml_t;


pmove_t	*pm;
pml_t pml;


// movement parameters
float pm_stopspeed = 100;
float pm_maxspeed = 320;
float pm_maxwalkspeed = 160;
float pm_duckspeed = 100;

float pm_accelerate = 15; // Kurim : initially 10
float pm_wateraccelerate = 10; // initially 6

float pm_friction = 8; // initially 6
float pm_waterfriction = 1;

// wsw physics

// cpm-like
float pm_airstopaccelerate = 2.5;
float pm_aircontrol = 150;
float pm_strafeaccelerate = 70;
float pm_wishspeed = 30;

// jump
#ifdef MOREGRAVITY
float pm_jump_velocity = ( 280.0f * GRAVITY_COMPENSATE ) + 6;
float dash_height = ( 170.0f * GRAVITY_COMPENSATE ) + 6;
float wj_height = ( 300.0f * GRAVITY_COMPENSATE ) + 8;
float dash_ups = 500;
#else
float pm_jump_velocity = 280;
float dash_height = 170;
float wj_height = 300;
float dash_ups = 450;
#endif

float wj_angle = 45.0;
float wj_cooldown = 700;

//
// Kurim : some functions/defines that can be useful to work on the horizontal mouvement of player :
//
#define VectorScale2D( in, scale, out ) ( ( out )[0] = ( in )[0]*( scale ), ( out )[1] = ( in )[1]*( scale ) )
#define DotProduct2D( x, y )	       ( ( x )[0]*( y )[0]+( x )[1]*( y )[1] )

static vec_t VectorNormalize2D( vec3_t v ) // ByMiK : normalize horizontally (don't affect Z value)
{
	float length, ilength;
	length = v[0]*v[0] + v[1]*v[1];
	if( length )
	{
		length = sqrt( length ); // FIXME
		ilength = 1.0f/length;
		v[0] *= ilength;
		v[1] *= ilength;
	}
	return length;
}

// Could be used to test if player walk touching a wall, if not used in any other part of pm code i'll integrate
// this function to the walljumpcheck function.
// usage : nbTestDir = nb of direction to test around the player
// maxZnormal is the Z value of the normal of a poly to considere it as a wall
// normal is a pointer to the normal of the nearest wall

static void PlayerTouchWall( int nbTestDir, float maxZnormal, vec3_t *normal )
{
	vec3_t min, max, dir;
	int i, j;
	trace_t	trace;
	float dist = 1.0;

	for( i = 0; i < nbTestDir; i++ )
	{
		dir[0] = pml.origin[0] + ( pm->maxs[0]*cos( ( 360/nbTestDir )*i ) ); // cos( (360/Imax) * i )
		dir[1] = pml.origin[1] + ( pm->maxs[1]*sin( ( 360/nbTestDir )*i ) );
		dir[2] = pml.origin[2];

		for( j = 0; j < 2; j++ )
		{
			min[j] = pm->mins[j];
			max[j] = pm->maxs[j];
		}
		min[2] = max[2] = 0;
		VectorScale( dir, 1.002, dir );

		GS_Trace( &trace, pml.origin, min, max, dir, pm->passent, pm->contentmask );

		if( trace.allsolid ) return;

		if( trace.fraction == 1 )
			continue; // no wall in this direction

		if( trace.fraction > 0 )
		{
			if( dist > trace.fraction && abs( trace.plane.normal[2] ) < maxZnormal )
			{
				dist = trace.fraction;
				VectorCopy( trace.plane.normal, *normal );
			}
		}
	}
}

//
//  walking up a step should kill some velocity
//

//==================
//PM_SlideMove
//
//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_AddTouchEnt( int entNum )
{
	int i;

	if( pm->numtouch >= MAXTOUCH || entNum < 0 )
		return;

	// see if it is already added
	for( i = 0; i < pm->numtouch; i++ )
	{
		if( pm->touchents[i] == entNum )
			return;
	}

	// add it
	pm->touchents[pm->numtouch] = entNum;
	pm->numtouch++;
}


static int PM_SlideMove( void )
{
	vec3_t end, dir;
	vec3_t old_velocity, last_valid_origin;
	float value;
	vec3_t planes[MAX_CLIP_PLANES];
	int numplanes = 0;
	trace_t	trace;
	int moves, i, j, k;
	int maxmoves = 4;
	float remainingTime = pml.frametime;
	int blockedmask = 0;

	VectorCopy( pml.velocity, old_velocity );
	VectorCopy( pml.origin, last_valid_origin );

	if( pm->groundentity != -1 )
	{                          // clip velocity to ground, no need to wait
		// if the ground is not horizontal (a ramp) clipping will slow the player down
		if( pml.groundplane.normal[2] == 1.0f && pml.velocity[2] < 0.0f )
			pml.velocity[2] = 0.0f;
	}

	numplanes = 0; // clean up planes count for checking

	for( moves = 0; moves < maxmoves; moves++ )
	{
		VectorMA( pml.origin, remainingTime, pml.velocity, end );
		GS_Trace( &trace, pml.origin, pm->mins, pm->maxs, end, pm->passent, pm->contentmask );
		if( trace.allsolid )
		{               // trapped into a solid
			VectorCopy( last_valid_origin, pml.origin );
			return SLIDEMOVEFLAG_TRAPPED;
		}

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

		if( trace.fraction == 1 )
			break; // move done

		// save touched entity for return output
		PM_AddTouchEnt( trace.ent );

		// at this point we are blocked but not trapped.

		blockedmask |= SLIDEMOVEFLAG_BLOCKED;
		if( trace.plane.normal[2] < SLIDEMOVE_PLANEINTERACT_EPSILON )
		{                                                       // is it a vertical wall?
			blockedmask |= SLIDEMOVEFLAG_WALL_BLOCKED;
		}

		remainingTime -= ( trace.fraction * remainingTime );

		// we got blocked, add the plane for sliding along it

		// if this is a plane we have touched before, try clipping
		// the velocity along it's normal and repeat.
		for( i = 0; i < numplanes; i++ )
		{
			if( DotProduct( trace.plane.normal, planes[i] ) > ( 1.0f - SLIDEMOVE_PLANEINTERACT_EPSILON ) )
			{
				VectorAdd( trace.plane.normal, pml.velocity, pml.velocity );
				break;
			}
		}
		if( i < numplanes )  // found a repeated plane, so don't add it, just repeat the trace
			continue;

		// security check: we can't store more planes
		if( numplanes >= MAX_CLIP_PLANES )
		{
			VectorClear( pml.velocity );
			return SLIDEMOVEFLAG_TRAPPED;
		}

		// put the actual plane in the list
		VectorCopy( trace.plane.normal, planes[numplanes] );
		numplanes++;

		//
		// modify original_velocity so it parallels all of the clip planes
		//

		for( i = 0; i < numplanes; i++ )
		{
			if( DotProduct( pml.velocity, planes[i] ) >= SLIDEMOVE_PLANEINTERACT_EPSILON )  // would not touch it
				continue;

			GS_ClipVelocity( pml.velocity, planes[i], pml.velocity, PM_OVERBOUNCE );
			// see if we enter a second plane
			for( j = 0; j < numplanes; j++ )
			{
				if( j == i )  // it's the same plane
					continue;
				if( DotProduct( pml.velocity, planes[j] ) >= SLIDEMOVE_PLANEINTERACT_EPSILON )
					continue; // not with this one

				//there was a second one. Try to slide along it too
				GS_ClipVelocity( pml.velocity, planes[j], pml.velocity, PM_OVERBOUNCE );

				// check if the slide sent it back to the first plane
				if( DotProduct( pml.velocity, planes[i] ) >= SLIDEMOVE_PLANEINTERACT_EPSILON )
					continue;

				// bad luck: slide the original velocity along the crease
				CrossProduct( planes[i], planes[j], dir );
				VectorNormalize( dir );
				value = DotProduct( dir, pml.velocity );
				VectorScale( dir, value, pml.velocity );

				// check if there is a third plane, in that case we're trapped
				for( k = 0; k < numplanes; k++ )
				{
					if( j == k || i == k )  // it's the same plane
						continue;
					if( DotProduct( pml.velocity, planes[k] ) >= SLIDEMOVE_PLANEINTERACT_EPSILON )
						continue; // not with this one
					VectorClear( pml.velocity );
					break;
				}
			}
		}
	}

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

	return blockedmask;
}

//==================
//PM_StepSlideMove
//
//Each intersection will try to step over the obstruction instead of
//sliding along it.
//==================
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 up, down;
	int blocked;

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

	blocked = PM_SlideMove();

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

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

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

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

	PM_SlideMove();

	// push down the final amount
	VectorCopy( pml.origin, down );
	down[2] -= STEPSIZE;
	GS_Trace( &trace, pml.origin, pm->mins, pm->maxs, down, pm->passent, pm->contentmask );
	if( !trace.allsolid )
	{
		VectorCopy( trace.endpos, pml.origin );
	}

	VectorCopy( pml.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] );

	if( down_dist >= up_dist || trace.allsolid || ( trace.fraction != 1.0 && trace.plane.normal[2] < MIN_STEP_NORMAL ) )
	{
		VectorCopy( down_o, pml.origin );
		VectorCopy( down_v, pml.velocity );
		return;
	}

	// only add the stepping output when it was a vertical step (second case is at the exit of a ramp)
	if( ( blocked & SLIDEMOVEFLAG_WALL_BLOCKED ) || trace.plane.normal[2] == 1.0f - SLIDEMOVE_PLANEINTERACT_EPSILON )
	{
		pm->step = ( pml.origin[2] - pml.previous_origin[2] );
	}

	// wsw : jal : The following line is what produces the ramp sliding.

	//!! Special case
	// if we were walking along a plane, then we need to copy the Z over
	pml.velocity[2] = down_v[2];
}

//==================
//PM_Friction -- Modified for wsw
//
//Handles both ground friction and water friction
//==================
static void PM_Friction( void )
{
	float *vel;
	float speed, newspeed, control;
	float friction;
	float drop;

	vel = pml.velocity;

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

	speed = sqrt( speed );
	drop = 0;

	// apply ground friction
	if( ( ( ( ( pm->groundentity != -1 ) && !( pml.groundsurfFlags & SURF_SLICK ) ) ) && ( pm->waterlevel < 2 ) ) || ( pml.ladder ) )
	{
		if( pm->s->stats[PM_STAT_KNOCKBACK] <= 0 )
		{
			friction = pm_friction;
			control = speed < pm_stopspeed ? pm_stopspeed : speed;
			drop += control*friction*pml.frametime;
		}
	}

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

	// scale the velocity
	newspeed = speed - drop;
	if( newspeed <= 0 )
	{
		newspeed = 0;
		VectorClear( vel );
	}
	else
	{
		newspeed /= speed;
		VectorScale( vel, newspeed, vel );
	}
}

//==============
//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 )
{
	vec3_t curvel, wishvel, acceldir, curdir;
	float addspeed, accelspeed, curspeed;
	float dot;
	// air movement parameters:
	float airforwardaccel = 1.00001f; // Default: 1.0f : how fast you accelerate until you reach pm_maxspeed
	float bunnyaccel = 0.1593f; // 0.1585f how fast you accelerate after reaching pm_maxspeed
	// (it gets harder as you near bunnytopspeed)
	float bunnytopspeed = 925; // soft speed limit (can get faster with rjs and on ramps)
	float turnaccel = 9.0f;    // Default: 7 max sharpness of turns
	float backtosideratio = 0.8f; // Default: 0.8f lower values make it easier to change direction without
	// losing speed; the drawback is "understeering" in sharp turns

	if( !wishspeed )
		return;

	VectorCopy( pml.velocity, curvel );
	curvel[2] = 0;
	curspeed = VectorLength( curvel );

	if( wishspeed > curspeed * 1.01f )
	{
		float accelspeed = curspeed + airforwardaccel * pm_maxspeed * pml.frametime;
		if( accelspeed < wishspeed )
			wishspeed = accelspeed;
	}
	else
	{
		float f = ( bunnytopspeed - curspeed ) / ( bunnytopspeed - pm_maxspeed );
		if( f < 0 )
			f = 0;
		wishspeed = max( curspeed, pm_maxspeed ) + bunnyaccel * f * pm_maxspeed * pml.frametime;
	}
	VectorScale( wishdir, wishspeed, wishvel );
	VectorSubtract( wishvel, curvel, acceldir );
	addspeed = VectorNormalize( acceldir );

	accelspeed = turnaccel * pm_maxspeed /* wishspeed */ * pml.frametime;
	if( accelspeed > addspeed )
		accelspeed = addspeed;

	if( backtosideratio < 1.0f )
	{
		VectorNormalize2( curvel, curdir );
		dot = DotProduct( acceldir, curdir );
		if( dot < 0 )
			VectorMA( acceldir, -( 1.0f - backtosideratio ) * dot, curdir, acceldir );
	}

	VectorMA( pml.velocity, accelspeed, acceldir, pml.velocity );
}

static void PM_Aircontrol( pmove_t *pm, vec3_t wishdir, float wishspeed )
{
	float zspeed, speed, dot, k;
	int i;
	float fmove, smove;

	// accelerate
	fmove = pml.forwardPush;
	smove = pml.sidePush;

	if( ( smove > 0 || smove < 0 ) || ( wishspeed == 0.0 ) )
		return; // can't control movement if not moveing forward or backward

	zspeed = pml.velocity[2];
	pml.velocity[2] = 0;
	speed = VectorNormalize( pml.velocity );


	dot = DotProduct( pml.velocity, wishdir );
	k = 32;
	k *= pm_aircontrol*dot*dot*pml.frametime;


	if( dot > 0 )
	{           // we can't change direction while slowing down
		for( i = 0; i < 2; i++ )
			pml.velocity[i] = pml.velocity[i]*speed + wishdir[i]*k;
		VectorNormalize( pml.velocity );
	}

	for( i = 0; i < 2; i++ )
		pml.velocity[i] *= speed;

	pml.velocity[2] = zspeed;
}

#if 0 // never used
static void PM_AirAccelerate( vec3_t wishdir, float wishspeed, float accel )
{
	int i;
	float addspeed, accelspeed, currentspeed, wishspd = wishspeed;

	if( wishspd > 30 )
		wishspd = 30;
	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];
}
#endif


//=============
//PM_AddCurrents
//=============
static void PM_AddCurrents( vec3_t wishvel )
{
	//
	// account for ladders
	//

	if( pml.ladder && fabs( pml.velocity[2] ) <= 200 )
	{
		if( ( pm->viewangles[PITCH] <= -15 ) && ( pml.forwardPush > 0 ) )
			wishvel[2] = 200;
		else if( ( pm->viewangles[PITCH] >= 15 ) && ( pml.forwardPush > 0 ) )
			wishvel[2] = -200;
		else if( pml.upPush > 0 )
			wishvel[2] = 200;
		else if( pml.upPush < 0 )
			wishvel[2] = -200;
		else
			wishvel[2] = 0;

		// limit horizontal speed when on a ladder
		clamp( wishvel[0], -25, 25 );
		clamp( wishvel[1], -25, 25 );
	}
}

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

	// user intentions
	for( i = 0; i < 3; i++ )
		wishvel[i] = pml.forward[i]*pml.forwardPush + pml.right[i]*pml.sidePush;

	if( !pml.forwardPush && !pml.sidePush && !pml.upPush )
		wishvel[2] -= 60; // drift towards bottom
	else
		wishvel[2] += pml.upPush;

	PM_AddCurrents( wishvel );

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

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

	PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate );
	PM_StepSlideMove();
}

//===================
//PM_AirMove -- Kurim
//
//===================
static void PM_AirMove( void )
{
	int i;
	vec3_t wishvel;
	float fmove, smove;
	vec3_t wishdir;
	float wishspeed;
	float maxspeed;
	float accel;
	float wishspeed2;

	fmove = pml.forwardPush;
	smove = pml.sidePush;

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

	PM_AddCurrents( wishvel );

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

	// clamp to server defined max speed

	// wsw : jal : fixme?: shall we use a pmflag?
	if( pm->s->pm_flags & PMF_DUCKED )
	{
		maxspeed = pm_duckspeed;
	}
	else if( pm->cmd.buttons & BUTTON_WALK )
	{
		maxspeed = pm_maxwalkspeed;
	}
	else
		maxspeed = pm_maxspeed;

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

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

		if( !wishvel[2] )
		{
			if( pml.velocity[2] > 0 )
			{
				pml.velocity[2] -= pm->s->gravity * pml.frametime;
				if( pml.velocity[2] < 0 )
					pml.velocity[2]  = 0;
			}
			else
			{
				pml.velocity[2] += pm->s->gravity * pml.frametime;
				if( pml.velocity[2] > 0 )
					pml.velocity[2]  = 0;
			}
		}

		PM_StepSlideMove();
	}
	else if( pm->groundentity != -1 )
	{ // walking on ground
		if( pml.velocity[2] > 0 )
			pml.velocity[2] = 0; //!!! this is before the accel
		PM_Accelerate( wishdir, wishspeed, pm_accelerate );

		// fix for negative trigger_gravity fields
		if( pm->s->gravity > 0 )
		{
			if( pml.velocity[2] > 0 )
				pml.velocity[2] = 0;
		}
		else
			pml.velocity[2] -= pm->s->gravity * pml.frametime;

		if( !pml.velocity[0] && !pml.velocity[1] )
			return;

		PM_StepSlideMove();
	}
	else if( pm->movestyle == GS_CLASSICBUNNY )
	{
		// Air Control
		wishspeed2 = wishspeed;
		if( DotProduct( pml.velocity, wishdir ) < 0 && !( pm->s->pm_flags & PMF_WALLJUMPING ) )
			accel = pm_airstopaccelerate;
		else
			accel = 1; //instead of pm_airaccelerate, (correcting the forward bug);

		if( pm->s->pm_flags & PMF_WALLJUMPING )
			accel = 0; // no stopmove while walljumping

		if( ( smove > 0 || smove < 0 ) && !fmove )
		{
			if( wishspeed > pm_wishspeed )
				wishspeed = pm_wishspeed;
			accel = pm_strafeaccelerate;
		}
		// Air control
		PM_Accelerate( wishdir, wishspeed, accel );
		if( pm_aircontrol && !( pm->s->pm_flags & PMF_WALLJUMPING ) )  // no air ctrl while wjing
			PM_Aircontrol( pm, wishdir, wishspeed2 );

		// add gravity
		pml.velocity[2] -= pm->s->gravity * pml.frametime;
		PM_StepSlideMove();
	}
	else
	{
		qboolean inhibit = qfalse;

		qboolean accelerating, decelerating, aircontrol;

		accelerating = ( DotProduct( pml.velocity, wishdir ) > 0.0f );
		decelerating = ( DotProduct( pml.velocity, wishdir ) < -0.0f );
		aircontrol = qfalse;

		if( ( pm->s->pm_flags & PMF_WALLJUMPING ) &&
			( pm->s->stats[PM_STAT_WJTIME] >= ( PM_WALLJUMP_TIMEDELAY - PM_AIRCONTROL_BOUNCE_DELAY ) ) )
			inhibit = qtrue;

		if( ( pm->s->pm_flags & PMF_DASHING ) &&
			( pm->s->stats[PM_STAT_DASHTIME] >= ( PM_DASHJUMP_TIMEDELAY - PM_AIRCONTROL_BOUNCE_DELAY ) ) )
			inhibit = qtrue;

		// pressing forward or backward but not pressing strafe and not dashing
		if( accelerating && !inhibit && !smove && fmove )
		{
			PM_AirAccelerate( wishdir, wishspeed );
		}
		else // strafe running
		{
			wishspeed2 = wishspeed;
			if( decelerating && !( pm->s->pm_flags & PMF_WALLJUMPING ) )
				accel = pm_airstopaccelerate;
			else
				accel = 1;

			if( pm->s->pm_flags & PMF_WALLJUMPING )
				accel = 0; // no stop-move while wall-jumping

			if( smove && !fmove )
			{
				if( wishspeed > pm_wishspeed )
					wishspeed = pm_wishspeed;
				accel = pm_strafeaccelerate;
				if( pm_aircontrol )
					aircontrol = qtrue;
			}

			PM_Accelerate( wishdir, wishspeed, accel );

			// disable aircontrol in the first instants of dashing or walljumping
			//if( inhibit )
			//	aircontrol = qfalse;

			if( ( pm->s->pm_flags & PMF_DASHING ) &&
				( pm->s->stats[PM_STAT_DASHTIME] >= ( PM_DASHJUMP_TIMEDELAY - PM_AIRCONTROL_BOUNCE_DELAY ) ) )
				aircontrol = qfalse;

			// air control
			if( aircontrol )
				PM_Aircontrol( pm, wishdir, wishspeed2 );
		}


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

//=============
//PM_CategorizePosition
//=============
//#define JITSPOES_CLIPBUGFIX // jal : it bugs more than fixes?
static void PM_CategorizePosition( void )
{
	vec3_t point;
	int cont;
	trace_t	trace;
	int sample1;
	int sample2;

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

	// see if standing on something solid
	point[0] = pml.origin[0];
	point[1] = pml.origin[1];
	point[2] = pml.origin[2] - 0.25;

	if( pml.velocity[2] > 180 ) // !!ZOID changed from 100 to 180 (ramp accel)
	{
		pm->s->pm_flags &= ~PMF_ON_GROUND;
		pm->groundentity = -1;
	}
	else
	{
		GS_Trace( &trace, pml.origin, pm->mins, pm->maxs, point, pm->passent, pm->contentmask );
		pml.groundplane = trace.plane;
		pml.groundsurfFlags = trace.surfFlags;
		pml.groundcontents = trace.contents;

		if( ( trace.fraction == 1 ) || ( trace.plane.normal[2] < 0.7 && !trace.startsolid ) )
		{
#ifdef JITSPOES_CLIPBUGFIX
			trace_t trace2;
			vec3_t mins, maxs;

			// try a slightly smaller bounding box -- this is to fix getting stuck up
			// on angled walls and not being able to move (like you're stuck in the air)
			mins[0] = pm->mins[0] ? pm->mins[0] + 1 : 0;
			mins[1] = pm->mins[1] ? pm->mins[1] + 1 : 0;
			mins[2] = pm->mins[2];
			maxs[0] = pm->maxs[0] ? pm->maxs[0] - 1 : 0;
			maxs[1] = pm->maxs[1] ? pm->maxs[1] - 1 : 0;
			maxs[2] = pm->maxs[2];
			pm->trace( &trace2, pml.origin, mins, maxs, point );

			if( !( trace2.plane.normal[2] < 0.7f && !trace2.startsolid ) )
			{
				memcpy( &trace, &trace2, sizeof( trace ) );
				pml.groundplane = trace.plane;
				pml.groundsurfFlags = trace.surfFlags;
				pml.groundcontents = trace.contents;
				pm->groundentity = trace.ent;
			}
#else //JITSPOES_CLIPBUGFIX
			pm->groundentity = -1;
			pm->s->pm_flags &= ~PMF_ON_GROUND;
#endif //JITSPOES_CLIPBUGFIX
		}
		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.fraction < 1.0 ) )
		{
			pm->touchents[pm->numtouch] = trace.ent;
			pm->numtouch++;
		}
	}

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

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

	point[2] = pml.origin[2] + pm->mins[2] + 1;
	cont = GS_PointContents( point );

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

static void PM_ClearDash( void )
{
	pm->s->pm_flags &= ~PMF_DASHING;
	pm->s->stats[PM_STAT_DASHTIME] = 0;
}

static void PM_ClearWallJump( void )
{
	pm->s->pm_flags &= ~PMF_WALLJUMPING;
	pm->s->stats[PM_STAT_WJCOUNT] = 0;
	pm->s->stats[PM_STAT_WJTIME] = 0;
}

//=============
//PM_CheckJump
//=============
static void PM_CheckJump( void )
{
	if( pml.upPush < 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 )
		return;

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

	if( pm->waterlevel >= 2 )
	{ // swimming, not jumping
		pm->groundentity = -1;
		return;
	}

	if( pm->groundentity == -1 )
		return;

	pm->s->pm_flags |= PMF_JUMP_HELD;
	pm->groundentity = -1;

	//if( gs_module == GS_MODULE_GAME ) GS_Printf( "upvel %f\n", pml.velocity[2] );
	if( pml.velocity[2] > 100 )
	{
		GS_PredictedEvent( pm->passent, EV_DOUBLEJUMP, 0 );
		pml.velocity[2] += pm_jump_velocity;
	}
	else if( pml.velocity[2] > 0 )
	{
		GS_PredictedEvent( pm->passent, EV_JUMP, 0 );
		pml.velocity[2] += pm_jump_velocity;
	}
	else
	{
		GS_PredictedEvent( pm->passent, EV_JUMP, 0 );
		pml.velocity[2] = pm_jump_velocity;
	}

	// remove wj count
	pm->s->pm_flags &= ~PMF_JUMPPAD_TIME;
	PM_ClearDash();
	PM_ClearWallJump();
}

//=============
//PM_CheckDash -- by Kurim
//=============
static void PM_CheckDash( void )
{
	float actual_velocity;
	float upspeed;
	vec3_t dashdir;

	if( !( pm->cmd.buttons & BUTTON_SPECIAL ) )
		pm->s->pm_flags &= ~PMF_SPECIAL_HELD;

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

	if( pm->s->stats[PM_STAT_DASHTIME] > 0 )
		return;

	if( pm->cmd.buttons & BUTTON_SPECIAL && pm->groundentity != -1 )
	{
		if( pm->s->pm_flags & PMF_SPECIAL_HELD )
			return;

		pm->s->pm_flags &= ~PMF_JUMPPAD_TIME;
		PM_ClearWallJump();

		pm->s->pm_flags |= PMF_DASHING;
		pm->s->pm_flags |= PMF_SPECIAL_HELD;
		pm->groundentity = -1;

		if( pml.velocity[2] <= 0.0f )
			upspeed = dash_height;
		else
			upspeed = dash_height + pml.velocity[2];

		VectorMA( vec3_origin, pml.forwardPush, pml.flatforward, dashdir );
		VectorMA( dashdir, pml.sidePush, pml.right, dashdir );
		dashdir[2] = 0.0;

		if( VectorLength( dashdir ) < 0.01f )  // if not moving, dash like a "forward dash"
			VectorCopy( pml.flatforward, dashdir );

		VectorNormalizeFast( dashdir );

		actual_velocity = VectorNormalize2D( pml.velocity );
		if( actual_velocity <= dash_ups )
			VectorScale( dashdir, dash_ups, dashdir );
		else
			VectorScale( dashdir, actual_velocity, dashdir );

		VectorCopy( dashdir, pml.velocity );
		pml.velocity[2] = upspeed;

		pm->s->stats[PM_STAT_DASHTIME] = PM_DASHJUMP_TIMEDELAY;

		// return sound events
		if( abs( pml.sidePush ) > 10 && abs( pml.sidePush ) >= abs( pml.forwardPush ) )
		{
			if( pml.sidePush > 0 )
			{
				GS_PredictedEvent( pm->passent, EV_DASH, 2 );
			}
			else
			{
				GS_PredictedEvent( pm->passent, EV_DASH, 1 );
			}
		}
		else if( pml.forwardPush < -10 )
		{
			GS_PredictedEvent( pm->passent, EV_DASH, 3 );
		}
		else
		{
			GS_PredictedEvent( pm->passent, EV_DASH, 0 );
		}
	}
	else if( pm->groundentity == -1 )
		pm->s->pm_flags &= ~PMF_DASHING;
}

//=================
//PM_CheckWallJump -- By Kurim
//=================
static void PM_CheckWallJump( void )
{
	vec3_t normal;
	float length;
	vec3_t vWallJump;
	int parm;

	if( !( pm->cmd.buttons & BUTTON_SPECIAL ) )
		pm->s->pm_flags &= ~PMF_SPECIAL_HELD;

	if( pm->groundentity != -1 )
	{
		pm->s->pm_flags &= ~PMF_WALLJUMPING;
		pm->s->stats[PM_STAT_WJCOUNT] = 0;
	}

	if( pm->s->pm_flags & PMF_WALLJUMPING && pml.velocity[2] < 0.0 )
		pm->s->pm_flags &= ~PMF_WALLJUMPING;

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

	if( pm->s->stats[PM_STAT_WJTIME] <= 0 )  // reset the wj count after wj delay
		pm->s->stats[PM_STAT_WJCOUNT] = 0;

	// don't walljump in the first 100 milliseconds of a dash jump
	if( pm->s->pm_flags & PMF_DASHING && ( pm->s->stats[PM_STAT_DASHTIME] > ( PM_DASHJUMP_TIMEDELAY - 100 ) ) )
		return;

	if( pm->groundentity == -1 && pm->cmd.buttons & BUTTON_SPECIAL )
	{
		if( pm->s->stats[PM_STAT_WJCOUNT] >= pm->max_walljumps )
			return;

		VectorClear( normal );
		PlayerTouchWall( 12, 0.3f, &normal );
		if( !VectorLength( normal ) )
			return;

		if( !( pm->s->pm_flags & PMF_SPECIAL_HELD ) && !( pm->s->pm_flags & PMF_WALLJUMPING ) )
		{
			float oldupvelocity = pml.velocity[2];
			pml.velocity[2] = 0.0;

			length = VectorNormalize2D( pml.velocity );

			if( length < 320.0 )
				length = 320.0;

			GS_ClipVelocity( pml.velocity, normal, pml.velocity, PM_OVERBOUNCE );
			VectorCopy( normal, vWallJump );
			VectorMA( pml.velocity, 0.5, vWallJump, pml.velocity );
			VectorNormalize( pml.velocity );
			VectorScale( pml.velocity, length, pml.velocity );
			pml.velocity[2] = ( oldupvelocity > 370.0 ) ? oldupvelocity : 370.0; // jal: if we had a faster upwards speed, keep it

			PM_ClearDash();
			pm->s->pm_flags &= ~PMF_JUMPPAD_TIME;

			pm->s->pm_flags |= PMF_WALLJUMPING;
			pm->s->pm_flags |= PMF_SPECIAL_HELD;

			pm->s->stats[PM_STAT_WJCOUNT]++;
			pm->s->stats[PM_STAT_WJTIME] = PM_WALLJUMP_TIMEDELAY;

			// find in what direction relative to the player (left, right) is the jump happening
			if( DotProduct( normal, pml.right ) > 0.3 )
			{
				parm = 2;
			}
			else if( -DotProduct( normal, pml.right ) > 0.3 )
			{
				parm = 1;
			}
			else if( DotProduct( normal, pml.flatforward ) > 0.3 )
			{
				parm = 3;
			}
			else
			{
				parm = 0;
			}
			// Add the direction wallsurface also to the parameter
			parm = ( ( parm << 8 ) & 0xFFFFFF00 ) | ( DirToByte( normal ) & 0xFF );
			// Create the event
			GS_PredictedEvent( pm->passent, EV_WALLJUMP, parm );
		}
	}
	else
		pm->s->pm_flags &= ~PMF_WALLJUMPING;
}

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

	if( pm->s->pm_time )
		return;

	pml.ladder = qfalse;

	// check for ladder
	VectorMA( pml.origin, 1, pml.flatforward, spot );
	GS_Trace( &trace, pml.origin, pm->mins, pm->maxs, spot, pm->passent, pm->contentmask );
	if( ( trace.fraction < 1 ) && ( trace.surfFlags & SURF_LADDER ) )
		pml.ladder = qtrue;

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

	VectorMA( pml.origin, 30, pml.flatforward, spot );
	spot[2] += 4;
	cont = GS_PointContents( spot );
	if( !( cont & CONTENTS_SOLID ) )
		return;

	spot[2] += 16;
	cont = GS_PointContents( spot );
	if( cont )
		return;
	// jump out of water
	VectorScale( pml.flatforward, 50, pml.velocity );
	pml.velocity[2] = 350;

	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, maxspeed;
	int i;
	vec3_t wishvel;
	float fmove, smove;
	vec3_t wishdir;
	float wishspeed;
	vec3_t end;
	trace_t	trace;

	maxspeed = pm_maxspeed * 1.5;

	if( pm->cmd.buttons & BUTTON_SPECIAL )
		maxspeed *= 2;

	pm->viewheight = playerbox_stand_viewheight;

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

		friction = pm_friction*1.5; // extra friction
		control = speed < pm_stopspeed ? pm_stopspeed : 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 = pml.forwardPush;
	smove = pml.sidePush;

	if( pm->cmd.buttons & BUTTON_SPECIAL )
	{
		fmove *= 2;
		smove *= 2;
	}

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

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

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


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

	currentspeed = DotProduct( pml.velocity, wishdir );
	addspeed = wishspeed - currentspeed;
	if( addspeed > 0 )
	{
		accelspeed = pm_accelerate*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];

		GS_Trace( &trace, pml.origin, pm->mins, pm->maxs, end, pm->passent, pm->contentmask );

		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;
	vec3_t end;


	//pm->mins[0] = playerbox_stand_mins[0];
	//pm->mins[1] = playerbox_stand_mins[1];

	//pm->maxs[0] = playerbox_stand_maxs[0];
	//pm->maxs[1] = playerbox_stand_maxs[1];

	VectorCopy( playerbox_stand_mins, pm->mins );
	VectorCopy( playerbox_stand_maxs, pm->maxs );

	if( pm->s->pm_type == PM_GIB )
	{
		//VectorCopy( playerbox_gib_maxs, pm->maxs );
		//VectorCopy( playerbox_gib_mins, pm->mins );
		pm->mins[2] = playerbox_gib_mins[2];
		pm->maxs[2] = playerbox_gib_maxs[2];
		pm->viewheight = playerbox_gib_viewheight;
		return;
	}

	pm->mins[2] = playerbox_stand_mins[2];

	if( pm->s->pm_type >= PM_DEAD )
	{
		//VectorCopy( playerbox_stand_maxs, pm->maxs );
		//VectorCopy( playerbox_stand_mins, pm->mins );

		pm->maxs[2] = -8;
		pm->viewheight = 8;
		return;
	}

	// wsw: pb allow player to duck while in air
	if( pml.upPush < 0 ) //&& (pm->s.pm_flags & PMF_ON_GROUND) )
	{ // duck
		pm->s->pm_flags |= PMF_DUCKED;
	}
	else
	{ // stand up if possible
		if( pm->s->pm_flags & PMF_DUCKED )
		{
			// try to stand up
			VectorCopy( pml.origin, end );
			end[2] += playerbox_stand_maxs[2] - pm->maxs[2];
			GS_Trace( &trace, pml.origin, pm->mins, pm->maxs, end, pm->passent, pm->contentmask );
			if( trace.fraction == 1 )
			{
				pm->maxs[2] = playerbox_stand_maxs[2];
				pm->s->pm_flags &= ~PMF_DUCKED;
			}
		}
	}

	if( pm->s->pm_flags & PMF_DUCKED )
	{
		//VectorCopy( playerbox_crouch_mins, pm->mins );
		//VectorCopy( playerbox_crouch_maxs, pm->maxs );
		pm->maxs[2] = playerbox_crouch_maxs[2];
		pm->viewheight = playerbox_crouch_viewheight;
	}
	else
	{
		//VectorCopy( playerbox_stand_mins, pm->mins );
		//VectorCopy( playerbox_stand_maxs, pm->maxs );
		pm->maxs[2] = playerbox_stand_maxs[2];
		pm->viewheight = playerbox_stand_viewheight;
	}
}

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

	if( pm->groundentity == -1 )
		return;

	// extra friction
	forward = VectorLength( pml.velocity ) - 20;

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


static qboolean PM_GoodPosition( int snaptorigin[3] )
{
	trace_t	trace;
	vec3_t origin, end;
	int i;

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

	for( i = 0; i < 3; i++ )
		origin[i] = end[i] = snaptorigin[i]*( 1.0/PM_VECTOR_SNAP );
	GS_Trace( &trace, origin, pm->mins, pm->maxs, end, pm->passent, pm->contentmask );

	return !trace.allsolid;
}

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

	// snap velocity to sixteenths
	for( i = 0; i < 3; i++ )
	{
		velint[i] = (int)( pml.velocity[i]*PM_VECTOR_SNAP );
		pm->s->velocity[i] = velint[i]*( 1.0/PM_VECTOR_SNAP );
	}

	for( i = 0; i < 3; i++ )
	{
		if( pml.origin[i] >= 0 )
			sign[i] = 1;
		else
			sign[i] = -1;
		origint[i] = (int)( pml.origin[i]*PM_VECTOR_SNAP );
		if( origint[i]*( 1.0/PM_VECTOR_SNAP ) == pml.origin[i] )
			sign[i] = 0;
	}
	VectorCopy( origint, base );

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

		if( PM_GoodPosition( origint ) )
		{
			VectorScale( origint, ( 1.0/PM_VECTOR_SNAP ), pm->s->origin );
			return;
		}
	}

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


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

	VectorScale( pm->s->origin, PM_VECTOR_SNAP, origint );
	VectorCopy( origint, base );

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

//================
//PM_ClampAngles
//
//================
#if defined ( _WIN32 ) && ( _MSC_VER >= 1400 )
#pragma warning( push )
#pragma warning( disable : 4310 )   // cast truncates constant value
#endif
static void PM_ClampAngles( void )
{
	int i;
	short temp;

	for( i = 0; i < 3; i++ )
	{
		temp = pm->cmd.angles[i] + pm->s->delta_angles[i];
		if( i == PITCH )
		{
			// don't let the player look up or down more than 90 degrees
			if( temp > (short)ANGLE2SHORT( 90 ) - 1 )
			{
				pm->s->delta_angles[i] = ( ANGLE2SHORT( 90 ) - 1 ) - pm->cmd.angles[i];
				temp = ANGLE2SHORT( 90 ) - 1;
			}
			else if( temp < (short)ANGLE2SHORT( -90 ) + 1 )
			{
				pm->s->delta_angles[i] = ( ANGLE2SHORT( -90 ) + 1 ) - pm->cmd.angles[i];
				temp = ANGLE2SHORT( -90 ) + 1;
			}
		}

		pm->viewangles[i] = SHORT2ANGLE( temp );
	}

	AngleVectors( pm->viewangles, pml.forward, pml.right, pml.up );

	VectorCopy( pml.forward, pml.flatforward );
	pml.flatforward[2] = 0.0f;
	VectorNormalize( pml.flatforward );
}
#if defined ( _WIN32 ) && ( _MSC_VER >= 1400 )
#pragma warning( pop )
#endif

//================
//Pmove
//
//Can be called by either the server or the client
//================
void Pmove( pmove_t *pmove )
{
	float fallvelocity, falldelta, damage;
	int oldGroundEntity;

	pm = pmove;

	// clear results
	pm->numtouch = 0;
	VectorClear( pm->viewangles );
	pm->viewheight = 0;
	pm->groundentity = -1;
	pm->watertype = 0;
	pm->waterlevel = 0;
	pm->step = qfalse;

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

	VectorCopy( pm->s->origin, pml.origin );
	VectorCopy( pm->s->velocity, pml.velocity );

	fallvelocity = ( ( pml.velocity[2] < 0.0f ) ? abs( pml.velocity[2] ) : 0.0f );

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

	if( pm->s->stats[PM_STAT_SLOW] > 0 && pm->cmd.msec > 0 )
		pm->cmd.msec /= 5;

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

	// drop timing counters
	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->stats[PM_STAT_NOUSERCONTROL] > 0 )
		pm->s->stats[PM_STAT_NOUSERCONTROL] -= pm->cmd.msec;
	if( pm->s->stats[PM_STAT_KNOCKBACK] > 0 )
		pm->s->stats[PM_STAT_KNOCKBACK] -= pm->cmd.msec;
	if( pm->s->stats[PM_STAT_DASHTIME] > 0 )
		pm->s->stats[PM_STAT_DASHTIME] -= pm->cmd.msec;
	if( pm->s->stats[PM_STAT_WJTIME] > 0 )
		pm->s->stats[PM_STAT_WJTIME] -= pm->cmd.msec;
	if( pm->s->stats[PM_STAT_SLOW] > 0 )
		pm->s->stats[PM_STAT_SLOW] -= pm->cmd.msec;

	pml.forwardPush = pm->cmd.forwardfrac * SPEEDKEY;
	pml.sidePush = pm->cmd.sidefrac * SPEEDKEY;
	pml.upPush = pm->cmd.upfrac * SPEEDKEY;

	if( pm->s->stats[PM_STAT_NOUSERCONTROL] > 0 )
	{
		pml.forwardPush = 0;
		pml.sidePush = 0;
		pml.upPush = 0;
		pm->cmd.buttons = 0;
	}

	PM_ClampAngles();

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

	if( pm->s->pm_type >= PM_DEAD )
	{
		pml.forwardPush = 0;
		pml.sidePush = 0;
		pml.upPush = 0;
	}

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

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

	if( pm->snapinitial )
		PM_InitialSnapPosition();

	// set groundentity, watertype, and waterlevel
	PM_CategorizePosition();
	oldGroundEntity = pm->groundentity;

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

	PM_CheckSpecialMovement();

	if( pm->s->pm_flags & PMF_TIME_TELEPORT )
	{ // teleport pause stays exactly in place
	}
	else if( pm->s->pm_flags & PMF_TIME_WATERJUMP )
	{ // waterjump has no control, but falls
		pml.velocity[2] -= pm->s->gravity * pml.frametime;
		if( pml.velocity[2] < 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();
	}
	else
	{
		// Kurim
		// Keep this order !
		PM_CheckJump();
		PM_CheckDash();
		PM_CheckWallJump();

		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, pml.forward, pml.right, pml.up );

			// hack to work when looking straight up and straight down
			if( pml.forward[2] == -1.0f )
			{
				VectorCopy( pml.up, pml.flatforward );
			}
			else if( pml.forward[2] == 1.0f )
			{
				VectorCopy( pml.up, pml.flatforward );
				VectorNegate( pml.flatforward, pml.flatforward );
			}
			else
			{
				VectorCopy( pml.forward, pml.flatforward );
			}
			pml.flatforward[2] = 0.0f;
			VectorNormalize( pml.flatforward );

			PM_AirMove();
		}
	}

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

	// falling event

#define FALL_DAMAGE_MIN_DELTA 675
#define FALL_STEP_MIN_DELTA 400
#define MAX_FALLING_DAMAGE 15
#define FALL_DAMAGE_SCALE 1.0

	if( pm->s->pm_type != PM_NORMAL )
	{
		pm->s->pm_flags &= ~PMF_JUMPPAD_TIME;
	}
	else // check for falling damage
	{
		GS_PMoveTouchTriggers( pm );
		// touching triggers may force groundentity off
		if( !( pm->s->pm_flags & PMF_ON_GROUND ) && pm->groundentity != -1 )
			pm->groundentity = -1;

		if( pm->groundentity != -1 ) // remove wall-jump and dash bits when touching ground
		{
			// always keep the dash flag 50 msecs at least (to prevent being removed at the start of the dash)
			if( pm->s->stats[PM_STAT_DASHTIME] < ( PM_DASHJUMP_TIMEDELAY - 50 ) )
				pm->s->pm_flags &= ~PMF_DASHING;

			if( pm->s->stats[PM_STAT_WJTIME] < ( PM_WALLJUMP_TIMEDELAY - 50 ) )
				PM_ClearWallJump();
		}

		if( oldGroundEntity == -1 && pm->groundentity != -1 )
		{
			falldelta = fallvelocity - ( ( pml.velocity[2] < 0.0f ) ? abs( pml.velocity[2] ) : 0.0f );

			// scale delta if in water
			if( pm->waterlevel == 3 )
				falldelta = 0;
			if( pm->waterlevel == 2 )
				falldelta *= 0.25;
			if( pm->waterlevel == 1 )
				falldelta *= 0.5;

			if( falldelta > FALL_STEP_MIN_DELTA )
			{
				if( pml.groundsurfFlags & SURF_NODAMAGE || ( pm->s->pm_flags & PMF_JUMPPAD_TIME ) )
					damage = 0;
				else
				{
					damage = ( ( falldelta - FALL_DAMAGE_MIN_DELTA ) / 10 ) * FALL_DAMAGE_SCALE;
					clamp( damage, 0.0f, MAX_FALLING_DAMAGE );
				}

				GS_PredictedEvent( pm->passent, EV_FALL, damage );
			}

			pm->s->pm_flags &= ~PMF_JUMPPAD_TIME;
		}
	}
}
