/*
Copyright (C) 1997-2001 Id Software, Inc.
Copyright (C) 2000-2002 Mr. Hyde and Mad Dog

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.

*/

#include "g_local.h"

#define RFAST    -3
#define RMEDIUM  -2
#define RSLOW    -1
#define STOP      0
#define SLOW      1
#define MEDIUM    2
#define FAST      3

// func_tracktrain spawnflags
#define SF_TRACKTRAIN_NOPITCH		0x0001		// yaw only (maybe roll)
#define SF_TRACKTRAIN_NOCONTROL		0x0002		// no player control
#define SF_TRACKTRAIN_ONEWAY		0x0004		// cannot back up
#define SF_TRACKTRAIN_OTHERMAP		0x0008		// "parent" train resides in another map
#define SF_TRACKTRAIN_NOHUD         0x0010		// do not display speed on HUD
#define SF_TRACKTRAIN_INDICATOR     0x0020		// train has an animated speed indicator
#define SF_TRACKTRAIN_ANIMATED      0x0040		// normal animation
#define SF_TRACKTRAIN_STARTOFF      0x0080		// starts disabled
#define SF_TRACKTRAIN_DISABLED      0x1000		// internal use only
#define SF_TRACKTRAIN_ROLLSPEED		0x2000      // internal use only - roll is a function of speed
#define SF_TRACKTRAIN_SLOWSTOP      0x4000      // internal use only - train slows to stop after driver death
#define SF_TRACKTRAIN_ANIMMASK      (SF_TRACKTRAIN_INDICATOR | SF_TRACKTRAIN_ANIMATED)
// path_track spawnflags
#define SF_PATH_ALTPATH				0x0001		// branch point
#define SF_PATH_DISABLED			0x0002
#define SF_PATH_FIREONCE			0x0004		// pathtarget fired only once
#define SF_PATH_DISABLE_TRAIN       0x0008		// player loses control of train
#define SF_PATH_ABS_SPEED           0x0010      // speed modifier is absolute rather than a multiplier

void tracktrain_next (edict_t *self);
void tracktrain_reach_dest (edict_t *self);

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

static float track_AngleMod (float in)
{
	float out;

	if (in < 0)
		out = in + 360 * ((int)(in / 360)+1);
	else if (in >= 360)
		out = in - 360 * ((int)(in / 360));

	return out;
}

static float track_ApproachAngle (float targAngle, float inAngle, float speed)
{
	float deltaAngle, out;

	targAngle = track_AngleMod (targAngle);
	out = track_AngleMod (targAngle);

	deltaAngle = targAngle - out;

	// fix speed to be non-negative
	if (speed < 0)
		speed *= -1;
	
	if (deltaAngle < -180)
		deltaAngle += 360;
	else if (deltaAngle > 180)
		deltaAngle -= 360;

	if (deltaAngle > speed)
		out += speed;
	else if (deltaAngle < -speed)
		out -= speed;
	else
		out = targAngle;

	return out;
}

static float track_AngleDistance (float nextAngle, float curAngle)
{
	float deltaAngle;

	deltaAngle = nextAngle - curAngle;
	if (deltaAngle < -180)
		deltaAngle += 360;
	else if (deltaAngle > 180)
		deltaAngle -= 360;

	return deltaAngle;
}

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

PATH_TRACK

target:			next path_track
target2:		alternate path
speed:			new tracktrain speed
pathtarget:		entity to trigger when touched
deathtarget:	entity to trigger at dead end

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

void path_track_use (edict_t *self, edict_t *other, edict_t *activator)
{
	char	*temp;

	if(self->spawnflags & SF_PATH_ALTPATH)
	{
		temp = self->target;
		self->target = self->target2;
		self->target2 = temp;
	}
	else
	{
		// toggle enabled/disabled
		self->spawnflags ^= SF_PATH_DISABLED;
	}
}

void SP_path_track (edict_t *self)
{
	if (!self->targetname)
	{
		gi.dprintf ("%s with no targetname at %s\n", self->classname,vtos(self->s.origin));
		G_FreeEdict (self);
		return;
	}

	self->class_id = ENTITY_PATH_TRACK;

	self->solid = SOLID_TRIGGER;
	self->use = path_track_use;
	VectorSet (self->mins, -8, -8, -8);
	VectorSet (self->maxs, 8, 8, 8);
	self->svflags |= SVF_NOCLIENT;
	if(!self->count) self->count = -1;
	gi.linkentity (self);
}

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

FUNC_TRACKCHANGE

height			= Travel altitude (viewheight)
distance		= Rotation amount, degrees (moveinfo.distance)
target			= Name of train
pathtarget		= Top path_track
combattarget	= Bottom path_track
speed			= Move/Rotate speed
"sounds"
0,1	default
2-99 custom sound- e.g. plats/pt02_strt.wav, plats/pt02_mid.wav, plats/pt02_end.wav

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

#define STATE_TOP      0
#define STATE_MOVEDOWN 1
#define STATE_BOTTOM   2
#define STATE_MOVEUP   3

#define SF_TRACK_ACTIVATETRAIN      0x0001
#define SF_TRACK_STARTBOTTOM        0x0008
#define SF_TRACK_XAXIS              0x0040
#define SF_TRACK_YAXIS              0x0080


void trackchange_done (edict_t *self)
{
	edict_t	*train = self->target_ent;

	VectorClear(self->velocity);
	VectorClear(self->avelocity);
	if (self->s.sound && self->moveinfo.sound_end)
		gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->moveinfo.sound_end, 1, self->attenuation, 0); // was ATTN_NORM
	self->s.sound = 0;

	if(train)
	{
		VectorClear(train->velocity);
		VectorClear(train->avelocity);
		train->spawnflags &= ~SF_TRACKTRAIN_DISABLED;
		if(self->spawnflags & SF_TRACK_ACTIVATETRAIN)
		{
			train->moveinfo.state = train->moveinfo.prevstate;
			if(train->moveinfo.state && (train->sounds > 0)) {
				train->s.sound = gi.soundindex(va("%sspeed%d.wav",train->source,abs(train->moveinfo.state)));
	#ifdef LOOP_SOUND_ATTENUATION
				train->s.attenuation = train->attenuation;
	#endif
			}
			train->moveinfo.next_speed = train->moveinfo.state * train->moveinfo.speed/3;
		}
		else
		{	// A little redundant, but clear
			train->moveinfo.state = STOP;
			train->moveinfo.next_speed = 0;
		}
		if(self->moveinfo.state == STATE_MOVEUP)
			train->target = self->pathtarget;
		else
			train->target = self->combattarget;
		train->target_ent = G_PickTarget(train->target);
		// If map is constructed properly, train
		// SHOULD be at the target path_track now. But
		// physics may have caused lift to outrun train,
		// so force it:
		VectorCopy(train->target_ent->s.origin,train->s.origin);
		train->s.origin[2] += train->viewheight;
		gi.linkentity(train);
		tracktrain_next(train);
	}
	if(self->moveinfo.state == STATE_MOVEDOWN)
		self->moveinfo.state = STATE_BOTTOM;
	else
		self->moveinfo.state = STATE_TOP;
	gi.linkentity(self);
}

void trackchange_use (edict_t *self, edict_t *other, edict_t *activator)
{
	float	time, tt, tr;
	float	tspeed, rspeed;

	if((self->moveinfo.state != STATE_TOP) && (self->moveinfo.state != STATE_BOTTOM))
	{
		// already moving
		return;
	}

	if(self->target)
	{
		edict_t	*track;

		self->target_ent = G_PickTarget(self->target);
		if(self->target_ent)
		{
			// Make sure this sucker is at the path_track it's supposed to
			// be at
			if(self->moveinfo.state == STATE_BOTTOM)
				track = G_PickTarget(self->combattarget);
			else
				track = G_PickTarget(self->pathtarget);
			if(!track || (self->target_ent->target_ent != track))
				self->target_ent = NULL;
			else
			{
				vec3_t	v;
				VectorSubtract(track->s.origin,self->target_ent->s.origin,v);
				if(VectorLength(v) > self->target_ent->moveinfo.distance)
					self->target_ent = NULL;
			}
		}
	}
	else
		self->target_ent = NULL;

	if(self->target_ent)
		self->target_ent->s.sound = 0;

	// Adjust speed so that "distance" rotation and "height" movement are achieved
	// simultaneously.
	tt = fabs(self->viewheight) / self->speed;
	tr = fabs(self->moveinfo.distance) / self->speed;
	time = max(tt,tr);
	time = 0.1 * ((int)(10.*time-0.5)+1);
	tspeed = -self->viewheight / time;
	rspeed = self->moveinfo.distance / time;
	if(self->moveinfo.state == STATE_BOTTOM)
	{
		tspeed = -tspeed;
		rspeed = -rspeed;
	}
	VectorClear(self->velocity);
	VectorClear(self->avelocity);
	if (self->spawnflags & SF_TRACK_XAXIS)
		self->velocity[0] = tspeed;
	else if (self->spawnflags & SF_TRACK_YAXIS)
		self->velocity[1] = tspeed;
	else
		self->velocity[2] = tspeed;

	VectorScale(self->movedir,rspeed,self->avelocity);

	if(self->target_ent)
	{
		VectorCopy(self->velocity,self->target_ent->velocity);
		VectorCopy(self->avelocity,self->target_ent->avelocity);
		self->target_ent->spawnflags |= SF_TRACKTRAIN_DISABLED;
		gi.linkentity(self->target_ent);
	}

	if(self->moveinfo.state == STATE_TOP)
		self->moveinfo.state = STATE_MOVEDOWN;
	else
		self->moveinfo.state = STATE_MOVEUP;

	if (self->moveinfo.sound_start)
		gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->moveinfo.sound_start, 1, self->attenuation, 0); // was ATTN_NORM
	self->s.sound = self->moveinfo.sound_middle;
#ifdef LOOP_SOUND_ATTENUATION
	self->s.attenuation = self->attenuation;
#endif

	self->think     = trackchange_done;
	self->nextthink = level.time + time;
	gi.linkentity(self);
}

void SP_func_trackchange (edict_t *self)
{
	self->class_id = ENTITY_FUNC_TRACKCHANGE;

	self->movetype = MOVETYPE_PUSH;
	VectorClear (self->s.angles);

	// set the axis of rotation
	VectorClear(self->movedir);
	if (self->spawnflags & SF_TRACK_XAXIS)
		self->movedir[2] = 1.0;
	else if (self->spawnflags & SF_TRACK_YAXIS)
		self->movedir[0] = 1.0;
	else // Z_AXIS
		self->movedir[1] = 1.0;

	// Rotation
	self->moveinfo.distance = st.distance;

	// Travel height
	self->viewheight = st.height;

	// Max rotation/translation speed
	if(!self->speed)
		self->speed = 100;

	VectorCopy(self->s.origin,self->pos1);
	VectorCopy(self->pos1,self->pos2);
	if (self->spawnflags & SF_TRACK_XAXIS)
		self->pos2[0] -= self->viewheight;
	else if (self->spawnflags & SF_TRACK_YAXIS)
		self->pos2[1] -= self->viewheight;
	else
		self->pos2[2] -= self->viewheight;

	self->solid = SOLID_BSP;
	gi.setmodel (self, self->model);
	self->use = trackchange_use;

	// bottom starters:
	if(self->spawnflags & SF_TRACK_STARTBOTTOM)
	{
		vec3_t	temp;
		VectorCopy(self->pos1,temp);
		VectorCopy(self->pos2,self->pos1);
		VectorCopy(temp,self->pos2);
		VectorCopy(self->pos1,self->s.origin);
		VectorMA(self->s.angles,self->moveinfo.distance,self->movedir,self->s.angles);
		self->moveinfo.state = STATE_BOTTOM;
	}
	else
		self->moveinfo.state = STATE_TOP;

	if (self->sounds > 1 && self->sounds < 100) // custom sounds
	{
		self->moveinfo.sound_start = gi.soundindex  (va("plats/pt%02i_strt.wav", self->sounds));
		self->moveinfo.sound_middle = gi.soundindex  (va("plats/pt%02i_mid.wav", self->sounds));
		self->moveinfo.sound_end = gi.soundindex  (va("plats/pt%02i_end.wav", self->sounds));
	}
	else
	{
		self->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
		self->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
		self->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
	}

	if (self->attenuation <= 0)
		self->attenuation = ATTN_IDLE;

	gi.linkentity(self);

}


/*=====================================================================================
func_tracktrain 

target		first path_track stop
dmg			damage applied to blocker
speed		max. speed, default=64
sounds		currently unused
distance	wheelbase, determines turn rate. default=50
height		height of origin above path_tracks, default=4
roll		roll angle while turning, default=0
*/
void tracktrain_think (edict_t *self);
void tracktrain_drive (edict_t *train, edict_t *other )
{
	vec3_t	angles, offset;
	vec3_t	f1, l1, u1;
	vec3_t	forward, left;

	if (!(other->svflags & SVF_MONSTER))
		return;

	if(train->spawnflags & (SF_TRACKTRAIN_NOCONTROL | SF_TRACKTRAIN_STARTOFF))
		return;

	// See if monster is in driving position
	VectorCopy(train->s.angles,angles);
	VectorNegate(angles,angles);
	AngleVectors(angles,f1,l1,u1);
			
	VectorSubtract(other->s.origin,train->s.origin,offset);
	VectorScale(f1, offset[0],f1);
	VectorScale(l1,-offset[1],l1);
	VectorScale(u1, offset[2],u1);
	VectorCopy(f1,offset);
	VectorAdd(offset,l1,offset);
	VectorAdd(offset,u1,offset);

	// Relax the constraints on driving position just a bit for monsters

	if(offset[0] < train->bleft[0] - 16)
		return;
	if(offset[1] < train->bleft[1] - 16)
		return;
	if(offset[2] < train->bleft[2] - 16)
		return;
	if(offset[0] > train->tright[0] + 16)
		return;
	if(offset[1] > train->tright[1] + 16)
		return;
	if(offset[2] > train->tright[2] + 16)
		return;

	train->owner = other;
	other->vehicle = train;

	// Store the offset and later keep driver at same relative position
	// (with height adjustments for pitch)
	AngleVectors(train->s.angles, forward, left, NULL);
	VectorSubtract(other->s.origin,train->s.origin,train->offset);
	VectorScale(forward,train->offset[0],f1);
	VectorScale(left,train->offset[1],l1);
	VectorCopy(f1,train->offset);
	VectorAdd(train->offset,l1,train->offset);
	train->offset[1] = -train->offset[1];
	train->offset[2] = other->s.origin[2] - train->s.origin[2];
	gi.linkentity(other);
	gi.linkentity(train);

	other->monsterinfo.pausetime = level.time + 100000000;
	other->monsterinfo.aiflags |= AI_STAND_GROUND;
	other->monsterinfo.stand (other);

	train->moveinfo.state = FAST;
	train->moveinfo.next_speed = train->moveinfo.speed;
	if(train->sounds) {
		train->s.sound = gi.soundindex(va("%sspeed%d.wav",train->source,abs(train->moveinfo.state)));
#ifdef LOOP_SOUND_ATTENUATION
		train->s.attenuation = train->attenuation;
#endif
	}
	else
		train->s.sound = 0;
	train->think = tracktrain_think;
	train->think(train);

}

void tracktrain_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
	if(self->deathtarget)
	{
		self->target = self->deathtarget;
		G_UseTargets (self, attacker);
	}
	BecomeExplosion1 (self);
}

void tracktrain_disengage (edict_t *train)
{
	edict_t *driver;

	driver = train->owner;
	if(!driver) return;

	if(driver->client)
	{
		vec3_t	forward, left, up, f1, l1, u1;

		driver->movetype = MOVETYPE_WALK;
		AngleVectors(train->s.angles, forward, left, up);
		VectorScale(forward,train->offset[0],f1);
		VectorScale(left,-train->offset[1],l1);
		VectorScale(up,train->offset[2],u1);
		VectorAdd(train->s.origin,f1,driver->s.origin);
		VectorAdd(driver->s.origin,l1,driver->s.origin);
		VectorAdd(driver->s.origin,u1,driver->s.origin);
		driver->s.origin[2] += 16 * ( fabs(up[0]) + fabs(up[1]) );
		VectorCopy(train->velocity,driver->velocity);
		driver->client->vehicle_framenum = level.framenum;
		// turn ON client side prediction for this player
		driver->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
	}
	else
	{
		if(train->moveinfo.state != STOP)
		{
			train->spawnflags |= SF_TRACKTRAIN_SLOWSTOP;
			train->moveinfo.state = STOP;
			train->moveinfo.next_speed = 0;
			train->s.sound = gi.soundindex(va("%sspeed1.wav",train->source));
	#ifdef LOOP_SOUND_ATTENUATION
			train->s.attenuation = train->attenuation;
	#endif
		}

		driver->movetype = MOVETYPE_STEP;
		if(driver->health > 0)
			VectorCopy(train->velocity,driver->velocity);
		else
			VectorClear(driver->velocity);
		driver->monsterinfo.pausetime = 0;
		driver->monsterinfo.aiflags &= ~AI_STAND_GROUND;
	}
	gi.linkentity (driver);

	train->owner = NULL;
	driver->vehicle = NULL;
}

void tracktrain_hide (edict_t *self)
{
	self->solid = SOLID_NOT;
	self->svflags |= SVF_NOCLIENT;
	gi.linkentity(self);
}

void tracktrain_think (edict_t *self)
{
	float	distance, speed, time;
	float	yaw, pitch;
	vec3_t	forward, left, up, f1, l1, u1, v;
	int		i;
	edict_t	*ent;

	if(self->spawnflags & SF_TRACKTRAIN_OTHERMAP)
	{
		if( (self->spawnflags & SF_TRACKTRAIN_INDICATOR) && !(self->spawnflags & SF_TRACKTRAIN_ANIMATED))
			self->s.frame = 6;
		else
			self->s.effects &= ~(EF_ANIM_ALL | EF_ANIM_ALLFAST);
		self->spawnflags |= SF_TRACKTRAIN_DISABLED;
		self->think = tracktrain_hide;
		self->nextthink = level.time + FRAMETIME;
		VectorClear(self->velocity);
		VectorClear(self->avelocity);
		self->moveinfo.state = self->moveinfo.prevstate = STOP;
		self->s.sound = 0;
		self->moveinfo.current_speed = 0;
		self->owner = NULL;
		gi.linkentity(self);
		return;
	}

	self->nextthink = level.time + FRAMETIME;

	if(!self->owner && (self->spawnflags & SF_TRACKTRAIN_DISABLED))
		return;

	if(self->spawnflags & SF_TRACKTRAIN_ANIMATED)
	{
		if(self->moveinfo.state)
		{
			if(self->spawnflags & SF_TRACKTRAIN_INDICATOR)
				self->s.effects |= EF_ANIM_ALLFAST;
			else
				self->s.effects |= EF_ANIM_ALL;
		}
		else
			self->s.effects &= ~(EF_ANIM_ALL | EF_ANIM_ALLFAST);
	}
	else if(self->spawnflags & SF_TRACKTRAIN_INDICATOR)
	{
		if((self->moveinfo.state == STOP) && !self->owner)
		{
			if(self->spawnflags & SF_TRACKTRAIN_STARTOFF)
				self->s.frame = 14;
			else
				self->s.frame = 6;
		}
		else if((level.framenum % 5) == 0)
			self->s.frame = (self->moveinfo.state - RFAST)*2 + (1 - (self->s.frame % 2));
	}

	AngleVectors(self->s.angles, forward, left, up);

	if(!(self->spawnflags & SF_TRACKTRAIN_DISABLED))
	{
		VectorCopy(self->velocity,v);
		speed = VectorLength(v);
		if(self->moveinfo.state < STOP)
			speed = -speed;
		self->moveinfo.current_speed = speed;
		if(self->moveinfo.state != STOP)
			self->moveinfo.prevstate = self->moveinfo.state;
	}

	if (self->owner)
	{
		edict_t	*driver = self->owner;

		// ... then we have a driver
		if ((driver->health <= 0) || (driver->movetype == MOVETYPE_NOCLIP))
		{
			tracktrain_disengage(self);
			return;
		}
		if (driver->client)
		{
			if (driver->client->use)
			{
				// if he's pressing the use key, and he didn't just
				// get on or off, disengage
				if(level.framenum - driver->client->vehicle_framenum > 2)
				{
					VectorCopy(self->velocity,driver->velocity);
					tracktrain_disengage(self);
					return;
				}
			}
			if ( (driver->client->ucmd.sidemove < -199) || (driver->client->ucmd.sidemove > 199) || (driver->client->ucmd.upmove > 0) )
			{
				VectorCopy(self->velocity,driver->velocity);
				tracktrain_disengage(self);
				return;
			}
		}
		if (!(self->spawnflags & SF_TRACKTRAIN_DISABLED))
		{
			if (driver->client)
			{
				if ((driver->client->ucmd.forwardmove > -199) && (driver->client->ucmd.forwardmove < 199))
					self->moveinfo.wait = 0;
				else if(!self->moveinfo.wait)
				{
					if(driver->client->ucmd.forwardmove > 0)
					{
						if(self->moveinfo.state < FAST)
						{
							self->moveinfo.state++;
							self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
							self->moveinfo.wait = 1;
							if((self->spawnflags & SF_TRACKTRAIN_ANIMMASK) == SF_TRACKTRAIN_INDICATOR)
								self->s.frame = (self->moveinfo.state - RFAST)*2 + (1 - (self->s.frame % 2));
							if(self->moveinfo.state && (self->sounds > 0)) {
								self->s.sound = gi.soundindex(va("%sspeed%d.wav",self->source,abs(self->moveinfo.state)));
						#ifdef LOOP_SOUND_ATTENUATION
								self->s.attenuation = self->attenuation;
						#endif
							}
							else
								self->s.sound = 0;
						}
					}
					else
					{
						if( (self->moveinfo.state > STOP) ||
							((self->moveinfo.state > RFAST) && !(self->spawnflags & SF_TRACKTRAIN_ONEWAY)))
						{
							self->moveinfo.state--;
							self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
							self->moveinfo.wait = 1;
							if((self->spawnflags & SF_TRACKTRAIN_ANIMMASK) == SF_TRACKTRAIN_INDICATOR)
								self->s.frame = (self->moveinfo.state - RFAST)*2 + (1 - (self->s.frame % 2));
							if(self->moveinfo.state && (self->sounds > 0)) {
								self->s.sound = gi.soundindex(va("%sspeed%d.wav",self->source,abs(self->moveinfo.state)));
						#ifdef LOOP_SOUND_ATTENUATION
								self->s.attenuation = self->attenuation;
						#endif
							}
							else
								self->s.sound = 0;
						}
					}
				}
			}
			else
			{
				// Driver is monster.
				if(driver->enemy && driver->enemy->inuse)
				{
					qboolean	vis;

					vis = visible(driver,driver->enemy);

					if(vis)
						self->monsterinfo.search_time = 0;

					if(self->monsterinfo.search_time == 0)
					{
						float		dot, r;
						vec3_t		vec;

						if(vis)
							VectorSubtract(driver->enemy->s.origin,driver->s.origin,vec);
						else
							VectorSubtract(driver->monsterinfo.last_sighting,driver->s.origin,vec);

						r = VectorNormalize(vec);
						dot = DotProduct(vec,forward);
						if((r > 2000) && (dot < 0))
						{
							self->moveinfo.state = -self->moveinfo.state;
							self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
							self->monsterinfo.search_time = level.time;
							tracktrain_next(self);
							return;
						}
					}
				}
			}
			// Check for change in direction
			if( self->moveinfo.prevstate < STOP && self->moveinfo.state > STOP )
			{
				self->moveinfo.prevstate = self->moveinfo.state;
				tracktrain_next(self);
				return;
			}
			else if(self->moveinfo.prevstate > STOP && self->moveinfo.state < STOP)
			{
				self->moveinfo.prevstate = self->moveinfo.state;
				tracktrain_next(self);
				return;
			}

			if(self->moveinfo.current_speed < self->moveinfo.next_speed)
			{
				speed = self->moveinfo.current_speed + self->moveinfo.accel/10;
				if(speed > self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
			}
			else if(self->moveinfo.current_speed > self->moveinfo.next_speed)
			{
				speed = self->moveinfo.current_speed - self->moveinfo.decel/10;
				if(speed < self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
			}
			
			VectorSubtract(self->moveinfo.end_origin,self->s.origin,v);
			distance = VectorLength(v);
			if(speed != 0)
			{
				time = distance/fabs(speed);
				time = 0.1 * ((int)(10*time - 0.5)+1);
				if( (time > 0) && (distance > 0) )
					speed = distance/time;
			}
			else
				time = 100000;
			VectorNormalize(v);
			VectorScale(v,fabs(speed),self->velocity);
			
			//		gi.dprintf("distance to %s=%g, time=%g\n",
			//			self->target_ent->targetname,distance,time);
			
			gi.linkentity(self);
		}

		// Set driver velocity, position, and angles
		VectorCopy(self->velocity,driver->velocity);

		VectorScale(forward,self->offset[0],f1);
		VectorScale(left,-self->offset[1],l1);
		VectorScale(up,self->offset[2],u1);
		VectorAdd(self->s.origin,f1,driver->s.origin);
		VectorAdd(driver->s.origin,l1,driver->s.origin);
		VectorAdd(driver->s.origin,u1,driver->s.origin);
		driver->s.origin[2] += 16 * ( fabs(up[0]) + fabs(up[1]) );

		yaw = self->avelocity[YAW]*FRAMETIME;
		if(yaw != 0)
		{
			driver->s.angles[YAW] += yaw;
			if(driver->client)
			{
				driver->client->ps.pmove.delta_angles[YAW] += ANGLE2SHORT(yaw);
				driver->client->ps.viewangles[YAW] += yaw;
			}
		}
		pitch = self->avelocity[PITCH]*FRAMETIME;
		if(pitch != 0)
		{
			float	delta_yaw;

			delta_yaw = driver->s.angles[YAW] - self->s.angles[YAW];
			delta_yaw *= M_PI / 180.;
			pitch *= cos(delta_yaw);
			if(driver->client)
			{
				driver->client->ps.pmove.delta_angles[PITCH] += ANGLE2SHORT(pitch);
				driver->client->ps.viewangles[PITCH] += pitch;
			}
		}
		if((self->moveinfo.state != STOP) || (yaw != 0) || (pitch != 0))
			if(driver->client)
				driver->client->ps.pmove.pm_type = PM_FREEZE;

		gi.linkentity(driver);

	}
	else if(self->spawnflags & (SF_TRACKTRAIN_NOCONTROL | SF_TRACKTRAIN_STARTOFF))
	{
		// No driver, either can't be controlled or is "off"

		if (!(self->spawnflags & SF_TRACKTRAIN_DISABLED))
		{
			self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;

			if(self->moveinfo.current_speed < self->moveinfo.next_speed)
			{
				speed = self->moveinfo.current_speed + self->moveinfo.accel/10;
				if(speed > self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
			}
			else if(self->moveinfo.current_speed > self->moveinfo.next_speed)
			{
				speed = self->moveinfo.current_speed - self->moveinfo.decel/10;
				if(speed < self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
			}
			
			if(speed != 0)
			{
				VectorSubtract(self->moveinfo.end_origin,self->s.origin,v);
				distance = VectorLength(v);
				time = distance/fabs(speed);
				time = 0.1 * ((int)(10*time - 0.5)+1);
				if( (time > 0) && (distance > 0) )
					speed = distance/time;
				VectorNormalize(v);
				VectorScale(v,fabs(speed),self->velocity);
				gi.linkentity(self);
			}

			if( !(self->spawnflags & SF_TRACKTRAIN_NOCONTROL) &&
				 (self->spawnflags & SF_TRACKTRAIN_STARTOFF)  &&
				 self->viewmessage )
			{
				vec3_t	angles, offset;

				// Check for player entering bleft/tright field of train
				VectorCopy(self->s.angles,angles);
				VectorNegate(angles,angles);
				AngleVectors(angles,f1,l1,u1);
				for (i=1, ent=&g_edicts[1] ; i<=maxclients->value ; i++, ent++)
				{
					if (!ent->inuse) continue;
					if (ent->movetype == MOVETYPE_NOCLIP) continue;
					VectorSubtract(ent->s.origin,self->s.origin,offset);
					VectorScale(f1, offset[0],f1);
					VectorScale(l1,-offset[1],l1);
					VectorScale(u1, offset[2],u1);
					VectorCopy(f1,offset);
					VectorAdd(offset,l1,offset);
					VectorAdd(offset,u1,offset);
					if(offset[0] < self->bleft[0])
						continue;
					if(offset[1] < self->bleft[1])
						continue;
					if(offset[2] < self->bleft[2])
						continue;
					if(offset[0] > self->tright[0])
						continue;
					if(offset[1] > self->tright[1])
						continue;
					if(offset[2] > self->tright[2])
						continue;

					safe_centerprintf(ent,"%s",self->viewmessage);
					self->viewmessage = NULL;
				}
			}

			if(!speed)
			{
				if(self->viewmessage)
					time = 100000;
				else
				{
					VectorClear(self->avelocity);
					VectorClear(self->avelocity);
					self->nextthink = 0;
					if(self->movewith_next && (self->movewith_next->movewith_ent == self))
						set_child_movement(self);
					gi.linkentity(self);
					return;
				}
			}
		}
	}
	else
	{
		//
		// No driver, CAN be controlled and isn't currently turned off
		//
		self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
		if(self->moveinfo.current_speed < self->moveinfo.next_speed)
		{
			if(self->spawnflags & SF_TRACKTRAIN_SLOWSTOP)
				speed = self->moveinfo.current_speed + self->moveinfo.accel/25;
			else
				speed = self->moveinfo.current_speed + self->moveinfo.accel/10;
			if(speed > self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
		}
		else if(self->moveinfo.current_speed > self->moveinfo.next_speed)
		{
			if(self->spawnflags & SF_TRACKTRAIN_SLOWSTOP)
				speed = self->moveinfo.current_speed - self->moveinfo.decel/25;
			else
				speed = self->moveinfo.current_speed - self->moveinfo.decel/10;
			if(speed < self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
		}

		if( speed != 0 ) 
		{
			VectorSubtract(self->moveinfo.end_origin,self->s.origin,v);
			distance = VectorNormalize(v);
			time = distance/fabs(speed);
			time = 0.1 * ((int)(10*time - 0.5)+1);
			if( (time > 0) && (distance > 0) )
				speed = distance/time;
			VectorScale(v,fabs(speed),self->velocity);
			gi.linkentity(self);
		}
		else if(self->moveinfo.current_speed != 0)
		{
			VectorClear(self->velocity);
			self->s.sound = 0;
			gi.linkentity(self);
			time = 100000;
		}
		else
		{
			self->spawnflags &= ~SF_TRACKTRAIN_SLOWSTOP;
			time = 100000;
		}

		if(!(self->spawnflags & SF_TRACKTRAIN_NOCONTROL))
		{
			vec3_t	angles, offset;

			// Check if a player is in driving position (monsters handled elsewhere)

			VectorCopy(self->s.angles,angles);
			VectorNegate(angles,angles);
			AngleVectors(angles,f1,l1,u1);
			
			// find a player
			for (i=1, ent=&g_edicts[1] ; i<=maxclients->value ; i++, ent++) {
				if (!ent->inuse) continue;
				if (ent->movetype == MOVETYPE_NOCLIP) continue;
				if (!ent->client->use && !self->message) continue;
				if (level.framenum - ent->client->vehicle_framenum <= 2) continue;

				VectorSubtract(ent->s.origin,self->s.origin,offset);
				VectorScale(f1, offset[0],f1);
				VectorScale(l1,-offset[1],l1);
				VectorScale(u1, offset[2],u1);
				VectorCopy(f1,offset);
				VectorAdd(offset,l1,offset);
				VectorAdd(offset,u1,offset);
//				gi.dprintf("offset=%g %g %g\n",offset[0],offset[1],offset[2]);
				if(offset[0] < self->bleft[0])
					continue;
				if(offset[1] < self->bleft[1])
					continue;
				if(offset[2] < self->bleft[2])
					continue;
				if(offset[0] > self->tright[0])
					continue;
				if(offset[1] > self->tright[1])
					continue;
				if(offset[2] > self->tright[2])
					continue;

				if(self->message)
				{
					safe_centerprintf(ent,"%s",self->message);
					self->message = NULL;
				}
				if (!ent->client->use) continue;

				// Got a driver!
				ent->client->vehicle_framenum = level.framenum;
				self->owner = ent;
				ent->vehicle = self;
				// turn off client side prediction for this player
				ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;

				// Store the offset and later keep driver at same relative position
				// (with height adjustments for pitch)
				VectorSubtract(ent->s.origin,self->s.origin,self->offset);
				VectorScale(forward,self->offset[0],f1);
				VectorScale(left,self->offset[1],l1);
				VectorCopy(f1,self->offset);
				VectorAdd(self->offset,l1,self->offset);
				self->offset[1] = -self->offset[1];
				self->offset[2] = ent->s.origin[2] - self->s.origin[2];
				gi.linkentity(ent);
				self->moveinfo.wait = 1;
				gi.linkentity(self);
			}
		}
	}
	if(self->movewith_next && (self->movewith_next->movewith_ent == self))
		set_child_movement(self);

	if( (time < 1.5*FRAMETIME) && !(self->spawnflags & SF_TRACKTRAIN_DISABLED))
		self->think = tracktrain_reach_dest;
}

void tracktrain_blocked (edict_t *self, edict_t *other)
{
	vec3_t	dir;
	int		knockback;

	// Correct owner's velocity
	if (self->owner)
	{
		edict_t	*driver = self->owner;
		vec3_t	forward, left, up;
		vec3_t	f1, l1, u1;
		VectorCopy(self->velocity, driver->velocity);
		AngleVectors(self->s.angles,forward,left,up);
		VectorScale(forward,self->offset[0],f1);
		VectorScale(left,-self->offset[1],l1);
		VectorScale(up,self->offset[2],u1);
		VectorAdd(self->s.origin,f1,driver->s.origin);
		VectorAdd(driver->s.origin,l1,driver->s.origin);
		VectorAdd(driver->s.origin,u1,driver->s.origin);
		driver->s.origin[2] += 16 * ( fabs(up[0]) + fabs(up[1]) );
		gi.linkentity(driver);
	}
	VectorSubtract(other->s.origin,self->s.origin,dir);
	dir[2] += 16;
	VectorNormalize(dir);
	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
	{
		// give it a chance to go away on it's own terms (like gibs)
		T_Damage (other, self, self, dir, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
		// if it's still there, nuke it
		if (other)
		{
			// Some of our ents don't have origin near the model
			vec3_t save;
			VectorCopy(other->s.origin,save);
			VectorMA (other->absmin, 0.5, other->size, other->s.origin);
			BecomeExplosion1 (other);
		}
		return;
	}

	if (level.time < self->touch_debounce_time)
		return;

	if (!self->dmg)
		return;

	if(other->client && (other->groundentity == self))
	{
		// Don't cream riders who've become embedded - just do minor damage
		// and *maybe* help them get unstuck by pushing them up.
		knockback = 2;
		VectorSet(dir,0,0,1);
		T_Damage (other, self, self, dir, other->s.origin, vec3_origin, 1, knockback, 0, MOD_CRUSH);
	}
	else
	{
		knockback = (int)(fabs(self->moveinfo.current_speed) * other->mass / 300.);
		T_Damage (other, self, self, dir, other->s.origin, vec3_origin, self->dmg, knockback, 0, MOD_CRUSH);
	}
	self->touch_debounce_time = level.time + 0.5;
}

void tracktrain_reach_dest (edict_t *self)
{
	edict_t	*path = self->target_ent;

	if (path && path->pathtarget)
	{
		char	*savetarget;

		savetarget = path->target;
		path->target = path->pathtarget;
		if(self->owner)
			G_UseTargets (path, self->owner);
		else
			G_UseTargets (path, self);
		path->target = savetarget;

		// make sure we didn't get killed by a killtarget
		if (!self->inuse)
			return;
		if(path->spawnflags & SF_PATH_FIREONCE)
			path->pathtarget = NULL;
	}

	if(path && (path->spawnflags & SF_PATH_DISABLE_TRAIN))
	{
		self->spawnflags |= SF_TRACKTRAIN_NOCONTROL;
		if(self->owner)
			tracktrain_disengage(self);
	}
	if(path && path->speed)
	{
		if(path->spawnflags & SF_PATH_ABS_SPEED)
		{
			self->moveinfo.speed = path->speed;
			self->moveinfo.next_speed = self->moveinfo.speed;
			self->moveinfo.state = (self->moveinfo.state >= STOP ? FAST : RFAST);
		}
		else
		{
			self->moveinfo.speed = path->speed * self->speed;
			self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
		}
		self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
	}
	tracktrain_next (self);
}

qboolean is_backing_up (edict_t *train)
{
	vec3_t	forward, v_norm;

	VectorCopy(train->velocity,v_norm);
	VectorNormalize(v_norm);
	AngleVectors(train->s.angles,forward,NULL,NULL);
	if(DotProduct(forward,v_norm) < 0.)
		return true;
	else
		return false;
}

edict_t *NextPathTrack(edict_t *train, edict_t *path)
{
	edict_t		*next=NULL;
	vec3_t		forward;
	vec3_t		to_next;
	qboolean	in_reverse;

	AngleVectors(train->s.angles,forward,NULL,NULL);

	if( (train->moveinfo.prevstate < STOP && train->moveinfo.state > STOP) ||
		(train->moveinfo.prevstate > STOP && train->moveinfo.state < STOP)   )
	{
		next = path->prevpath;
		if(next)
		{
			VectorSubtract(next->s.origin,path->s.origin,to_next);
			VectorNormalize(to_next);
			if((train->moveinfo.state > STOP) && (DotProduct(forward,to_next) < 0))
				next = NULL;
			else if((train->moveinfo.state < STOP) && (DotProduct(forward,to_next) > 0))
				next = NULL;
			else
			{
				next->prevpath = path;
				return next;
			}
		}
	}

	if(train->moveinfo.state == STOP)
	{
		if(is_backing_up(train))
			in_reverse = true;
		else
			in_reverse = false;
	}
	else
		in_reverse = (train->moveinfo.state < STOP ? true : false);

	if(in_reverse)
	{
		if(path->spawnflags & SF_PATH_ALTPATH)
		{
			next = G_PickTarget (path->target);
			if(next)
			{
				VectorSubtract(next->s.origin,path->s.origin,to_next);
				VectorNormalize(to_next);
				if(DotProduct(forward,to_next) > 0)
					next = NULL;
			}
		}

		if(!next)
		{
			next = path->prevpath;

			if(next)
			{
				// Ensure we don't flipflop
				VectorSubtract(next->s.origin,path->s.origin,to_next);
				VectorNormalize(to_next);
				if(DotProduct(forward,to_next) > 0)
					next = NULL;
			}
			
			if(!next)
			{
				// Find path_track whose target or target2 is set to 
				// the current path_track
				edict_t	*e;
				int		i;
				for(i=maxclients->value; i<globals.num_edicts && !next; i++)
				{
					e = &g_edicts[i];
					if(!e->inuse)
						continue;
					if(e==path)
						continue;
					if(!e->classname)
						continue;
					if(Q_stricmp(e->classname,"path_track"))
						continue;
					if(e->target && !Q_stricmp(e->target,path->targetname))
					{
						next = e;
						VectorSubtract(next->s.origin,path->s.origin,to_next);
						VectorNormalize(to_next);
						if(DotProduct(forward,to_next) > 0)
							next = NULL;
//						else
//							path->prevpath = next;
					}
					if(!next && e->target2 && !Q_stricmp(e->target2,path->targetname))
					{
						next = e;
						VectorSubtract(next->s.origin,path->s.origin,to_next);
						VectorNormalize(to_next);
						if(DotProduct(forward,to_next) > 0)
							next = NULL;
//						else
//							path->prevpath = next;
					}
				}
			}

			if (!next)
			{
				float	dot;

				// Finally, check this path_track's target and target2
				if(path->target)
				{
					next = G_PickTarget (path->target);
					if(next)
					{
						VectorSubtract(next->s.origin,path->s.origin,to_next);
						VectorNormalize(to_next);
						dot = DotProduct(forward,to_next);
						if(dot > 0)
							next = NULL;
					}
				}
				if(path->target2 && !(path->spawnflags & SF_PATH_ALTPATH))
				{
					edict_t	*next2;
					float	dot2;
					
					next2 = G_PickTarget (path->target2);
					if( next2 == path )
						next2 = NULL;
					
					if(next2)
					{
						VectorSubtract(next2->s.origin,path->s.origin,to_next);
						VectorNormalize(to_next);
						dot2 = DotProduct(forward,to_next);
						if(dot2 > 0)
							next2 = NULL;
						else if(!next)
						{
							next = next2;
							next2 = NULL;
						}
					}
					
					if((next && next2) && (dot2 < dot))
						next = next2;
				}
			}
		}
	}
	else	// Moving forward
	{
		float	dot;

		if(path->target)
		{
			next = G_PickTarget (path->target);
			if(next)
			{
				VectorSubtract(next->s.origin,path->s.origin,to_next);
				VectorNormalize(to_next);
				dot = DotProduct(forward,to_next);
				if(dot < 0)
					next = NULL;
			}
		}
		if(path->target2 && !(path->spawnflags & SF_PATH_ALTPATH))
		{
			edict_t	*next2;
			float	dot2;

			next2 = G_PickTarget (path->target2);
			if( next2 == path )
				next2 = NULL;

			if(next2)
			{
				VectorSubtract(next2->s.origin,path->s.origin,to_next);
				VectorNormalize(to_next);
				dot2 = DotProduct(forward,to_next);
				if(dot2 < 0)
					next2 = NULL;
				else if(!next)
				{
					next = next2;
					next2 = NULL;
				}
			}

			if((next && next2) && (dot2 > dot))
				next = next2;
		}
		if(next == path)
			next = NULL;

		if(!next)
		{	// Check for path_tracks that target (or target2) this path_track.
			edict_t	*e;
			int		i;
			for(i=maxclients->value; i<globals.num_edicts && !next; i++)
			{
				e = &g_edicts[i];
				if(!e->inuse)
					continue;
				if(e==path)
					continue;
				if(!e->classname)
					continue;
				if(Q_stricmp(e->classname,"path_track"))
					continue;
				if(e->target && !Q_stricmp(e->target,path->targetname))
				{
					next = e;
					VectorSubtract(next->s.origin,path->s.origin,to_next);
					VectorNormalize(to_next);
					dot = DotProduct(forward,to_next);
					if(dot < 0)
						next = NULL;
				}
				if(e->target2 && !Q_stricmp(e->target2,path->targetname))
				{
					edict_t	*next2;
					float	dot2;

					next2 = e;
					VectorSubtract(next2->s.origin,path->s.origin,to_next);
					VectorNormalize(to_next);
					dot2 = DotProduct(forward,to_next);
					if(dot2 < 0)
						next2 = NULL;
					else if(!next)
					{
						next = next2;
						next2 = NULL;
					}
					if((next && next2) && (dot2 > dot))
						next = next2;
				}
			}
		}
//		if(next)
//			next->prevpath = path;

	}
	if(developer->value)
		gi.dprintf("prev=%s, current=%s, next=%s\n",
			(path->prevpath ? path->prevpath->targetname : "nada"),
			path->targetname,
			(next ? next->targetname : "nada"));

	if(next)
		next->prevpath = path;
	return next;
}

void LookAhead( edict_t *train, vec3_t point, float dist )
{
	float originalDist = dist;
	float length;
	vec3_t	v;
	edict_t	*path;
	int		n=0;
	
	path = train->target_ent;
	if(!path || dist < 0)
		return;

	while ( dist > 0 )
	{
		n++;
		if(n>20)
		{
			gi.dprintf("WTF??? n=%d\n",n);
			return;
		}

		VectorSubtract(path->s.origin,point,v);
		length = VectorLength(v);
		if(length >= dist)
		{
			VectorMA(point,dist/length,v,point);
			return;
		}
		dist -= length;
		VectorCopy(path->s.origin,point);

		// Don't go past a switch
/*		if(path->spawnflags & SF_PATH_ALTPATH)
		{
			return;
		} */
		path = NextPathTrack(train,path);
		if(!path)
			return;
	}
}

void train_angles(edict_t *train)
{
	vec3_t	v, angles;

	VectorCopy(train->s.origin,v);
	v[2] -= train->viewheight;
	LookAhead(train,v,train->moveinfo.distance);
	v[2] += train->viewheight;
	VectorSubtract (v, train->s.origin, v);
	if( (train->moveinfo.state < STOP) || (train->moveinfo.state==STOP && is_backing_up(train)) )
		VectorNegate(v,v);

//	gi.dprintf("v = %g, %g, %g\n",v[0],v[1],v[2]);

	if(VectorLength(v))
	{
		vectoangles2(v,angles);
		train->ideal_yaw = angles[YAW];
		train->ideal_pitch = angles[PITCH];
		if(train->ideal_pitch < 0) train->ideal_pitch += 360;
	}
	else
	{
		train->ideal_pitch = train->s.angles[PITCH];
		train->ideal_yaw = train->s.angles[YAW];
	}

	// determine angular velocitys from wheelbase and target angles

//	gi.dprintf("tracktrain_next: target_ent=%s, ideal_yaw=%g, ideal_pitch=%g\n",
//		(train->target_ent ? train->target_ent->targetname : "none"),
//		train->ideal_yaw, train->ideal_pitch);

	angles[PITCH] = train->ideal_pitch - train->s.angles[PITCH];
	angles[YAW]   = train->ideal_yaw   - train->s.angles[YAW];
	AnglesNormalize(angles);

	// If yaw angle is > 90, we're about to flipflop (there's no way ideal_yaw
	// can be more than 90 because the path_track selection code doesn't
	// allow that
	if( (angles[YAW] > 90) || (angles[YAW] < -90) )
	{
		angles[YAW]   += 180;
		if(angles[PITCH] != 0)
			angles[PITCH] += 180;
		AnglesNormalize(angles);
	}
	train->pitch_speed = fabs(angles[PITCH])*10;
	train->yaw_speed   = fabs(angles[YAW])*10;
}

void tracktrain_turn (edict_t *self)
{
	edict_t	*train;
	float	cur_yaw, idl_yaw, cur_pitch, idl_pitch;
	float	yaw_vel, pitch_vel;
	float	Dist_1, Dist_2, Distance;
	float	new_speed;

	train = self->enemy;
	if(!train || !train->inuse)
		return;

	self->nextthink = level.time + FRAMETIME;

	if(train->spawnflags & (SF_TRACKTRAIN_DISABLED | SF_TRACKTRAIN_OTHERMAP))
		return;

	// Train doesn't turn if at a complete stop
	if((train->velocity[0]==0.) && (train->velocity[1]==0.) && (train->velocity[2]==0.))
	{
		VectorClear(train->avelocity);
		gi.linkentity(train);
		return;
	}

	train_angles(train);

	cur_yaw   = train->s.angles[YAW];
	idl_yaw   = train->ideal_yaw;
	cur_pitch = train->s.angles[PITCH];
	idl_pitch = train->ideal_pitch;

//	gi.dprintf("current angles=%g %g, ideal angles=%g %g\n",
//		cur_pitch,cur_yaw,idl_pitch,idl_yaw);

	yaw_vel   = train->yaw_speed;
	pitch_vel = train->pitch_speed;

	if (train->spawnflags & SF_TRACKTRAIN_NOPITCH)
		idl_pitch = cur_pitch;

	if (cur_yaw == idl_yaw)
		train->avelocity[YAW] = 0;
	else
	{
		if (cur_yaw < idl_yaw)
		{
			Dist_1 = (idl_yaw - cur_yaw)*10;
			Dist_2 = ((360 - idl_yaw) + cur_yaw)*10;
			
			if (Dist_1 < Dist_2)
			{
				Distance = Dist_1;
				
				if (Distance < yaw_vel)
					yaw_vel = Distance;
				
				new_speed = yaw_vel;
			}
			else
			{
				Distance = Dist_2;
				
				if (Distance < yaw_vel)
					yaw_vel = Distance;
				
				new_speed = -yaw_vel;
			}
		}
		else
		{
			Dist_1 = (cur_yaw - idl_yaw)*10;
			Dist_2 = ((360 - cur_yaw) + idl_yaw)*10;
			
			if (Dist_1 < Dist_2)
			{
				Distance = Dist_1;
				
				if (Distance < yaw_vel)
					yaw_vel = Distance;
				
				new_speed = -yaw_vel;
			}
			else
			{
				Distance = Dist_2;
				
				if (Distance < yaw_vel)
					yaw_vel = Distance;
				
				new_speed = yaw_vel;
			}
		}
		train->avelocity[YAW] = new_speed;

//		if(developer->value)
//			gi.dprintf ("current yaw: %g ideal yaw: %g yaw speed: %g\n", cur_yaw, idl_yaw, self->enemy->avelocity[1]);
		
		if (train->s.angles[YAW] < 0)
			train->s.angles[YAW] += 360;
		
		if (train->s.angles[YAW] >= 360)
			train->s.angles[YAW] -= 360;
	}

	if ( train->roll != 0 )
	{
		float	roll;

		if(train->moveinfo.state < STOP)
			roll = -train->roll;
		else
			roll = train->roll;

		if(train->spawnflags & SF_TRACKTRAIN_ROLLSPEED)
			roll *= VectorLength(train->velocity)/train->moveinfo.speed;

		if ( train->avelocity[YAW] < -5 )
			train->avelocity[ROLL] = track_AngleDistance( track_ApproachAngle( -roll, train->s.angles[ROLL], roll*2 ), train->s.angles[ROLL]);
		else if ( train->avelocity[YAW] > 5 )
			train->avelocity[ROLL] = track_AngleDistance( track_ApproachAngle( roll, train->s.angles[ROLL], roll*2 ), train->s.angles[ROLL]);
		else
			train->avelocity[ROLL] = track_AngleDistance( track_ApproachAngle( 0, train->s.angles[ROLL], roll*4 ), train->s.angles[ROLL]) * 4;
	}

	if (cur_pitch == idl_pitch)
		train->avelocity[PITCH] = 0;
	else
	{
		if (cur_pitch < idl_pitch)
		{
			Dist_1 = (idl_pitch - cur_pitch)*10;
			Dist_2 = ((360 - idl_pitch) + cur_pitch)*10;
			
			if (Dist_1 < Dist_2)
			{
				Distance = Dist_1;
				
				if (Distance < pitch_vel)
					pitch_vel = Distance;
				
				new_speed = pitch_vel;
			}
			else
			{
				Distance = Dist_2;
				
				if (Distance < pitch_vel)
					pitch_vel = Distance;
				
				new_speed = -pitch_vel;
			}
		}
		else
		{
			Dist_1 = (cur_pitch - idl_pitch)*10;
			Dist_2 = ((360 - cur_pitch) + idl_pitch)*10;
			
			if (Dist_1 < Dist_2)
			{
				Distance = Dist_1;
				
				if (Distance < pitch_vel)
					pitch_vel = Distance;
				
				new_speed = -pitch_vel;
			}
			else
			{
				Distance = Dist_2;
				
				if (Distance < pitch_vel)
					pitch_vel = Distance;
				
				new_speed = pitch_vel;
			}
		}
		train->avelocity[PITCH] = new_speed;
		
		if (train->s.angles[PITCH] <  0)
			train->s.angles[PITCH] += 360;
		
		if (train->s.angles[PITCH] >= 360)
			train->s.angles[PITCH] -= 360;
	}
	gi.linkentity(train);

}


void tracktrain_next (edict_t *self)
{
	edict_t		*ent=NULL;
	vec3_t		dest;

	if (!self->target_ent)
	{
		self->s.sound = 0;
		return;
	}

	ent = NextPathTrack(self,self->target_ent);

	if(ent && (ent->spawnflags & SF_PATH_DISABLED))
		ent = NULL;

	if(!ent)
	{
		// Dead end
		if(self->owner && (self->owner->svflags & SVF_MONSTER) && !self->target_ent->deathtarget )
		{
			// For monster drivers, immediately reverse course at
			// dead ends (but NOT at dead ends that have a "deathtarget",
			// which is usually an indication of a trackchange
			self->moveinfo.prevstate = self->moveinfo.state;
			self->moveinfo.state = -self->moveinfo.state;
			self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
			self->think = tracktrain_think;
			self->think(self);
			self->monsterinfo.search_time = level.time;
			return;
		}

		VectorClear(self->velocity);
		VectorClear(self->avelocity);
		self->moveinfo.prevstate = self->moveinfo.state;
		self->moveinfo.state = STOP;
		self->s.sound = 0;
		self->moveinfo.current_speed = 0;
		self->moveinfo.next_speed    = 0;
		gi.linkentity(self);
		if(self->owner)
		{
			VectorClear(self->owner->velocity);
			gi.linkentity(self->owner);
		}
		if(self->target_ent->deathtarget)
		{
			char	*temp;
			temp = self->target_ent->target;
			self->target_ent->target = self->target_ent->deathtarget;
			G_UseTargets(self->target_ent,self);
			self->target_ent->target = temp;
		}
		self->think = tracktrain_think;
		self->think(self);
		return;
	}

	self->target_ent = ent;
	self->target = ent->targetname;

	VectorCopy (ent->s.origin, dest);
	dest[2] += self->viewheight;
	VectorCopy (dest, self->moveinfo.end_origin);

	train_angles(self);

	if(!(self->spawnflags & SF_TRACKTRAIN_NOCONTROL) || !(self->spawnflags & SF_TRACKTRAIN_STARTOFF))
	{
		self->think = tracktrain_think;
		self->think(self);
	}
}

void func_tracktrain_find (edict_t *self)
{
	edict_t *ent;
	edict_t	*next;
	vec3_t	vec;

	if (!self->target)
	{
		gi.dprintf ("tracktrain_find: no target\n");
		return;
	}
	ent = G_PickTarget (self->target);
	if (!ent)
	{
		gi.dprintf ("tracktrain_find: target %s not found\n", self->target);
		return;
	}

	if(ent->speed) {
		self->moveinfo.speed = ent->speed * self->speed;
		self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
		self->moveinfo.next_speed = self->moveinfo.state * self->moveinfo.speed/3;
	}
	
	self->target_ent = ent;

	// Get angles to next path_track
	next = G_PickTarget (ent->target);
	if (!next)
	{
		gi.dprintf ("tracktrain_find: next target %s not found\n", ent->target);
		return;
	}
	VectorSubtract (next->s.origin, ent->s.origin, vec);
	vectoangles2(vec,self->s.angles);

	ent->think = tracktrain_turn;
	ent->enemy = self;
	ent->nextthink = level.time + FRAMETIME;

	VectorCopy (ent->s.origin, self->s.origin);
	self->s.origin[2] += self->viewheight;

	if (self->spawnflags & SF_TRACKTRAIN_OTHERMAP)
	{
		self->solid = SOLID_NOT;
		self->svflags |= SVF_NOCLIENT;
		self->spawnflags |= SF_TRACKTRAIN_DISABLED;
		self->nextthink = 0;
	}
	else
	{
		self->nextthink = level.time + FRAMETIME;
		self->think = tracktrain_next;
	}
	gi.linkentity (self);
}

void tracktrain_use (edict_t *self, edict_t *other, edict_t *activator)
{
	if(self->spawnflags & SF_TRACKTRAIN_STARTOFF)
	{
		if(self->spawnflags & SF_TRACKTRAIN_NOCONTROL)
		{
			self->moveinfo.state = FAST;
			self->moveinfo.next_speed = self->moveinfo.speed;
			if(self->sounds) {
				self->s.sound = gi.soundindex(va("%sspeed%d.wav",self->source,abs(self->moveinfo.state)));
		#ifdef LOOP_SOUND_ATTENUATION
				self->s.attenuation = self->attenuation;
		#endif
			}
			else
				self->s.sound = 0;
		}
		self->spawnflags &= ~SF_TRACKTRAIN_STARTOFF;
		self->think = tracktrain_think;
		self->think(self);
	}
	else
	{
		if(self->owner)
			tracktrain_disengage(self);
		self->moveinfo.state = STOP;
		self->moveinfo.next_speed = 0;
		self->s.sound = 0;
		self->think = NULL;
		self->nextthink = 0;
		self->spawnflags |= SF_TRACKTRAIN_STARTOFF;
	}
}

void SP_func_tracktrain (edict_t *self)
{
	self->class_id = ENTITY_FUNC_TRACKTRAIN;

	self->movetype = MOVETYPE_PUSH;
	self->flags |= FL_TRACKTRAIN;

	VectorClear (self->s.angles);
	self->blocked = tracktrain_blocked;
	if (!self->dmg)
		self->dmg = 100;
	// Wheelbase determines angular velocities
	if(st.distance)
		self->moveinfo.distance = st.distance;
	else
		self->moveinfo.distance = 50;

	// Origin rides by "height" above path_tracks
	if(st.height)
		self->viewheight = st.height;
	else
		self->viewheight = 4;

	// Default mass for collisions:
	self->mass = 2000;

	// Driving position
	if( (VectorLength(self->bleft) == 0) && (VectorLength(self->tright) == 0))
	{
		VectorSet(self->bleft,-8,-8,-8);
		VectorSet(self->tright,8,8,8);
	}
	VectorAdd(self->bleft,self->tright,self->move_origin);
	VectorScale(self->move_origin,0.5,self->move_origin);

	self->solid = SOLID_BSP;
	gi.setmodel (self, self->model);

	// usermodel (for alias model trains) goes on modelindex2
	if (self->usermodel && strlen(self->usermodel)) {
		char	modelname[256];
		// check for "models/" already in path
		if ( !strncmp(self->usermodel, "models/", 7) )
			Com_sprintf(modelname, sizeof(modelname), "%s", self->usermodel);
		else
			Com_sprintf(modelname, sizeof(modelname), "models/%s", self->usermodel);
		self->s.modelindex2 = gi.modelindex (modelname);
	}

	if (!self->speed)
		self->speed = 100;

	self->moveinfo.speed = self->speed;
	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;

	if (self->roll_speed)
	{
		self->roll = self->roll_speed;
		self->roll_speed = 0;
		self->spawnflags |= SF_TRACKTRAIN_ROLLSPEED;
	}

	if (self->health) {
		self->die = tracktrain_die;
		self->takedamage = DAMAGE_YES;
	} else {
		self->die = NULL;
		self->takedamage = DAMAGE_NO;
	}

	self->spawnflags &= ~SF_TRACKTRAIN_DISABLED;	// insurance
	if(self->spawnflags & SF_TRACKTRAIN_NOCONTROL)
		self->spawnflags |= SF_TRACKTRAIN_STARTOFF;

	self->use = tracktrain_use;
	self->moveinfo.current_speed = 0;
	self->moveinfo.state = STOP;
	self->moveinfo.prevstate = STOP+1;	// Assumed, so that initial reverse works correctly
	self->s.sound = 0;
	self->turn_rider = 1;
	VectorClear(self->s.angles);

	if (self->target)
	{
		self->nextthink = level.time + FRAMETIME;
		self->think = func_tracktrain_find;
	}
	else if(!(self->spawnflags & SF_TRACKTRAIN_OTHERMAP))
	{
		gi.dprintf ("func_tracktrain without a target at %s\n", vtos(self->absmin));
		G_FreeEdict(self);
		return;
	}

	if(!self->sounds)
		self->sounds = 1;
	if(self->sounds > 0)
	{
		if(self->sounds > 9)
			self->sounds = 9;
		self->source = gi.TagMalloc(10,TAG_LEVEL);
		Com_sprintf(self->source, 10, "train/%d/",self->sounds);
		gi.soundindex(va("%sspeed1.wav",self->source));
		gi.soundindex(va("%sspeed2.wav",self->source));
		gi.soundindex(va("%sspeed3.wav",self->source));
	}

	if (self->attenuation <= 0)
		self->attenuation = ATTN_IDLE;

	gi.linkentity (self);

}

void find_tracktrain (edict_t *self)
{
	edict_t		*train;
	qboolean	train_found=false;
	vec3_t		forward;

	// This gives game a chance to put player in place before
	// restarting train
	if(!g_edicts[1].linkcount)
	{
		self->nextthink = level.time + FRAMETIME;
		return;
	}

	train = G_Find(NULL,FOFS(targetname),self->targetname);
	while(train && !train_found)
	{
		if(!Q_stricmp(train->classname,"func_tracktrain"))
			train_found = true;
		else
			train = G_Find(train,FOFS(targetname),self->targetname);
	}
	if(!train_found)
	{
		gi.dprintf("find_tracktrain: no matching func_tracktrain with targetname=%s\n",
			self->targetname);
		G_FreeEdict(self);
		return;
	}
	train->solid = SOLID_BSP;
	train->svflags &= ~SVF_NOCLIENT;
	train->spawnflags = self->spawnflags;
	train->spawnflags &= ~(SF_TRACKTRAIN_OTHERMAP | SF_TRACKTRAIN_DISABLED);
	VectorCopy(self->s.origin,train->s.origin);
	VectorCopy(self->s.angles,train->s.angles);
	VectorCopy(self->bleft,   train->bleft);
	VectorCopy(self->tright,  train->tright);
	VectorClear(train->avelocity);
	train->viewheight     = self->viewheight;
	train->speed = self->speed;
	train->moveinfo.distance = self->radius;
	train->moveinfo.accel = train->moveinfo.decel = train->moveinfo.speed = train->speed;
	train->moveinfo.state = train->moveinfo.prevstate = self->count;
	train->sounds = self->sounds;
	if(train->sounds > 0)
	{
		train->source = gi.TagMalloc(10,TAG_LEVEL);
		Com_sprintf(train->source, 10, "train/%d/",train->sounds);
	}
	if(train->moveinfo.state && (train->sounds > 0)) {
		train->s.sound = gi.soundindex(va("%sspeed%d.wav",train->source,abs(train->moveinfo.state)));
#ifdef LOOP_SOUND_ATTENUATION
		train->s.attenuation = train->attenuation;
#endif
	}
	else
		train->s.sound = 0;
	train->moveinfo.next_speed = train->moveinfo.state * train->moveinfo.speed/3;
	// Assume train was already at it's "next_speed" when the level change occurred
	AngleVectors(train->s.angles,forward,NULL,NULL);
	VectorScale(forward,train->moveinfo.next_speed,train->velocity);
	// Force a wait before taking player input
	train->moveinfo.wait = 1;

	if(self->style && (self->style <= game.maxclients) && &g_edicts[self->style].inuse)
	{
		train->owner = &g_edicts[self->style];
		train->owner->vehicle = train;
		if(train->owner->client)
		{
			train->owner->client->vehicle_framenum = level.framenum;
			train->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
		}
		VectorCopy(self->offset,train->offset);
	}
	else
		train->owner = NULL;

	gi.linkentity(train);
	if(self->target)
	{
		vec3_t	dest;

		train->target = self->target;
		train->target_ent = G_Find(NULL,FOFS(targetname),train->target);
		VectorCopy (train->target_ent->s.origin, dest);
		dest[2] += train->viewheight;
		VectorCopy (dest, train->moveinfo.end_origin);
		train_angles(train);
		if(!(train->spawnflags & SF_TRACKTRAIN_NOCONTROL) || !(train->spawnflags & SF_TRACKTRAIN_STARTOFF))
		{
			train->think = tracktrain_think;
			train->think(train);
		}
	}
	else
		gi.dprintf("info_train_start with no target\n");

	G_FreeEdict(self);
}

void SP_info_train_start (edict_t *self)
{
	if(!self->targetname)
	{
		gi.dprintf("crosslevel train with no targetname\n");
		G_FreeEdict(self);
	}
	self->class_id = ENTITY_INFO_TRAIN_START;

	self->think = find_tracktrain;
	self->nextthink = level.time + 1;
}
