/*
 * 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->clipmask)
		mask = ent->clipmask;
	else
		mask = MASK_SOLID;
	trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);

	if (trace.startsolid)
		return g_edicts;

	return NULL;
}


/*
 * ================ SV_CheckVelocity ================
 */
void
SV_CheckVelocity(edict_t * ent)
{
	int		i;

	//
	/* bound velocity */
	    //
	    for (i = 0; i < 3; i++) {
		if (ent->velocity[i] > sv_maxvelocity->value)
			ent->velocity[i] = sv_maxvelocity->value;
		else if (ent->velocity[i] < -sv_maxvelocity->value)
			ent->velocity[i] = -sv_maxvelocity->value;
	}
}

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

	thinktime = ent->nextthink;
	if (thinktime <= 0)
		return true;
	if (thinktime > level.time + 0.001)
		return true;

	ent->nextthink = 0;
	if (!ent->think)
		gi.error("NULL ent->think");
	ent->think(ent);

	return false;
}

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

	/* cplane_t	backplane; */

	e2 = trace->ent;

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

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


/*
 * ================== 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];

		trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, mask);

		if (trace.allsolid) {	/* entity is trapped in another solid */
			VectorCopy(vec3_origin, 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 = trace.ent;

		if (trace.plane.normal[2] > 0.7) {
			blocked |= 1;	/* floor */
			if (hit->solid == SOLID_BSP) {
				ent->groundentity = hit;
				ent->groundentity_linkcount = hit->linkcount;
			}
		}
		if (!trace.plane.normal[2]) {
			blocked |= 2;	/* step */
		}
		//
		/* run the impact function */
		    //
		    SV_Impact(ent, &trace);
		if (!ent->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 */
			VectorCopy(vec3_origin, 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) && !VectorCompare(planes[i], planes[j])) {
					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) {

				/*
				 * gi.dprintf ("clip velocity, numplanes ==
				 * %i\n",numplanes);
				 */
				VectorCopy(vec3_origin, ent->velocity);
				return 7;
			}
			CrossProduct(planes[0], planes[1], 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) {
			VectorCopy(vec3_origin, ent->velocity);
			return blocked;
		}
	}

	return blocked;
}


/*
 * ============ SV_AddGravity
 *
 * ============
 */
void
SV_AddGravity(edict_t * ent)
{
	ent->velocity[2] -= ent->gravity * sv_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->clipmask)
		mask = ent->clipmask;
	else
		mask = MASK_SOLID;

	trace = gi.trace(start, ent->mins, ent->maxs, end, ent, mask);

	if (trace.startsolid || trace.allsolid) {
		mask ^= CONTENTS_DEADMONSTER;
		trace = gi.trace(start, ent->mins, ent->maxs, end, ent, mask);
	}
	VectorCopy(trace.endpos, ent->s.origin);
	gi.linkentity(ent);

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

		/*
		 * if the pushed entity went away and the pusher is still
		 * there
		 */
		if (!trace.ent->inuse && ent->inuse) {
			/* move the pusher back and try again */
			VectorCopy(start, ent->s.origin);
			gi.linkentity(ent);
			goto retry;
		}
	}
	if (ent->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		org    , org2, move2, forward, right, up;

	/* 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] * 8.0;
		if (temp > 0.0)
			temp += 0.5;
		else
			temp -= 0.5;
		move[i] = 0.125 * (int)temp;
	}

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

	/* we need this for pushing things later */
	VectorSubtract(vec3_origin, amove, org);
	AngleVectors(org, forward, right, up);

	/* 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->client)
		pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW];
	pushed_p++;

	/* move the pusher to it's final position */
	VectorAdd(pusher->s.origin, move, pusher->s.origin);
	VectorAdd(pusher->s.angles, amove, pusher->s.angles);
	gi.linkentity(pusher);

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

		if (!check->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->absmin[0] >= maxs[0]
			    || check->absmin[1] >= maxs[1]
			    || check->absmin[2] >= maxs[2]
			    || check->absmax[0] <= mins[0]
			    || check->absmax[1] <= mins[1]
			    || check->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->client) {	/* FIXME: doesn't rotate
						 * monsters? */
				check->client->ps.pmove.delta_angles[YAW] += amove[YAW];
			}
			/* figure movement due to the pusher's amove */
			VectorSubtract(check->s.origin, pusher->s.origin, org);
			org2[0] = DotProduct(org, forward);
			org2[1] = -DotProduct(org, right);
			org2[2] = DotProduct(org, up);
			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 */
				gi.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->client) {
				p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw;
			}
			gi.linkentity(p->ent);
		}
		return false;
	}

	/* 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 true;
}

/*
 * ================ 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])
		gi.error(ERR_FATAL, "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 += FRAMETIME;
		}

		/* 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);

	gi.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;

	/* 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->inuse)
			ent->groundentity = NULL;

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

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

	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->inuse)
		return;

	if (trace.fraction < 1) {
		if (ent->movetype == MOVETYPE_BOUNCE)
			backoff = 1.5;
		else
			backoff = 1;

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

		/* stop if on ground */
		if (trace.plane.normal[2] > 0.7) {
			if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE) {
				ent->groundentity = trace.ent;
				ent->groundentity_linkcount = trace.ent->linkcount;
				VectorCopy(vec3_origin, ent->velocity);
				VectorCopy(vec3_origin, 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 = gi.pointcontents(ent->s.origin);
	isinwater = ent->watertype & MASK_WATER;

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

	if (!wasinwater && isinwater)
		gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
	else if (wasinwater && !isinwater)
		gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);

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

/*
 *
 * ============================================================================
 * ===
 *
 * STEPPING MOVEMENT
 *
 * =================
 * =============================================================
 */

/*
 * ============= SV_Physics_Step
 *
 * Monsters freefall when they don't have a ground entity, otherwise all
 * movement is done with discrete steps.
 *
 * This is also used for objects that have become still on the ground, but will
 * fall if the floor is pulled out from under them. FIXME: is this true?
 * =============
 */

/* FIXME: hacked in for E3 demo */
#define	sv_stopspeed		100
#define sv_friction			6
#define sv_waterfriction	1

void
SV_AddRotationalFriction(edict_t * ent)
{
	int		n;
	float		adjustment;

	VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
	adjustment = FRAMETIME * sv_stopspeed * sv_friction;
	for (n = 0; n < 3; n++) {
		if (ent->avelocity[n] > 0) {
			ent->avelocity[n] -= adjustment;
			if (ent->avelocity[n] < 0)
				ent->avelocity[n] = 0;
		} else {
			ent->avelocity[n] += adjustment;
			if (ent->avelocity[n] > 0)
				ent->avelocity[n] = 0;
		}
	}
}

void
SV_Physics_Step(edict_t * ent)
{
	qboolean	wasonground;
	qboolean	hitsound = false;
	float          *vel;
	float		speed   , newspeed, control;
	float		friction;
	edict_t        *groundentity;
	int		mask;

	/* airborn monsters should always check for ground */
	if (!ent->groundentity)
		M_CheckGround(ent);

	groundentity = ent->groundentity;

	SV_CheckVelocity(ent);

	if (groundentity)
		wasonground = true;
	else
		wasonground = false;

	if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
		SV_AddRotationalFriction(ent);

	/* add gravity except: */
	/* flying monsters */
	/* swimming monsters who are in the water */
	if (!wasonground)
		if (!(ent->flags & FL_FLY))
			if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) {
				if (ent->velocity[2] < sv_gravity->value * -0.1)
					hitsound = true;
				if (ent->waterlevel == 0)
					SV_AddGravity(ent);
			}

	/*
	 * friction for flying monsters that have been given vertical
	 * velocity
	 */
	if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) {
		speed = fabs(ent->velocity[2]);
		control = speed < sv_stopspeed ? sv_stopspeed : speed;
		friction = sv_friction / 3;
		newspeed = speed - (FRAMETIME * control * friction);
		if (newspeed < 0)
			newspeed = 0;
		newspeed /= speed;
		ent->velocity[2] *= newspeed;
	}

	/*
	 * friction for flying monsters that have been given vertical
	 * velocity
	 */
	if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) {
		speed = fabs(ent->velocity[2]);
		control = speed < sv_stopspeed ? sv_stopspeed : speed;
		newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
		if (newspeed < 0)
			newspeed = 0;
		newspeed /= speed;
		ent->velocity[2] *= newspeed;
	}
	if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) {
		/* apply friction */
		/* let dead monsters who aren't completely onground slide */
		if ((wasonground) || (ent->flags & (FL_SWIM | FL_FLY)))
			if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) {
				vel = ent->velocity;
				speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]);
				if (speed) {
					friction = sv_friction;

					control = speed < sv_stopspeed ? sv_stopspeed : speed;
					newspeed = speed - FRAMETIME * control * friction;

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

					vel[0] *= newspeed;
					vel[1] *= newspeed;
				}
			}
		if (ent->svflags & SVF_MONSTER)
			mask = MASK_MONSTERSOLID;
		else
			mask = MASK_SOLID;
		SV_FlyMove(ent, FRAMETIME, mask);

		gi.linkentity(ent);
		G_TouchTriggers(ent);
		if (!ent->inuse)
			return;

		if (ent->groundentity)
			if (!wasonground)
				if (hitsound)
					gi.sound(ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
	}
	/* regular thinking */
	SV_RunThink(ent);
}

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

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

	switch ((int)ent->movetype) {
#ifdef WITH_ACEBOT
		/* ACEBOT_ADD */
	case MOVETYPE_WALK:
		SV_RunThink(ent);
		break;
		/* ACEBOT_END */
#endif
	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_STEP:
		SV_Physics_Step(ent);
		break;
	case MOVETYPE_TOSS:
	case MOVETYPE_BOUNCE:
	case MOVETYPE_FLY:
	case MOVETYPE_FLYMISSILE:
		SV_Physics_Toss(ent);
		break;
	default:
		gi.error("SV_Physics: bad movetype %i", (int)ent->movetype);
	}
}
