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

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

/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
Fire an origin based temp entity event to the clients.
"style"		type byte
*/
void Use_Target_Tent( edict_t *ent, edict_t *other, edict_t *activator )
{
	G_SpawnEvent( ent->style, 0, ent->s.origin );
}

void SP_target_temp_entity( edict_t *ent )
{
	ent->use = Use_Target_Tent;
}


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

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


/*QUAKED target_speaker (0 .7 .7) (-8 -8 -8) (8 8 8) LOOPED_ON LOOPED_OFF RELIABLE GLOBAL ACTIVATOR
Normal sounds play each time the target is used.  The reliable flag can be set for crucial voiceovers. Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off. Multiple identical looping sounds will just increase volume without any speed cost.
-------- KEYS --------
noise : path/name of .wav file to play (eg. sounds/world/growl1.wav - see Notes).
volume : 0.0 to 1.0
attenuation : -1 = none, send to whole level, 1 = normal fighting sounds, 2 = idle sound level, 3 = ambient sound level
wait : delay in seconds between each time the sound is played ("random" key must be set - see Notes).
random : random time variance in seconds added or subtracted from "wait" delay ("wait" key must be set - see Notes).
targetname : the activating button or trigger points to this.
notsingle : when set to 1, entity will not spawn in Single Player mode
notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.
notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. 
notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
-------- SPAWNFLAGS --------
LOOPED_ON : &1 sound will loop and initially start on in level (will toggle on/off when triggered).
LOOPED_OFF : &2 sound will loop and initially start off in level (will toggle on/off when triggered).
RELIABLE : &4
GLOBAL : &8 Overrides attenuation setting. Sound will play full volume throughout the level as if it had attenuation -1
ACTIVATOR : &16 sound will play only for the player that activated the target.
-------- NOTES --------
The path portion value of the "noise" key can be replaced by the implicit folder character "*" for triggered sounds that belong to a particular player model. For example, if you want to create a "bottomless pit" in which the player screams and dies when he falls into, you would place a trigger_multiple over the floor of the pit and target a target_speaker with it. Then, you would set the "noise" key to "*falling1.wav". The * character means the current player model's sound folder. So if your current player model is Visor, * = sounds/players/visor, if your current player model is Sarge, * = sounds/players/sarge, etc. This cool feature provides an excellent way to create "player-specific" triggered sounds in your levels.*/


void Use_Target_Speaker( edict_t *ent, edict_t *other, edict_t *activator )
{
	if( ent->spawnflags & 3 )
	{	// looping sound toggles
		if (ent->s.sound)
			ent->s.sound = 0;	// turn it off
		else
			ent->s.sound = ent->noise_index;	// start it
	}
	else
	{	// normal sound
		if( ent->spawnflags & 8 )
			G_Sound( activator, CHAN_VOICE|CHAN_RELIABLE, ent->noise_index, ent->volume, ent->attenuation );
		// use a G_PositionedSound, because this entity won't normally be
		// sent to any clients because it is invisible
		else if( ent->spawnflags & 4 )
			G_PositionedSound( ent->s.origin, ent, CHAN_VOICE|CHAN_RELIABLE, ent->noise_index, ent->volume, ent->attenuation );
		else
			G_PositionedSound( ent->s.origin, ent, CHAN_VOICE, ent->noise_index, ent->volume, ent->attenuation );
	}
}

void SP_target_speaker( edict_t *ent )
{
	char	buffer[MAX_QPATH];

	if( !st.noise )
	{
		if( developer->integer )
			G_Printf( "target_speaker with no noise set at %s\n", vtos(ent->s.origin) );
		return;
	}

	if( !strstr (st.noise, ".wav") )
		Q_snprintfz( buffer, sizeof(buffer), "%s.wav", st.noise );
	else
		Q_strncpyz( buffer, st.noise, sizeof(buffer) );
	ent->noise_index = trap_SoundIndex( buffer );

	if( !ent->volume )
		ent->volume = 1.0;
	
	if( ent->attenuation == -1 || ent->spawnflags & 8 )	// use -1 so 0 defaults to ATTN_NONE
		ent->attenuation = ATTN_NONE;
	else if( !ent->attenuation )
		ent->attenuation = ATTN_NORM;

	// check for prestarted looping sound
	if( ent->spawnflags & 1 )
		ent->s.sound = ent->noise_index;

	ent->use = Use_Target_Speaker;

	// must link the entity so we get areas and clusters so
	// the server can determine who to send updates to
	trap_LinkEntity(ent);
}

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


/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
Spawns an explosion temporary entity when used.

"delay"		wait this long before going off
"dmg"		how much radius damage should be done, defaults to 0
*/
void target_explosion_explode( edict_t *self )
{
	float		save;
	
	G_SpawnEvent( EV_EXPLOSION1, 0, self->s.origin );
	T_RadiusDamage( self, self->activator, NULL, self->dmg, self->dmg, 0, NULL, self->dmg+40, MOD_EXPLOSIVE );

	save = self->delay;
	self->delay = 0;
	G_UseTargets( self, self->activator );
	self->delay = save;
}

void use_target_explosion( edict_t *self, edict_t *other, edict_t *activator )
{
	self->activator = activator;

	if( !self->delay )
	{
		target_explosion_explode( self );
		return;
	}

	self->think = target_explosion_explode;
	self->nextthink = level.timemsec + self->delay * 1000;
}

void SP_target_explosion( edict_t *ent )
{
	ent->use = use_target_explosion;
	ent->r.svflags = SVF_NOCLIENT;
}

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

/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
Creates a particle splash effect when used.

"count"	how many pixels in the splash
"dmg"	if set, does a radius damage at this location when it splashes
		useful for lava/sparks
"color" color in r g b floating point format
*/

void use_target_splash( edict_t *self, edict_t *other, edict_t *activator )
{
	edict_t *event;

	event = G_SpawnEvent( EV_LASER_SPARKS, DirToByte (self->movedir), self->s.origin );
	event->s.eventCount = self->count;

	// default to none
	if( VectorCompare (self->color, vec3_origin) )
		event->s.colorRGBA = 0;
	else 
		event->s.colorRGBA = COLOR_RGBA (
		(int)(self->color[0]*255)&255,
		(int)(self->color[1]*255)&255,
		(int)(self->color[2]*255)&255, 
		255);

	if( self->dmg )
		T_RadiusDamage( self, activator, NULL, self->dmg, self->dmg, 0, NULL, self->dmg+40, MOD_SPLASH );
}

void SP_target_splash( edict_t *self )
{
	self->use = use_target_splash;
	G_SetMovedir( self->s.angles, self->movedir );

	if( !self->count )
		self->count = 32;

	self->r.svflags = SVF_NOCLIENT;
}


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

/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
Set target to the type of entity you want spawned.
Useful for spawning monsters and gibs in the factory levels.

For monsters:
	Set direction to the facing you want it to have.

For gibs:
	Set direction if you want it moving and
	speed how fast it should be moving otherwise it
	will just be dropped
*/

void use_target_spawner( edict_t *self, edict_t *other, edict_t *activator )
{
	edict_t	*ent;

	ent = G_Spawn();
	ent->classname = self->target;
	VectorCopy( self->s.origin, ent->s.origin );
	VectorCopy( self->s.angles, ent->s.angles );
	G_CallSpawn( ent );
	trap_UnlinkEntity( ent );
	KillBox( ent );
	trap_LinkEntity( ent );
	if( self->speed )
		VectorCopy( self->movedir, ent->velocity );
}

void SP_target_spawner( edict_t *self )
{
	self->use = use_target_spawner;
	self->r.svflags = SVF_NOCLIENT;
	if( self->speed )
	{
		G_SetMovedir( self->s.angles, self->movedir );
		VectorScale( self->movedir, self->speed, self->movedir );
	}
}

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

/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
Fires a blaster bolt in the set direction when triggered.

dmg		default is 15
speed	default is 1000
*/

void use_target_blaster( edict_t *self, edict_t *other, edict_t *activator )
{
	int type;

	if( self->spawnflags & 2 )
		type = ET_GENERIC;
	else if( self->spawnflags & 1 )
		type = ET_ELECTRO_WEAK;
	else
		type = ET_BLASTER;

	// FIXME
	//fire_blaster( self, self->s.origin, self->movedir, self->dmg, self->speed, type, MOD_TARGET_BLASTER );
	//W_Fire_Plasma( self, self->s.origin, self->movedir, self->dmg, self->dmg, 70, self->speed, MOD_TARGET_BLASTER );
	G_Sound( self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM );
}

void SP_target_blaster( edict_t *self )
{
	self->use = use_target_blaster;
	G_SetMovedir (self->s.angles, self->movedir);
	self->noise_index = trap_SoundIndex ("sounds/weapons/laser2.wav");

	if (!self->dmg)
		self->dmg = 15;
	if (!self->speed)
		self->speed = 1000;

	self->r.svflags = SVF_NOCLIENT;
}


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

/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit.  It is OK to check multiple triggers.  Message, delay, target, and killtarget also work.
*/
void trigger_crosslevel_trigger_use( edict_t *self, edict_t *other, edict_t *activator )
{
	game.serverflags |= self->spawnflags;
	G_FreeEdict( self );
}

void SP_target_crosslevel_trigger( edict_t *self )
{
	self->r.svflags = SVF_NOCLIENT;
	self->use = trigger_crosslevel_trigger_use;
}

/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
Triggered by a trigger_crosslevel elsewhere within a unit.  If multiple triggers are checked, all must be true.  Delay, target and
killtarget also work.

"delay"		delay before using targets if the trigger has been activated (default 1)
*/
void target_crosslevel_target_think (edict_t *self)
{
	if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
	{
		G_UseTargets (self, self);
		G_FreeEdict (self);
	}
}

void SP_target_crosslevel_target (edict_t *self)
{
	if (! self->delay)
		self->delay = 1;
	self->r.svflags = SVF_NOCLIENT;

	self->think = target_crosslevel_target_think;
	self->nextthink = level.timemsec + self->delay * 1000;
}

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

/*
QUAKED target_laser (0 .5 0) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
When triggered, fires a laser.  You can either set a target or a direction.
-------- KEYS --------
angles: alternate "pitch, yaw, roll" angles method of aiming laser (default 0 0 0).
target : point this to a target_position entity to set the laser's aiming direction.
targetname : the activating trigger points to this.
notsingle : when set to 1, entity will not spawn in Single Player mode
notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.
notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. 
notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
-------- SPAWNFLAGS --------
START_ON : when set, the laser will start on in the game.
RED : 
GREEN : BLUE :
YELLOW :
ORANGE :
FAT :
*/
void target_laser_think( edict_t *self )
{
	edict_t	*ignore;
	vec3_t	start;
	vec3_t	end;
	trace_t	tr;
	vec3_t	point;
	vec3_t	last_movedir;
	int		count;

	// our lifetime has expired
	if( self->delay && (self->wait < level.time) )
	{
		if ( self->r.owner && self->r.owner->use ) {
			self->r.owner->use ( self->r.owner, self, self->activator );
		}

		G_FreeEdict( self );
		return;
	}

	if( self->spawnflags & 0x80000000 )
		count = 8;
	else
		count = 4;

	if( self->enemy )
	{
		VectorCopy( self->movedir, last_movedir );
		VectorMA( self->enemy->r.absmin, 0.5, self->enemy->r.size, point );
		VectorSubtract( point, self->s.origin, self->movedir );
		VectorNormalize( self->movedir );
		if( !VectorCompare(self->movedir, last_movedir) )
			self->spawnflags |= 0x80000000;
	}

	ignore = self;
	VectorCopy( self->s.origin, start );
	VectorMA( start, 2048, self->movedir, end );
	VectorClear( tr.endpos ); // shut up compiler
	while( 1 )
	{
		trap_Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT );
		if( tr.fraction == 1 )
			break;

		// hurt it if we can
		if( (game.edicts[tr.ent].takedamage) && !(game.edicts[tr.ent].flags & FL_IMMUNE_LASER) )
		{
			if( game.edicts[tr.ent].r.client && self->activator->r.client )
			{
				if ( !GS_Gametype_IsTeamBased(game.gametype) || 
					game.edicts[tr.ent].s.team != self->activator->s.team )
					T_Damage( &game.edicts[tr.ent], self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, self->count );
			}
			else
			{
				T_Damage( &game.edicts[tr.ent], self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, self->count );
			}
		}

		// if we hit something that's not a monster or player or is immune to lasers, we're done
		if( !(game.edicts[tr.ent].r.svflags & SVF_MONSTER) && (!game.edicts[tr.ent].r.client) )
		{
			if( self->spawnflags & 0x80000000 )
			{
				edict_t *event;

				self->spawnflags &= ~0x80000000;

				event = G_SpawnEvent( EV_LASER_SPARKS, DirToByte (tr.plane.normal), tr.endpos );
				event->s.eventCount = count;
				event->s.colorRGBA = self->s.colorRGBA;
			}
			break;
		}

		ignore = &game.edicts[tr.ent];
		VectorCopy( tr.endpos, start );
	}

	VectorCopy( tr.endpos, self->s.origin2 );
	self->nextthink = level.timemsec + game.framemsec;
}

void target_laser_on( edict_t *self )
{
	if( !self->activator )
		self->activator = self;
	self->spawnflags |= 0x80000001;
	self->r.svflags &= ~SVF_NOCLIENT;
	self->wait = level.time + self->delay;
	target_laser_think( self );
}

void target_laser_off( edict_t *self )
{
	self->spawnflags &= ~1;
	self->r.svflags |= SVF_NOCLIENT;
	self->nextthink = 0;
}

void target_laser_use( edict_t *self, edict_t *other, edict_t *activator )
{
	self->activator = activator;
	if( self->spawnflags & 1 )
		target_laser_off( self );
	else
		target_laser_on( self );
}

void target_laser_start( edict_t *self )
{
	edict_t *ent;

	self->movetype = MOVETYPE_NONE;
	self->r.solid = SOLID_NOT;
	self->s.type = ET_BEAM;
	self->s.modelindex = 1;			// must be non-zero
	self->r.svflags = SVF_FORCEOLDORIGIN;

	// set the beam diameter
	if( self->spawnflags & 64 )
		self->s.frame = 16;
	else
		self->s.frame = 4;

	// set the color
	if (self->spawnflags & 2)
		self->s.colorRGBA = COLOR_RGBA(220, 0, 0, 76);
	else if (self->spawnflags & 4)
		self->s.colorRGBA = COLOR_RGBA(0, 220, 0, 76);
	else if (self->spawnflags & 8)
		self->s.colorRGBA = COLOR_RGBA(0, 0, 220, 76);
	else if (self->spawnflags & 16)
		self->s.colorRGBA = COLOR_RGBA(220, 220, 0, 76);
	else if (self->spawnflags & 32)
		self->s.colorRGBA = COLOR_RGBA(255, 255, 0, 76);

	if( !self->enemy )
	{
		if( self->target )
		{
			ent = G_Find( NULL, FOFS(targetname), self->target );
			if( !ent )
				if( developer->integer )
					G_Printf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
			self->enemy = ent;
		}
		else
		{
			G_SetMovedir( self->s.angles, self->movedir );
		}
	}
	self->use = target_laser_use;
	self->think = target_laser_think;

	if( !self->dmg )
		self->dmg = 1;

	VectorSet( self->r.mins, -8, -8, -8 );
	VectorSet( self->r.maxs, 8, 8, 8 );
	trap_LinkEntity( self );

	if( self->spawnflags & 1 )
		target_laser_on( self );
	else
		target_laser_off( self );
}

void SP_target_laser( edict_t *self )
{
	// let everything else get spawned before we start firing
	self->think = target_laser_start;
	self->nextthink = level.timemsec + 1000;
	self->count = MOD_TARGET_LASER;
}

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

/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
speed		How many seconds the ramping will take
message		two letters; starting lightlevel and ending lightlevel
*/

void target_lightramp_think (edict_t *self)
{
	char	style[2];

	style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
	style[1] = 0;
	trap_ConfigString (CS_LIGHTS+self->enemy->style, style);

	if ((level.time - self->timestamp) < self->speed)
	{
		self->nextthink = level.timemsec + game.framemsec;
	}
	else if (self->spawnflags & 1)
	{
		char	temp;

		temp = self->movedir[0];
		self->movedir[0] = self->movedir[1];
		self->movedir[1] = temp;
		self->movedir[2] *= -1;
	}
}

void target_lightramp_use( edict_t *self, edict_t *other, edict_t *activator )
{
	if( !self->enemy )
	{
		edict_t		*e;

		// check all the targets
		e = NULL;
		while( 1 )
		{
			e = G_Find( e, FOFS(targetname), self->target );
			if( !e )
				break;
			if( Q_stricmp(e->classname, "light") )
			{
				if( developer->integer )
				{
					G_Printf( "%s at %s ", self->classname, vtos(self->s.origin) );
					G_Printf( "target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin) );
				}
			}
			else
			{
				self->enemy = e;
			}
		}

		if( !self->enemy )
		{
			if( developer->integer )
				G_Printf( "%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin) );
			G_FreeEdict( self );
			return;
		}
	}

	self->timestamp = level.time;
	target_lightramp_think( self );
}

void SP_target_lightramp( edict_t *self )
{
	if( !self->message || strlen(self->message) != 2 ||
		self->message[0] < 'a' || self->message[0] > 'z' ||
		self->message[1] < 'a' || self->message[1] > 'z' ||
		self->message[0] == self->message[1] )
	{
		if( developer->integer )
			G_Printf( "target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin) );
		G_FreeEdict( self );
		return;
	}
/*
	if( deathmatch->value )
	{
		G_FreeEdict( self );
		return;
	}
*/
	if( !self->target )
	{
		if( developer->integer )
			G_Printf( "%s with no target at %s\n", self->classname, vtos(self->s.origin) );
		G_FreeEdict( self );
		return;
	}

	self->r.svflags |= SVF_NOCLIENT;
	self->use = target_lightramp_use;
	self->think = target_lightramp_think;

	self->movedir[0] = self->message[0] - 'a';
	self->movedir[1] = self->message[1] - 'a';
	self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
}

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

/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
When triggered, this initiates a level-wide earthquake.
All players and monsters are affected.
"speed"		severity of the quake (default:200)
"count"		duration of the quake (default:5)
*/

void target_earthquake_think (edict_t *self)
{
	int		i;
	edict_t	*e;

	if (self->last_move_time < level.time)
	{
		G_Sound (self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE);
		self->last_move_time = level.time + 0.5;
	}

	for (i=1, e=game.edicts+i; i < game.numentities; i++,e++)
	{
		if (!e->r.inuse)
			continue;
		if (!e->r.client)
			continue;
		if (!e->groundentity)
			continue;

		e->groundentity = NULL;
		e->velocity[0] += crandom()* 150;
		e->velocity[1] += crandom()* 150;
		e->velocity[2] = self->speed * (100.0 / e->mass);
	}

	if (level.time < self->timestamp)
		self->nextthink = level.timemsec + game.framemsec;
}

void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator)
{
	self->timestamp = level.time + self->count;
	self->nextthink = level.timemsec + game.framemsec;
	self->activator = activator;
	self->last_move_time = 0;
}

void SP_target_earthquake (edict_t *self)
{
	if (!self->targetname)
		if (developer->integer)
			G_Printf ("untargeted %s at %s\n", self->classname, vtos(self->s.origin));

	if (!self->count)
		self->count = 5;

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

	self->r.svflags |= SVF_NOCLIENT;
	self->think = target_earthquake_think;
	self->use = target_earthquake_use;

	self->noise_index = trap_SoundIndex ("sounds/world/quake.wav");
}

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

/*QUAKED target_character (0 0 1) ?
used with target_string (must be on same "team")
"count" is position in the string (starts at 1)
*/

void SP_target_character (edict_t *self)
{
	self->movetype = MOVETYPE_PUSH;
	trap_SetBrushModel (self, self->model);
	self->r.solid = SOLID_BSP;
	self->s.frame = 12;
	trap_LinkEntity (self);
	return;
}

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

/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
*/

void target_string_use( edict_t *self, edict_t *other, edict_t *activator )
{
	edict_t *e;
	int		n, l;
	char	c;

	l = strlen(self->message);
	for( e = self->teammaster; e; e = e->teamchain )
	{
		if( !e->count )
			continue;
		n = e->count - 1;
		if( n > l )
		{
			e->s.frame = 12;
			continue;
		}

		c = self->message[n];
		if( c >= '0' && c <= '9' )
			e->s.frame = c - '0';
		else if (c == '-')
			e->s.frame = 10;
		else if (c == ':')
			e->s.frame = 11;
		else
			e->s.frame = 12;
	}
}

void SP_target_string( edict_t *self )
{
	if( !self->message )
		self->message = "";
	self->use = target_string_use;
}

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

/*QUAKED target_position (0 .5 0) (-8 -8 -8) (8 8 8)
Aiming target for entities like light, misc_portal_camera and trigger_push (jump pads) in particular.
-------- KEYS --------
targetname : the entity that requires an aiming direction points to this.
notsingle : when set to 1, entity will not spawn in Single Player mode
notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.
notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. 
notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
-------- NOTES --------
To make a jump pad, place this entity at the highest point of the jump and target it with a trigger_push entity.*/

void SP_target_position (edict_t *self) 
{
	self->r.svflags |= SVF_NOCLIENT;
}


/*QUAKED target_location (0 .5 0) (-8 -8 -8) (8 8 8)
Location marker used by bots and players for team orders and team chat in the course of Teamplay games. The closest target_location in sight is used for the location. If none is in sight, the closest in distance is used.
-------- KEYS --------
message :  name of the location (text string). Displayed in parentheses in front of all team chat and order messages.
count : color of the location text displayed in parentheses during team chat. Set to 0-7 for color.
   0 : white (default)
   1 : red
   2 : green
   3 : yellow
   4 : blue
   5 : cyan
   6 : magenta
   7 : white
notsingle : when set to 1, entity will not spawn in Single Player mode
notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.
notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. 
notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
*/
void SP_target_location( edict_t *self )
{
	self->r.svflags |= SVF_NOCLIENT;

	if ( self->count ) {
		if ( self->count < 0 ) {
			self->count = 0;
		} else if ( self->count > 7 ) {
			self->count = 7;
		}
	}
}



/*QUAKED target_print (0 .5 0) (-8 -8 -8) (8 8 8) SAMETEAM OTHERTEAM PRIVATE
This will print a message on the center of the screen when triggered. By default, all the clients will see the message.
-------- KEYS --------
message : text string to print on screen.
targetname : the activating trigger points to this.
notsingle : when set to 1, entity will not spawn in Single Player mode
notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.
notduel : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. 
notctf : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes. (jal: todo)
-------- SPAWNFLAGS --------
SAMETEAM : &1 only players in activator's team will see the message.
OTHERTEAM : &2 only players in other than activator's team will see the message.
PRIVATE : &4 only the player that activates the target will see the message.*/

void SP_target_print_use( edict_t *self, edict_t *other, edict_t *activator )
{
	int n;
	edict_t *player;

	if( activator->r.client && (self->spawnflags & 4) ) 
	{
		G_CenterPrintMsg( activator, self->message );
		return;
	}

	// print to team
	if ( activator->r.client && self->spawnflags & 3 ) {
		edict_t *e;
		for( e = game.edicts + 1; PLAYERNUM(e) < game.maxclients; e++ ) {
			if( e->r.inuse && e->s.team ) {
				if( self->spawnflags & 1 && e->s.team == activator->s.team )
					G_CenterPrintMsg( e, self->message );
				if( self->spawnflags & 2 && e->s.team != activator->s.team )
					G_CenterPrintMsg( e, self->message );
			}
		}
		return;
	}
		
	for (n = 1; n <= game.maxclients; n++)
	{
		player = &game.edicts[n];
		if (!player->r.inuse)
			continue;
		
		G_CenterPrintMsg( player, self->message );
	}
}

void SP_target_print (edict_t *self)
{
	if ( !self->message ) {
		G_FreeEdict ( self );
		return;
	}

	self->use = SP_target_print_use;
	// do nothing
}


// JALFIXME: We have trigger_relay (and I already commented it should be a target), Q3 has
// this target_relay. IMO we should do the move into target_relay too.

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

/*QUAKED target_relay (0 .7 .7) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM
This can only be activated by other triggers which will cause it in turn to activate its own targets.
-------- KEYS --------
targetname : activating trigger points to this.
target : this points to entities to activate when this entity is triggered.
notfree : when set to 1, entity will not spawn in "Free for all" and "Tournament" modes.
notteam : when set to 1, entity will not spawn in "Teamplay" and "CTF" modes.
notsingle : when set to 1, entity will not spawn in Single Player mode (bot play mode).
-------- SPAWNFLAGS --------
RED_ONLY : only red team players can activate trigger.
BLUE_ONLY : only red team players can activate trigger.
RANDOM : one one of the targeted entities will be triggered at random.*/


/************************************************************************/
/* RACES ENTITIES (For a minimal DEFRAG support)                        */
/************************************************************************/

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

/*QUAKED target_startTimer (1 0 0) (-8 -8 -8) (8 8 8)
Timer Start
*/
void target_starttimer_use( edict_t *self, edict_t *other, edict_t *activator )
{
	if(activator->r.client->resp.race_in_race==qfalse)
	{
		// dispatch start of race event to client
		G_Match_AutorecordCommand(qtrue,qfalse);
	}

	// restart the timer
	activator->r.client->resp.race_start_time=level.timemsec;
	activator->r.client->resp.race_in_race = qtrue;

	// checkpoint
	activator->r.client->resp.race_current_checkpoint=0;
	memset(activator->r.client->resp.race_checkpoint_used,qfalse,sizeof(qboolean)*MAX_RACE_CHECKPOINT);
}

void SP_target_starttimer( edict_t *self )
{
	if( game.gametype != GAMETYPE_RACE )
	{
		G_FreeEdict( self );
		return;
	}
	self->use = target_starttimer_use;
}

//=============================================================================
extern void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mod);

void target_stoptimer_respawn( edict_t *self)
{
	// we should only kill the player if he did not already killed himself
	if(self->activator->deathtimestamp<level.timemsec-5000)
		Killed(self->activator, self->activator, world, 100000, vec3_origin, MOD_SUICIDE );
	trap_UnlinkEntity( self );
}

/*QUAKED target_stopTimer (1 0 0) (-8 -8 -8) (8 8 8)
Timer Stop
-------- KEYS --------
target: stopTimer triggers its targets when a best time occurs.
*/
void target_stoptimer_use( edict_t *self, edict_t *other, edict_t *activator )
{
	float delta;
	unsigned int min,sec,milli;
	unsigned int dmin,dsec,dmilli;
	int type;
	edict_t	*ent;

	if( activator->r.client->resp.race_in_race )
	{
		activator->r.client->resp.race_last_lap_time=level.timemsec-activator->r.client->resp.race_start_time;	

		// convert time into MM:SS:mm
		milli=activator->r.client->resp.race_last_lap_time/100.0f;
		min=milli/600;
		milli-=min*600;
		sec=milli/10;
		milli-=sec*10; // only one digit for deciseconds

		// in case of no record ?
		type=0;
		delta=fabs(activator->r.client->teamchange.race_best_lap_time-activator->r.client->resp.race_last_lap_time);

		if( (activator->r.client->teamchange.race_best_lap_time > activator->r.client->resp.race_last_lap_time) || (activator->r.client->teamchange.race_best_lap_time == 0.0) )
		{
			// best personal
			delta=fabs(activator->r.client->teamchange.race_best_lap_time-activator->r.client->resp.race_last_lap_time);
			activator->r.client->teamchange.race_best_lap_time=activator->r.client->resp.race_last_lap_time;
			type=1;
		}
		activator->r.client->resp.race_in_race = qfalse;
	
		if( (game.besttime > activator->r.client->resp.race_last_lap_time) || (game.besttime == 0.0) )
		{
			// best on server
			delta=fabs(game.besttime-activator->r.client->resp.race_last_lap_time);
			game.besttime = activator->r.client->resp.race_last_lap_time;
			type=2;
		}
		
		// extract SS:mm from delta
		dmilli=delta/100.0f;
		dmin=dmilli/600;
		dmilli-=dmin*600;
		dsec=dmilli/10;
		dmilli-=dsec*10; // only one digit for deci seconds

		switch(type) 
		{
		case 0: // standard time
			G_CenterPrintMsg( activator, 
				va("%sRace finished: %02d:%02d.%1d\nTry Again\n%s+%02d:%02d.%1d",
												S_COLOR_WHITE,min,sec,milli,
												S_COLOR_RED, dmin,dsec,dmilli));
			break;
		case 1: // best personal lap
			G_CenterPrintMsg( activator, 
				va("%sRace finished: %02d:%02d.%1d\nPersonal Record\n%s-%02d:%02d.%1d",
												S_COLOR_WHITE,min,sec,milli,
												S_COLOR_GREEN,dmin,dsec,dmilli));
			break;
		case 2: // server best time
			G_CenterPrintMsg( activator, 
				va("%sRace finished: %02d:%02d.%1d\nServer Record\n%s-%02d:%02d.%1d",
				                S_COLOR_WHITE,min,sec,milli,
				                S_COLOR_GREEN,dmin,dsec,dmilli));
			break;
		}
		
		// dispatch end of race event to client
		G_Match_AutorecordCommand(qfalse,qfalse);

		// wsw : pb kill the player at the end of the race after 5 sec
		ent=G_Spawn( );
		ent->nextthink= level.timemsec + 5000;
		ent->think=target_stoptimer_respawn;
		ent->activator=activator;  // the player to kill
		trap_LinkEntity( ent );
	}
}

void SP_target_stoptimer( edict_t *self )
{
	if( game.gametype != GAMETYPE_RACE )
	{
		G_FreeEdict( self );
		return;
	}	
	self->use = target_stoptimer_use;
}

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

/*QUAKED target_checkpoint (1 0 0) (-8 -8 -8) (8 8 8)
Checkpoint
*/
void target_checkpoint_use( edict_t *self, edict_t *other, edict_t *activator )
{
	// player just pass a checkpoint
	unsigned int min,sec,milli;
	unsigned int dmin,dsec,dmilli;
	float time;
	float previous_time=0.0f;
	float delta;
	char color;
	char c;
	unsigned int current_checkpoint;

	if(activator->r.client->resp.race_in_race==qtrue)
	{
		current_checkpoint=activator->r.client->resp.race_current_checkpoint;
		time=level.timemsec-activator->r.client->resp.race_start_time;	

		// store time in checkpoint structure
		if(current_checkpoint<MAX_RACE_CHECKPOINT)
		{
			// already used ?
			if(activator->r.client->resp.race_checkpoint_used[current_checkpoint]==qtrue)
				return;
			
			activator->r.client->resp.race_checkpoint_used[current_checkpoint]=qtrue;

			previous_time=activator->r.client->teamchange.race_checkpoint_time[current_checkpoint];
			activator->r.client->teamchange.race_checkpoint_time[current_checkpoint]=time;
			activator->r.client->resp.race_current_checkpoint++;

			if(previous_time==0.0f)
			{
				// first time on this checkpoint
				color=COLOR_GREEN;
				c='-';
				delta=time;
			}
			else if(time<previous_time)
			{
				color=COLOR_GREEN;
				c='-';
				delta=previous_time-time;
			}
			else
			{
				color=COLOR_RED;
				c='+';
				delta=time-previous_time;
			}

			// convert time into MM:SS:mm
			milli=time/100.0f;
			min=milli/600;
			milli-=min*600;
			sec=milli/10;
			milli-=sec*10; // only one digit for deciseconds

			// extract SS:mm from delta
			dmilli=delta/100.0f;
			dmin=dmilli/600;
			dmilli-=dmin*600;
			dsec=dmilli/10;
			dmilli-=dsec*10; // only one digit

			G_CenterPrintMsg( activator, va("%sCurrent: %02d:%02d.%1d\n^%c%c%02d:%02d.%1d",
										S_COLOR_WHITE,min,sec,milli,
										color,c,dmin,dsec,dmilli));
		}
	}
}

void SP_target_checkpoint( edict_t *self )
{
	if( game.gametype != GAMETYPE_RACE )
	{
		G_FreeEdict( self );
		return;
	}
	self->use = target_checkpoint_use;
	level.nbcheckpoint++;
}

//target_give wait classname weapon_xxx
void target_give_use( edict_t *self, edict_t *other, edict_t *activator )
{
	edict_t *give;

	give=NULL;

	// more than one item can be given
	while((give=G_Find(give, FOFS(targetname), self->target))!=NULL)
	{
		// sanity
		if(!give->item)
			continue;

		if( !(give->item->flags & ITFLAG_PICKABLE) || (give->item->type & IT_FLAG) ) {
			continue;
		}
		G_PickupItem(give, activator);
		SetRespawn(give, 0.001f);
 
		// give it to player
		//Touch_Item(give,activator,NULL,0);
		//item=G_ItemForEntity( give );
	}
}

void SP_target_give( edict_t *self )
{
	self->r.svflags |= SVF_NOCLIENT;
	self->use = target_give_use;
}

