/*
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.

*/
// g_phys.c

#include "g_local.h"


//pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move.
//
//onground is set for toss objects when they come to a complete rest.  it is set for steping or walking objects 
//
//doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
//bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
//corpses are SOLID_NOT and MOVETYPE_TOSS
//crates are SOLID_BBOX and MOVETYPE_TOSS
//walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
//flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
//
//solid_edge items only clip against bsp models.



//============
//SV_TestEntityPosition
//
//============
edict_t	*SV_TestEntityPosition(edict_t *ent)
{
	trace_t	trace;
	int		mask;

	
	if (ent->r.clipmask)
		mask = ent->r.clipmask;
	else
		mask = MASK_SOLID;
	trap_Trace (&trace, ent->s.origin, ent->r.mins, ent->r.maxs, ent->s.origin, ent, mask);

	if (trace.startsolid)
		return game.edicts;
		
	return NULL;
}

//================
//SV_CheckVelocity
//================
void SV_CheckVelocity (edict_t *ent)
{
	float	scale;

//
// bound velocity
//
	scale = VectorLength ( ent->velocity );
	if ( (scale > g_maxvelocity->value) && (scale) ) {
		scale = g_maxvelocity->value / scale;
		VectorScale ( ent->velocity, scale, ent->velocity );
	}
}

//=============
//SV_RunThink
//
//Runs thinking code for this frame if necessary
//=============
qboolean SV_RunThink( edict_t *ent )
{
	unsigned int	thinktime;

	thinktime = ent->nextthink;
	if (thinktime <= 0)
		return qtrue;
	if (thinktime > level.timemsec+1)
		return qtrue;
	
	ent->nextthink = 0;
	if (!ent->think)
		G_Error ("NULL ent->think");
	ent->think (ent);

	return qfalse;
}

//==================
//SV_Impact
//
//Two entities have touched, so run their touch functions
//==================
void SV_Impact(edict_t *e1, trace_t *trace)
{
	edict_t		*e2;

	e2 = &game.edicts[trace->ent];

	if (e1->touch && e1->r.solid != SOLID_NOT)
		e1->touch (e1, e2, &trace->plane, trace->surfFlags);

	if (e2->touch && e2->r.solid != SOLID_NOT)
		e2->touch (e2, e1, NULL, 0);
}

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

int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
{
	float	backoff;
	float	change;
	int		i, blocked;
	
	blocked = 0;
	if (normal[2] > 0)
		blocked |= 1;		// floor
	if (!normal[2])
		blocked |= 2;		// step
	
	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;
	}
	
	return blocked;
}

//============
//SV_FlyMove
//
//The basic solid body movement clip that slides along multiple planes
//Returns the clipflags if the velocity was modified (hit something solid)
//1 = floor
//2 = wall / step
//4 = dead stop
//============
#define	MAX_CLIP_PLANES	5
int SV_FlyMove (edict_t *ent, float time, int mask)
{
	edict_t		*hit;
	int			bumpcount, numbumps;
	vec3_t		dir;
	float		d;
	int			numplanes;
	vec3_t		planes[MAX_CLIP_PLANES];
	vec3_t		primal_velocity, original_velocity, new_velocity;
	int			i, j;
	trace_t		trace;
	vec3_t		end;
	float		time_left;
	int			blocked;
	
	numbumps = 4;
	
	blocked = 0;
	VectorCopy (ent->velocity, original_velocity);
	VectorCopy (ent->velocity, primal_velocity);
	numplanes = 0;
	
	time_left = time;

	ent->groundentity = NULL;
	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
	{
		for (i=0 ; i<3 ; i++)
			end[i] = ent->s.origin[i] + time_left * ent->velocity[i];

		trap_Trace (&trace, ent->s.origin, ent->r.mins, ent->r.maxs, end, ent, mask);

		if (trace.allsolid)
		{	// entity is trapped in another solid
			VectorClear (ent->velocity);
			return 3;
		}

		if (trace.fraction > 0)
		{	// actually covered some distance
			VectorCopy (trace.endpos, ent->s.origin);
			VectorCopy (ent->velocity, original_velocity);
			numplanes = 0;
		}

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

		hit = &game.edicts[trace.ent];

		if (trace.plane.normal[2] > 0.7)
		{
			blocked |= 1;		// floor
			if (hit->r.solid == SOLID_BSP)
			{
				ent->groundentity = hit;
				ent->groundentity_linkcount = hit->r.linkcount;
			}
		}
		if (!trace.plane.normal[2])
		{
			blocked |= 2;		// step
		}

//
// run the impact function
//
		SV_Impact (ent, &trace);
		if (!ent->r.inuse)
			break;		// removed by the impact function

		
		time_left -= time_left * trace.fraction;
		
	// cliped to another plane
		if (numplanes >= MAX_CLIP_PLANES)
		{	// this shouldn't really happen
			VectorClear (ent->velocity);
			return 3;
		}

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

//
// modify original_velocity so it parallels all of the clip planes
//
		for (i=0 ; i<numplanes ; i++)
		{
			ClipVelocity (original_velocity, planes[i], new_velocity, 1);
			for (j=0 ; j<numplanes ; j++)
				if (j != i)
				{
					if (DotProduct (new_velocity, planes[j]) < 0)
						break;	// not ok
				}
			if (j == numplanes)
				break;
		}
		
		if (i != numplanes)
		{	// go along this plane
			VectorCopy (new_velocity, ent->velocity);
		}
		else
		{	// go along the crease
			if (numplanes != 2)
			{
				VectorClear (ent->velocity);
				return 7;
			}
			CrossProduct (planes[0], planes[1], dir);
			VectorNormalize (dir);
			d = DotProduct (dir, ent->velocity);
			VectorScale (dir, d, ent->velocity);
		}

//
// if original velocity is against the original velocity, stop dead
// to avoid tiny occilations in sloping corners
//
		if (DotProduct (ent->velocity, primal_velocity) <= 0)
		{
			VectorClear (ent->velocity);
			return blocked;
		}
	}

	return blocked;
}

//============
//SV_AddGravity
//
//============
void SV_AddGravity(edict_t *ent)
{
	ent->velocity[2] -= ent->gravity * g_gravity->value * FRAMETIME;
}


//===============================================================================
//
//PUSHMOVE
//
//===============================================================================


//============
//SV_PushEntity
//
//Does not change the entities velocity at all
//============
trace_t SV_PushEntity(edict_t *ent, vec3_t push)
{
	trace_t	trace;
	vec3_t	start;
	vec3_t	end;
	int		mask;

	VectorCopy (ent->s.origin, start);
	VectorAdd (start, push, end);

retry:
	if (ent->r.clipmask)
		mask = ent->r.clipmask;
	else
		mask = MASK_SOLID;

	trap_Trace (&trace, start, ent->r.mins, ent->r.maxs, end, ent, mask);
	
	VectorCopy (trace.endpos, ent->s.origin);
	trap_LinkEntity (ent);

	if (trace.fraction != 1.0)
	{
		SV_Impact (ent, &trace);

		// if the pushed entity went away and the pusher is still there
		if (!game.edicts[trace.ent].r.inuse && ent->r.inuse)
		{
			// move the pusher back and try again
			VectorCopy (start, ent->s.origin);
			trap_LinkEntity (ent);
			goto retry;
		}
	}

	if (ent->r.inuse)
		G_TouchTriggers (ent);

	return trace;
}					


typedef struct
{
	edict_t	*ent;
	vec3_t	origin;
	vec3_t	angles;
	float	deltayaw;
} pushed_t;
pushed_t	pushed[MAX_EDICTS], *pushed_p;

edict_t	*obstacle;


//============
//SV_Push
//
//Objects need to be moved back on a failed push,
//otherwise riders would continue to slide.
//============
qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove)
{
	int			i, e;
	edict_t		*check, *block;
	vec3_t		mins, maxs;
	pushed_t	*p;
	vec3_t		axis[3];
	vec3_t		org, org2, move2;

	// clamp the move to 1/8 units, so the position will
	// be accurate for client side prediction
	for (i=0 ; i<3 ; i++)
	{
		float	temp;
		temp = move[i]*16.0;
		move[i] = (1.0/16.0) * (int)temp;
	}

	// find the bounding box
	for (i=0 ; i<3 ; i++)
	{
		mins[i] = pusher->r.absmin[i] + move[i];
		maxs[i] = pusher->r.absmax[i] + move[i];
	}

// we need this for pushing things later
	VectorNegate (amove, org);
	AnglesToAxis (org, axis);

// save the pusher's original position
	pushed_p->ent = pusher;
	VectorCopy (pusher->s.origin, pushed_p->origin);
	VectorCopy (pusher->s.angles, pushed_p->angles);
	if (pusher->r.client)
		pushed_p->deltayaw = pusher->r.client->ps.pmove.delta_angles[YAW];
	pushed_p++;

// move the pusher to its final position
	VectorAdd (pusher->s.origin, move, pusher->s.origin);
	VectorAdd (pusher->s.angles, amove, pusher->s.angles);
	trap_LinkEntity (pusher);

// see if any solid entities are inside the final position
	check = game.edicts+1;
	for (e = 1; e < game.numentities; e++, check++)
	{
		if (!check->r.inuse)
			continue;
		if (check->movetype == MOVETYPE_PUSH
		|| check->movetype == MOVETYPE_STOP
		|| check->movetype == MOVETYPE_NONE
		|| check->movetype == MOVETYPE_NOCLIP)
			continue;

		if (!check->r.area.prev)
			continue;		// not linked in anywhere

	// if the entity is standing on the pusher, it will definitely be moved
		if (check->groundentity != pusher)
		{
			// see if the ent needs to be tested
			if ( check->r.absmin[0] >= maxs[0]
			|| check->r.absmin[1] >= maxs[1]
			|| check->r.absmin[2] >= maxs[2]
			|| check->r.absmax[0] <= mins[0]
			|| check->r.absmax[1] <= mins[1]
			|| check->r.absmax[2] <= mins[2] )
				continue;

			// see if the ent's bbox is inside the pusher's final position
			if (!SV_TestEntityPosition (check))
				continue;
		}

		if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher))
		{
			// move this entity
			pushed_p->ent = check;
			VectorCopy (check->s.origin, pushed_p->origin);
			VectorCopy (check->s.angles, pushed_p->angles);
			pushed_p++;

			// try moving the contacted entity 
			VectorAdd (check->s.origin, move, check->s.origin);
			if (check->r.client)
			{	// FIXME: doesn't rotate monsters?
				check->r.client->ps.pmove.delta_angles[YAW] += amove[YAW];
			}

			// figure movement due to the pusher's amove
			VectorSubtract (check->s.origin, pusher->s.origin, org);
			Matrix_TransformVector (axis, org, org2);
			VectorSubtract (org2, org, move2);
			VectorAdd (check->s.origin, move2, check->s.origin);

			// may have pushed them off an edge
			if (check->groundentity != pusher)
				check->groundentity = NULL;

			block = SV_TestEntityPosition (check);
			if (!block)
			{	// pushed ok
				trap_LinkEntity (check);
				// impact?
				continue;
			}

			// if it is ok to leave in the old position, do it
			// this is only relevent for riding entities, not pushed
			// FIXME: this doesn't acount for rotation
			VectorSubtract (check->s.origin, move, check->s.origin);
			block = SV_TestEntityPosition (check);
			if (!block)
			{
				pushed_p--;
				continue;
			}
		}
		
		// save off the obstacle so we can call the block function
		obstacle = check;

		// move back any entities we already moved
		// go backwards, so if the same entity was pushed
		// twice, it goes back to the original position
		for (p=pushed_p-1 ; p>=pushed ; p--)
		{
			VectorCopy (p->origin, p->ent->s.origin);
			VectorCopy (p->angles, p->ent->s.angles);
			if (p->ent->r.client)
			{
				p->ent->r.client->ps.pmove.delta_angles[YAW] = p->deltayaw;
			}
			trap_LinkEntity (p->ent);
		}
		return qfalse;
	}

//FIXME: is there a better way to handle this?
	// see if anything we moved has touched a trigger
	for (p=pushed_p-1 ; p>=pushed ; p--)
		G_TouchTriggers (p->ent);

	return qtrue;
}

//================
//SV_Physics_Pusher
//
//Bmodel objects don't interact with each other, but
//push all box objects
//================
void SV_Physics_Pusher(edict_t *ent)
{
	vec3_t		move, amove;
	edict_t		*part, *mv;

	// if not a team captain, so movement will be handled elsewhere
	if ( ent->flags & FL_TEAMSLAVE)
		return;

	// make sure all team slaves can move before commiting
	// any moves or calling any think functions
	// if the move is blocked, all moved objects will be backed out
//retry:
	pushed_p = pushed;
	for (part = ent ; part ; part=part->teamchain)
	{
		if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
			part->avelocity[0] || part->avelocity[1] || part->avelocity[2]
			)
		{	// object is moving
			VectorScale (part->velocity, FRAMETIME, move);
			VectorScale (part->avelocity, FRAMETIME, amove);

			if (!SV_Push (part, move, amove))
				break;	// move was blocked
		}
	}
	if (pushed_p > &pushed[MAX_EDICTS])
		G_Error ("pushed_p > &pushed[MAX_EDICTS], memory corrupted");

	if (part)
	{
		// the move failed, bump all nextthink times and back out moves
		for (mv = ent ; mv ; mv=mv->teamchain)
		{
			if (mv->nextthink > 0)
				mv->nextthink += game.framemsec;
		}

		// if the pusher has a "blocked" function, call it
		// otherwise, just stay in place until the obstacle is gone
		if (part->blocked)
			part->blocked (part, obstacle);
#if 0
		// if the pushed entity went away and the pusher is still there
		if (!obstacle->inuse && part->inuse)
			goto retry;
#endif
	}
	else
	{
		// the move succeeded, so call all think functions
		for (part = ent ; part ; part=part->teamchain)
		{
			SV_RunThink (part);
		}
	}
}

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

//=============
//SV_Physics_None
//
//Non moving objects can only think
//=============
void SV_Physics_None(edict_t *ent)
{
// regular thinking
	SV_RunThink(ent);
}

//=============
//SV_Physics_Noclip
//
//A moving object that doesn't obey physics
//=============
void SV_Physics_Noclip( edict_t *ent )
{
// regular thinking
	if( !SV_RunThink(ent) )
		return;
	
	VectorMA( ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles );
	VectorMA( ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin );

	trap_LinkEntity(ent);
}

//==============================================================================
//
//TOSS / BOUNCE
//
//==============================================================================

//=============
//SV_Physics_Toss
//
//Toss, bounce, and fly movement.  When onground, do nothing.
//=============
void SV_Physics_Toss( edict_t *ent )
{
	trace_t		trace;
	vec3_t		move;
	float		backoff;
	edict_t		*slave;
	qboolean	wasinwater;
	qboolean	isinwater;
	vec3_t		old_origin;
	float		d;

	// regular thinking
	SV_RunThink(ent);

	// if not a team captain, so movement will be handled elsewhere
	if( ent->flags & FL_TEAMSLAVE )
		return;

	if( ent->velocity[2] > 0 )
		ent->groundentity = NULL;

	// check for the groundentity going away
	if( ent->groundentity )
		if( !ent->groundentity->r.inuse )
			ent->groundentity = NULL;

	// if onground, return without moving
	if( ent->groundentity )
		return;

	VectorCopy( ent->s.origin, old_origin );

	SV_CheckVelocity(ent);

	if(ent->accel != 0) {
		if(ent->accel < 0 && VectorLength(ent->velocity) < 50) {
			VectorClear(ent->velocity);
		} else {
			vec3_t acceldir;
			VectorNormalize2(ent->velocity, acceldir);
			VectorScale(acceldir, ent->accel*FRAMETIME, acceldir);
			VectorAdd(ent->velocity, acceldir, ent->velocity);
		}
		SV_CheckVelocity(ent);
	}

	// add gravity
	if(ent->movetype != MOVETYPE_FLY && ent->movetype != MOVETYPE_FLYMISSILE)
		SV_AddGravity(ent);

	// move angles
	VectorMA( ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles );

	// move origin
	VectorScale( ent->velocity, FRAMETIME, move );
	trace = SV_PushEntity(ent, move);
	if(!ent->r.inuse)
		return;

	if(trace.fraction < 1)
	{
		if( ent->movetype == MOVETYPE_BOUNCE )
			backoff = 1.5;
		else if( ent->movetype == MOVETYPE_BOUNCEGRENADE ) {
			cvar_t *g_grenade_backoff = trap_Cvar_Get( "g_grenade_backoff", "1.6", CVAR_ARCHIVE );
			backoff = g_grenade_backoff->value;
		} else
			backoff = 1;

		ClipVelocity(ent->velocity, trace.plane.normal, ent->velocity, backoff);

		// stop if on ground
		// LA: hopefully will fix grenades bouncing down slopes
		// method taken from Darkplaces sourcecode
		if(trace.plane.normal[2] > 0.7)
		{
			d = DotProduct(trace.plane.normal, ent->velocity);
			// wsw: Lardase fix for grenade bouncing in stairs
			if(fabs(d) < 60 || // was ent->velocity[2] < 60
				(ent->movetype != MOVETYPE_BOUNCE && ent->movetype != MOVETYPE_BOUNCEGRENADE) )
			{
				ent->groundentity = &game.edicts[trace.ent];
				ent->groundentity_linkcount = ent->groundentity->r.linkcount;
				VectorClear(ent->velocity);
				VectorClear(ent->avelocity);
			}
		}

//		if(ent->touch)
//			ent->touch (ent, trace.ent, &trace.plane, trace.surface);
	}
	
	// check for water transition
	wasinwater = (ent->watertype & MASK_WATER);
	ent->watertype = trap_PointContents (ent->s.origin);
	isinwater = ent->watertype & MASK_WATER;

	// never allow items in CONTENTS_NODROP
	if( ent->item && (ent->watertype & CONTENTS_NODROP) ) {
		// flags are important
		if( ent->item->type & IT_FLAG )
			G_Gametype_CTF_ResetFlag(ent->s.team);

		G_FreeEdict(ent);
		return;
	}

	if(isinwater)
		ent->waterlevel = 1;
	else
		ent->waterlevel = 0;

	if(!wasinwater && isinwater)
		G_PositionedSound(old_origin, game.edicts, CHAN_AUTO, trap_SoundIndex(S_HIT_WATER), 1, 1);
	else if (wasinwater && !isinwater)
		G_PositionedSound(ent->s.origin, game.edicts, CHAN_AUTO, trap_SoundIndex(S_HIT_WATER), 1, 1);

	// move teamslaves
	for(slave = ent->teamchain; slave; slave = slave->teamchain)
	{
		VectorCopy(ent->s.origin, slave->s.origin);
		trap_LinkEntity(slave);
	}
}

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

int AI_NPCPhysMove (edict_t *ent, float time, int mask, qboolean step, float bouncescale);
void M_Phys_Momentum_AddPush( vec3_t accel, vec3_t pushdir, float push, float mass, float timestep );
void M_Phys_Momentum_AddFriction( float classfriction, float class_stopspeed, vec3_t origin, vec3_t vel, float timestep, vec3_t mins, vec3_t maxs, edict_t *passent, int solidmask );
void SV_Physics_Grenade( edict_t *self )
{
	int		mask = MASK_SOLID;
	int		move;
	float	forwardaccel = 0;
	float	sideaccel = 0;
	//float	upaccel = 0;
	vec3_t	forward, right, up;
	vec3_t	vaccel;
	//vec3_t	xzvelocity;
	//float	speed;
	qboolean	GroundEntity = qfalse;
	trace_t	trace;
	vec3_t	v1;
	//jalmoveme to defines or something
	//float	RUNaccel = 90;//26
	//float	CLASS_MAX_velocity;
	float	CLASS_TRACTION = 0.0f;
	float	CLASS_FRICTION = 5.0f;
	float	stopspeed, CLASS_STOPDECCEL = 0;
	float	CLASS_BOUNCESCALE = 1.0f;

	// regular thinking
	SV_RunThink(self);

	//CLASS_MAX_velocity = ((firedef_t *)game.items[WEAP_GRENADELAUNCHER]->info)->speed;

	forwardaccel = 0.0f;
	sideaccel = 0.0f;

	VectorCopy( self->s.origin, self->s.old_origin );

	if( self->r.clipmask )
		mask = self->r.clipmask;

	//see if we are on floor (I don't trust ent->groundentity right now)
	VectorCopy( self->s.origin, v1 ); v1[2] -= 0.25f;//inside floor
	trap_Trace( &trace, self->s.origin, self->r.mins, self->r.maxs, v1, self, mask );
	if( trace.fraction < 1.0f && trace.plane.normal[2] >= 0.7 ) { // We have ground entity
		GroundEntity = qtrue;
	}

	stopspeed = 0.0f;
	if( GroundEntity == qtrue )	//don't accelerate if not at floor
	{
		//intended accel
		forwardaccel *= CLASS_TRACTION;
		sideaccel *= CLASS_TRACTION;

		// if the client is NOT pushing movement keys, it's assumed that the
		// player will stop moving, not only by friction, but by using their legs to stop
		if( !forwardaccel && !sideaccel ) {
			if( self->velocity[0] || self->velocity[1] || self->velocity[2] ) {
				stopspeed = CLASS_STOPDECCEL * CLASS_TRACTION;
			}
		}
	} else {

		//air accel
		forwardaccel *= 0.05f;
		sideaccel *= 0.05f;
	}

	M_Phys_Momentum_AddFriction( CLASS_FRICTION, stopspeed, self->s.origin, self->velocity, FRAMETIME, self->r.mins, self->r.maxs, self, mask );

	//calculate accel
	VectorClear( vaccel );
	AngleVectors( tv( 0, self->s.angles[YAW], 0), forward, right, up);
	M_Phys_Momentum_AddPush( vaccel, forward, forwardaccel, self->mass, FRAMETIME );
	M_Phys_Momentum_AddPush( vaccel, right, sideaccel, self->mass, FRAMETIME );
	//gravity
	if( GroundEntity == qtrue ) {
		if( self->velocity[2] < 0.0f )
			self->velocity[2] = 0.0f;
	} else {
		if( self->velocity[2] > -g_maxvelocity->value )
			self->velocity[2] -= self->gravity * g_gravity->value * FRAMETIME;
	}

	//add momentum to velocity
	VectorAdd( self->velocity, vaccel, self->velocity );

/*	//speed limit
	xzvelocity[0] = self->velocity[0];
	xzvelocity[1] = self->velocity[1];
	xzvelocity[2] = 0.0f;
	speed = VectorLength ( xzvelocity );
	if( speed > CLASS_MAX_velocity ) {
		speed = CLASS_MAX_velocity / speed;
		self->velocity[0] *= speed;
		self->velocity[1] *= speed;
	}*/

	//move
	move = AI_NPCPhysMove( self, FRAMETIME, mask, qtrue, CLASS_BOUNCESCALE );
	if( move == 3 )
	{
		//VectorCopy( oldorigin, self->s.old_origin );
		VectorClear( self->velocity );
		trap_LinkEntity(self);
		return;
	}

	//relink
	trap_LinkEntity(self);
	G_TouchTriggers(self);
	return;
}


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

//================
//G_RunEntity
//
//================
void G_RunEntity( edict_t *ent )
{
	if( ent->prethink )
		ent->prethink(ent);

	switch( (int)ent->movetype )
	{
	//MbotGame[start]
	case MOVETYPE_WALK:
		SV_RunThink(ent);
		break;
	//[end]
	case MOVETYPE_PUSH:
	case MOVETYPE_STOP:
		SV_Physics_Pusher(ent);
		break;
	case MOVETYPE_NONE:
		SV_Physics_None(ent);
		break;
	case MOVETYPE_NOCLIP:
		SV_Physics_Noclip(ent);
		break;
	case MOVETYPE_BOUNCEGRENADE:
		//SV_Physics_Grenade( ent );
		SV_Physics_Toss(ent);
		break;
	case MOVETYPE_TOSS:
	case MOVETYPE_BOUNCE:
	
	case MOVETYPE_FLY:
	case MOVETYPE_FLYMISSILE:
		SV_Physics_Toss(ent);
		break;
	default:
		G_Error( "SV_Physics: bad movetype %i", (int)ent->movetype );			
	}
}
