//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
// $Log$
// Revision 1.33  2005/02/15 16:33:39  makro
// Tons of updates (entity tree attachment system, UI vectors)
//
// Revision 1.32  2004/03/13 01:17:32  makro
// no message
//
// Revision 1.31  2004/03/12 15:56:46  makro
// no message
//
// Revision 1.30  2003/09/18 00:05:06  makro
// Lens flares. Opendoor trigger_multiple fixes
//
// Revision 1.29  2003/09/16 23:25:32  makro
// trigger_multiple - new spawnflag, 3 new keys
//
// Revision 1.28  2003/09/08 19:19:20  makro
// New code for respawning entities in TP
//
// Revision 1.27  2003/07/30 16:05:46  makro
// no message
//
// Revision 1.26  2003/04/26 22:33:07  jbravo
// Wratted all calls to G_FreeEnt() to avoid crashing and provide debugging
//
// Revision 1.25  2003/03/22 20:29:26  jbravo
// wrapping linkent and unlinkent calls
//
// Revision 1.24  2002/07/22 06:32:27  niceass
// cleaned up the powerup code
//
// Revision 1.23  2002/06/16 20:06:14  jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.22  2002/06/06 18:08:01  makro
// Removed pathtarget code for trigger_pushes for now
//
// Revision 1.21  2002/06/04 21:35:40  makro
// Updated trigger_push code
//
// Revision 1.20  2002/06/04 20:53:19  makro
// Trigger_pushes can now be teamed up
//
// Revision 1.19  2002/05/30 21:18:28  makro
// Bots should reload/bandage when roaming around
// Added "pathtarget" key to all the entities
//
// Revision 1.18  2002/05/29 13:49:26  makro
// Elevators/doors
//
// Revision 1.17  2002/05/23 18:37:50  makro
// Bots should crouch more often when they attack with a SSG
// Made this depend on skill. Also, elevator stuff
//
// Revision 1.16  2002/05/23 15:55:25  makro
// Elevators
//
// Revision 1.15  2002/05/22 04:19:45  blaze
// Sound entity changes as per Sze
//
// Revision 1.14  2002/05/18 14:52:16  makro
// Bot stuff. Other stuff. Just... stuff :p
//
// Revision 1.13  2002/05/16 06:57:54  makro
// Doors with health (again !), bot-only trigger_pushes
//
// Revision 1.12  2002/05/11 22:01:41  makro
// Trigger_hurt
//
// Revision 1.11  2002/05/11 17:59:49  makro
// Trigger_hurt update
//
// Revision 1.10  2002/05/11 00:38:47  blaze
// trigger_push and target_push default to no noise when the noise flag is not set.
//
// Revision 1.9  2002/05/05 15:18:03  makro
// Fixed some crash bugs. Bot stuff. Triggerable func_statics.
// Made flags only spawn in CTF mode
//
// Revision 1.8  2002/03/18 06:20:39  blaze
// noise tag will play sounds for trigger_push and target_push
//
// Revision 1.7  2002/01/11 19:48:30  jbravo
// Formatted the source in non DOS format.
//
// Revision 1.6  2001/12/31 16:28:42  jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
// Copyright (C) 1999-2000 Id Software, Inc.
//
#include "g_local.h"

void InitTrigger(gentity_t * self)
{
	if (!VectorCompare(self->s.angles, vec3_origin))
		G_SetMovedir(self->s.angles, self->movedir);

	trap_SetBrushModel(self, self->model);
	self->r.contents = CONTENTS_TRIGGER;	// replaces the -1 from trap_SetBrushModel
	self->r.svFlags = SVF_NOCLIENT;
}

/*QUAKED trigger_multiple (.5 .5 .5) ? DOOR
"wait" : Seconds between triggerings, 0.5 default, -1 = one time only.
"random"	wait variance, default is 0
Variable sized repeatable trigger.  Must be targeted at one or more entities.
so, the basic time between firing is a random time between
(wait - random) and (wait + random)
*/

#define SF_TRIGGER_MULTIPLE_RED		1
#define SF_TRIGGER_MULTIPLE_BLUE	2
#define SF_TRIGGER_MULTIPLE_DOOR	4

// the wait time has passed, so set back up for another activation
void multi_wait(gentity_t * ent)
{
	ent->nextthink = 0;
}

// the trigger was just activated
// ent->activator should be set to the activator so it can be held through a delay
// so wait for the delay time before firing
void multi_trigger(gentity_t * ent, gentity_t * activator)
{
	ent->activator = activator;
	if (ent->nextthink) {
		return;		// can't retrigger until the wait is over
	}
	//Makro - inactive trigger ?
	if (ent->inactive) {
		//note - since the trigger is not sent to the clients, we cannot send the event
		//either, so we'll just play the sound locally
		if (ent->soundInactive)
			G_AddEvent(ent->activator, EV_GENERAL_SOUND, ent->soundInactive);
		if (ent->targetInactive)
			G_UseEntities(ent->activator, ent->targetInactive, activator);
		ent->think = multi_wait;
		ent->nextthink = level.time + (ent->wait + ent->random * crandom()) * 1000;
		return;
	}
	//Makro - added check; Q3 crashed in archives when playing
	//with .dll's and shooting one of the barrels
	if (activator != NULL) {
		if (activator->client) {
			if ((ent->spawnflags & SF_TRIGGER_MULTIPLE_RED) && activator->client->sess.sessionTeam != TEAM_RED) {
				return;
			}
			if ((ent->spawnflags & SF_TRIGGER_MULTIPLE_BLUE) && activator->client->sess.sessionTeam != TEAM_BLUE) {
				return;
			}
		}
	}

	if (ent->sound1to2)
		G_AddEvent(ent->activator, EV_GENERAL_SOUND, ent->sound1to2);
	G_UseTargets(ent, ent->activator);

	if (ent->wait > 0) {
		ent->think = multi_wait;
		ent->nextthink = level.time + (ent->wait + ent->random * crandom()) * 1000;
	} else {
		// we can't just remove (self) here, because this is a touch function
		// called while looping through area links...
		//Makro - we no longer remove the entity, we just make it inactive
		//otherwise, we would only be able to use it during the first
		//round in TP games
		/*
		ent->touch = 0;
		ent->nextthink = level.time + FRAMETIME;
		ent->think = G_FreeEntity;
		*/
		//On second thought, now that I've added those soundInactive/targetInactive keys
		//I think just setting touch to 0 will do
		//ent->inactive = 1;
		ent->touch = 0;
	}
}

void Use_Multi(gentity_t * ent, gentity_t * other, gentity_t * activator)
{
	multi_trigger(ent, activator);
}

void Touch_Multi(gentity_t * self, gentity_t * other, trace_t * trace)
{
	if (!other || !other->client) {
		return;
	}
	if (self->spawnflags & SF_TRIGGER_MULTIPLE_DOOR) {
		if (!other->client->openDoor || other->client->openDoorTime <= self->timestamp)
			return;
		self->timestamp = other->client->openDoorTime;
	}
	multi_trigger(self, other);
}

void Reset_Multi(gentity_t *ent)
{
	ent->inactive = ent->unbreakable;
	ent->think = 0;
	ent->nextthink = 0;
	ent->touch = Touch_Multi;
}

void SP_trigger_multiple(gentity_t * ent)
{
	char *s;

	G_SpawnFloat("wait", "0.5", &ent->wait);
	G_SpawnFloat("random", "0", &ent->random);

	if (ent->random >= ent->wait && ent->wait >= 0) {
		ent->random = ent->wait - FRAMETIME;
		G_Printf("trigger_multiple has random >= wait\n");
	}
	//Makro - added
	if (G_SpawnString("soundInactive", "", &s))
		ent->soundInactive = G_SoundIndex(s);
	if (G_SpawnString("noise", "", &s))
		ent->sound1to2 = G_SoundIndex(s);
	else if (G_SpawnString("sound", "", &s))
		ent->sound1to2 = G_SoundIndex(s);


	ent->touch = Touch_Multi;
	ent->use = Use_Multi;
	ent->unbreakable = ent->inactive;
	ent->reset = Reset_Multi;

	InitTrigger(ent);
	trap_LinkEntity(ent);
}

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

trigger_always

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

void trigger_always_think(gentity_t * ent)
{
	G_UseTargets(ent, ent);
	//Makro - we want to be able to re-use this entity (round-based gametypes)
	//so we're not going to free it
	//G_FreeEntity(ent);
	trap_UnlinkEntity(ent);
}

//Makro - reset function
void trigger_always_reset(gentity_t *ent)
{
	trap_LinkEntity(ent);
	ent->nextthink = level.time + 300;
	ent->think = trigger_always_think;
}

/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
This trigger will always fire.  It is activated by the world.
*/
void SP_trigger_always(gentity_t * ent)
{
	// we must have some delay to make sure our use targets are present
	ent->nextthink = level.time + 300;
	ent->think = trigger_always_think;
	//Makro - reset function
	ent->reset = trigger_always_reset;
}

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

trigger_push

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

void trigger_push_touch(gentity_t * self, gentity_t * other, trace_t * trace)
{

	if (!other || !self)
		return;

	if (!other->client) {
		return;
	}
	//Makro - too soon to activate ?
	/*if (level.time < self->s.legsAnim) {
	   return;
	   } */

	//Makro - bot only triggers
	if (self->spawnflags & 1) {
		if (!(other->r.svFlags & SVF_BOT)) {
			return;
		}
	}

	BG_TouchJumpPad(&other->client->ps, &self->s);
	//Makro - "team up" trigger_pushes
	//something is broken here :/
	/*
	   if (self->pathtarget) {
	   if (self->pathtarget[0]) {
	   gentity_t    *loop = NULL;
	   for (loop = G_Find2(NULL, FOFS(classname), self->classname, FOFS(pathtarget), self->pathtarget); loop; G_Find2(loop, FOFS(classname), self->classname, FOFS(pathtarget), self->pathtarget)) {
	   //Makro - delay 5 seconds before triggering another trigger_push from the same "team"
	   if (self->distance) {
	   loop->s.legsAnim = level.time + self->distance * 1000;
	   } else {
	   loop->s.legsAnim = level.time + 5 * 1000;
	   }
	   }
	   }
	   } */

}

/*
=================
AimAtTarget

Calculate origin2 so the target apogee will be hit
=================
*/
void AimAtTarget(gentity_t * self)
{
	gentity_t *ent;
	vec3_t origin;
	float height, gravity, time, forward;
	float dist;

	VectorAdd(self->r.absmin, self->r.absmax, origin);
	VectorScale(origin, 0.5, origin);

	ent = G_PickTarget(self->target);
	if (!ent) {
		G_FreeEntity(self);
		return;
	}

	height = ent->s.origin[2] - origin[2];
	gravity = g_gravity.value;
	time = sqrt(height / (.5 * gravity));
	if (!time) {
		G_FreeEntity(self);
		return;
	}
	// set s.origin2 to the push velocity
	VectorSubtract(ent->s.origin, origin, self->s.origin2);
	self->s.origin2[2] = 0;
	dist = VectorNormalize(self->s.origin2);

	forward = dist / time;
	VectorScale(self->s.origin2, forward, self->s.origin2);

	self->s.origin2[2] = time * gravity;
}

/*QUAKED trigger_push (.5 .5 .5) ? BOT_ONLY
Must point at a target_position, which will be the apex of the leap.
This will be client side predicted, unlike target_push
*/
void SP_trigger_push(gentity_t * self)
{
	char *sound;

	InitTrigger(self);

	// unlike other triggers, we need to send this one to the client
	// NiceAss: Added for custom push sounds. Default is none. Q3 is "sounds/world/bouncepad.wav"
	//Changed from noise to sound as per Sze
	if (G_SpawnString("sound", "sound/misc/silence.wav", &sound)) {
		self->s.generic1 = G_SoundIndex(sound);
	}
	//Makro - for bot-only triggers
	if (!(self->spawnflags & 1)) {
		self->r.svFlags &= ~SVF_NOCLIENT;
	}
	self->s.powerups = (self->spawnflags & 1);
	self->s.eType = ET_PUSH_TRIGGER;
	self->touch = trigger_push_touch;
	self->think = AimAtTarget;
	self->nextthink = level.time + FRAMETIME;
	trap_LinkEntity(self);
}

void Use_target_push(gentity_t * self, gentity_t * other, gentity_t * activator)
{
	if (!activator->client) {
		return;
	}

	if (activator->client->ps.pm_type != PM_NORMAL) {
		return;
	}

	VectorCopy(self->s.origin2, activator->client->ps.velocity);

	// play fly sound every 1.5 seconds
	if (activator->fly_sound_debounce_time < level.time) {
		activator->fly_sound_debounce_time = level.time + 1500;
		//Elder: added to check noise_index
		if (self->noise_index)
			G_Sound(activator, CHAN_AUTO, self->noise_index);
	}
}

/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad
Pushes the activator in the direction.of angle, or towards a target apex.
"speed"		defaults to 1000
if "bouncepad", play bounce noise instead of windfly
*/
void SP_target_push(gentity_t * self)
{
	char *sound;

	if (!self->speed) {
		self->speed = 1000;
	}
	G_SetMovedir(self->s.angles, self->s.origin2);
	VectorScale(self->s.origin2, self->speed, self->s.origin2);
	//Changed from noise to sound as per Sze
	if (G_SpawnString("sound", "sound/misc/silence.wav", &sound)) {
		//Makro - debug message, no longer needed
		//G_Printf("^2Sound was %s\n",sound);
		self->noise_index = G_SoundIndex(sound);
	}

	if (self->spawnflags & 1) {
		//Elder: edited for now TODO: let mappers use a key pair
		//self->noise_index = G_SoundIndex("sound/world/jumppad.wav");
	} else {
		//Elder: edited for now TODO: let mappers use a key pair
		//self->noise_index = G_SoundIndex("sound/misc/windfly.wav");
	}
	if (self->target) {
		VectorCopy(self->s.origin, self->r.absmin);
		VectorCopy(self->s.origin, self->r.absmax);
		self->think = AimAtTarget;
		self->nextthink = level.time + FRAMETIME;
	}
	self->use = Use_target_push;
}

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

trigger_teleport

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

void trigger_teleporter_touch(gentity_t * self, gentity_t * other, trace_t * trace)
{
	gentity_t *dest;

	if (!other->client) {
		return;
	}
	if (other->client->ps.pm_type == PM_DEAD) {
		return;
	}
	// Spectators only?
	if ((self->spawnflags & 1) && other->client->sess.sessionTeam != TEAM_SPECTATOR) {
		return;
	}

	dest = G_PickTarget(self->target);
	if (!dest) {
		G_Printf("Couldn't find teleporter destination\n");
		return;
	}

	TeleportPlayer(other, dest->s.origin, dest->s.angles);
}

/*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR
Allows client side prediction of teleportation events.
Must point at a target_position, which will be the teleport destination.

If spectator is set, only spectators can use this teleport
Spectator teleporters are not normally placed in the editor, but are created
automatically near doors to allow spectators to move through them
*/
void SP_trigger_teleport(gentity_t * self)
{
	InitTrigger(self);

	// unlike other triggers, we need to send this one to the client
	// unless is a spectator trigger
	if (self->spawnflags & 1) {
		self->r.svFlags |= SVF_NOCLIENT;
	} else {
		self->r.svFlags &= ~SVF_NOCLIENT;
	}

	// make sure the client precaches this sound
	G_SoundIndex("sound/world/jumppad.wav");

	self->s.eType = ET_TELEPORT_TRIGGER;
	self->touch = trigger_teleporter_touch;

	trap_LinkEntity(self);
}

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

trigger_hurt

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

/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW
Any entity that touches this will be hurt.
It does dmg points of damage each server frame
Targeting the trigger will toggle its on / off state.

SILENT			supresses playing the sound
SLOW			changes the damage rate to once per second
NO_PROTECTION	*nothing* stops the damage

"dmg"			default 5 (whole numbers only)

*/
//Makro - reset function
void hurt_reset(gentity_t *ent)
{
	ent->timestamp = level.time;
	if (!(ent->spawnflags & 1)) {
		trap_LinkEntity(ent);
	} else {
		//Makro - added
		trap_UnlinkEntity(ent);
	}
}

void hurt_use(gentity_t * self, gentity_t * other, gentity_t * activator)
{
	if (self->r.linked) {
		trap_UnlinkEntity(self);
	} else {
		trap_LinkEntity(self);
	}
}

void hurt_touch(gentity_t * self, gentity_t * other, trace_t * trace)
{
	int dflags;

	if (!other->takedamage) {
		return;
	}

	if (self->timestamp > level.time) {
		return;
	}

	if (self->spawnflags & 16) {
		self->timestamp = level.time + 1000;
	} else {
		self->timestamp = level.time + FRAMETIME;
	}

	// play sound
	if (!(self->spawnflags & 4)) {
		G_Sound(other, CHAN_AUTO, self->noise_index);
	}

	if (self->spawnflags & 8)
		dflags = DAMAGE_NO_PROTECTION;
	else
		dflags = 0;
	if (self->methodOfDeath)
	{
		G_Damage(other, self, self, NULL, NULL, self->damage, dflags, MOD_CUSTOM+self->methodOfDeath-1);
	} else {
		G_Damage(other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT);
	}
}

void SP_trigger_hurt(gentity_t * self)
{
	InitTrigger(self);

	self->noise_index = G_SoundIndex("sound/world/electro.wav");
	self->touch = hurt_touch;

	if (!self->damage) {
		self->damage = 5;
	}

	//self->r.contents = CONTENTS_TRIGGER;

	//Makro - custom death message
	G_InitCustomDeathMessage(self, &self->methodOfDeath);

	//Makro - removed this check
	//if ( self->spawnflags & 2 ) {
	self->use = hurt_use;
	//}
	//Makro - reset function
	self->reset = hurt_reset;

	// link in to the world if starting active
	if (!(self->spawnflags & 1)) {
		trap_LinkEntity(self);
	} else {
		//Makro - added
		trap_UnlinkEntity(self);
	}
}

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

timer

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

/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
This should be renamed trigger_timer...
Repeatedly fires its targets.
Can be turned on or off by using.

"wait"			base time between triggering all targets, default is 1
"random"		wait variance, default is 0
so, the basic time between firing is a random time between
(wait - random) and (wait + random)

*/
void func_timer_think(gentity_t * self)
{
	G_UseTargets(self, self->activator);
	// set time before next firing
	self->nextthink = level.time + 1000 * (self->wait + crandom() * self->random);
}

void func_timer_use(gentity_t * self, gentity_t * other, gentity_t * activator)
{
	self->activator = activator;

	// if on, turn it off
	if (self->nextthink) {
		self->nextthink = 0;
		return;
	}
	// turn it on
	func_timer_think(self);
}

void SP_func_timer(gentity_t * self)
{
	G_SpawnFloat("random", "1", &self->random);
	G_SpawnFloat("wait", "1", &self->wait);

	self->use = func_timer_use;
	self->think = func_timer_think;

	if (self->random >= self->wait) {
		self->random = self->wait - FRAMETIME;
		G_Printf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
	}

	if (self->spawnflags & 1) {
		self->nextthink = level.time + FRAMETIME;
		self->activator = self;
	}

	self->r.svFlags = SVF_NOCLIENT;
}
