/*

WARNING: This entire file is pretty much GPLed.
If you want to release your csqc mod free from the GPL, do not define OWNPLAYERPHYSICS, and remove this file from your progs.src

*/

#ifdef OWNPLAYERPHYSICS

/*
be very careful about the fields/globals that are read/written in this code.
Using any that are changed elsewhere can and will result in prediction errors.
Any fields that are expected to persist need to be added to csqc code to revert them.
Any fields that are read need to be the same between csqc and ssqc code somehow. Changing such fields will result in brief errors.
*/


#define movevars_stepheight 22
#define movevars_friction 4
#define movevars_gravity 800
#define movevars_accelerate 10
#define movevars_stopspeed 100
#define movevars_maxspeed 320
#define movevars_jumpheight 270

.float pmove_flags;

#ifdef HAVE_DOTGRAVITY
.float gravity;
#endif

enumflags
{
	PMF_JUMP_HELD,
	PMF_RESERVED,
	PMF_ONGROUND
};

static void(entity tother) dotouch =
{
	entity oself;
	//if (tother.touch == __NULL__)
		return;

	oself = self;
	
	other = self;
	self = tother;

	self.touch();

	self = oself;
};

//this function 'bounces' off any surfaces that were hit
void(vector surfnorm) PMove_Rebound =
{
	float v;
	v = self.velocity*surfnorm;
	self.velocity = self.velocity - surfnorm*(v);
};

void(void) PMove_Move =	//move forwards (preferably on the level) (does step ups)
{
	vector dest;
	vector saved_plane_normal;
	float stepped;
	float movetime;
	float attempts;

	//we need to bounce off surfaces (in order to slide along them), so we need at 2 attempts
	for (attempts = 3, movetime = input_timelength; movetime>0 && attempts; attempts--)
	{
		dest = self.origin + self.velocity*movetime;
		tracebox(self.origin, self.mins, self.maxs, dest, false, self);	//try going straight there
		self.origin = trace_endpos;

		if (trace_fraction < 1)
		{
			saved_plane_normal = trace_plane_normal;

			movetime -= movetime * trace_fraction;

			if (movetime)
			{
				//step up if we can
				trace_endpos = self.origin;
				trace_endpos_z += movevars_stepheight;
				tracebox(self.origin, self.mins, self.maxs, trace_endpos, false, self);
				stepped = trace_endpos_z - self.origin_z;

				dest = trace_endpos + self.velocity*movetime;
				dest_z = trace_endpos_z;
				//move forwards
				tracebox(trace_endpos, self.mins, self.maxs, dest, false, self);

				//if we got anywhere, make this raised-step move count
				if (trace_fraction != 0)
				{
					if (trace_fraction < 1)
						PMove_Rebound(trace_plane_normal);

					//move down
					dest = trace_endpos;
					dest_z -= stepped+1;
					tracebox(trace_endpos, self.mins, self.maxs, dest, false, self);
					if (trace_fraction < 1)
						PMove_Rebound(trace_plane_normal);

					self.origin = trace_endpos;

					movetime -= movetime * input_timelength;

					continue;
				}
			}
			
			//stepping failed, just bounce off
			PMove_Rebound(saved_plane_normal);

			dotouch(trace_ent);
		}
		else
			break;
	}
};
/*
void(vector dest) PMove_StepMove =
{
	//we hit something...

	//step up
	src = trace_endpos;
	trace_endpos_z += movevars_stepheight;
	tracebox(src, self.mins, self.maxs, dest, false, self);
	stepped = trace_endpos_z - src_z;
	dest_z += stepped;

	//move forwards
	tracebox(trace_endpos, self.mins, self.maxs, dest, false, self);

	//move down
	dest_z -= stepped;
	tracebox(trace_endpos, self.mins, self.maxs, dest, false, self);
}
*/
void() PMove_ApplyFriction =
{
	float newspeed, oldspeed;
	oldspeed = vlen(self.velocity);
	if (oldspeed < 1)
	{
		self.velocity = '0 0 0';
		return;
	}
	
	//calculate what their new speed should be
	newspeed = oldspeed - oldspeed*movevars_friction*input_timelength;

	//and slow them
	if (newspeed < 0)
		newspeed = 0;
	self.velocity = self.velocity * (newspeed/oldspeed);
};

void(vector wishdir, float wishspeed, float accel) PMove_Accelerate =
{
	float addspeed, accelspeed;
	float d;
	d = self.velocity*wishdir;
	addspeed = wishspeed - (d);
	if (addspeed <= 0)
		return;
	accelspeed = accel*input_timelength*wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	self.velocity = self.velocity + accelspeed*wishdir;
};

void() PMove_InAirAccelerate =
{
	vector hforward;
	vector hright;
	vector desireddir;
	float desiredspeed;

	hforward = v_forward;
	hforward_z = 0;
	hforward = normalize(hforward);
	hright = v_right;
	hright_z = 0;
	hright = normalize(hright);

	desireddir = hforward*input_movevalues_x + hright*input_movevalues_y;
	desiredspeed = vlen(desireddir);
	desireddir = normalize(desireddir);

	if (desiredspeed > movevars_maxspeed)
		desiredspeed = movevars_maxspeed;

	if (self.pmove_flags & PMF_ONGROUND)
	{
		if (input_buttons & 2)
		{
			if (!(self.pmove_flags & PMF_JUMP_HELD))
			{
				self.velocity_z += movevars_jumpheight;
				self.pmove_flags (+) PMF_ONGROUND;
			}
		}
	}

	if (self.pmove_flags & PMF_ONGROUND)
	{
		PMove_ApplyFriction();
		PMove_Accelerate(desireddir, desiredspeed, movevars_accelerate);
	}
	else
	{
		//there's no friction in air...
		if (desiredspeed > 30)
			desiredspeed = 30;
		PMove_Accelerate(desireddir, desiredspeed, movevars_accelerate);

#ifdef HAVE_DOTGRAVITY
		if (self.gravity)
			self.velocity_z -= self.gravity * movevars_gravity * input_timelength;
		else
#endif
			self.velocity_z -= movevars_gravity * input_timelength;
	}
};

void() PMove_NoclipAccelerate =
{
	vector desireddir;
	float desiredspeed;

	desireddir = v_forward*input_movevalues_x + v_right*input_movevalues_y+v_up*input_movevalues_z;
	desiredspeed = vlen(desireddir);
	desireddir = normalize(desireddir);

	PMove_ApplyFriction();
	PMove_Accelerate(desireddir, desiredspeed, movevars_accelerate);
};

void() PMove_Categorise =
{
	//if we're moving up, we're not on the ground
	if (self.velocity_z > 0)
		self.pmove_flags (-) PMF_ONGROUND;
	else
	{
		//don't know, maybe we are, maybe we're not
		tracebox(self.origin, self.mins, self.maxs, self.origin-'0 0 1', false, self);
		if (trace_fraction == 1 || trace_plane_normal_z < 0.7)
		{
			self.pmove_flags (-) PMF_ONGROUND;
//			self.groundentity = trace_ent;
		}
		else
			self.pmove_flags (+) PMF_ONGROUND;
	}
};

void(entity ent) PMove =
{
	self = ent;
	makevectors(input_angles);

	if (!(input_buttons & PMF_JUMP_HELD))
		self.pmove_flags (-) PMF_JUMP_HELD;

	PMove_Categorise();

	self.movetype = MOVETYPE_WALK;

	switch(self.movetype)
	{
	case MOVETYPE_WALK:
		PMove_InAirAccelerate();
		PMove_Move();
		break;
	case MOVETYPE_FLY:
		PMove_NoclipAccelerate();
		PMove_Move();
		break;
	case MOVETYPE_NOCLIP:
		PMove_NoclipAccelerate();
		self.origin += self.velocity*input_timelength;
		break;
	case MOVETYPE_NONE:
		break;
	}
};

#endif
