// Triggers
// FIXME: default sounds for each trigger type

#include "world.h"
#include "triggers.h"
#include "misc.h"
#include "nodelist.h"
#include "ai_move.h"
#include "chasecam.h"
#include "client.h"

///////////////////////////////////////////////////////////////////////////////
//	exports
///////////////////////////////////////////////////////////////////////////////

DllExport	void	trigger_secret (userEntity_t *self);
DllExport	void	trigger_relay (userEntity_t *self);
DllExport	void	trigger_once (userEntity_t *self);
DllExport	void	trigger_console (userEntity_t *self);
DllExport	void	trigger_multiple (userEntity_t *self);
DllExport	void	trigger_setskill (userEntity_t *self);
DllExport	void	info_teleport_destination (userEntity_t *self);
DllExport	void	trigger_teleport (userEntity_t *self);
DllExport	void	trigger_counter (userEntity_t *self);
DllExport	void	trigger_push (userEntity_t *self);
DllExport	void	trigger_hurt (userEntity_t *self);
DllExport	void	trigger_changelevel (userEntity_t *self);
DllExport	void	func_wall_explode (userEntity_t *self);
DllExport	void	trigger_fog_value (userEntity_t *self);
DllExport	void	trigger_warp (userEntity_t *self);
DllExport	void	info_warp_destination (userEntity_t *self);
DllExport	void	warp_cylinder (userEntity_t *self);

///////////////////////////////////////////////////////////////////////////////
//	defines
///////////////////////////////////////////////////////////////////////////////

#define		SPAWNFLAG_NOMESSAGE 1
#define		SPAWNFLAG_NOTOUCH	1

// func_wall_explode
#define		ROCK_CHUNKS			1
#define		WOOD_CHUNKS			2
#define		EXTRA_CHUNKS		4
#define		EXTRA_VELOCITY		8
#define		NO_CHUNKS			16
#define		NO_SOUND			32
#define		METAL_CHUNKS		64
#define		NO_EXPLOSIONS		128

//	teleporter/warp spawnflags
#define		PLAYER_ONLY			1

//	teleporter spawnflags
#define		NO_FLASH			2
#define		NO_ANGLE_ADJUST		4

//	info_warp_destination spawnflags
#define		INFOWARP_SKIPTO		1
#define		INFOWARP_GLOW		2

///////////////////////////////////////////////////////////////////////////////
//	typedefs
///////////////////////////////////////////////////////////////////////////////

typedef	struct	warpHook_s
{
	think_t		think;
	float		nextthink;

	int			movetype;
	solid_t		solid;

	unsigned	long	effects;

	vec3_t		velocity;
	vec3_t		render_scale;
} warpHook_t;


///////////////////////////////////////////////////////////////////////////////
//	trigger_init
///////////////////////////////////////////////////////////////////////////////

void trigger_init (userEntity_t *self)
{
// trigger angles are used for one-way touches.  An angle of 0 is assumed
// to mean no restrictions, so use a yaw of 360 instead.

	if (Vector (self->s.angles) == Vector (0.0, 0.0, 0.0))
		com->SetMovedir (self);

	self->solid = SOLID_TRIGGER;
	self->movetype = MOVETYPE_NONE;
	self->svflags |= SVF_NOCLIENT;

	gstate->SetModel (self, self->modelName);
	gstate->LinkEntity (self);

	self->userHook = com->malloc_hook (sizeof (trigHook_t), MEM_HUNK);
};

///////////////////////////////////////////////////////////////////////////////
//	trigger_remove
//
//	doesn't bother to deallocate the userHook hunk since there
//	should be very few triggers on a level and that memory will
//	be reclaimed each time the level ends
///////////////////////////////////////////////////////////////////////////////

void trigger_remove (userEntity_t *self)
{
	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_skill_touch
///////////////////////////////////////////////////////////////////////////////

void trigger_skill_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	if (!com->ValidTouch (self, other))
		return;

	gstate->SetCvar ("skill", self->message);
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_setskill
///////////////////////////////////////////////////////////////////////////////

void trigger_setskill (userEntity_t *self)
{
	int		i;

	trigger_init (self);

	for (i = 0; self->epair [i].key != NULL; i++)
	{
		if (!stricmp (self->epair [i].key, "message"))
			self->message = self->epair [i].value;
	}

	self->touch = trigger_skill_touch;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_reactivate
///////////////////////////////////////////////////////////////////////////////

void trigger_reactivate (userEntity_t *self)
{
	self->solid = SOLID_TRIGGER;
}

///////////////////////////////////////////////////////////////////////////////
//	multi_wait
//
//	reset trigger to touch again
///////////////////////////////////////////////////////////////////////////////

void multi_wait (userEntity_t *self)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	if (hook->max_health)
	{
		self->health = hook->max_health;
		self->takedamage = DAMAGE_YES;
		self->solid = SOLID_BBOX;
	}
	else
		trigger_reactivate (self);
}

///////////////////////////////////////////////////////////////////////////////
//	multi_trigger
///////////////////////////////////////////////////////////////////////////////

void multi_trigger (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	if (self->nextthink > gstate->time)
		return;

	if (!com->ValidTouch (self, activator))
		return;

	if (self->className && !stricmp (self->className, "trigger_secret"))
	{
		//FIXME: increment secrets found
	}

	if (hook->sound)
		gstate->sound (self, CHAN_AUTO, hook->sound, 255, ATTN_NORM);

	// don't trigger again until reset
	self->takedamage = DAMAGE_NO;
	self->solid = SOLID_NOT;

	com->UseTargets (self, other, activator);

	if (hook->wait >= 0)
	{
		self->think = multi_wait;
		self->nextthink = gstate->time + hook->wait;
	}
	else
	{
		// trigger_onces remove themselves
		self->touch = NULL;
		self->nextthink = gstate->time + 0.1;
		self->think = trigger_remove;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	multi_killed
///////////////////////////////////////////////////////////////////////////////

void multi_killed (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damgae, vec3_t point)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	hook->activator = gstate->attacker;
	
	//	Q2FIXME:	other == attacker???
	multi_trigger (self, attacker, attacker);
}

///////////////////////////////////////////////////////////////////////////////
//	multi_pain
///////////////////////////////////////////////////////////////////////////////

void multi_pain (userEntity_t *self, userEntity_t *other, float kick, int damage)
{
	multi_killed (self, other, other, damage, self->s.origin);
}

///////////////////////////////////////////////////////////////////////////////
//	multi_use
///////////////////////////////////////////////////////////////////////////////

//	Q2FIXME:	activator?!?!?

void multi_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;
	
	hook->activator = activator;

	multi_trigger (self, other, activator);
}

///////////////////////////////////////////////////////////////////////////////
//	multi_touch
///////////////////////////////////////////////////////////////////////////////

void multi_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	if (!com->ValidTouch (self, other))
		return;

	// FIXME!!!!
/*
	if (!com->CmpVector (self->movedir, 0.0, 0.0, 0.0))
	{
		com->AngleVectors (other->s.angles, forward, right, up);
		
		if (com->DotProduct (forward, self->movedir) < 0)
		{
			gstate->Con_Printf ("angle failed\n");
			return;
		}
	}
*/

	hook->activator = other;
	multi_trigger (self, other, other);

	if (self->message && other->flags & FL_CLIENT)
		gstate->centerprintf (other, self->message);
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_setup
//
//	shared code for setting up a trigger
///////////////////////////////////////////////////////////////////////////////

void	trigger_setup (userEntity_t *self)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;
	int			i;

	// parse epairs
	hook = (trigHook_t *) self->userHook;

	com->ZeroVector (self->movedir);

	for (i = 0; self->epair [i].key != NULL; i++)
	{
		if (!stricmp (self->epair [i].key, "health"))
			self->health = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "wait"))
			hook->wait = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "sound"))
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)
				gstate->Precache_Sound (hook->sound);
		}
		else if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "target"))
			self->target = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "killtarget"))
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "message"))
			self->message = self->epair [i].value;
	}

	if (!hook->wait)
		hook->wait = 0.2;
	
	self->use = multi_use;

	if (self->health)
	{
		if (self->spawnflags & SPAWNFLAG_NOTOUCH)
			gstate->Con_Printf ("health and NOTOUCH don't make sense\n");

		hook->max_health = self->health;
		self->die = multi_killed;
		self->pain = multi_pain;
		self->takedamage = DAMAGE_YES;
		self->solid = SOLID_BBOX;
		gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	}
	else
	{
		if (!(self->spawnflags & SPAWNFLAG_NOTOUCH))
			self->touch = multi_touch;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_console_touch
//
//	FIXME: test this out
//	sends message as a console command  
///////////////////////////////////////////////////////////////////////////////

void trigger_console_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	if (!com->ValidTouch (self, other))
		return;

	gstate->Con_Printf ("trigger_console_touch %s\n", other->className);
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_console
//
//	FIXME: probably needs testing
//
//	Basically you only want a trigger to happen once in most cases
//	ummm... I guess
//	key "command"
//	value -- text to send to console
///////////////////////////////////////////////////////////////////////////////

void trigger_console (userEntity_t *self)
{
	trigger_init (self);
	self->touch = trigger_console_touch;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_once
///////////////////////////////////////////////////////////////////////////////
//#define	QUAKE2
#ifdef	QUAKE2
void trigger_multiple (edict_t *ent)
{
/*
	if (ent->sounds == 1)
		ent->noise_index = gi.soundindex ("misc/secret.wav");
	else if (ent->sounds == 2)
		ent->noise_index = gi.soundindex ("misc/talk.wav");
	else if (ent->sounds == 3)
		ent->noise_index = gi.soundindex ("misc/trigger1.wav");
*/	
	if (!ent->wait)
		ent->wait = 0.2;
	ent->touch = multi_touch;
	ent->movetype = MOVETYPE_NONE;
	ent->svflags |= SVF_NOCLIENT;


	if (ent->spawnflags & 4)
	{
		ent->solid = SOLID_NOT;
//		ent->use = trigger_enable;
	}
	else
	{
		ent->solid = SOLID_TRIGGER;
		ent->use = multi_use;
	}

//	if (!VectorCompare(ent->s.angles, vec3_origin))
//		G_SetMovedir (ent->s.angles, ent->movedir);

	gstate->gi->setmodel (ent, ent->model);
	gstate->gi->linkentity (ent);
}

void trigger_once (userEntity_t *ent)
{
	trigHook_t	*hook;

	ent->userHook = com->malloc_hook (sizeof (trigHook_t), MEM_HUNK);
	hook = (trigHook_t *) ent->userHook;

	hook->wait = -1;
	Q2_trigger_multiple (ent);
}

#else

///////////////////////////////////////////////////////////////////////////////
//	trigger_once
///////////////////////////////////////////////////////////////////////////////

void	trigger_once (userEntity_t *self)
{
	trigHook_t	*hook;

	trigger_init (self);
	hook = (trigHook_t *) self->userHook;

	// never retriggers
	hook->wait = -1.0;
	trigger_setup (self);

	self->className = "trigger_once";
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_multiple
//
//	see world.c, I don't feel like writing it again
///////////////////////////////////////////////////////////////////////////////

void	trigger_multiple (userEntity_t *self)
{
	trigger_init (self);

	trigger_setup (self);
}
#endif

///////////////////////////////////////////////////////////////////////////////
//	trigger_relay
///////////////////////////////////////////////////////////////////////////////

void trigger_relay (userEntity_t *self)
{
	trigHook_t	*hook;

	trigger_init (self);
	self->touch = NULL;

	hook = (trigHook_t *) self->userHook;
	self->use = com->UseTargets;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_secret
///////////////////////////////////////////////////////////////////////////////

void	trigger_secret (userEntity_t *self)
{
	trigHook_t	*hook;

	//FIXME: total secrets variable in server type...
	trigger_init (self);
	hook = (trigHook_t *) self->userHook;

	hook->wait = -1.0;

	trigger_setup (self);

	if (!self->message)
		self->message = "You suck, you suck, and you suck again!";
}

/* ---------------------------- Teleport Triggers ---------------------------- */


///////////////////////////////////////////////////////////////////////////////
//	spawn_tele_sound
///////////////////////////////////////////////////////////////////////////////

void	spawn_tele_sound (userEntity_t *self)
{
	trigHook_t	*owner_hook = (trigHook_t *) self->owner->userHook;

	if (owner_hook->sound)
		gstate->sound (self, CHAN_AUTO, owner_hook->sound, 255, ATTN_NORM);

	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	spawn_tele_fog
///////////////////////////////////////////////////////////////////////////////

void	spawn_tele_fog (userEntity_t *self, Vector &org)
{
	userEntity_t	*temp;

	temp = gstate->SpawnEntity ();
	
	org.set (temp->s.origin);
	temp->nextthink = gstate->time + 0.2;
	temp->owner = self;
	
	temp->think = spawn_tele_sound;

	//	Q2FIXME:	change to Q2 network functions
	/*
	gstate->WriteByte (self, MSG_BROADCAST, SVC_TEMPENTITY);
	gstate->WriteByte (self, MSG_BROADCAST, TE_TELEPORT);
	gstate->WriteCoord (self, MSG_BROADCAST, org.x);
	gstate->WriteCoord (self, MSG_BROADCAST, org.y);
	gstate->WriteCoord (self, MSG_BROADCAST, org.z);
	*/
}

///////////////////////////////////////////////////////////////////////////////
//	tele_gib_touch
///////////////////////////////////////////////////////////////////////////////

void	tele_gib_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	userEntity_t	*head;

	if (other == self->owner)
		return;

	if (other->className)
	{
		if (stricmp (other->className, "player") == 0)
		{
			//FIXME: check for invincible

			// Check the touching player's tele_gib time against this
			// one.  If it's more recent, then the touching player doesn't
			// get gibbed because he came through last
			head = gstate->FirstEntity ();

			while (head)
			{
				if (head->className)
				{
					if ((head->owner == other) && (qv_dist (head->s.origin, self->s.origin) < 32) &&
						(stricmp (head->className, "tele_gib") == 0))
					{
						if (head->nextthink > self->nextthink)
						{
							//gstate->Con_Printf ("%s came through tele last\n", other->className);
							return;
						}
					}
				}
				
				head = gstate->NextEntity (head);
			}

			if (stricmp (self->owner->className, "player") != 0)
			{
				// explode monsters teleporting onto players
				com->Damage (self->owner, self, self, zero_vector, zero_vector, 50000.0, DAMAGE_TELEFRAG | DAMAGE_NO_BLOOD);
				return;
			}
		}
	}
	else return;

	if (other->health)
		com->Damage (other, self, self, zero_vector, zero_vector, 50000.0, DAMAGE_TELEFRAG | DAMAGE_NO_BLOOD);
}

///////////////////////////////////////////////////////////////////////////////
//	tele_gib_remove
///////////////////////////////////////////////////////////////////////////////

void	tele_gib_remove (userEntity_t *self)
{
	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	spawn_tele_gib
///////////////////////////////////////////////////////////////////////////////

void	spawn_tele_gib (Vector &org, userEntity_t *gib_owner)
{
	userEntity_t	*death;
	Vector			origin, mins, maxs, temp;

	death = gstate->SpawnEntity ();
	death->className = "tele_gib";
	death->movetype = MOVETYPE_NONE;
	death->solid = SOLID_TRIGGER;
	death->modelName = "";
	death->owner = gib_owner;

	com->ZeroVector (death->s.angles);
	
	com->GetMinsMaxs (gib_owner, origin, mins, maxs);
	temp = Vector (1.0, 1.0, 1.0);

	mins = mins - temp;
	maxs = maxs + temp;

	gstate->SetSize (death, mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z);
	gstate->SetOrigin (death, org.x, org.y, org.z);

	death->touch = tele_gib_touch;
	death->nextthink = gstate->time + 0.2;
	death->think = tele_gib_remove;
	
	// FIXME: no global for this...
	//force_retouch = 2;
}

///////////////////////////////////////////////////////////////////////////////
//	info_teleport_destination
///////////////////////////////////////////////////////////////////////////////

void	info_teleport_destination (userEntity_t *self)
{
	Vector		temp;
	int			i;
	trigHook_t	*hook;

	self->userHook = com->malloc_hook (sizeof (trigHook_t), MEM_MALLOC);
	hook = (trigHook_t *) self->userHook;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)			
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		i++;
	}
	
	self->className = "info_teleport_destination";

	//self->mangle = self->s.angles;
	
	hook->mangle = Vector (self->s.angles);
	com->ZeroVector (self->s.angles);
	self->modelName = "";
	
	temp = Vector (0.0, 0.0, 27.0) + Vector (self->s.origin);
	temp.set (self->s.origin);

	if (!self->targetname)
		gstate->Con_Printf ("No targetname for teleport_dest\n");
}	

///////////////////////////////////////////////////////////////////////////////
//	teleport_touch
///////////////////////////////////////////////////////////////////////////////

void	teleport_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	userEntity_t	*dest;
	Vector			org, ang_diff, enter_dir, facing_dir;
	float			t_float;
	int				found;
	trigHook_t		*hook, *hook2 = (trigHook_t *) self->userHook;
	playerHook_t	*ohook;
	teleportNode_t	*node_data;	

	if (self->targetname)
	{
		if (self->nextthink < gstate->time)
			return;
	}

	if ((int)self->spawnflags & PLAYER_ONLY)
	{
		if (!com->ValidTouch (self, other))
			return;
	}

	com->UseTargets (self, other, other);

	// put a teleport fog where the player was
	if (!((int)self->spawnflags & NO_FLASH))
		spawn_tele_fog (self, Vector (other->s.origin));

	// find the teleporters target
	dest = gstate->FirstEntity ();

	found = FALSE;
	while (dest && !found)
	{
		if (dest->targetname)
		{
			if (stricmp (dest->targetname, self->target) == 0)
				found = TRUE;
			else
				dest = gstate->NextEntity (dest);
		}
		else
	 		dest = gstate->NextEntity (dest);
	}
	
	hook = (trigHook_t *) dest->userHook;

	// spawn a flash in front of the destination

	hook->mangle.AngleVectors (forward, right, up);	
	//com->GetMinsMaxs (dest, org, mins, maxs);
	//org = org + Vector (dest->s.origin);
	org = Vector (dest->s.origin) + forward * 32.0;
	
	if (!((int)self->spawnflags & NO_FLASH))
		spawn_tele_fog (self, org);
	spawn_tele_gib (Vector (dest->s.origin), other);

	if (!other->health)
	{
		org = Vector (dest->s.origin);
		org.set (other->s.origin);

		org = forward * other->velocity [0] + forward * other->velocity [1];
		org.set (other->velocity);

		return;
	}

	// set fog_value if set for teleport
	// FIXME: local commands to a specific client!!!

/*
	if (other->flags & FL_CLIENT)
	{
		if (hook2->model_1)
		{
			gstate->Con_Dprintf ("setting fog\n");

			if (!stricmp (hook2->model_1, "0"))
			{
				gstate->WriteByte (self, MSG_ONE, SVC_FOGVALUE);
				gstate->WriteByte (self, MSG_ONE, 0);
			}
			else
			{
				if (!gstate->foglevel)
				{
					gstate->WriteByte (self, MSG_ONE, SVC_FOGVALUE);
					gstate->WriteByte (self, MSG_ONE, 1);
				}
				gstate->WriteByte (self, MSG_ONE, SVC_FOGVALUE);
				gstate->WriteByte (self, MSG_ONE, atoi (hook2->model_1));
			}
		}
	}
*/
	gstate->SetOrigin (other, dest->s.origin [0], dest->s.origin [1], dest->s.origin [2]);

	if (!((int)self->spawnflags & NO_ANGLE_ADJUST))
		hook->mangle.set (other->s.angles);
	else
		// adjust roll angle
		self->s.angles [2] = 0.0;
	
	if (other->flags & (FL_CLIENT + FL_BOT + FL_MONSTER))
	{
		other->s.angles [2] = 0.0;
		other->fixangle = 1.0;
		other->teleport_time = gstate->time + 0.7;
		if ((int)other->flags & FL_ONGROUND)
			other->flags = other->flags - FL_ONGROUND;

		// set velocity of teleported player to the direction
		// of the teleporter's mangle, at the same speed as they
		// were traveling when they entered the teleporter
		org = Vector (other->velocity);
		forward = forward * org.vlen ();
		if (!((int)self->spawnflags & NO_ANGLE_ADJUST))
			forward.set (other->velocity);
	}

	if (self->message && other->flags & FL_CLIENT)
		gstate->centerprintf (other, self->message);

	gstate->sound (self, CHAN_AUTO, "misc/teleportstart.wav", 255, ATTN_NORM);
	gstate->sound (other, CHAN_AUTO, "misc/teleportend.wav", 255, ATTN_NORM);

	if (other->flags & FL_CLIENT || other->flags & FL_BOT || other->flags & FL_MONSTER)
	{
		///////////////////////////////////////////////////////////////////////
		//	force a teleport node to be placed at the location of this teleport
		///////////////////////////////////////////////////////////////////////

		ohook = (playerHook_t *) other->userHook;
		hook = (trigHook_t *) self->userHook;

		org = (Vector (self->absmin) + Vector (self->absmax)) * 0.5;

		//	create the necessary node data for this teleport
		node_data = (teleportNode_t *) com->malloc (sizeof (teleportNode_t), MEM_MALLOC);
		org.set (node_data->start_origin);
		com->CopyVector (node_data->dest_origin, dest->s.origin);

		node_force_node (other, ohook->node_list, node_data->start_origin, NODE_TELEPORTER, node_data);

		//	so exit node from teleporter/warp gets placed at actual exit!
		ai_teleported (other);
	}
}

///////////////////////////////////////////////////////////////////////////////
//	teleport_use
///////////////////////////////////////////////////////////////////////////////

void	teleport_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	self->nextthink = gstate->time + 0.2;
	
	// FIXME: don't know what this does
	//force_retouch = 2;
	
	self->think = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_teleport
///////////////////////////////////////////////////////////////////////////////

void	trigger_teleport (userEntity_t *self)
{
	trigHook_t	*hook;
	int			i;

	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;
	hook->count = 0;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)	
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "fog_value"))
		{
			gstate->Con_Printf ("fog_value = %s\n", self->epair [i].value);
			hook->model_1 = self->epair [i].value;
		}
		else if (stricmp (self->epair [i].key, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "PLAYER_ONLY") == 0)
				self->spawnflags = self->spawnflags | PLAYER_ONLY;
			else if (stricmp (self->epair [i].value, "NO_FLASH") == 0)
				self->spawnflags = self->spawnflags | NO_FLASH;
			else if (stricmp (self->epair [i].value, "NO_ANGLE_ADJUST") == 0)
				self->spawnflags = self->spawnflags | NO_ANGLE_ADJUST;
		}
		i++;
	}

//	trigger_setup (self);

	self->touch = teleport_touch;
	self->className = "trigger_teleport";

	if (!self->target)
		gstate->Con_Printf ("teleporter has no target!\n");

	if (!hook->sound)
	{
		hook->sound = "misc/teleportstart.wav";
		gstate->Precache_Sound (hook->sound);
		gstate->Precache_Sound ("misc/teleportend.wav");
	}

	self->use = teleport_use;
}

///////////////////////////////////////////////////////////////////////////////
//	onlyreg_touch
///////////////////////////////////////////////////////////////////////////////

void	onlyreg_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	if (!com->ValidTouch (self, other))
		return;

	if (hook->next_trigger_time > gstate->time)
		return;

	hook->next_trigger_time = gstate->time + 2.0;

	if (gstate->GetCvar ("registered"))
	{
		self->message = NULL;
		com->UseTargets (self, other, other);
		gstate->RemoveEntity (self);
	}
	else
	{
		if (self->message && other->flags & FL_CLIENT)
		{
			gstate->centerprintf (other, self->message);

			if (hook->sound)
				gstate->sound (self, CHAN_AUTO, hook->sound, 255, ATTN_NORM);
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_onlyregistered
///////////////////////////////////////////////////////////////////////////////

void	trigger_onlyregistered (userEntity_t *self)
{
	int			i;
	trigHook_t	*hook;

	// FIXME: precache only reg. sound
	
	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "message") == 0)
			self->message = self->epair [i].value;

		i++;
	}

	self->touch = onlyreg_touch;
}

///////////////////////////////////////////////////////////////////////////////
//	hurt_reset
///////////////////////////////////////////////////////////////////////////////

void	hurt_reset (userEntity_t *self)
{
	self->solid = SOLID_TRIGGER;
	self->nextthink = -1;
}

///////////////////////////////////////////////////////////////////////////////
//	hurt_touch
///////////////////////////////////////////////////////////////////////////////

void	hurt_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	if (other->takedamage)
	{
		self->solid = SOLID_NOT;
		com->Damage (other, self, self, zero_vector, zero_vector, hook->damage, DAMAGE_TRIGGER | DAMAGE_NO_BLOOD);
	}

	if (self->message && other->flags & FL_CLIENT)
		gstate->centerprintf (other, self->message);

	self->think = hurt_reset;
	self->nextthink = gstate->time + hook->wait;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_hurt
///////////////////////////////////////////////////////////////////////////////

void	trigger_hurt (userEntity_t *self)
{
	int			i;
	trigHook_t	*hook;

	// FIXME: precache only reg. sound
	
	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "dmg") == 0)
			hook->damage = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "wait") == 0)
			hook->wait = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "message") == 0)
			self->message = self->epair [i].value;

		i++;
	}

	self->touch = hurt_touch;

	if (!hook->damage)
		hook->damage = 5;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_push_touch
///////////////////////////////////////////////////////////////////////////////

#define	PUSH_ONCE 1

void	trigger_push_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;
	Vector		temp;

	if (other->className)
	{
		// FIXME: add pushes for certain weapon types
		temp = hook->speed * Vector (self->movedir) * 10.0;

		if (other->health)
		{
			if (stricmp (other->className, "player") == 0)
			{
				if (hook->sound)
					gstate->sound (other, CHAN_AUTO, hook->sound, 1, ATTN_NORM);
			}
		}

		if ((int)self->spawnflags & PUSH_ONCE)
			gstate->RemoveEntity (self);
	}
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_push
//
//	FIXME: doesn't assign movedir  
///////////////////////////////////////////////////////////////////////////////

void	trigger_push (userEntity_t *self)
{
	int			i;
	trigHook_t	*hook;

	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "message") == 0)
			self->message = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "PUSH_ONCE") == 0)
				self->spawnflags = self->spawnflags | PUSH_ONCE;
		}


		i++;
	}

	self->touch = trigger_push_touch;

	if (!hook->speed)
		hook->speed = 1000.0;
}

///////////////////////////////////////////////////////////////////////////////
//	counter_use
///////////////////////////////////////////////////////////////////////////////

void	counter_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	hook->count = hook->count - 1;

	if (hook->count < 0)
		return;
/*
	if (activator->className)
	{

		if ((stricmp (activator->className, "player") == 0) && !((int)self->spawnflags & SPAWNFLAG_NOMESSAGE)) 
		{
			if (hook->count >= 4)
				gstate->centerprint (activator, 2.0, "There are more to go...");
			else if (hook->count == 3)
				gstate->centerprint (activator, 2.0, "Only 3 more to go...");
			else if (hook->count == 2)
				gstate->centerprint (activator, 2.0, "Only 2 more to go...");
			else if (hook->count == 1)
				gstate->centerprint (activator, 2.0, "Only 1 more to go...");
			else
			{
				if ((int)self->spawnflags & SPAWNFLAG_NOMESSAGE)
					gstate->centerprint (activator, 2.0, "Sequence completed!");
				hook->activator = activator;
				multi_trigger (self);
			}
		}
	}
*/
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_counter
///////////////////////////////////////////////////////////////////////////////

void	trigger_counter (userEntity_t *self)
{
	int			i;
	trigHook_t	*hook;

	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "message") == 0)
			self->message = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "count") == 0)
			hook->count = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "NO_MESSAGE") == 0)
				self->spawnflags = self->spawnflags | SPAWNFLAG_NOMESSAGE;
		}

		i++;
	}

	hook->wait = -1.0;

	if (!hook->count)
		hook->count = 2;

	self->use = counter_use;
}

//////////////////////////
//	changelevel_execute
//////////////////////////

void	changelevel_execute (userEntity_t *self)
{
	char	msg [256];

	sprintf (msg, "changelevel %s\n", self->message);
	gstate->ServerCmd (msg);

//	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	changelevel_touch
///////////////////////////////////////////////////////////////////////////////

#define NO_INTERMISSION 1

void	changelevel_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	char	msg [256];

	if (!com->ValidTouch (self, other))
		return;

	if (gstate->GetCvar ("noexit"))
	{
		com->Damage (other, self, self, zero_vector, zero_vector, 50000, DAMAGE_EXPLOSION | DAMAGE_NO_BLOOD);
		return;
	}

	if (gstate->deathmatch || gstate->coop)
		gstate->bprint ("%s exited the level\n", other->className);

	com->UseTargets (self, other, other);

	if (global_spawntarget)
		com->Error ("global_spawntarget not NULL\n");
	else if (self->target)
	{
		global_spawntarget = (char *) com->malloc (256, MEM_PERMANENT);
		strcpy (global_spawntarget, self->target);
	}

	if ((self->spawnflags & NO_INTERMISSION) && gstate->deathmatch == 0)
	{
		changelevel_execute (self);

		return;
	}

	self->touch = NULL;

	self->think = changelevel_execute;
	self->nextthink = gstate->time + 0.1;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_changelevel
///////////////////////////////////////////////////////////////////////////////

void	trigger_changelevel (userEntity_t *self)
{
	int			i;
	trigHook_t	*hook;

	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		//gstate->Con_Printf ("key = %s, value = %s\n", self->epair [i].key, self->epair [i].value);
		
		if (!stricmp (self->epair [i].key, "sound"))
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)			
				gstate->Precache_Sound (hook->sound);
		}
		else if (!stricmp (self->epair [i].key, "target"))
			self->target = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "map"))
			self->message = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "flag"))
		{
			if (stricmp (self->epair [i].value, "NO_INTERMISSION") == 0)
				self->spawnflags = self->spawnflags | NO_INTERMISSION;
		}

		i++;
	}

	if (!self->message)
		com->Error ("trigger_changelevel has no map.\n");

	self->touch = changelevel_touch;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_monstrjump_touch
///////////////////////////////////////////////////////////////////////////////

void	trigger_monsterjump_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	trigHook_t	*hook = (trigHook_t *) self->userHook;

	if (!(other->flags & FL_MONSTER))
		return;

	other->velocity [0] = self->movedir [0] * hook->speed;
	other->velocity [0] = self->movedir [0] * hook->speed;

	if (!(other->flags & FL_ONGROUND))
		return;
	
	other->flags = other->flags - FL_ONGROUND;

	other->velocity [2]  = hook->damage;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_monstrjump
///////////////////////////////////////////////////////////////////////////////

void	trigger_monsterjump (userEntity_t *self)
{
	int			i;
	trigHook_t	*hook;

	//gstate->Con_Printf ("--- trigger_counter ---\n");

	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)			
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "height") == 0)
			hook->damage = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "NO_MESSAGE") == 0)
				self->spawnflags = self->spawnflags | SPAWNFLAG_NOMESSAGE;
		}

		i++;
	}

	if (!hook->speed)
		hook->speed = 200;
	if (!hook->damage)
		hook->damage = 200;

	if (Vector (self->s.angles) == Vector (0.0, 0.0, 0.0))
	{
		com->ZeroVector (self->s.angles);
		self->s.angles [1] = 360.0;
	}

	self->touch = trigger_monsterjump_touch;
}

///////////////////////////////////////////////////////////////////////////////
//	rock_water_friction
//
//	adds friction and random motion to an object that is 
//	travelling through water
///////////////////////////////////////////////////////////////////////////////

void	rock_water_friction (userEntity_t *self)
{
	rockHook_t	*hook = (rockHook_t *) self->userHook;
	Vector			dir, ang;
	float			pc, wobble_speed, wander_speed;

	pc = gstate->PointContents (self->s.origin);
	if (pc != CONTENTS_LAVA && pc != CONTENTS_SLIME && pc != CONTENTS_WATER)
	{
		if (hook->last_water == CONTENTS_LAVA || hook->last_water == CONTENTS_SLIME ||
			hook->last_water == CONTENTS_WATER)
		{
			// just left water.  if this is a self-propelled MOVETYPE_FLYMISSILE then 
			// scale its velocity by 2
			if (self->movetype == MOVETYPE_FLYMISSILE)
			{
				dir = Vector (self->velocity) * 2;
				dir.set (self->velocity);
			}
			
			// make sure gravity goes back to normal
			self->gravity = 1.0;
		}

		hook->last_water = pc;
		return;
	}

	if (hook->last_water != pc)
	{
		// just hit water, so slow down
		hook->hit_water_pos = Vector (self->s.origin);
		dir = Vector (self->velocity) * 0.5 * hook->mass_scale;
		dir.set (self->velocity);
		hook->change_time = gstate->time;

		// decrease effect of gravity
		self->gravity = 0.5 * hook->mass_scale;		
		gstate->Con_Dprintf ("gravity decreased to %f\n", self->gravity);

//		gstate->sound (self, CHAN_AUTO, "bloop.wav", 255, ATTN_NORM);
	}
	else if ((self->movetype == MOVETYPE_BOUNCE) || (self->movetype == MOVETYPE_TOSS))
	{
		if (hook->change_time <= gstate->time)
		{
			hook->hit_water_pos.x = self->s.origin [2] + (rnd () - 0.2) * (rnd () * 256.0 + 32.0);

			dir = Vector (hook->hit_water_pos) - Vector (self->s.origin);
			dir.Normalize ();

			com->VecToAngles (ang, dir);
			ang.AngleVectors (forward, right, up);
			
			// randomly choose left or right
			if (rnd () < 0.5)
				right = right * -1.0;

			// randomly choose towards or away
			if (rnd () < 0.5)
				right = right * -1.0;
				
			wobble_speed = rnd () * 240;
			wander_speed = rnd () * 180;

			dir = Vector (self->velocity) + dir * wander_speed;
			dir.set (self->velocity);

			right = right * wobble_speed;
			right.set (self->velocity);

			hook->change_time = gstate->time + rnd () * 1.0 + 0.1;

			// change angular velocity because grenade just bounced
			com->SetVector (self->avelocity, hook->rotation_scale * (rnd () - 0.5) * 700.0, hook->rotation_scale * (rnd () - 0.5) * 700.0, hook->rotation_scale * (rnd () - 0.5) * 700.0);
		}

		self->velocity [0] = self->velocity [0] * 0.5 * hook->mass_scale;	
		self->velocity [1] = self->velocity [1] * 0.5 * hook->mass_scale;	
	}

	hook->last_water = pc;
}

///////////////////////////////////////////////////////////////////////////////
//	rock_touch
///////////////////////////////////////////////////////////////////////////////

void	rock_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	rockHook_t	*hook = (rockHook_t *) self->userHook;
	Vector		temp;

	temp = Vector (hook->last_pos) - Vector (self->s.origin);

	hook->last_pos = Vector (self->s.origin);	

	if (temp.vlen () < 1.0)
	{
		com->SetVector (self->velocity, 0.0, 0.0, 0.0);
		com->SetVector (self->avelocity, 0.0, 0.0, 0.0);
		self->s.angles [0] = 0.0;

	//	gstate->SetSize (self, -8.0, -8.0, -8.0, 8.0, 8.0, 8.0);

		if (other == self->owner)
		{
			temp = Vector (other->s.angles);
			temp.AngleVectors (forward, right, up);

			temp = forward * 32.0 + Vector (other->s.origin);
	
			gstate->SetOrigin (self, temp.x, temp.y, temp.z);

			temp = forward * qv_len (other->velocity) * 2.2;
			temp.set (self->velocity);
			self->velocity [2] = self->velocity [2] + qv_len (other->velocity) / 2 + 100.0;
		
			return;
		}
	}

	if (gstate->time < hook->last_touch_time + 0.1)
		return;

	if (qv_len (self->velocity) != 0.0)
	{
		// change angular velocity because grenade just bounced
		com->SetVector (self->avelocity, hook->rotation_scale * (rnd () - 0.5) * 1400.0, hook->rotation_scale * (rnd () - 0.5) * 1400.0, hook->rotation_scale * (rnd () - 0.5) * 1400.0);

		temp = Vector (self->avelocity);
		temp = Vector (self->size);
		
//		gstate->sound (self, CHAN_AUTO, "bounce.wav", 255, ATTN_NORM);
	}

	hook->last_touch_time = gstate->time;
}

///////////////////////////////////////////////////////////////////////////////
//	rock_think
///////////////////////////////////////////////////////////////////////////////

void	rock_think (userEntity_t *self)
{
	if (gstate->time >= self->delay)
	{
		com->free (self->userHook);
		gstate->RemoveEntity (self);
		return;
	}

	rock_water_friction (self);

	self->nextthink = gstate->time + 0.1;
	self->think = rock_think;
}

///////////////////////////////////////////////////////////////////////////////
//	spawn_rock
///////////////////////////////////////////////////////////////////////////////

void	spawn_rock (userEntity_t *self, Vector org)
{
	trigHook_t		*ohook = (trigHook_t *) self->userHook;
	rockHook_t		*hook;
	userEntity_t	*temp;
	Vector			mins, maxs;
	int				r;
	float			scale, x_width, y_width, z_width;

	///////////////////////////////////////////////////////////////////////////
	//	get size and determine if we're spawning inside a solid
	///////////////////////////////////////////////////////////////////////////

	scale = rnd () * 1.2 + 0.8;
	
	mins = Vector (-8, -8, -8) * scale;
	maxs = Vector (8, 8, 8) * scale;

	tr = gstate->trace_q2 (org.vec (), mins.vec (), maxs.vec (), org.vec (), self, MASK_SOLID);

	if (tr.startsolid || tr.allsolid)
		//	spawning in a solid, so just abort
		return;

	temp = gstate->SpawnEntity ();

	temp->s.render_scale [0] = temp->s.render_scale [1] = temp->s.render_scale [2] = scale;
	mins.set (temp->s.mins);
	maxs.set (temp->s.maxs);

	temp->solid = SOLID_BBOX;
	temp->takedamage = DAMAGE_NO;
	temp->movetype = MOVETYPE_BOUNCE;
	temp->svflags |= SVF_MONSTER;
	temp->clipmask = MASK_SHOT;
/*
	org.x = org.x + (rnd () * 40) - 20;
	org.y = org.y + (rnd () * 40) - 20;
	org.z = org.z + (rnd () * 40) - 20;
*/
	gstate->SetOrigin (temp, org.x, org.y, org.z);

 	r = rand () & 3;

	if (r == 0)
		temp->s.modelindex = gstate->ModelIndex (ohook->model_1);
	else if (r == 1)
		temp->s.modelindex = gstate->ModelIndex (ohook->model_2);
	else
		temp->s.modelindex = gstate->ModelIndex (ohook->model_3);

	temp->avelocity [0] = rnd () * 1400 - 700;
	temp->avelocity [1] = rnd () * 1400 - 700;
	temp->avelocity [2] = rnd () * 1400 - 700;

	temp->velocity [0] = rnd () * 500 - 250;
	temp->velocity [1] = rnd () * 500 - 250;
	temp->velocity [2] = 270;

	temp->delay = gstate->time + 10.0 + rnd () * 10.0;

	if ((self->spawnflags & EXTRA_VELOCITY)) 
	{
		temp->velocity [0] = temp->velocity [0] * 2.0;
		temp->velocity [1] = temp->velocity [1] * 2.0;
		temp->velocity [2] = temp->velocity [2] * 2.0;
	}

	temp->userHook = com->malloc_hook (sizeof (rockHook_t), MEM_MALLOC);
	hook = (rockHook_t *) temp->userHook;

	// used to scale rotational velocity
	hook->rotation_scale = 0.9;
	hook->mass_scale = 1.0;
	hook->last_water = gstate->PointContents (temp->s.origin);
	hook->hit_water_pos = Vector (temp->s.origin);

	temp->think = rock_think;
	temp->nextthink = gstate->time + 0.1;
}

///////////////////////////////////////////////////////////////////////////////
//	blow_up
///////////////////////////////////////////////////////////////////////////////

void	blow_up (userEntity_t *self)
{
	int				i, r;
	vec3_t			org;
	Vector			rock_org;
	float			x_width, y_width, z_width;

	// throw rock gibbage
	if (self->spawnflags & EXTRA_CHUNKS)
		r = rand () & 3;
	else
		r = 0;

	//	get width, height and depth of func_wall_explode
	x_width = self->s.maxs [0] - self->s.mins [0];
	y_width = self->s.maxs [1] - self->s.mins [1];	
	z_width = self->s.maxs [2] - self->s.mins [2];

	for (i = 0; i <= r; i++)
	{
		rock_org = Vector (self->s.mins [0] + x_width * rnd (), 
							self->s.mins [1] + y_width * rnd (),
							self->s.mins [2] + z_width * rnd ());
		spawn_rock (self, rock_org);
	}

	self->nextthink = -1;

	com->CopyVector (org, self->s.origin);

	if (!(self->spawnflags & NO_EXPLOSIONS))
		spawn_sprite_explosion (self, org, (int)self->delay);

	com->free (self->userHook);
	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	spawn_explosion
///////////////////////////////////////////////////////////////////////////////

float	spawn_explosion (userEntity_t *wall, Vector org, int play_sound)
{
	userEntity_t	*temp;
	float			time;
	trigHook_t  	*hook;

	temp = gstate->SpawnEntity ();

	//	copy func_wall_explode's user hook
	temp->userHook = com->malloc_hook (sizeof (trigHook_t), MEM_MALLOC);
	memcpy (temp->userHook, wall->userHook, sizeof (trigHook_t));

	temp->solid = SOLID_NOT;
	temp->movetype = MOVETYPE_NONE;
	temp->modelName = NULL;
	temp->owner = NULL;
	temp->s.modelindex = 0;
	temp->spawnflags = wall->spawnflags;

	temp->delay = (float)play_sound;

	//	just set origin, but don't link this entity in for clipping
	org.set (temp->s.origin);
	
	//	save mins and maxs for debris origin calculation. Because the 
	//	func_wall_explode is a bmodel, these are absolute world coords
	com->CopyVector (temp->s.mins, wall->s.mins);
	com->CopyVector (temp->s.maxs, wall->s.maxs);

	time = rnd () * 0.25;

	temp->nextthink = gstate->time + time;
	temp->think = blow_up;

	return	time;
}

///////////////////////////////////////////////////////////////////////////////
//	func_wall_explode_remove2
///////////////////////////////////////////////////////////////////////////////

void	func_wall_explode_remove2 (userEntity_t * self)
{
	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	func_wall_explode_remove2
///////////////////////////////////////////////////////////////////////////////

void	func_wall_explode_remove (userEntity_t * self)
{
	self->solid = SOLID_NOT;
	self->modelName = NULL;

	//	self->nextthink = self->ltime + self->frags;
	self->nextthink = gstate->time + self->frags;
	self->think = func_wall_explode_remove2;
}

///////////////////////////////////////////////////////////////////////////////
//	func_wall_explode_die
///////////////////////////////////////////////////////////////////////////////

void	func_wall_explode_die(userEntity_t * self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	Vector	temp, center;
	float	width, x_width, y_width, z_width, explo_count, new_time, max_time;
	int		i;

	// calc the number of explosions this wall will need based on it's size

	temp = Vector (self->size);
	width = temp.vlen ();

	// set the activator to whoever shot the wall
	gstate->activator = gstate->attacker;

	center = Vector (self->absmin) + temp * 0.5;

	x_width = self->absmax [0] - self->absmin [0];
	y_width = self->absmax [1] - self->absmin [1];	
	z_width = self->absmax [2] - self->absmin [2];

	// figure number of explosions based on size of wall
	explo_count = ((x_width / 60) + 1) * ((y_width / 60) + 1) * ((z_width / 60) + 1) - 2;
	if (explo_count < 1) explo_count = 1;

	if (self->spawnflags & NO_CHUNKS)
		explo_count = -1;

	if (!(self->spawnflags & NO_SOUND))
		gstate->sound (self, CHAN_VOICE, "explosions/explode2.wav", 255, ATTN_NORM);

	max_time = 0;
	for (i = 0; i <= explo_count; i++)
	{
		temp = Vector (self->absmax);
		
		temp.x = temp.x - rnd () * x_width;
		temp.y = temp.y - rnd () * y_width;
		temp.z = temp.z - rnd () * z_width;
		
		new_time = spawn_explosion (self, temp, (self->spawnflags & NO_SOUND));
		if (new_time > max_time)
			max_time = new_time;
	}

	if (self->message && attacker->flags & FL_CLIENT)
		gstate->centerprintf (attacker, self->message);

	//	don't remove immediately, because blow_up still refers back to this entity 
	//	and may do so for another couple of thinks
//	self->nextthink = gstate->time + 0.1;
//	self->think = func_wall_explode_remove;

	//	Q2FIXME:	other == attacker??? 
	com->UseTargets (self, attacker, attacker);

	self->takedamage = DAMAGE_NO;

	self->frags = max_time;

	com->free (self->userHook);
	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	func_wall_explode_use
///////////////////////////////////////////////////////////////////////////////

void	func_wall_explode_use (userEntity_t * self, userEntity_t *other, userEntity_t *activator)
{
	func_wall_explode_die (self, activator, activator, 32768, self->s.origin);
}

///////////////////////////////////////////////////////////////////////////////
//	func_wall_explode
///////////////////////////////////////////////////////////////////////////////

void	func_wall_explode (userEntity_t *self)
{
	int			i;
	trigHook_t	*hook;

	// parse epairs
	self->userHook = com->malloc_hook (sizeof (trigHook_t), MEM_MALLOC);
	hook = (trigHook_t *) self->userHook;
	hook->max_health = 0;

	self->targetname = self->target = self->killtarget = NULL;

	for (i = 0; self->epair [i].key != NULL; i++)
	{
		if (!stricmp (self->epair [i].key, "sound"))
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)
				gstate->Precache_Sound (hook->sound);
		}
		else if (!stricmp (self->epair [i].key, "health"))
			self->health = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "model_1"))
			hook->model_1 = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "model_2"))
			hook->model_2 = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "model_3"))
			hook->model_3 = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "target"))
			self->target = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "killtarget"))
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "message"))
			self->message = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
	}

	hook->max_health = self->health;

	if (!hook->model_1)
	{
		if (self->spawnflags & WOOD_CHUNKS)
			hook->model_1 = "models/gibs/wood1/wood11.md2";
		else if (self->spawnflags & ROCK_CHUNKS)
			hook->model_1 = "models/gibs/rock1/rock1.md2";
		else
			hook->model_1 = "models/gibs/rock1/rock1.md2";
	}

	if (!hook->model_2)
	{
		if (self->spawnflags & WOOD_CHUNKS)
			hook->model_2 = "models/gibs/wood2/wood2.md2";
		else if (self->spawnflags & ROCK_CHUNKS)
			hook->model_2 = "models/gibs/rock2/rock2.md2";
		else
			hook->model_2 = "models/gibs/rock2/rock2.md2";
	}

	if (!hook->model_3)
	{
		if (self->spawnflags & WOOD_CHUNKS)
			hook->model_3 = "models/gibs/wood1/wood1.md2";
		else if (self->spawnflags & ROCK_CHUNKS)
			hook->model_3 = "models/gibs/rock3/rock3.md2";
		else
			hook->model_3 = "models/gibs/rock3/rock3.md2";
	}

	gstate->SetOrigin2 (self, self->s.origin);

	self->solid = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;
	self->takedamage = DAMAGE_YES;
	gstate->SetModel (self, self->modelName);

	if (self->health)
		self->die = func_wall_explode_die;
	else
		self->use = func_wall_explode_use;
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_fog_value_touch
///////////////////////////////////////////////////////////////////////////////

void	trigger_fog_value_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	int	i;

	qboolean on;
	int inc, dec, start, end;
	byte r, g, b;

	if (other->flags & FL_CLIENT)
	{

		for (i = 0; self->epair[i].key != NULL; i++)
		{
			if (!stricmp (self->epair[i].key, "on"))
				on = atoi (self->epair[i].value);
			else if (!stricmp (self->epair[i].key, "inc"))
				inc = atoi (self->epair[i].value);
			else if (!stricmp (self->epair[i].key, "dec"))
				inc = -atoi (self->epair[i].value);
			else if (!stricmp (self->epair[i].key, "r"))
				r = atoi (self->epair[i].value);
			else if (!stricmp (self->epair[i].key, "g"))
				g = atoi (self->epair[i].value);
			else if (!stricmp (self->epair[i].key, "b"))
				b = atoi (self->epair[i].value);
			else if (!stricmp (self->epair[i].key, "start"))
				start = atoi (self->epair[i].value);
			else if (!stricmp (self->epair[i].key, "end"))
				end = atoi (self->epair[i].value);
		}

		gstate->WriteByte (SVC_EFFECT_NOTIFY);
		gstate->WriteLong (ET_FOG);
		gstate->WriteByte (on);
		gstate->WriteByte (r);
		gstate->WriteByte (g);
		gstate->WriteByte (b);
		gstate->WriteLong (start);
		gstate->WriteLong (end);
		gstate->WriteByte (inc);
		gstate->UniCast (other, true);
	}
}

///////////////////////////////////////////////////////////////////////////////
//	trigger_fog_value
///////////////////////////////////////////////////////////////////////////////

void	trigger_fog_value (userEntity_t *self)
{
	int		i;

	self->solid = SOLID_TRIGGER;
	gstate->SetModel (self, self->modelName);
	self->movetype = MOVETYPE_NONE;
	self->modelName = NULL;

	self->touch = trigger_fog_value_touch;
}

//////////////////////////
//	warp_continue
//
//	think function for an object other than a monster or client
//	that has gone through a warp
//////////////////////////

void	warp_continue (userEntity_t *self)
{
	warpHook_t		*whook = (warpHook_t *) self->ptr1;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	self->think = whook->think;
	self->nextthink = gstate->time + whook->nextthink;

	self->solid = whook->solid;
	self->movetype = whook->movetype;

	self->s.effects = whook->effects;

	gstate->SetOrigin2 (self, self->s.origin);
	com->CopyVector (self->velocity, whook->velocity);
	com->CopyVector (self->s.render_scale, whook->render_scale);

	com->free (whook);

	self->flags -= FL_INWARP;

	if (self->flags & (FL_MONSTER + FL_BOT))
	{
		//	so exit node from teleporter/warp gets placed at actual exit!
		ai_teleported (self);

		//	set render scale of all parts
		if (hook->dflags & DFL_HIERARCHICAL)
		{
			if (hook->legs)
			{
				com->CopyVector (hook->legs->s.render_scale, self->s.render_scale);

				if (hook->torso)
				{
					com->CopyVector (hook->torso->s.render_scale, self->s.render_scale);

					if (hook->head)
						com->CopyVector (hook->head->s.render_scale, self->s.render_scale);
					if (hook->weapon)
						com->CopyVector (hook->weapon->s.render_scale, self->s.render_scale);
				}
			}
		}
	}
}

//////////////////////////
//	warp_appear_think
//
//	Makes object stretch out of nothing when appearing from
//	a teleporter
//
//	client is immobile until reaching full size
//////////////////////////

#define	SCALE_UP	1.2
#define	SCALE_DOWN	0.8

void	warp_appear_think (userEntity_t *self)
{
	playerHook_t	*hook;
	warpHook_t		*whook = (warpHook_t *) self->ptr1;
	userEntity_t	*child;

	self->s.render_scale [0] *= SCALE_UP;
	self->s.render_scale [1] *= SCALE_UP;
	self->s.render_scale [2] *= SCALE_DOWN;

	if (self->flags & (FL_MONSTER + FL_BOT))
	{
		hook = (playerHook_t *) self->userHook;

		if (hook->dflags & DFL_HIERARCHICAL)
		{
			if (hook->legs)
			{
				hook->legs->s.render_scale [0] *= SCALE_UP;
				hook->legs->s.render_scale [1] *= SCALE_UP;
				hook->legs->s.render_scale [2] *= SCALE_DOWN;
			}
			if (hook->torso)
			{
				hook->torso->s.render_scale [0] *= SCALE_UP;
				hook->torso->s.render_scale [1] *= SCALE_UP;
				hook->torso->s.render_scale [2] *= SCALE_DOWN;
			}
			if (hook->head)
			{
				hook->head->s.render_scale [0] *= SCALE_UP;
				hook->head->s.render_scale [1] *= SCALE_UP;
				hook->head->s.render_scale [2] *= SCALE_DOWN;
			}
			if (hook->weapon)
			{
				hook->weapon->s.render_scale [0] *= SCALE_UP;
				hook->weapon->s.render_scale [1] *= SCALE_UP;
				hook->weapon->s.render_scale [2] *= SCALE_DOWN;
			}
		}
	}

	if (self->s.render_scale [0] > whook->render_scale [0])
	{
		warp_continue (self);

		return;
	}

	self->think = warp_appear_think;
	self->nextthink = gstate->time + 0.05;
}

//////////////////////////
//	warp_appear
//
//	Makes teleporting entity stretch out of nothing when appearing from
//	a teleporter
//
//	entity is immobile until reaching full size
//////////////////////////

void	warp_appear (userEntity_t *self)
{
	playerHook_t	*hook;

	com->SetVector (self->s.render_scale, 0.1, 0.1, 7.4);
	self->s.renderfx -= RF_NODRAW;

	if (self->flags & (FL_MONSTER + FL_BOT))
	{
		hook = (playerHook_t *) self->userHook;

		if (hook->dflags & DFL_HIERARCHICAL)
		{
			//	make hierarchical parts visible
			if (hook->legs)
			{
				com->CopyVector (hook->legs->s.render_scale, self->s.render_scale);
				hook->legs->s.renderfx -= RF_NODRAW;

				if (hook->torso)
				{
					com->CopyVector (hook->torso->s.render_scale, self->s.render_scale);
					hook->torso->s.renderfx -= RF_NODRAW;

					if (hook->head)
					{
						com->CopyVector (hook->head->s.render_scale, self->s.render_scale);
						hook->head->s.renderfx -= RF_NODRAW;
					}
					if (hook->weapon)
					{
						com->CopyVector (hook->weapon->s.render_scale, self->s.render_scale);
						hook->weapon->s.renderfx -= RF_NODRAW;
					}
				}
			}
		}
	}

	////////////////////////////////////////////////////////////////////////////////
	//	splat
	////////////////////////////////////////////////////////////////////////////////

	com->TeleFrag (self, 65535);

	warp_appear_think (self);
}

//////////////////////////
//	warp_path_length
//
//	pass a pointer to the trigger_warp and the length of the path
//	to the last info_warp_destination will be returned
//////////////////////////

float	warp_path_length (userEntity_t *warp)
{
	userEntity_t	*head, *target;
	Vector			temp;
	float			dist;

	head = warp;
	dist = 0.0;
	
	while (head)
	{
		if (!head->target) break;
		target = com->FindTarget (head->target);
		if (!target) break;

		temp = Vector (head->s.origin) - Vector (target->s.origin);
		dist = dist + temp.vlen ();

		head = target;
	}

	return	dist;
}

//////////////////////////
//	warp_final_dest
//
//	returns the final destination of the warp
//////////////////////////

userEntity_t *warp_final_dest (userEntity_t *warp)
{
	userEntity_t	*head, *target;

	head = warp;
	
	while (head && head->target)
	{
		head = com->FindTarget (head->target);
	}

	return	head;
}

//////////////////////////
//	warp_setvalues
//
//////////////////////////

void	warp_setvalues (userEntity_t *self, userEntity_t *target)
{
	float			dist, changes;
	float			looking_yaw, to_targ_yaw;
	float			looking_pitch, to_targ_pitch;
	float			yaw_diff, pitch_diff;
	float			time_to_target, time_scale;
	float			pitch_dir, yaw_dir;
	Vector			temp, ang;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	trigHook_t		*thook = (trigHook_t *) target->userHook;

	////////////////////////////////////////////////////////////
	//	setup speed to next path
	////////////////////////////////////////////////////////////

	if (thook->speed)
		hook->run_speed = thook->speed;

//	hook->run_speed = 50.0;

	// get vector from client to target
	temp = Vector (target->s.origin) - Vector (self->s.origin);
	dist = temp.vlen ();
	temp.Normalize ();

	// get the angles in direction of target
	com->VecToAngles (ang, temp);

	// set the client's velocity
	temp = temp * hook->run_speed;
	temp.set (self->velocity);

	looking_yaw = com->AngleMod (self->client->ps.viewangles [1]);
	hook->force_angles.y = com->AngleMod (ang.y);
	self->ideal_ang [1] = hook->force_angles.y;

	looking_pitch = self->client->ps.viewangles [0];
	
	// VecToAngles returns a pitch of 0 - 360 so we have to
	// adjust ideal_pitch to -80 - 70, like v_angle [0] normally is

	hook->ideal_pitch = -ang.x;
	if (hook->ideal_pitch >= 180.0) 
		hook->ideal_pitch -= 360.0;
	else if (hook->ideal_pitch <= -180)
		hook->ideal_pitch += 360.0;
	hook->force_angles.x = hook->ideal_pitch;

	com->BestDelta (looking_yaw, hook->force_angles.y, &yaw_dir, &yaw_diff);
	com->BestDelta (looking_pitch, hook->force_angles.x, &pitch_dir, &pitch_diff);

	// divide dist by speed to get time to target
	time_to_target = dist / hook->run_speed;
	self->teleport_time = gstate->time + time_to_target;

	//	find rate of pitch change in degrees per second 
	hook->force_rate.x = (pitch_diff / time_to_target) * pitch_dir * 4.0;
	//	find rate of yaw change in degrees per second 
	hook->force_rate.y = (yaw_diff / time_to_target) * yaw_dir * 4.0;

	//	do one half revolution while traveling from one dest to the next
	hook->force_rate.z = 90.0 / time_to_target;
}

//////////////////////////
//	stretchy_think
//
//////////////////////////

void	stretchy_think (userEntity_t *self)
{
	////////////////////////////////////////////////
	//	side to side stretch
	////////////////////////////////////////////////
/*	
	self->s.render_scale [1] *= self->delay;
	
	self->s.render_scale [0] *= self->frags;
	self->s.render_scale [2] *= self->frags;

	if (self->s.render_scale [0] < 0.1 && self->delay > 1.0)
		self->delay = 0.5;
	if (self->s.render_scale [1] < 0.1)
	{
		if (self->flags & FL_STICKYCHILD)
			gstate->StickyRemove (self, self->owner);
		gstate->RemoveEntity (self);
		return;
	}
*/
	////////////////////////////////////////////////
	//	upward stretch
	////////////////////////////////////////////////
	
	self->s.render_scale [2] *= self->delay;
	
	self->s.render_scale [0] *= self->frags;
	self->s.render_scale [1] *= self->frags;

	if (self->s.render_scale [0] < 0.1 && self->delay > 1.0)
		self->delay = 0.5;
	if (self->s.render_scale [2] < 0.1)
	{
//		if (self->flags & FL_HCHILD)
//			gstate->hr_RemoveChild (self, self->owner, false);
		gstate->RemoveEntity (self);
		return;
	}

	////////////////////////////////////////////////
	//	forward stretch -- bleh
	////////////////////////////////////////////////
/*	
	self->s.render_scale [0] *= self->delay;
	
	self->s.render_scale [1] *= self->frags;
	self->s.render_scale [2] *= self->frags;

	if (self->s.render_scale [1] < 0.1 && self->delay > 1.0)
		self->delay = 0.5;
	if (self->s.render_scale [0] < 0.1)
	{
		if (self->flags & FL_STICKYCHILD)
			gstate->hr_RemoveChild (self, self->owner, false);
		gstate->RemoveEntity (self);
		return;
	}
*/
	self->nextthink = gstate->time + 0.05;
	self->think = stretchy_think;
}

//////////////////////////
//	warp_spawn_stretchy
//
//////////////////////////

void	warp_spawn_stretchy (userEntity_t *self)
{
	userEntity_t	*torso, *head, *legs, *gun, *temp, *rubber;
	playerHook_t	*hook;
	int				i, imax;

	if (self->flags & (FL_CLIENT + FL_BOT + FL_MONSTER))
	{
		hook = (playerHook_t *) self->userHook;

		if (hook->dflags & DFL_HIERARCHICAL)
			imax = 4;
		else
			imax = 1;
	}
	else
		imax = 1;
	
	//	if hierarchical model... spawn copies of head, torso, legs and weapon
	for (i = 0; i < imax; i++)
	{
		if (imax == 4)
		{
			if (i == 0)
				temp = hook->legs;
			else if (i == 1)
				temp = hook->torso;
			else if (i == 2)
				temp = hook->head;
			else if (i == 3)
				temp = hook->weapon;
		}
		else
			temp = self;

		rubber = gstate->SpawnEntity ();
		rubber->movetype = MOVETYPE_NONE;
		rubber->solid = SOLID_NOT;

		rubber->s.modelindex = temp->s.modelindex;

		VectorCopy (zero_vector, rubber->s.mins);
		VectorCopy (zero_vector, rubber->s.maxs);

		VectorCopy (temp->s.origin, rubber->s.origin);
		gstate->LinkEntity (rubber);

		VectorCopy (temp->s.render_scale, rubber->s.render_scale);
		VectorCopy (temp->s.angles, rubber->s.angles);

		rubber->delay = 1.2;
		rubber->frags = 0.8;

		rubber->think = stretchy_think;
		rubber->nextthink = gstate->time + 0.05;
	}
}

//////////////////////////
//	warp_touch
//
//////////////////////////

void	warp_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	userEntity_t	*target, *dest;
	Vector			temp, ang;
	playerHook_t	*hook;
	trigHook_t		*thook = (trigHook_t *) self->userHook;
	warpHook_t		*whook;
	float			dist, speed, time_to_target;
	teleportNode_t	*node_data;

	if (other->flags & FL_INWARP)
		return;

	dest = warp_final_dest (self);

	if (other->flags & (FL_CLIENT + FL_BOT + FL_MONSTER))
	{
		//	force a teleport node to be placed at the location of this teleport
		hook = (playerHook_t *) other->userHook;
		temp = (Vector (self->absmin) + Vector (self->absmax)) * 0.5;

		//	create the necessary node data for this teleport
		node_data = (teleportNode_t *) com->malloc (sizeof (teleportNode_t), MEM_MALLOC);
		temp.set (node_data->start_origin);
		com->CopyVector (node_data->dest_origin, dest->s.origin);

		node_force_node (other, hook->node_list, node_data->start_origin, NODE_TELEPORTER, node_data);
	}

	if (other->flags & FL_CLIENT)
	{
		if (!self->target)
		{
			gstate->Con_Dprintf ("trigger_warp without target!");
			return;
		}

		target = com->FindTarget (self->target);
		if (!target) 
		{
			gstate->Con_Dprintf ("trigger_warp could not find target!");
			return;
		}

		//	make a duplicate of this model for warp effect
		warp_spawn_stretchy (other);

		other->solid = SOLID_NOT;
		other->movetype = MOVETYPE_NOCLIP;

		gstate->LinkEntity (other);

		// move client towards target ent
		hook = (playerHook_t *) other->userHook;

		Client_SetFOVRamp (other, 120, 170);

		//	don't draw any of the hierarchical models
		other->s.renderfx |= RF_NODRAW;
		if (hook->legs)
			hook->legs->s.renderfx |= RF_NODRAW;
		if (hook->torso)
			hook->torso->s.renderfx |= RF_NODRAW;
		if (hook->head)
			hook->head->s.renderfx |= RF_NODRAW;
		if (hook->weapon)
			hook->weapon->s.renderfx |= RF_NODRAW;

		other->view_ofs [2] = 0.0;

		if (!thook->speed)
		{
			// preferred 2 second travel time
			hook->run_speed = warp_path_length (self) / 2.0;
			if (hook->run_speed > 1500.0) hook->run_speed = 1500.0;
		}
		else
			hook->run_speed = thook->speed;

		// save the target for checking in the client's prethink
		other->enemy = target;

		warp_setvalues (other, target);

		//	make client glow if spawnflag is set on target
		if (target->spawnflags & INFOWARP_GLOW)
			other->s.effects |= EF_DIMLIGHT;

		//	if destination is not visible, then go straight to next path
		if (target->spawnflags & INFOWARP_SKIPTO)
		{
			//	put player right on path corner
			VectorCopy (target->s.origin, other->s.origin);
			gstate->LinkEntity (other);

			//	make player face next target
			if (other->enemy->target)
			{
				target = com->FindTarget (other->enemy->target);
			
				if (target)
				{
					temp = Vector (target->s.origin) - Vector (other->enemy->s.origin);
					temp.vlen ();
					com->VecToAngles (ang, temp);

					gstate->SetClientAngles (other, ang.vec ());
					other->flags |= FL_FIXANGLES;
				}
			}

			self->teleport_time = gstate->time;
		}

		other->flags |= FL_INWARP;

		//	turn off chasecam if it is on
		camera_stop (other);

		//	turn on camera
		gstate->SetCameraState (other, true, CAMFL_LOCK_X + CAMFL_LOCK_Y + CAMFL_LOCK_Z);
		Client_SetForceAngles (other);

		gstate->sound (other, CHAN_BODY, "misc/warpstart.wav", 255, ATTN_NORM);
	}
	///////////////////////////////////////////////////////////////////
	//	other object warping
	//
	//	save some important data and then set objects think to
	//	warp_appear at time it takes to go through warp
	//
	//	objects with self->ptr1 != NULL won't teleport (floaters, etc.)
	///////////////////////////////////////////////////////////////////
	else if (!other->ptr1)
	{
		dist = warp_path_length (self);
		speed = dist / 2.0;
		if (speed > 1500.0) speed = 1500.0;

		time_to_target = dist / speed;

		//	make a duplicate of this model for warp effect
		warp_spawn_stretchy (other);
		
		whook = (warpHook_t *) com->malloc_hook (sizeof (warpHook_t), MEM_MALLOC);
		other->ptr1 = whook;

		whook->think = other->think;
		whook->nextthink = other->nextthink - gstate->time;;
		whook->movetype = other->movetype;
		whook->solid = other->solid;
		whook->effects = other->s.effects;
		com->CopyVector (whook->velocity, other->velocity);
		com->CopyVector (whook->render_scale, other->s.render_scale);

		other->solid = SOLID_NOT;
		other->movetype = MOVETYPE_NONE;
		other->s.renderfx = RF_NODRAW;
		other->flags |= FL_INWARP;
		com->CopyVector (other->s.angles, dest->s.angles);
		com->ZeroVector (other->velocity);

		gstate->SetOrigin2 (other, dest->s.origin);

		if (other->flags & FL_BOT + FL_MONSTER)
		{
			hook = (playerHook_t *) other->userHook;
			com->CopyVector (self->s.angles, dest->s.angles);

			if (hook->dflags & DFL_HIERARCHICAL)
			{
				if (hook->legs)
				{
					com->CopyVector (hook->legs->s.angles, dest->s.angles);
					hook->legs->s.renderfx |= RF_NODRAW;

					if (hook->torso)
					{
						com->CopyVector (hook->torso->s.angles, dest->s.angles);
						hook->torso->s.renderfx |= RF_NODRAW;

						if (hook->head)
						{
							com->CopyVector (hook->head->s.angles, dest->s.angles);
							hook->head->s.renderfx |= RF_NODRAW;
						}

						if (hook->weapon)
						{
							com->CopyVector (hook->weapon->s.angles, dest->s.angles);
							hook->weapon->s.renderfx |= RF_NODRAW;
						}
					}
				}
			}
		}

		other->think = warp_appear;
		other->nextthink = gstate->time + time_to_target;
	}
}

///////////////////////////////////////////////////////////////////////////////
//  trigger_warp
//
///////////////////////////////////////////////////////////////////////////////

void	trigger_warp (userEntity_t *self)
{
	trigHook_t	*hook;
	int			i;

	trigger_init (self);

	// parse epairs
	hook = (trigHook_t *) self->userHook;
	hook->count = 0;
	hook->speed = 0.0;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)	
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "fog_value"))
			hook->model_1 = self->epair [i].value;

		i++;
	}

//	trigger_setup (self);

	self->touch = warp_touch;
	self->className = "trigger_warp";

	if (!self->target)
		com->Error ("warp has no target!\n");

	if (!hook->sound)
	{
		hook->sound = "misc/teleportstart.wav";
		gstate->Precache_Sound (hook->sound);
		gstate->Precache_Sound ("misc/teleportend.wav");
	}
}

///////////////////////////////////////////////////////////////////////////////
//	info_warp_destination
///////////////////////////////////////////////////////////////////////////////

void	info_warp_destination (userEntity_t *self)
{
	Vector		temp;
	int			i;
	trigHook_t	*hook;

	self->userHook = com->malloc_hook (sizeof (trigHook_t), MEM_MALLOC);
	hook = (trigHook_t *) self->userHook;
	hook->speed = 0.0;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound = self->epair [i].value;
			if (hook->sound)			
				gstate->Precache_Sound (hook->sound);
		}
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		i++;
	}
	
	self->className = "info_warp_destination";

	hook->mangle = Vector (self->s.angles);
	hook->v_angle = Vector (0, 0, 0);
	self->modelName = "";
	
	temp = Vector (0.0, 0.0, 27.0) + Vector (self->s.origin);
	temp.set (self->s.origin);

	if (!self->targetname)
		com->Error ("No targetname for info_warp_dest\n");
}

//	testing

void	warp_cylinder (userEntity_t *self)
{
	gstate->Precache_Model("models/warp.mdl");

	gstate->SetModel (self, "models/warp.mdl");
	self->movetype = MOVETYPE_NONE;
	self->solid = SOLID_NOT;

	self->translucent = 4;
}