/*
	SP_Tramcar
*/
#include "g_local.h"

// defines
#define TRAMCAR_START_ON		1
#define TRAMCAR_TOGGLE			2
#define TRAMCAR_BLOCK_STOPS		4
#define TRAMCAR_LEADER			8

void props_me109_think (gentity_t *ent);
void ExplodePlaneSndFx (gentity_t *self);

void Think_SetupAirplaneWaypoints ( gentity_t *ent );
void truck_cam_think (gentity_t *ent);

// extern calls
extern void Think_SetupTrainTargets (gentity_t *ent );
extern void Reached_BinaryMover( gentity_t *ent );
extern void MatchTeam( gentity_t *teamLeader, int moverState, int time );
extern void SetMoverState( gentity_t *ent, moverState_t moverState, int time );
extern void Blocked_Door( gentity_t *ent, gentity_t *other );
extern void Think_BeginMoving (gentity_t *ent);
extern void propExplosionLarge (gentity_t *ent);

////////////////////////
// truck states
////////////////////////
typedef enum
{
	truck_nosound = 0,
	truck_idle,
	truck_gear1,
	truck_gear2,
	truck_gear3,
	truck_reverse,
	truck_moving,
	truck_breaking,
	truck_bouncy1,
	truck_bouncy2,
	truck_bouncy3
} truck_states1;

/////////////////////////
// truck sounds
/////////////////////////

int	truck_idle_snd;
int	truck_gear1_snd;
int	truck_gear2_snd;
int	truck_gear3_snd;
int	truck_reverse_snd;
int	truck_moving_snd;
int	truck_breaking_snd;
int	truck_bouncy1_snd;
int	truck_bouncy2_snd;
int	truck_bouncy3_snd;
int	truck_sound;

////////////////////////
// plane states
////////////////////////
typedef enum
{
	plane_nosound = 0,
	plane_idle,
	plane_flyby1,
	plane_flyby2,
	plane_loop,
	plane_choke,
	plane_startup
} truck_states2;

///////////////////////////
// aircraft sounds
///////////////////////////

int fploop_snd;
int	fpchoke_snd;
int	fpattack_snd;
int	fpexpdebris_snd;

int fpflyby1_snd;
int	fpflyby2_snd;
int	fpidle_snd;
int	fpstartup_snd;

///////////////////////////
// airplane parts 
///////////////////////////
int	fuse_part;
int	wing_part;
int	tail_part;
int	nose_part;
int	crash_part;

// functions to be added
void InitTramcar (gentity_t *ent)
{
	vec3_t		move;
	float		distance;
	float		light;
	vec3_t		color;
	qboolean	lightSet, colorSet;
	char		*sound;

	// This is here just for show
	// if the "model2" key is set, use a seperate model
	// for drawing, but clip against the brushes
	if ( ent->model2 ) {
		ent->s.modelindex2 = G_ModelIndex( ent->model2 );
	}

	if (!Q_stricmp (ent->classname, "props_me109"))
		ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/vehicles/m109s.md3" );
	
	if (!Q_stricmp (ent->classname, "truck_cam"))
		ent->s.modelindex2 = G_ModelIndex ( "models/mapobjects/vehicles/truck_base.md3" );

	// if the "loopsound" key is set, use a constant looping sound when moving
	if ( G_SpawnString( "noise", "100", &sound ) ) {
		ent->s.loopSound = G_SoundIndex( sound );
	}

	// if the "color" or "light" keys are set, setup constantLight
	lightSet = G_SpawnFloat( "light", "100", &light );
	colorSet = G_SpawnVector( "color", "1 1 1", color );
	if ( lightSet || colorSet ) {
		int		r, g, b, i;

		r = color[0] * 255;
		if ( r > 255 ) {
			r = 255;
		}
		g = color[1] * 255;
		if ( g > 255 ) {
			g = 255;
		}
		b = color[2] * 255;
		if ( b > 255 ) {
			b = 255;
		}
		i = light / 4;
		if ( i > 255 ) {
			i = 255;
		}
		ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
	}

	ent->use = Use_BinaryMover;
//	ent->reached = Reached_BinaryMover;

	ent->moverState = MOVER_POS1;
	ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	ent->s.eType = ET_MOVER;

	VectorCopy (ent->pos1, ent->r.currentOrigin);
	
	trap_LinkEntity (ent);

	ent->s.pos.trType = TR_STATIONARY;
	VectorCopy( ent->pos1, ent->s.pos.trBase );

	// calculate time to reach second position from speed
	VectorSubtract( ent->pos2, ent->pos1, move );
	distance = VectorLength( move );
	if ( ! ent->speed ) {
		ent->speed = 100;
	}
	VectorScale( move, ent->speed, ent->s.pos.trDelta );
	ent->s.pos.trDuration = distance * 1000 / ent->speed;
	if ( ent->s.pos.trDuration <= 0 ) {
		ent->s.pos.trDuration = 1;
	}
}

void Calc_Roll (gentity_t *ent)
{
	gentity_t *target;
	vec3_t	vec;
	vec3_t	forward;
	vec3_t	right;
	float	dot;
	float	dot2;
	vec3_t	tang;

	target = ent->nextTrain;
	
	VectorCopy (ent->r.currentAngles, tang);
	tang[ROLL] = 0;

	AngleVectors (tang, forward, right, NULL);
	VectorSubtract (target->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec);
	VectorNormalize (vec);
	
	dot = DotProduct (vec, forward);
	dot2 = DotProduct (vec, right);
	
	ent->angle = (int) ent->angle;

	if (dot2 > 0)
	{
		if (ent->s.apos.trBase[ROLL] < -(ent->angle*2))
			ent->s.apos.trBase[ROLL] +=2;
		else if (ent->s.apos.trBase[ROLL] > -(ent->angle*2))
			ent->s.apos.trBase[ROLL] -=2;

		if (ent->s.apos.trBase[ROLL] > 90)
			ent->s.apos.trBase[ROLL] = 90;
	}
	else if (dot2 < 0)
	{
		if (ent->s.apos.trBase[ROLL] > -(ent->angle*2))
			ent->s.apos.trBase[ROLL] -=2;
		else if (ent->s.apos.trBase[ROLL] < -(ent->angle*2))
			ent->s.apos.trBase[ROLL] +=2;

		if (ent->s.apos.trBase[ROLL] < -90)
			ent->s.apos.trBase[ROLL] = -90;
	}
	else
		ent->s.apos.trBase[ROLL] = 0;


// G_Printf ("dot: %5.2f dot2: %5.2f\n", dot, dot2);


//	VectorCopy (ent->r.currentAngles, ent->TargetAngles);

	trap_LinkEntity (ent);

	ent->nextthink = level.time + 50;
}


#define MAXCHOICES	8

void GetNextTrack (gentity_t *ent)
{
	gentity_t	*track = NULL;
	gentity_t	*next;
	gentity_t	*choice[MAXCHOICES];
	int			num_choices = 0;
	int			rval;

	next = ent->nextTrain;

	if (!(next->track))
	{
		G_Printf ("NULL track name for %s on %s\n", ent->classname, next->targetname);
		return;
	}

	while (1)
	{
		track = G_Find (track, FOFS (targetname), next->track);

		if (!track)
			break;

		choice[num_choices++] = track;
		
		if (num_choices == MAXCHOICES)
			break;
	}

	if (!num_choices)
	{
		G_Printf ("GetNextTrack didnt find a track\n");
		return;
	}
	
	rval = rand()%num_choices;
	
	ent->nextTrain = NULL;
	ent->target = choice[rval]->targetname;

}

void Reached_Tramcar (gentity_t *ent)
{
	gentity_t		*next;
	float			speed;
	vec3_t			move;
	float			length;

	// copy the apropriate values
	next = ent->nextTrain;
	if ( !next || !next->nextTrain ) {
		return;		// just stop
	}
	
	// Rafael
	if (next->wait == -1 && next->count)
	{
		// G_Printf ("stoped wait = -1 count %i\n",next->count);
		return;
	}

	if (!Q_stricmp (ent->classname, "props_me109"))
	{
		vec3_t vec, angles;
		float	diff;

		if (next->spawnflags & 8) // laps
		{
			next->count --;
		
			if (!next->count)
			{
				next->count = next->count2;

				GetNextTrack (ent);
				Think_SetupAirplaneWaypoints (ent);
				
				next = ent->nextTrain;

				G_Printf ("changed track to %s\n", next->targetname);
			}
			else
				G_Printf ("%s lap %i\n", next->targetname, next->count);
		}
		else if ((next->spawnflags & 1) && !(next->count) && ent->health > 0) // SCRIPT flag
		{
			GetNextTrack (ent);
			Think_SetupAirplaneWaypoints (ent);
		}
		else if ((next->spawnflags & 2) && (ent->spawnflags & 8) && ent->health <= 0 && ent->takedamage) // death path
		{
			ent->takedamage = qfalse;

			GetNextTrack (ent);
			Think_SetupAirplaneWaypoints (ent);
		}
		else if ((next->spawnflags & 4)) // explode the plane
		{
			ExplodePlaneSndFx (ent);

			ent->s.modelindex = crash_part;
			// spawn the wing at the player effect

			ent->nextTrain = NULL;				
			G_UseTargets( next, NULL );
			
			return;
		}

		VectorSubtract (ent->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec);
		vectoangles (vec, angles);


		diff = AngleSubtract (ent->r.currentAngles [YAW], angles[YAW]);
		// diff = AngleSubtract (ent->TargetAngles [YAW], angles[YAW]);
			
		ent->rotate[1] = 1;
		ent->angle = -diff;
		
		//if (angles[YAW] == 0)
		//	ent->s.apos.trDuration = ent->s.pos.trDuration;
		//else
		//	ent->s.apos.trDuration = 1000;

		{
				VectorCopy( next->s.origin, ent->pos1 );
				VectorCopy( next->nextTrain->s.origin, ent->pos2 );

				// if the path_corner has a speed, use that
				if ( next->speed ) {
					speed = next->speed;
				} else {
					// otherwise use the train's speed
					speed = ent->speed;
				}
				if ( speed < 1 ) {
					speed = 1;
				}

				// calculate duration
				VectorSubtract( ent->pos2, ent->pos1, move );
				length = VectorLength( move );

				ent->s.apos.trDuration = length * 1000 / speed;

//testing
// ent->gDuration = ent->s.apos.trDuration;
ent->gDurationBack = ent->gDuration = ent->s.apos.trDuration;
// ent->gDeltaBack = ent->gDelta = 

		}

		VectorClear (ent->s.apos.trDelta);

		SetMoverState( ent, MOVER_1TO2ROTATE, level.time );
		VectorCopy( ent->r.currentAngles, ent->s.apos.trBase );

		trap_LinkEntity (ent);

		ent->think = props_me109_think;
		ent->nextthink = level.time + 50;
	}
	else if (!Q_stricmp (ent->classname, "truck_cam")) 
	{
		G_Printf ("target: %s\n", next->targetname );

		if (next->spawnflags & 2) // END
		{
			ent->s.loopSound = 0; // stop sound
			ent->nextTrain = NULL;
			return;
		}
		else
		{
			vec3_t vec, angles;
			float	diff;

			if (next->spawnflags & 4) // reverse
			{
				ent->props_frame_state = truck_reverse;
				VectorSubtract (ent->r.currentOrigin, ent->nextTrain->nextTrain->s.origin, vec);
			}
			else
			{
				ent->props_frame_state = truck_moving;
				VectorSubtract (ent->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec);
			}

			vectoangles (vec, angles);

			diff = AngleSubtract (ent->r.currentAngles [YAW], angles[YAW]);
				
			ent->rotate[1] = 1;
			ent->angle = -diff;

			if (angles[YAW] == 0)
				ent->s.apos.trDuration = ent->s.pos.trDuration;
			else
				ent->s.apos.trDuration = 1000;

//testing
ent->gDuration = ent->s.pos.trDuration;

			VectorClear (ent->s.apos.trDelta);

			SetMoverState( ent, MOVER_1TO2ROTATE, level.time );
			VectorCopy( ent->r.currentAngles, ent->s.apos.trBase );
				
			trap_LinkEntity (ent);
		}
		
		if (next->wait == -1)
			ent->props_frame_state = truck_idle;

		if (next->count2 == 1)
			ent->props_frame_state = truck_gear1;
		else if (next->count2 == 2)
			ent->props_frame_state = truck_gear2;
		else if (next->count2 == 3)
			ent->props_frame_state = truck_gear3;

		switch (ent->props_frame_state)
		{
		case truck_idle: ent->s.loopSound = truck_idle_snd; break;
		case truck_gear1: ent->s.loopSound = truck_gear1_snd; break;
		case truck_gear2: ent->s.loopSound = truck_gear2_snd; break;
		case truck_gear3: ent->s.loopSound = truck_gear3_snd; break;
		case truck_reverse: ent->s.loopSound = truck_reverse_snd; break;
		case truck_moving: ent->s.loopSound = truck_moving_snd; break;
		case truck_breaking: ent->s.loopSound = truck_breaking_snd; break;
		case truck_bouncy1: ent->s.loopSound = truck_bouncy1_snd; break;
		case truck_bouncy2: ent->s.loopSound = truck_bouncy2_snd; break;
		case truck_bouncy3: ent->s.loopSound = truck_bouncy3_snd; break;
		}

//testing
	ent->s.loopSound = truck_sound;
	ent->think = truck_cam_think;
	ent->nextthink = level.time + (FRAMETIME/2);

	}
	else if (!Q_stricmp (ent->classname, "camera_cam")) 
	{
		
	}
	
	// fire all other targets
	G_UseTargets( next, NULL );

	// set the new trajectory
	ent->nextTrain = next->nextTrain;

	if (next->wait == -1)
		next->count = 1;

	VectorCopy( next->s.origin, ent->pos1 );
	VectorCopy( next->nextTrain->s.origin, ent->pos2 );

	// if the path_corner has a speed, use that
	if ( next->speed ) {
		speed = next->speed;
	} else {
		// otherwise use the train's speed
		speed = ent->speed;
	}
	if ( speed < 1 ) {
		speed = 1;
	}

	// calculate duration
	VectorSubtract( ent->pos2, ent->pos1, move );
	length = VectorLength( move );

	ent->s.pos.trDuration = length * 1000 / speed;

//testing
// ent->gDuration = ent->s.pos.trDuration;
ent->gDurationBack = ent->gDuration = ent->s.pos.trDuration;
// ent->gDeltaBack = ent->gDelta = ;

	// looping sound
	if (next->soundLoop)
		ent->s.loopSound = next->soundLoop;

	// start it going
	SetMoverState( ent, MOVER_1TO2, level.time );

	// if there is a "wait" value on the target, don't start moving yet
	// if ( next->wait )
	if ( next->wait && next->wait != -1)
	{
		ent->nextthink = level.time + next->wait * 1000;
		ent->think = Think_BeginMoving;
		ent->s.pos.trType = TR_STATIONARY;
	}
}

extern void func_explosive_explode(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod);

void Tramcar_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
{
	gentity_t		*slave;

	func_explosive_explode(self, self, inflictor, 0, 0);

	// link all teammembers
	for ( slave = self ; slave ; slave = slave->teamchain ) 
	{
		
		if (slave == self)
			continue;
		
		// slaves need to inherit position
		slave->nextTrain = self->nextTrain;	

		slave->s.pos.trType = self->s.pos.trType;
		slave->s.pos.trTime = self->s.pos.trTime;
		slave->s.pos.trDuration = self->s.pos.trDuration;
		VectorCopy (self->s.pos.trBase, slave->s.pos.trBase);
		VectorCopy (self->s.pos.trDelta, slave->s.pos.trDelta);
		
		slave->s.apos.trType = self->s.apos.trType;
		slave->s.apos.trTime = self->s.apos.trTime;
		slave->s.apos.trDuration = self->s.apos.trDuration;
		VectorCopy (self->s.apos.trBase, slave->s.apos.trBase);
		VectorCopy (self->s.apos.trDelta, slave->s.apos.trDelta);

		slave->think = self->think;
		slave->nextthink = self->nextthink;

		VectorCopy (self->pos1, slave->pos1);
		VectorCopy (self->pos2, slave->pos2);

		slave->speed = self->speed;

		slave->flags &= ~FL_TEAMSLAVE;
		// make it visible

		if (self->use)
			slave->use = self->use;

		trap_LinkEntity (slave);
	}

	self->use = NULL;
		
	self->is_dead = qtrue;

	self->takedamage = qfalse;	

	if (self->nextTrain)
		self->nextTrain = 0;

	self->s.loopSound = 0;

	VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
	VectorCopy( self->r.currentAngles, self->s.apos.trBase );

	self->flags |= FL_TEAMSLAVE;
	trap_UnlinkEntity (self);

}

void TramCarUse (gentity_t *ent, gentity_t *other, gentity_t *activator) 
{
	gentity_t		*next;
	
	if ( level.time >= ent->s.pos.trTime + ent->s.pos.trDuration )
	{

		next = ent->nextTrain;

		if (next->wait == -1 && next->count)
		{
			next->count = 0;		
			//G_Printf ("Moving next->count %i\n", next->count);
		}

		Reached_Tramcar (ent);	

	}
//	else
//		G_Printf ("no can do havent reached yet\n");

}


void Blocked_Tramcar ( gentity_t *ent, gentity_t *other ) {
	// remove anything other than a client
	if ( !other->client ) {
		// except CTF flags!!!!
		if( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) {
			Team_DroppedFlagThink( other );
			return;
		}
		G_TempEntity( other->s.origin, EV_ITEM_POP );
		G_FreeEntity( other );
		return;
	}

	if (other->flags & FL_GODMODE)
	{
		other->flags &= ~FL_GODMODE;
		other->client->ps.stats[STAT_HEALTH] = other->health = 0;
	}

	G_Damage( other, ent, ent, NULL, NULL, 99999, 0, MOD_CRUSH );
	
}

/*QUAKED func_tramcar (0 .5 .8) ? START_ON TOGGLE - LEADER
health value of 999 will designate the piece as non damageable

The leader of the tramcar group must have the leader flag set or
you'll end up with co-planer poly heaven. When the health of the Leader
of the team hits zero it will unlink and the team will become visible

A tramcar is a mover that moves between path_corner target points.
all tramcar parts MUST HAVE AN ORIGIN BRUSH. (this is true for all parts)

The tramcar spawns at the first target it is pointing at.

If you are going to have enemies ride the tramcar it must be placed in its ending
position when you bsp the map you can the start it by targeting the desired path_corner

"model2"	.md3 model to also draw
"speed"		default 100
"dmg"		default	2
"noise"		looping sound to play when the train is in motion
"target"	next path corner
"color"		constantLight color
"light"		constantLight radius

"type" type of debris ("glass", "wood", "metal", "gibs") default is "wood"
"mass" defaults to 75.  This determines how much debris is emitted when it explodes.  You get one large chunk per 100 of mass (up to 8) and one small chunk per 25 of mass (up to 16).  So 800 gives the most.
*/
void SP_func_tramcar (gentity_t *self) {

	int		mass;
	char	*type;
	char	*s;
	char	buffer[MAX_QPATH];

	VectorClear (self->s.angles);

	//if (self->spawnflags & TRAMCAR_BLOCK_STOPS) {
	//	self->damage = 0;
	//	self->s.eFlags |= EF_MOVER_STOP;
	//} 
	//else {
		if (!self->damage) {
			self->damage = 100;
		}
	//}

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

	if ( !self->target ) {
		G_Printf ("func_tramcar without a target at %s\n", vtos(self->r.absmin));
		G_FreeEntity( self );
		return;
	}

	if (self->spawnflags & TRAMCAR_LEADER)
	{
		if (!self->health)
			self->health = 50;

		self->takedamage = qtrue;
		self->die = Tramcar_die;
		
		if (self->health < 999)
			self->isProp = qtrue;
	}
	
	trap_SetBrushModel( self, self->model );

	if(G_SpawnInt("mass", "75", &mass))
		self->count = mass;
	else
		self->count = 75;

	G_SpawnString("type", "wood", &type);
	if (!Q_stricmp(type,"wood"))		self->key = 0;
	else if (!Q_stricmp(type,"glass"))	self->key = 1;
	else if (!Q_stricmp(type,"metal"))	self->key = 2;
	else if (!Q_stricmp(type,"gibs"))	self->key = 3;

	if(G_SpawnString( "noise", "NOSOUND", &s ))
	{
		if(Q_stricmp(s, "nosound")) {
			Q_strncpyz( buffer, s, sizeof(buffer) );
			self->s.dl_intensity = G_SoundIndex(buffer);
		}
	}
	else {
		switch(self->key)
		{
			case 0:	// "wood"
				self->s.dl_intensity = G_SoundIndex("sound/world/boardbreak.wav");
				break;
			case 1:	// "glass"
				self->s.dl_intensity = G_SoundIndex("sound/world/glassbreak.wav");
				break;
			case 2:	// "metal"
				self->s.dl_intensity = G_SoundIndex("sound/world/metalbreak.wav");
				break;
			case 3:	// "gibs"
				self->s.dl_intensity = G_SoundIndex("sound/player/gibsplit1.wav");
				break;
		}
	}

	self->s.density = self->count;	// pass the "mass" to the client
	
	InitTramcar( self );

	self->reached = Reached_Tramcar;
	
	self->nextthink = level.time + (FRAMETIME/2);
	
	self->think = Think_SetupTrainTargets;

	self->blocked = Blocked_Tramcar;

	if (self->spawnflags & TRAMCAR_TOGGLE)
		self->use = TramCarUse;
	
}


////////////////////////////
// me109
////////////////////////////

void plane_AIScript_AlertEntity( gentity_t *ent ) {
	
	// when count reaches 0, the marker is active
	ent->count--;
	
	if (ent->count <= 0)
	{
		ent->count = 0;
	}
}

/*QUAKED plane_waypoint (.5 .3 0) (-8 -8 -8) (8 8 8) SCRIPTS DIE EXPLODE LAPS ATTACK
"count" number of times this waypoint needs to be triggered by an AIScript "alertentity" call before the aircraft changes tracks
"track"	tells it what track to branch off to there can be several track with the same track name
the program will pick one randomly there can be a maximum of eight tracks at any branch

the entity will fire its target when reached
*/
void SP_plane_waypoint ( gentity_t *self ) {
		
	if ( !self->targetname ) {
		G_Printf ("plane_waypoint with no targetname at %s\n", vtos(self->s.origin));
		G_FreeEntity( self );
		return;
	}

	if (self->spawnflags & 1)
		self->AIScript_AlertEntity = plane_AIScript_AlertEntity;

	if (self->count)
		self->count2 = self->count;

	if (self->wait == -1)
		self->count = 1;
}


/*QUAKED props_me109 (.7 .3 .1) (-128 -128 0) (128 128 64) START_ON TOGGLE SPINNINGPROP FIXED_DIE
default health = 1000
*/

void ExplodePlaneSndFx (gentity_t *self)
{
	gentity_t	*temp;
	vec3_t		dir;
	gentity_t	*part;
	int			i;
	vec3_t		start;

	temp = G_Spawn ();

	if (!temp)
		return;

	G_SetOrigin (temp, self->melee->s.pos.trBase);
	G_AddEvent( temp, EV_GLOBAL_SOUND, fpexpdebris_snd );
	temp->think = G_FreeEntity;
	temp->nextthink = level.time + 10000;
	trap_LinkEntity (temp);

	// added this because plane may be parked on runway
	// we may want to add some exotic deaths to parked aircraft
	if (self->nextTrain && self->nextTrain->spawnflags & 4) // explode the plane
	{
		// spawn the wing at the player
		gentity_t	*player;
		vec3_t		vec, ang;
	
		player = AICast_FindEntityForName( "player" );

		if (!player)
			return;

		VectorSubtract (player->s.origin, self->r.currentOrigin, vec);
		vectoangles (vec, ang);
		AngleVectors (ang, dir, NULL, NULL);

		dir[2] = 1;

		VectorCopy (self->r.currentOrigin, start);

		part = fire_flamebarrel ( temp, start, dir );

		if (!part)
		{
			G_Printf ("ExplodePlaneSndFx Failed to spawn part\n"); 
			return;
		}

		part->s.eType = ET_FP_PARTS;
		
		part->s.modelindex = wing_part;
		
		return;
	}

	AngleVectors (self->r.currentAngles, dir, NULL, NULL);

	for (i=0; i<4; i++)
	{
		VectorCopy (self->r.currentOrigin, start);

		start[0] += crandom () * 64;
		start[1] += crandom () * 64;
		start[2] += crandom () * 32;

		part = fire_flamebarrel ( temp, start, dir );
		
		if (!part)
			continue;

		part->s.eType = ET_FP_PARTS;
		
		if (i == 0)
			part->s.modelindex = fuse_part;
		else if (i == 1) 
			part->s.modelindex = wing_part;
		else if (i == 2)
			part->s.modelindex = tail_part;
		else 
			part->s.modelindex = nose_part;
	}
}

void props_me109_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
{
	G_Printf ("dead\n");

	VectorClear (self->rotate);
	VectorSet (self->rotate, 0, 1, 0); //sigh

	if (self->spawnflags & 8) // FIXED_DIE
		return;

	propExplosionLarge (self);
	self->melee->s.loopSound = self->melee->noise_index = 0;
	ExplodePlaneSndFx (self); 
	G_FreeEntity (self);
}

void props_me109_pain (gentity_t *self, gentity_t *attacker, int damage, vec3_t point)
{
	vec3_t	temp;

	G_Printf ("pain: health = %i\n", self->health);

	VectorCopy (self->r.currentOrigin, temp);
	VectorCopy (self->pos3, self->r.currentOrigin);
	Spawn_Shard (self, NULL, 6, 999);
	VectorCopy (temp, self->r.currentOrigin);
		
	VectorClear (self->rotate);
	VectorSet (self->rotate, 0, 1, 0); //sigh
}

void Plane_Fire_Lead (gentity_t *self)
{
	vec3_t	dir, right;
	vec3_t	pos1, pos2;
	vec3_t	position;

	AngleVectors (self->r.currentAngles, dir, right, NULL);
	VectorCopy (self->r.currentOrigin, position);
	VectorMA (position, 64, right, pos1);
	VectorMA (position, -64, right, pos2);

	fire_lead (self, pos1, dir, 12);
	fire_lead (self, pos2, dir, 12);
}

void Plane_Attack (gentity_t *self, qboolean in_PVS)
{
	if (self->nextTrain->spawnflags & 16)
	{
		self->count++;

		if (self->count == 3)
		{
			self->s.density = 8, self->count = 0;
			
			if (in_PVS)
				G_AddEvent( self, EV_GLOBAL_SOUND, fpattack_snd );
			else
				G_AddEvent( self, EV_GENERAL_SOUND, fpattack_snd );
			
			Plane_Fire_Lead (self);
		}
		else
			self->s.density = 7;
	}
	else if (self->spawnflags & 4) // spinning prop
		self->s.density = 7;
	else 
		self->s.density = 0;
}

void props_me109_think (gentity_t *self)
{

	qboolean in_PVS = qfalse;

	{
		gentity_t *player;
	
		player = AICast_FindEntityForName( "player" );

		if (player)
		{
			in_PVS = trap_InPVS (player->r.currentOrigin, self->s.pos.trBase);

			if (in_PVS)
			{
				self->melee->s.eType = ET_GENERAL;

				{
					float len;
					vec3_t	vec;
					vec3_t	forward;
					vec3_t	dir;
					vec3_t	point;

					VectorCopy (player->r.currentOrigin, point);
					VectorSubtract (player->r.currentOrigin, self->r.currentOrigin, vec);
					len = VectorLength (vec);
					vectoangles (vec, dir);
					AngleVectors (dir, forward, NULL, NULL);
					VectorMA (point, len * 0.1, forward, point);

					G_SetOrigin (self->melee, point); 				
				}
			}
			else
			{
				self->melee->s.eType = ET_GENERAL;
			}

			trap_LinkEntity (self->melee);
		}
	}

	Plane_Attack (self, in_PVS);
	
	Calc_Roll (self);

	if (self->health < 250)
	{
		gentity_t *tent;
		vec3_t		point;

		VectorCopy (self->r.currentOrigin, point);
		tent = G_TempEntity (point, EV_SMOKE);
		VectorCopy (point, tent->s.origin);
		tent->s.time = 2000;
		tent->s.time2 = 1000;
		tent->s.density = 4;
		tent->s.angles2[0] = 16;
		tent->s.angles2[1] = 48;
		tent->s.angles2[2] = 10;

		self->props_frame_state = plane_choke;
		self->health --;
	}
	
	if (self->health > 0)
	{
		self->nextthink = level.time + 50;

		if (self->props_frame_state == plane_choke)
		{
			self->melee->s.loopSound = self->melee->noise_index = fpchoke_snd;
		}
		else if (self->props_frame_state == plane_startup)
		{
			self->melee->s.loopSound = self->melee->noise_index = fpstartup_snd;
		}
		else if (self->props_frame_state == plane_idle)
		{
			self->melee->s.loopSound = self->melee->noise_index = fpidle_snd;
		}
		else if (self->props_frame_state == plane_flyby1)
		{
			self->melee->s.loopSound = self->melee->noise_index = fpflyby1_snd;
		}
		else if (self->props_frame_state == plane_flyby2)
		{
			self->melee->s.loopSound = self->melee->noise_index = fpflyby2_snd;
		}
	}
	else
	{
		propExplosionLarge (self);
		self->melee->s.loopSound = self->melee->noise_index = 0;

		ExplodePlaneSndFx (self); 
		G_FreeEntity (self->melee);
		G_FreeEntity (self);

		
	}

}

void Think_SetupAirplaneWaypoints ( gentity_t *ent ) 
{
	gentity_t		*path, *next, *start;

	ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target );
	if ( !ent->nextTrain ) {
		G_Printf( "plane at %s with an unfound target\n",
			vtos(ent->r.absmin) );
		return;
	}

	start = NULL;
	for ( path = ent->nextTrain ; path != start ; path = next ) {
		if ( !start ) {
			start = path;
		}

		if ( !path->target ) {
			G_Printf( "plane at %s without a target\n",
				vtos(path->s.origin) );
			return;
		}

		// find a path_corner among the targets
		// there may also be other targets that get fired when the corner
		// is reached
		next = NULL;
		do {
			next = G_Find( next, FOFS(targetname), path->target );
			if ( !next ) {
				G_Printf( "plane at %s without a target path_corner\n",
					vtos(path->s.origin) );
				return;
			}
		} while ( strcmp( next->classname, "plane_waypoint" ) );

		path->nextTrain = next;
	}

	if (ent->spawnflags & 2) // Toggle
	{
		VectorCopy (ent->nextTrain->s.origin, ent->s.pos.trBase);
		VectorCopy (ent->nextTrain->s.origin, ent->r.currentOrigin);
		trap_LinkEntity (ent);
	}
	else
		Reached_Tramcar (ent);
}


void PlaneUse (gentity_t *ent, gentity_t *other, gentity_t *activator) 
{
	gentity_t		*next;
	
	if ( level.time >= ent->s.pos.trTime + ent->s.pos.trDuration )
	{

		next = ent->nextTrain;

		if (next->wait == -1 && next->count)
		{
			next->count = 0;		
			//G_Printf ("Moving next->count %i\n", next->count);
		}

		Reached_Tramcar (ent);	

	}
//	else
//		G_Printf ("no can do havent reached yet\n");

}


void InitPlaneSpeaker (gentity_t *ent)
{
	gentity_t	*snd;

	snd = G_Spawn();

	snd->noise_index = fploop_snd;

	snd->s.eType = ET_SPEAKER;
	snd->s.eventParm = snd->noise_index;
	snd->s.frame = 0;
	snd->s.clientNum = 0;

	snd->s.loopSound = snd->noise_index;

	snd->r.svFlags |= SVF_BROADCAST;

	VectorCopy( ent->s.origin, snd->s.pos.trBase );

	ent->melee = snd;

	trap_LinkEntity( snd );

}

void SP_props_me109 (gentity_t *ent)
{

	VectorSet (ent->r.mins, -128, -128, -128);
	VectorSet (ent->r.maxs, 128, 128, 128);

	ent->clipmask   = CONTENTS_SOLID;
	ent->r.contents = CONTENTS_SOLID;
	ent->r.svFlags  = SVF_USE_CURRENT_ORIGIN;
	ent->s.eType = ET_MOVER;	

	ent->isProp = qtrue;

	ent->s.modelindex = G_ModelIndex( "models/mapobjects/vehicles/m109.md3" );
	
	if (!ent->health)
		ent->health = 500;

	ent->takedamage = qtrue;

	ent->die = props_me109_die;
	ent->pain = props_me109_pain;

	ent->reached = Reached_Tramcar;
	
	ent->nextthink = level.time + (FRAMETIME/2);
	
	ent->think = Think_SetupAirplaneWaypoints;

	ent->use = PlaneUse;

	if (!(ent->speed))
		ent->speed = 1000;

	G_SetOrigin (ent, ent->s.origin); 
	G_SetAngle (ent, ent->s.angles);

	if (ent->spawnflags & 4)
		ent->s.density = 7;

	trap_LinkEntity (ent);

	fploop_snd = G_SoundIndex ( "sound/fighterplane/fploop.wav" );
	fpchoke_snd = G_SoundIndex ( "sound/fighterplane/fpchoke.wav" );
	fpattack_snd = G_SoundIndex ( "sound/weapons/mg42/37mm.wav" );
	fpexpdebris_snd = G_SoundIndex ( "sound/fighterplane/fpexpdebris.wav" );


	fpflyby1_snd = G_SoundIndex ( "sound/fighterplane/fpflyby1.wav" );
	fpflyby2_snd = G_SoundIndex ( "sound/fighterplane/fpflyby2.wav" );
	fpidle_snd = G_SoundIndex ( "sound/fighterplane/fpidle.wav" );
	fpstartup_snd = G_SoundIndex ( "sound/fighterplane/fpstartup.wav" );


	fuse_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_a.md3" );
	wing_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_b.md3" );
	tail_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_c.md3" );
	nose_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_d.md3" );

	crash_part = G_ModelIndex( "models/mapobjects/vehicles/m109crash.md3" );

	InitPlaneSpeaker (ent);
	
}

/////////////////////////
// TRUCK DRIVE
/////////////////////////

/*QUAKED truck_cam (.7 .3 .1) ? START_ON TOGGLE - -
*/
void truck_cam_touch (gentity_t *self, gentity_t *other, trace_t *trace)
{
	gentity_t *player;
	
	player = AICast_FindEntityForName( "player" );

	if (player && player != other)
	{
	// G_Printf ("other: %s\n", other->aiName);
		return;
	}

	if (!self->nextTrain)
	{
		self->touch = NULL;
		return;
	}

	// lock the player to the moving truck
	{
		vec3_t	point;

		trap_UnlinkEntity (other);
		
		// VectorCopy ( self->r.currentOrigin, other->client->ps.origin );
		VectorCopy (self->r.currentOrigin, point);
		point[2] = other->client->ps.origin[2];
		VectorCopy ( point, other->client->ps.origin );

		// save results of pmove
		BG_PlayerStateToEntityState( &other->client->ps, &other->s, qtrue );
		
		// use the precise origin for linking
		VectorCopy( other->client->ps.origin, other->r.currentOrigin );

		other->client->ps.persistant[PERS_HWEAPON_USE] = 1;

		trap_LinkEntity (other);
	}

}

void truck_cam_think (gentity_t *ent)
{
	ent->nextthink = level.time + (FRAMETIME/2);
}

void SP_truck_cam (gentity_t *self)
{
	int	mass;

	VectorClear (self->s.angles);

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

	if ( !self->target ) {
		G_Printf ("truck_cam without a target at %s\n", vtos(self->r.absmin));
		G_FreeEntity( self );
		return;
	}

	trap_SetBrushModel( self, self->model );

	if(G_SpawnInt("mass", "20", &mass))
		self->count = mass;
	else
		self->count = 20;
	
	InitTramcar( self );

	self->nextthink = level.time + (FRAMETIME/2);
	
	self->think = Think_SetupTrainTargets;

	self->touch = truck_cam_touch;

	self->s.loopSound = 0;
	self->props_frame_state = 0;

	self->clipmask = CONTENTS_SOLID;

	// G_SetOrigin (self, self->s.origin); 
	// G_SetAngle (self, self->s.angles);

	self->reached = Reached_Tramcar;

	self->s.density = 6;

	//start_drive_grind_gears
	truck_sound = G_SoundIndex ( "sound/vehicles/start_drive_grind_gears_01_11k.wav" );
	// truck_sound = G_SoundIndex ( "sound/vehicles/tankmove1.wav" );

	truck_idle_snd = G_SoundIndex ( "sound/vehicles/truckidle.wav" );
	truck_gear1_snd = G_SoundIndex ( "sound/vehicles/truckgear1.wav" );
	truck_gear2_snd = G_SoundIndex ( "sound/vehicles/truckgear2.wav" );
	truck_gear3_snd = G_SoundIndex ( "sound/vehicles/truckgear3.wav" );
	truck_reverse_snd = G_SoundIndex ( "sound/vehicles/truckreverse.wav" );
	truck_moving_snd = G_SoundIndex ( "sound/vehicles/truckmoving.wav" );
	truck_breaking_snd = G_SoundIndex ( "sound/vehicles/truckbreaking.wav" );
	truck_bouncy1_snd = G_SoundIndex ( "sound/vehicles/truckbouncy1.wav" );
	truck_bouncy2_snd = G_SoundIndex ( "sound/vehicles/truckbouncy2.wav" );
	truck_bouncy3_snd = G_SoundIndex ( "sound/vehicles/truckbouncy3.wav" );
}

/////////////////////////
// camera cam
/////////////////////////

/*QUAKED camera_cam (.5 .7 .3) (-8 -8 -8) (8 8 8) ON TRACKING MOVING - 
"track" is the targetname of the entity providing the starting direction use an info_notnull 
*/

void delayOnthink (gentity_t *ent)
{
	if (ent->melee)
		ent->melee->use (ent->melee, NULL, NULL);
}

void Init_Camera (gentity_t *ent)
{
	vec3_t	move;
	float	distance;

	ent->moverState = MOVER_POS1;
	ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	ent->s.eType = ET_MOVER;

	VectorCopy (ent->pos1, ent->r.currentOrigin);
	
	trap_LinkEntity (ent);

	ent->s.pos.trType = TR_STATIONARY;
	VectorCopy( ent->pos1, ent->s.pos.trBase );

	// calculate time to reach second position from speed
	VectorSubtract( ent->pos2, ent->pos1, move );
	distance = VectorLength( move );
	if ( ! ent->speed ) {
		ent->speed = 100;
	}
	VectorScale( move, ent->speed, ent->s.pos.trDelta );
	ent->s.pos.trDuration = distance * 1000 / ent->speed;
	if ( ent->s.pos.trDuration <= 0 ) {
		ent->s.pos.trDuration = 1;
	}
}

void camera_cam_think (gentity_t *ent)
{
	gentity_t *player;

	player = AICast_FindEntityForName( "player" );

	if (!player)
	{
		return;
	}

	if (ent->spawnflags & 2) // tracking
	{
		vec3_t	point;

		trap_UnlinkEntity (player);

		// VectorCopy ( self->r.currentOrigin, other->client->ps.origin );
		VectorCopy (ent->r.currentOrigin, point);
		point[2] = player->client->ps.origin[2];
		VectorCopy ( point, player->client->ps.origin );

		// save results of pmove
		BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
		
		// use the precise origin for linking
		VectorCopy( player->client->ps.origin, player->r.currentOrigin );

		// tracking
		{
			gentity_t	*target = NULL;
			vec3_t		dang;
			vec3_t		vec;

			if (ent->track)
				target = G_Find (NULL, FOFS (targetname), ent->track);

			if (target)
			{
				VectorSubtract (target->r.currentOrigin, ent->r.currentOrigin, vec);
				vectoangles (vec, dang);
				SetClientViewAngle( player, dang );

				VectorCopy (ent->r.currentOrigin, ent->s.pos.trBase);
				VectorCopy (dang, ent->s.apos.trBase);

				trap_LinkEntity (ent);
			}
		}

		trap_LinkEntity (player);
	}

	ent->nextthink = level.time + (FRAMETIME/2); 
}

void camera_cam_use (gentity_t *ent, gentity_t *other, gentity_t *activator) 
{
	gentity_t *player;

	player = AICast_FindEntityForName( "player" );

	if (!player)
	{
		return;
	}

	if (!(ent->spawnflags & 1))
	{
		ent->think = camera_cam_think;
		ent->nextthink = level.time + (FRAMETIME/2); 
		ent->spawnflags |= 1;
		{
			player->client->ps.persistant[PERS_HWEAPON_USE] = 1;
			player->client->ps.viewlocked = 4;
			player->client->ps.viewlocked_entNum = ent->s.number;
		}
	}
	else
	{
		ent->spawnflags &= ~1;
		ent->think = NULL;
		{
			player->client->ps.persistant[PERS_HWEAPON_USE] = 0;
			player->client->ps.viewlocked = 0;
			player->client->ps.viewlocked_entNum = 0;
		}
	}

}	

void camera_cam_firstthink (gentity_t *ent)
{
	gentity_t	*target = NULL;
	vec3_t		dang;
	vec3_t		vec;

	if (ent->track)
		target = G_Find (NULL, FOFS (targetname), ent->track);

	if (target)
	{
		VectorSubtract (target->s.origin, ent->r.currentOrigin, vec);
		vectoangles (vec, dang);
		G_SetAngle (ent, dang);
	}

	if (ent->target)
	{
		ent->nextthink = level.time + (FRAMETIME/2);
		ent->think = Think_SetupTrainTargets;
	}
}

void SP_camera_cam (gentity_t *ent)
{
	Init_Camera (ent);

	ent->r.svFlags  = SVF_USE_CURRENT_ORIGIN;
	ent->s.eType = ET_MOVER;	

	G_SetOrigin (ent, ent->s.origin); 
	G_SetAngle (ent, ent->s.angles);

	ent->reached = Reached_Tramcar;

	ent->nextthink = level.time + (FRAMETIME/2);
	
	ent->think = camera_cam_firstthink;

	ent->use = camera_cam_use;

	if (ent->spawnflags & 1) // On
	{
		gentity_t *delayOn;

		delayOn = G_Spawn ();
		delayOn->think = delayOnthink;
		delayOn->nextthink = level.time + 1000;
		delayOn->melee = ent;
		trap_LinkEntity (delayOn);
	}

}


/*QUAKED screen_fade (.3 .7 .9) (-8 -8 -8) (8 8 8) 
"wait" duration of fade out
"delay" duration of fade in

  1 = 1 sec
  .5 = .5 sec

defaults are .5 sec
*/
void screen_fade_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
	if (ent->spawnflags & 1)
	{
		// fade out
		trap_SetConfigstring( CS_SCREENFADE, va("1 %i %i", level.time + 100, (int) ent->wait) );
		ent->spawnflags &= ~1;
	}
	else 
	{
		// fade in
		trap_SetConfigstring( CS_SCREENFADE, va("0 %i %i", level.time + 100, (int) ent->delay) );
		ent->spawnflags |= 1;
	}

}

void SP_screen_fade (gentity_t *ent)
{
	ent->use = screen_fade_use;

	if (!ent->wait)
		ent->wait = 500;
	if (!ent->delay)
		ent->delay = 500;
	
}

/*QUAKED camera_reset_player (.5 .7 .3) ? 
touched will record the players position and fire off its targets and or cameras

used will reset the player back to his last position
*/

void mark_players_pos (gentity_t *ent, gentity_t *other, trace_t *trace ) 
{
	gentity_t	*player;
	
	player = AICast_FindEntityForName( "player" );

	if (player == other)
	{
		VectorCopy (player->r.currentOrigin, ent->s.origin2);
		VectorCopy (player->r.currentAngles, ent->s.angles2);

		G_UseTargets ( ent, NULL );
	}

}

void reset_players_pos ( gentity_t *ent, gentity_t *other, gentity_t *activator ) 
{

	gentity_t *player;

	player = AICast_FindEntityForName( "player" );

	if (!player)
		return;

	trap_UnlinkEntity (player);
		
	VectorCopy ( ent->s.origin2, player->client->ps.origin );

	// save results of pmove
	BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
		
	// use the precise origin for linking
	VectorCopy( player->client->ps.origin, player->r.currentOrigin );

	SetClientViewAngle( player, ent->s.angles2 );

	player->client->ps.persistant[PERS_HWEAPON_USE] = 0;
	player->client->ps.viewlocked = 0;
	player->client->ps.viewlocked_entNum = 0;
	
	trap_LinkEntity (player);

}

extern void InitTrigger (gentity_t *self);

void SP_camera_reset_player (gentity_t *ent)
{
	InitTrigger (ent);

	ent->r.contents = CONTENTS_TRIGGER;

	ent->touch = mark_players_pos;
	ent->use = reset_players_pos;
	
	trap_LinkEntity (ent);
}
