float lastclientthink, sv_maxspeed, sv_friction, sv_accelerate, sv_stopspeed;
float sv_edgefriction, cl_rollangle, cl_divspeed;

// LadyHavoc:
// Highly optimized port of SV_ClientThink from engine code to QuakeC.
// No behavior changes!  This code is much shorter and probably faster than
// the engine code :)

// note that darkplaces engine will call this function if it finds it,
// so modify for your own mods and enjoy...

// note also, this code uses some builtin functions from dpextensions.qc
// (included with darkplaces engine releases)

void() SV_PlayerPhysics =
{
	local vector wishvel, wishdir, v;
	local float wishspeed, f;

	if (self.movetype == MOVETYPE_NONE)
		return;

	if (self.punchangle != '0 0 0')
	{
		f = vlen(self.punchangle) - 10 * frametime;
		if (f > 0)
			self.punchangle = normalize(self.punchangle) * f;
		else
			self.punchangle = '0 0 0';
	}

	// if dead, behave differently
	if (self.health <= 0)
		return;

	if (time != lastclientthink)
	{
		lastclientthink = time;
		sv_maxspeed = cvar("sv_maxspeed");
		sv_friction = cvar("sv_friction");
		sv_accelerate = cvar("sv_accelerate");
		sv_stopspeed = cvar("sv_stopspeed");
		sv_edgefriction = cvar("edgefriction");
		// LadyHavoc: this * 4 is an optimization
		cl_rollangle = cvar("cl_rollangle") * 4;
		// LadyHavoc: this 1 / is an optimization
		cl_divspeed = 1 / cvar("cl_rollspeed");
	}

	// show 1/3 the pitch angle and all the roll angle
	self.angles_z = bound(-1, self.velocity * v_right * cl_divspeed, 1) * cl_rollangle;
	if (!self.fixangle)
	{
		self.angles_x = (self.v_angle_x + self.punchangle_x) * -0.333;
		self.angles_y = self.v_angle_y + self.punchangle_y;
	}

	if (self.flags & FL_WATERJUMP )
	{
		self.velocity_x = self.movedir_x;
		self.velocity_y = self.movedir_y;
		if (time > self.teleport_time || self.waterlevel == 0)
		{
			self.flags = self.flags - (self.flags & FL_WATERJUMP);
			self.teleport_time = 0;
		}
		return;
	}

	makevectors(self.v_angle);

	// swim
	if (self.waterlevel >= 2)
	if (self.movetype != MOVETYPE_NOCLIP)
	{
		self.flags = self.flags - (self.flags & FL_ONGROUND);

		if (self.movement == '0 0 0')
			wishvel = '0 0 -60'; // drift towards bottom
		else
			wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;

		wishspeed = vlen (wishvel);
		if (wishspeed > sv_maxspeed)
			wishspeed = sv_maxspeed;

		wishspeed = wishspeed * 0.7;

		// water friction
		if (self.velocity != '0 0 0')
		{
			f = vlen(self.velocity) * (1 - frametime * sv_friction);
			if (f > 0)
				self.velocity = normalize(self.velocity) * f;
			else
				self.velocity = '0 0 0';
		}
		else
			f = 0;

		// water acceleration
		if (wishspeed <= f)
			return;

		f = min(wishspeed - f, sv_accelerate * wishspeed * frametime);
		self.velocity = self.velocity + normalize(wishvel) * f;
		return;
	}

	// hack to not let you back into teleporter
	if (time < self.teleport_time && self.movement_x < 0)
		wishvel = v_right * self.movement_y;
	else
	{
		// LadyHavoc: this NOCLIP check is a fix for the 'not moving while looking straight up/down when noclipping' bug in Quake
		if ((self.flags & FL_ONGROUND) || (self.movetype == MOVETYPE_NOCLIP))
			makevectors (self.v_angle_y * '0 1 0');

		wishvel = v_forward * self.movement_x + v_right * self.movement_y;
	}

	if (self.movetype != MOVETYPE_WALK)
		wishvel_z = self.movement_z;
	else
		wishvel_z = 0;

	wishdir = normalize(wishvel);
	wishspeed = vlen(wishvel);
	if (wishspeed > sv_maxspeed)
		wishspeed = sv_maxspeed;

	if (self.movetype == MOVETYPE_NOCLIP) // noclip
	{
		self.flags = self.flags - (self.flags & FL_ONGROUND);
		self.velocity = wishdir * wishspeed;
	}
	else if (self.flags & FL_ONGROUND) // walking
	{
		// friction
		if (self.velocity_x || self.velocity_y)
		{
			v = self.velocity;
			v_z = 0;
			f = vlen(v);

			// if the leading edge is over a dropoff, increase friction
			v = self.origin + normalize(v) * 16 + '0 0 1' * self.mins_z;

			traceline(v, v + '0 0 -34', TRUE, self);

			// apply friction
			if (trace_fraction == 1.0)
			{
				if (f < sv_stopspeed)
					f = 1 - frametime * (sv_stopspeed / f) * sv_friction * sv_edgefriction;
				else
					f = 1 - frametime * sv_friction * sv_edgefriction;
			}
			else
			{
				if (f < sv_stopspeed)
					f = 1 - frametime * (sv_stopspeed / f) * sv_friction;
				else
					f = 1 - frametime * sv_friction;
			}

			if (f < 0)
				self.velocity = '0 0 0';
			else
				self.velocity = self.velocity * f;
		}

		// acceleration
		f = wishspeed - (self.velocity * wishdir);
		if (f > 0)
			self.velocity = self.velocity + wishdir * min(f, sv_accelerate * frametime * wishspeed);
	}
	else // airborn
	{
		if (wishspeed < 30)
			f = wishspeed - (self.velocity * wishdir);
		else
			f = 30 - (self.velocity * wishdir);
		if (f > 0)
			self.velocity = self.velocity + wishdir * (min(f, sv_accelerate) * wishspeed * frametime);
	}
}
