/***********************************************
*                                              *
*                FrikBot Physics               *
*        The near-perfect emulation of         *
*                Client movement               *
*                                              *
*         Special Thanks to: Asdf, Frog        *
*             Alan "Strider" Kivlin            *
*                                              *
*                                              *
***********************************************/

/*

This program is in the Public Domain. My crack legal
team would like to add:

RYAN "FRIKAC" SMITH IS PROVIDING THIS SOFTWARE "AS IS"
AND MAKES NO WARRANTY, EXPRESS OR IMPLIED, AS TO THE
ACCURACY, CAPABILITY, EFFICIENCY, MERCHANTABILITY, OR
FUNCTIONING OF THIS SOFTWARE AND/OR DOCUMENTATION. IN
NO EVENT WILL RYAN "FRIKAC" SMITH BE LIABLE FOR ANY
GENERAL, CONSEQUENTIAL, INDIRECT, INCIDENTAL,
EXEMPLARY, OR SPECIAL DAMAGES, EVEN IF RYAN "FRIKAC"
SMITH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES, IRRESPECTIVE OF THE CAUSE OF SUCH DAMAGES.

You accept this software on the condition that you
indemnify and hold harmless Ryan "FrikaC" Smith from
any and all liability or damages to third parties,
including attorney fees, court costs, and other
related costs and expenses, arising out of your use
of this software irrespective of the cause of said
liability.

The export from the United States or the subsequent
reexport of this software is subject to compliance
with United States export control and munitions
control restrictions. You agree that in the event you
seek to export this software, you assume full
responsibility for obtaining all necessary export
licenses and approvals and for assuring compliance
with applicable reexport restrictions.

Any reproduction of this software must contain
this notice in its entirety.

*/

/*
=========================================

Stuff mimicking cl_input.c code

=========================================
*/
float(float key) CL_KeyState =
{
	return ((self.keys & key) > 0);
};

void() CL_KeyMove = // CL_BaseMove + CL_AdjustAngles
{
	local float anglespeed;
	local vector view;
	if (self.keys != self.oldkeys)
	{
		self.movement = '0 0 0';
		self.movement_y = self.movement_y + (350 * CL_KeyState(KEY_MOVERIGHT));
		// 350 is the default cl_sidespeed
		self.movement_y = self.movement_y - (350 * CL_KeyState(KEY_MOVELEFT));
		// 350 is the default cl_sidespeed
		self.movement_x = self.movement_x + (200 * CL_KeyState(KEY_MOVEFORWARD));
		// 200 is the default cl_forwardspeed
		self.movement_x = self.movement_x - (200 * CL_KeyState(KEY_MOVEBACK));
		// 200 is the default cl_backspeed
		self.movement_z = self.movement_z + (200 * CL_KeyState(KEY_MOVEUP));
		// 200 is the default cl_upspeed
		self.movement_z = self.movement_z - (200 * CL_KeyState(KEY_MOVEDOWN));
		// 200 is the default cl_upspeed
		if (!self.b_aiflags & AI_PRECISION)
			self.movement = self.movement * 2;
		// 2 is the default cl_movespeedkey & bot always has +speed
	}
	self.oldkeys = self.keys;

	if (self.b_skill != 2) // use mouse emulation
	{
		anglespeed = 1.5 * real_frametime;
		// 1.5 is the default cl_anglespeedkey & bot always has +speed
		self.v_angle_y = self.v_angle_y + anglespeed * CL_KeyState(KEY_LOOKLEFT) * 140;
		// 140 is default cl_yawspeed
		self.v_angle_y = self.v_angle_y - anglespeed * CL_KeyState(KEY_LOOKRIGHT) * 140;
		// 140 is default cl_yawspeed
		self.v_angle_x = self.v_angle_x - anglespeed * CL_KeyState(KEY_LOOKUP) * 150;
		// 150 is default cl_pitchspeed
		self.v_angle_x = self.v_angle_x + anglespeed * CL_KeyState(KEY_LOOKDOWN) * 150;
		// 150 is default cl_pitchspeed
	}
	else
	{
		view_x = angcomp(self.b_angle_x, self.v_angle_x);
		view_y = angcomp(self.b_angle_y, self.v_angle_y);
		if (vlen(view) > 30)
		{
			self.mouse_emu = self.mouse_emu + (view * 30);
			if (vlen(self.mouse_emu) > 180)
				self.mouse_emu = normalize(self.mouse_emu) * 180;
		}
		else
			self.mouse_emu = view * (1 / real_frametime);
		self.v_angle = self.v_angle + self.mouse_emu * real_frametime;


	}
	if (self.v_angle_x > 80)
		self.v_angle_x = 80;
	else if (self.v_angle_x < -70)
		self.v_angle_x = -70;

	if (self.v_angle_z > 50)
		self.v_angle_z = 50;
	else if (self.v_angle_z < -50)
		self.v_angle_z = -50;
	self.v_angle_y = frik_anglemod(self.v_angle_y);

};

/*
=========================================

Stuff mimicking sv_user.c

=========================================
*/
void() SV_UserFriction =
{
	local vector vel;
	local float sped, friction, newspeed;

      vel = self.velocity;
	vel_z =0;
	sped = vlen(vel);
	vel = self.velocity;

	if (!sped)
		return;

    friction = sv_friction;
     if (sped < sv_stopspeed)
		newspeed = sped - real_frametime * sv_stopspeed * friction;
	else
		newspeed = sped - real_frametime * sped * friction;

	if (newspeed < 0)
		newspeed = 0;
	newspeed = newspeed / sped;

	self.velocity_y = vel_y * newspeed;
	self.velocity_x = vel_x * newspeed;
};
void() SV_WaterJump =
{
	if (time > self.teleport_time || !self.waterlevel)
	{
		self.flags = self.flags - (self.flags & FL_WATERJUMP);
		self.teleport_time = 0;
	}
	self.velocity_x = self.movedir_x;
	self.velocity_y = self.movedir_y;
};

void() DropPunchAngle =
{
	local float len;
	len = vlen(self.punchangle);
	self.punchangle = normalize(self.punchangle);
	len = len - 10 * real_frametime;
	if (len < 0)
		len = 0;
	self.punchangle = self.punchangle * len;
};


void(vector wishvel) SV_AirAccelerate =
{
	local float addspeed, wishspd, accelspeed, currentspeed;

	wishspd = vlen(wishvel);
	wishvel = normalize(wishvel);
	if (wishspd > 30)
		wishspd = 30;
	currentspeed = self.velocity * wishvel;
	addspeed = wishspd - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = 10 * sv_accelerate * wishspd * real_frametime;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

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

void(vector wishvel) SV_Accelerate =
{
	local float addspeed, wishspd, accelspeed, currentspeed;

	wishspd = vlen(wishvel);
	wishvel = normalize(wishvel);

	currentspeed = self.velocity * wishvel;
	addspeed = wishspd - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = sv_accelerate * wishspd * real_frametime;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	self.velocity = self.velocity + accelspeed * wishvel;
};
void() SV_WaterMove =
{
        local vector wishvel;
        local float wishspeed, addspeed, cspeed, newspeed;
	makevectors(self.v_angle);
	wishvel = v_right * self.movement_y + v_forward * self.movement_x;

	if (self.movement == '0 0 0')
                wishvel_z = wishvel_z - 60;
	else
                wishvel_z = wishvel_z + self.movement_z;
	wishspeed = vlen(wishvel);

	if (wishspeed > sv_maxspeed)
	{
		wishvel = (sv_maxspeed / wishspeed) * wishvel;
		wishspeed = sv_maxspeed;
	}
	wishspeed = wishspeed * 0.7;
        cspeed = vlen(self.velocity);
        if (cspeed)
	{
                newspeed = cspeed - (real_frametime * cspeed * sv_friction);
		if (newspeed < 0)
			newspeed = 0;
   	       self.velocity = self.velocity * (newspeed / cspeed);

	}
	else
		newspeed = 0;

	if (!wishspeed)
		return;
	addspeed = wishspeed - newspeed;
        if (addspeed <= 0)
		return;
	wishvel = normalize(wishvel);
        cspeed = sv_accelerate * wishspeed * real_frametime;
        if (cspeed > addspeed)
                cspeed = addspeed;
        self.velocity = self.velocity + cspeed * wishvel;
};
void() SV_AirMove =
{
	local vector wishvel, vangle;

	vangle = self.v_angle;
	vangle_x = vangle_z = 0;
	makevectors(vangle);
	if (time < self.teleport_time && (self.movement_x < 0))
		self.movement_x = 0;
	wishvel = v_right * self.movement_y + v_forward * self.movement_x;


	if (self.movetype != MOVETYPE_WALK)
		wishvel_z = self.movement_z;
	else
		wishvel_z = 0;
        if (vlen(wishvel) > sv_maxspeed)
                wishvel = normalize(wishvel) * sv_maxspeed;
	if (self.movetype == MOVETYPE_NOCLIP)
		self.velocity = wishvel;
	else if (self.flags & FL_ONGROUND)
	{
		SV_UserFriction();
		SV_Accelerate(wishvel);
	}
	else
		SV_AirAccelerate (wishvel);
};

void() SV_ClientThink =
{
	local vector vangle;

	if (self.movetype == MOVETYPE_NONE)
		return;
	DropPunchAngle();
	if (self.health <= 0)
		return;
	self.v_angle_z = 0; // V_CalcRoll removed, sucks
	self.angles_z = self.v_angle_z * 4;
	vangle = self.v_angle + self.punchangle;
	if (!self.fixangle)
	{
                self.angles_x = (vangle_x / -3);
		self.angles_y = vangle_y;
	} else
	{
		self.v_angle = self.angles;
		self.fixangle = 0;
	}
	if (self.flags & FL_WATERJUMP)
	{
		SV_WaterJump();
		return;
	}
	if ((self.waterlevel >= 2) && (self.movetype != MOVETYPE_NOCLIP))
	{
		SV_WaterMove();
		return;
	}
	SV_AirMove();

};
/*
=========================================

Stuff mimicking sv_phys.c

=========================================
*/

float() SV_RunThink  =
{
	local float thinktime, bkuptime;
	thinktime = self.nextthink;
	bkuptime = time;
	if (thinktime <= 0 || thinktime > (time + real_frametime))
		return TRUE;
	if (thinktime < time)
		thinktime = time;
	self.nextthink = 0;
	time = thinktime;
	other = world;
	makevectors(self.v_angle); // hack
	self.think();
	time = bkuptime;
	return TRUE;
};

void(float scal) SV_AddGravity =
{
	self.velocity_z = self.velocity_z - (scal * sv_gravity * real_frametime);
};

float() SV_CheckWater =
{
	local vector point;
	local float cont;

	point_x = self.origin_x;
	point_y = self.origin_y;
	self.waterlevel = 0;
	self.watertype = CONTENT_EMPTY;
	point_z = self.origin_z + self.mins_z + 1;
	cont = pointcontents(point);
	if (cont <= CONTENT_WATER)
	{
                self.watertype = cont;
                self.waterlevel = 1;
		point_z = self.origin_z + (self.mins_z + self.maxs_z) * 0.5;
		cont = pointcontents(point);
		if (cont <= CONTENT_WATER)
		{
			self.waterlevel = 2;
			point_z = self.origin_z + self.view_ofs_z;
			cont = pointcontents(point);
			if (cont <= CONTENT_WATER)
				self.waterlevel = 3;
		}
	}
	return (self.waterlevel > 1);

};
void() RemoveThud = // well sometimes
{
	local entity oself;
	if (other == world)
	{
		if (self.flags & FL_ONGROUND)
		{
			self.flags = self.flags - FL_ONGROUND;
		}
	}
	else
	{
		if (other.solid == SOLID_BSP && (self.flags & FL_ONGROUND))
		{
			// RM: Does this break anything?
			// If not, then some more thuds have been removed.
			self.flags = self.flags - FL_ONGROUND;
		}
		if (other == self.owner)
			return;
		if (self.owner.solid == SOLID_NOT)
			return;
		oself = other;
		other = self.owner;
		self = oself;
		if (self.solid == SOLID_BSP)
			if (self.touch)
				self.touch();
	}

};
void() SV_CheckOnGround =
{
	local vector org, v;
	org = self.origin;
	local float currentflags;
	currentflags = self.flags;
	self.flags = self.flags | FL_ONGROUND | FL_PARTIALGROUND;
	walkmove(0,0); // perform C touch function
	self.flags = currentflags | FL_ONGROUND;
	if ((org_x != self.origin_x) || (org_y != self.origin_y))
		org = self.origin;
	else
		self.origin = org;
	v = org;
	v_z = self.maxs_z + org_z + 1;
	traceline (org, v, TRUE, self);
	if ((self.waterlevel == 3) && (self.movetype == MOVETYPE_WALK))
		self.flags = self.flags - FL_ONGROUND;
	else if ((trace_plane_normal_z <= 0.7) && (trace_fraction != 1))
		self.flags = self.flags - FL_ONGROUND;
        else if (!droptofloor(0,0))
		self.flags = self.flags - FL_ONGROUND;
	else if (org_z - self.origin_z < 2)
		self.flags = self.flags | FL_ONGROUND;
	else
		self.flags = self.flags - FL_ONGROUND;
	setorigin(self, org);
};
// Thanks to Alan Kivlin for this function
// modified heavily by me
float(vector dir) botCheckForStep =
{
	local vector currentorigin, v;
	local float currentflags, yaw, stepdistance, movedistance;
	currentorigin = self.origin;
	currentflags = self.flags;
	self.flags = FL_ONGROUND | FL_PARTIALGROUND;
	dir = normalize(dir);
	dir_z = 0;
	yaw = vectoyaw(dir);
	if(walkmove(yaw, 3))
	{
		if(droptofloor(0,0))
		{
			stepdistance = self.origin_z - currentorigin_z;
			v = self.origin - currentorigin;
			v_z = 0;
			movedistance = vlen(v);
			if((stepdistance > 0 && stepdistance <= 16) && movedistance != 0)
			{
				self.flags = currentflags | FL_PARTIALGROUND;
				return 1;
			}
		}
	}
	self.flags = currentflags;
	setorigin(self, currentorigin);
	return 0;
};
// this is merely here to fix a problem with e3m5
void(vector dir) BruteForceStep =
{
	local vector currentorigin;
	local float currentflags, i, len;

	currentorigin = self.origin;
	currentflags = self.flags;
	len = vlen(dir);
	if (len > 16)
		dir = normalize(dir) * 16;

	setorigin(self, currentorigin + dir);

	while(i < 18 && !walkmove(0, 0))
	{
		self.origin_z = currentorigin_z + i;
		i = i + 2;
	}
	self.flags = currentflags;
	if (i >=18)
		setorigin(self, currentorigin);
};

void() PostPhysics =
{
	local vector obstr, org;
	local float back, dst,cflags;

	self = self.owner;

	self.velocity = self.velocity - self.phys_obj.dest1 + self.phys_obj.velocity;
	if (self.phys_obj.dest2 == self.origin)
	{
		setorigin(self, self.phys_obj.origin);
		// might've been moved during other person's physics
		// (teleporters / plats)

		if (self.movetype == MOVETYPE_WALK)
		{

			if (self.phys_obj.dest1_x || self.phys_obj.dest1_y)
			{
				if ((self.flags & FL_ONGROUND) || (self.waterlevel <= 2))
				{
					obstr = self.phys_obj.movedir  - self.origin;
					obstr_z = 0;
					if (vlen(obstr) > 0.1)
					{
						dst = vlen(obstr);
						back = vectoyaw(obstr);
						cflags = self.flags;
						self.flags = self.flags | FL_PARTIALGROUND;
						if(walkmove(back, dst))
						{
							self.flags = cflags;
							self.phys_obj.dest1_z = 0;
							self.velocity = self.velocity + self.phys_obj.dest1 - self.phys_obj.velocity;
						}
						else
						{
							if (dst > 1)
								frik_obstructed(obstr, FALSE);

							org = self.origin;
							self.flags = cflags;
							obstr = self.phys_obj.dest1;
							obstr_x = 0;
							if (!botCheckForStep(obstr))
							{
								obstr = self.phys_obj.dest1;
								obstr_y = 0;
								if (!botCheckForStep(obstr))
								{
									// if no steps were found, bot is really obstucted
									BruteForceStep(self.phys_obj.dest1);
								}
							}
						}
					}
				}
			}
		}
	}

	SV_CheckOnGround();

	PlayerPostThink();
	BotAI();
	self.dmg_take = self.dmg_save = 0;

};
// Avoid calling BotAI and the physics at the same time
// Can trip the runaway loop counter

void() SV_FlyMove =
{
	// This is nothing like the Quake function.

	if (self.phys_obj == world)
	{
		self.phys_obj = find(world,classname,"phys_obj");
		while (self.phys_obj.owner != self)
		{
			self.phys_obj = find(self.phys_obj,classname,"phys_obj");
			if (self.phys_obj == world)
			{
				error("No physics entity spawned!\nMake sure BotInit was called\n");
			}
		}
	}

	setmodel (self.phys_obj, string_null);
	self.phys_obj.movetype = MOVETYPE_STEP;

	self.phys_obj.solid = SOLID_TRIGGER;
	self.phys_obj.touch = RemoveThud;
	setsize(self.phys_obj, self.mins, self.maxs);
	self.phys_obj.dest2 = self.phys_obj.origin = self.origin;
	self.phys_obj.watertype = 0;
	self.phys_obj.movedir = self.origin + real_frametime * self.velocity;
	self.phys_obj.dest1 = self.phys_obj.velocity = self.velocity;
	self.phys_obj.velocity_z = self.phys_obj.velocity_z + sv_gravity * real_frametime;
	self.phys_obj.flags = 0;
	self.phys_obj.think = PostPhysics;
	self.phys_obj.nextthink = time;
};


void() SV_Physics_Toss =
{
	if (!SV_RunThink())
		return;
	if (self.flags & FL_ONGROUND)
	{
		self.velocity = '0 0 0';
		BotAI();
		return;
	}
	if (self.movetype != MOVETYPE_FLY)
		SV_AddGravity(1);
	self.angles = self.angles + real_frametime * self.avelocity;
	SV_FlyMove();

};
void() SV_Physics_Client =
{

	PlayerPreThink();

	if (self.movetype == MOVETYPE_NONE)
	{
		if (!SV_RunThink())
			return;
		PlayerPostThink();
		BotAI();

	}
	else if ((self.movetype == MOVETYPE_WALK) || (self.movetype == MOVETYPE_STEP))
	{
		if (!SV_RunThink())
			return;
            if (!(SV_CheckWater()) && (!(self.flags & FL_WATERJUMP)))
			SV_AddGravity(1);
		SV_FlyMove();
	}
	else if ((self.movetype == MOVETYPE_TOSS) || (self.movetype == MOVETYPE_BOUNCE))
	{
		SV_Physics_Toss();
	}
	else if (self.movetype == MOVETYPE_FLY)
	{
		if (!SV_RunThink())
			return;
		SV_FlyMove();
	}
	else if (self.movetype == MOVETYPE_NOCLIP)
	{
		if (!SV_RunThink())
			return;
		self.origin = self.origin + real_frametime * self.velocity;

		PlayerPostThink();
		BotAI();
	}
	else
		error ("SV_Physics_Client: Bad Movetype (BOT)");

};


