#include "world.h"
#include "triggers.h"
#include "misc.h"

//DllExport	void	func_floater (userEntity_t *self);
DllExport	void	func_debris (userEntity_t *self);
DllExport	void	func_debris_visible (userEntity_t *self);
DllExport	void	info_null (userEntity_t *self);
DllExport	void	info_not_null (userEntity_t *self);
DllExport	void	sound_ambient (userEntity_t *self);
DllExport	void	func_particlefield (userEntity_t *self);
DllExport	void	misc_lavaball_drop (userEntity_t *self);
DllExport	void	misc_lavaball_toss (userEntity_t *self);
DllExport	void	func_explosive (userEntity_t *self);

////////////////////////////////////////////////////////////////////////////////
// decorations
////////////////////////////////////////////////////////////////////////////////

DllExport	void	deco_e1		(userEntity_t *self);
DllExport	void	deco_e2		(userEntity_t *self);
DllExport	void	deco_e3		(userEntity_t *self);
DllExport	void	deco_e4		(userEntity_t *self);
//DllExport	void	deco_barrel		(userEntity_t *self);
//DllExport	void	deco_chair		(userEntity_t *self);
//DllExport	void	deco_vase  		(userEntity_t *self);
//DllExport	void	deco_trashcan	(userEntity_t *self);
//DllExport	void	deco_trashbag	(userEntity_t *self);
//DllExport	void	deco_tree		(userEntity_t *self);

////////////////////////////////////////////////////////////////////////////////
// your mother
////////////////////////////////////////////////////////////////////////////////

#define	EXPL_TRIGGER_SPAWN	0x0001
#define	EXPL_ANIM			0x0002
#define	EXPL_ANIMFAST		0x0004
#define	EXPL_GLASS			0x0008
#define	EXPL_WOOD			0x0010
#define	EXPL_METAL			0x0020

void throw_debris (edict_t *self, char *modelname, float speed, Vector origin, int renderfx, Vector scale);

static	int	temp_count;

// func_floater defines
#define		RANDOM_WANDER	1
#define		ROTATE_YAW		2

// func_debris defines
#define		GO_TO_ACTIVATOR		1
#define		NO_ROTATE			2
#define		MOMENTUM_DAMAGE		4
#define		NO_ROTATION_ADJUST	8
#define		DROP_ONLY			16
#define		QUARTER_SIZE		32

//	deco_ defines
#define		DECO_EXPLODE		0x0001
#define		DECO_NO_EXPLODE		0x0002
#define		DECO_PUSHABLE		0x0004
#define		DECO_WOOD     		0x0008
#define		DECO_METAL    		0x0010
#define		DECO_GLASS    		0x0020
#define		DECO_WHITE    		0x0040

typedef	struct	lavaballHook_s
{
	vec3_t			targ_origin;

	float			time_min;
	float			time_rng;

	float			z_min;
	float			z_rng;

	float			damage;
} lavaballHook_t;


typedef	struct funcExploHook_s
{
	int		damage;
} funcExploHook_t;

typedef	struct decoHook_s
{
	int				damage;
	userEntity_t	*inflictor;
	userEntity_t	*attacker;
	int				damage_done;
	vec3_t			point;
	int				explo_count;
	int				explo_max;
} decoHook_t;

///////////////////////////////////////////////////////////////////////////////
//	spawn_explosion
//
//	now done with a temporary entity
///////////////////////////////////////////////////////////////////////////////

void	spawn_sprite_explosion (userEntity_t *self, vec3_t org, int play_sound)
{
	gstate->WriteByte (SVC_TEMP_ENTITY);

	//	FIXME:	make a separate te_explosion type for no sound	
	if (play_sound)
		gstate->WriteByte (TE_EXPLOSION1);
	else
		gstate->WriteByte (TE_EXPLOSION2);

	gstate->WritePosition (org);
	gstate->MultiCast (org, MULTICAST_PVS);
}

///////////////////////////////////////////////////////////////////////////////
//	spawn_small_sprite_explosion
//
//	now done with a temporary entity
///////////////////////////////////////////////////////////////////////////////

void	spawn_small_sprite_explosion (userEntity_t *self, vec3_t org, int play_sound)
{
	gstate->WriteByte (SVC_TEMP_ENTITY);

	//	FIXME:	make a separate te_explosion type for no sound	
	if (play_sound)
		gstate->WriteByte (TE_EXPLOSION_SMALL1);
	else
		gstate->WriteByte (TE_EXPLOSION_SMALL2);

	gstate->WritePosition (org);
	gstate->MultiCast (org, MULTICAST_PVS);
}


/*
===============
func_debris_water_friction

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

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

	org = (Vector (self->absmin) + Vector (self->absmax)) * 0.5;
	pc = gstate->PointContents (org.vec ());

	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 entered the water, so scale velocity by half
		hook->hit_water_pos = org;
		dir = Vector (self->velocity) * 0.5 * hook->mass_scale;
		dir.set (self->velocity);
		hook->change_time = gstate->time;

		// make it fall slower
		self->gravity = 0.5 * hook->mass_scale;

//		gstate->sound (self, CHAN_AUTO, "bloop.wav", 255, ATTN_NORM);
	}
	else if (!self->targetname)
	{
		if ((self->movetype == MOVETYPE_BOUNCE) || (self->movetype == MOVETYPE_TOSS))
		{
			if (hook->change_time <= gstate->time)
			{
				hook->hit_water_pos.x = org.z + (rnd () - 0.2) * (rnd () * 256.0 + 32.0);
	
				dir = Vector (hook->hit_water_pos) - org;
				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 
				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;
}

/*
==================
func_debris_think

Nelno says "I da man!"
==================
*/

void	func_debris_think (userEntity_t *self)
{
	debrisHook_t	*hook = (debrisHook_t *) self->userHook;
	int				count;
	float			x_add, y_add, z_add;

//	func_debris_water_friction (self);

	// if it is not on the ground and it's velocity magnitude is > 80 set think and leave
	
	if (self->spawnflags & NO_ROTATION_ADJUST)
	{
		if (!(self->flags & FL_ONGROUND))
		{
			//	self->nextthink = self->ltime + 0.1;
			self->nextthink = gstate->time + 0.1;
			self->think = func_debris_think;

			hook->avel = Vector (self->avelocity);
		}
		else
		{
			// no more hurt
			self->touch = NULL;

			com->ZeroVector (self->avelocity);
	
			x_add = self->s.angles [0];
			y_add = self->s.angles [1];
			z_add = self->s.angles [2];

			// turn it into a bmodel again to make it walkable
			if (self->spawnflags & NO_ROTATION_ADJUST)	
			{
				self->solid = SOLID_BSP;
				self->movetype = MOVETYPE_PUSH;
			}
			else
			{
				self->solid = SOLID_BSP;
				self->movetype = MOVETYPE_PUSH;
				self->s.angles [0] = 0;
				self->s.angles [2] = 0;
			}

			gstate->SetModel (self, hook->modelName);
		}

	}
	else
	{
		if (qv_len (self->velocity) > 80 || !(self->flags & FL_ONGROUND))
		{
			//	self->nextthink = self->ltime + 0.1;
			self->nextthink = gstate->time + 0.1;
			self->think = func_debris_think;

			hook->avel = Vector (self->avelocity);
		}
		// velocity is low, so we are coming to a stop, start rotating bmodel into
		// correct position
		else if (!hook->has_rotated)
		{
			com->ZeroVector (self->avelocity);
	
			if (self->s.angles [0] < 180)
			{
				if (fabs (180 - self->s.angles [0]) < fabs (self->s.angles [0]))
					x_add = fabs (hook->avel.x) * 0.1;		
				else
					x_add = fabs (hook->avel.x) * -0.1;
			}
			else
			{
				if (fabs (180 - self->s.angles [0]) < fabs (self->s.angles [0]))
					x_add = fabs (hook->avel.x) * -0.1;		
				else
					x_add = fabs (hook->avel.x) * 0.1;
			}
	
			if (self->s.angles [2] < 180)
			{
				if (fabs (180 - self->s.angles [2]) < fabs (self->s.angles [2]))		
					z_add = fabs (hook->avel.z) * 0.1;
				else
					z_add = fabs (hook->avel.z) * -0.1;
			}
			else
			{
				if (fabs (180 - self->s.angles [2]) < fabs (self->s.angles [2]))		
					z_add = fabs (hook->avel.z) * -0.1;
				else
					z_add = fabs (hook->avel.z) * 0.1;
			}
	
			self->s.angles [0] = com->AngleMod (self->s.angles [0] + x_add);
			self->s.angles [2] = com->AngleMod (self->s.angles [2] + z_add);
		
			count = 0;
			z_add = fabs (z_add);
			x_add = fabs (x_add);
	
			// check pitch
			if (x_add > 0)
			{
				if (fabs (180 - self->s.angles [0]) < x_add)
				{
					hook->avel.x = 0;
					self->s.angles [0] = 180;
					count++;
				}	
				else if (self->s.angles [0] < x_add)
				{
					hook->avel.x = 0;
					self->s.angles [0] = 0;
					count++;
				}
			}
			else count++;
	
			if (z_add > 0)
			{
				// check roll rotation
				if (fabs (180 - self->s.angles [2]) < z_add)
				{
					hook->avel.z = 0;
					self->s.angles [2] = 180;
					count++;
				}
				else if (self->s.angles [2] < z_add)
				{	
					hook->avel.z = 0;
					self->s.angles [2] = 180;
					count++;
				}
			}
			else count++;
	
			// are both angles 0?
			if (count == 2)
				hook->has_rotated = TRUE;
	
			hook->avel.x = hook->avel.x * 1.05;
			hook->avel.z = hook->avel.z * 1.05;
	
			//	self->nextthink = self->ltime + 0.1;
			self->nextthink = gstate->time + 0.1;
			self->think = func_debris_think;
		}
		else
		{
			// no more hurt
			self->touch = NULL;
	
			com->ZeroVector (self->avelocity);
	
			x_add = self->s.angles [0];
			y_add = self->s.angles [1];
			z_add = self->s.angles [2];

			// turn it into a bmodel again to make it walkable
			if (self->spawnflags & NO_ROTATION_ADJUST)	
			{
				self->solid = SOLID_BSP;
				self->movetype = MOVETYPE_PUSH;
			}
			else
			{
				self->solid = SOLID_BSP;
				self->movetype = MOVETYPE_PUSH;
				self->s.angles [0] = 0;
				self->s.angles [2] = 0;
			}

			gstate->SetModel (self, hook->modelName);
		}
	}
}

/*
==================
func_debris_touch

damages the entity it touches
==================
*/

void	func_debris_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	debrisHook_t	*hook = (debrisHook_t *) self->userHook;
	float			damage;

	if (other->takedamage)
	{
		// calls with the activator as the attacker, so you can get a frag for
		// killing with debris
		if (self->spawnflags & MOMENTUM_DAMAGE)
			damage = qv_len (self->velocity) / hook->damage;
		else
			damage = hook->damage;
		com->Damage (other, self, gstate->activator, zero_vector, zero_vector, damage, DAMAGE_SQUISH | DAMAGE_NO_BLOOD);
	}
	
	if (hook->hit_sound)
	{
		gstate->sound (self, CHAN_AUTO, hook->hit_sound, 255, ATTN_NORM);
	}
}


/*
==================
debris_vel

sets the correct forward velocity need to fly distxy units forward and
land distz units below current position
==================
*/

float	debris_vel	(userEntity_t *self, float distxy, float z, float up_vel)
{
	float			t, end_z, g, vt;

	if (self->gravity != 0.0)
		g = (gstate->GetCvar ("sv_gravity") * self->gravity) / 2;
	else
		g = (gstate->GetCvar ("sv_gravity")) / 2;
	t = 0.1;

	do
	{
		t = t + 0.1;
		
		// get velocity at time 1
		vt = up_vel - g * 2.0 * t;	
		if (vt < 0.0)
		{
			// get z position at time t
			end_z = self->s.origin [2] + up_vel * t - g * t * t;

			if (end_z <= z) break;
		}
	} while (1);

	return	distxy / t;
};

/*
==========
func_debris_use
==========
*/

void	func_debris_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	debrisHook_t	*hook = (debrisHook_t *) self->userHook;
	Vector			temp, org;
	float			distxy, distz, forward_vel, up_vel;
	int				pc;

	self->movetype = MOVETYPE_BOUNCE;
	self->solid = SOLID_BBOX;

//	gstate->SetModel (self, hook->modelName);
	self->s.modelindex = gstate->ModelIndex (hook->modelName);

/*
	if (self->spawnflags & QUARTER_SIZE)
		gstate->SetSize (self, self->s.mins [0] / 4, self->s.mins [1] / 4, self->s.mins [2] / 4, self->s.maxs [0] / 4, self->s.maxs [1] / 4, self->s.maxs [2] / 4);
	else
		gstate->SetSize (self, self->s.mins [0], self->s.mins [1], self->s.mins [2], self->s.maxs [0], self->s.maxs [1], self->s.maxs [2]);
*/	
	temp = Vector (self->absmax) - Vector (self->s.origin);
	temp.set (self->s.maxs);
	
	temp = Vector (self->absmin) - Vector (self->s.origin);
	temp.set (self->s.mins);

	if (self->spawnflags & QUARTER_SIZE)
	{
		temp = Vector (self->s.mins) * 0.25;
		temp.set (self->s.mins);
		temp = Vector (self->s.maxs) * 0.25;
		temp.set (self->s.maxs);
	}

	// fix up origin
	org = (Vector (self->absmin) + Vector (self->absmax)) * 0.5;

	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);

	if (self->spawnflags & GO_TO_ACTIVATOR)// && activator)
		hook->dest = Vector (gstate->activator->s.origin);

	self->gravity = 1.0;
	//func_debris_water_friction (self);

	// if no target, then give random velocities
	if (!self->target && !(self->spawnflags & GO_TO_ACTIVATOR))
	{
		if (!(self->spawnflags & DROP_ONLY))
		{
			temp = Vector (rnd () * 500 - 250, rnd () * 500 - 250, rnd () * 300 + 200);
			temp.set (self->velocity);
		}
	}
	else
	{
		// has a target, so find velocity required to reach it
		temp = Vector (hook->dest.x, hook->dest.y, 0) - Vector (org.x, org.y, 0);
		temp = org - hook->dest;
		distxy = temp.vlen ();

		if (self->spawnflags & DROP_ONLY)
			up_vel = 0;
		else if (hook->last_water == CONTENTS_LAVA || hook->last_water == CONTENTS_WATER || 
			hook->last_water == CONTENTS_SLIME)
			up_vel = rnd () * 150 + 150;
		else
			up_vel = rnd () * 300 + 300;

		if (self->targetname)
			if (!stricmp (self->targetname, "fanboom"))
				up_vel = 50;
		
		forward_vel = debris_vel (self, distxy, hook->dest.z, up_vel);

		temp = hook->dest - org;
		temp.Normalize ();

		temp = temp * forward_vel;
		temp.z = up_vel;
		if (self->targetname)
			if (!stricmp (self->targetname, "fanboom"))
			{
//				temp = temp * 0.8;
//				gstate->Con_Printf ("fanboom, vel = %f\n", temp.vlen ());
//				temp.z = 60;
			}
		temp.set (self->velocity);
	}

	if (!(self->spawnflags & NO_ROTATE))
	{
		temp = Vector (rnd () * 200, 0, rnd () * 200);
		temp.set (self->avelocity);
		hook->avel = temp;
	}
	
	self->think = func_debris_think;
	//	self->nextthink = self->ltime + 0.1;
	self->nextthink = gstate->time + 0.1;
	self->touch = func_debris_touch;

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

/*
==========
func_debris_find_target
==========
*/

void	func_debris_find_target (userEntity_t *self)
{
	debrisHook_t	*hook = (debrisHook_t *) self->userHook;
	userEntity_t	*head;

	if (self->target)
	{
		// find target
		head = gstate->FirstEntity ();
		while (head)
		{
			if (head->targetname)
			{
				if (stricmp (self->target, head->targetname) == 0)
				{
					hook->dest = Vector (head->s.origin);
				}
			}
			// set up owners so debris won't clip against each other
			else if (head->target && self->targetname)
			{
				if (stricmp (head->target, self->targetname) == 0)
				{
					self->owner = head;
				}
			}

			head = gstate->NextEntity (head);
		}
	}		
}

/*
==========
func_debris
==========
*/

void	func_debris (userEntity_t *self)
{
	debrisHook_t	*hook;
	int				i;
	Vector			org;

	// parse epairs
	self->userHook = com->malloc_hook (sizeof (debrisHook_t), MEM_HUNK);
	hook = (debrisHook_t *) self->userHook;
	hook->damage = -1;
	hook->has_rotated = FALSE;
	hook->mass_scale = 1.2;
	hook->rotation_scale = 0.9;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "fly_sound") == 0)
		{
			hook->fly_sound = self->epair [i].value;
			if (hook->fly_sound)
				gstate->Precache_Sound (hook->fly_sound);
		}
		else if (stricmp (self->epair [i].key, "hit_sound") == 0)
		{
			hook->hit_sound = self->epair [i].value;
			if (hook->hit_sound)
				gstate->Precache_Sound (hook->hit_sound);
		}
		else if (stricmp (self->epair [i].key, "damage") == 0)
			hook->damage = atof (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, "targetname") == 0)
			self->targetname = 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, "message") == 0)
			self->message = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "mass_scale") == 0)
			hook->mass_scale = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "rotation_scale") == 0)
			hook->rotation_scale = 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, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "GO_TO_ACTIVATOR") == 0)
				self->spawnflags = self->spawnflags | GO_TO_ACTIVATOR;
			else if (stricmp (self->epair [i].value, "NO_ROTATE") == 0)
				self->spawnflags = self->spawnflags | NO_ROTATE;
			else if (stricmp (self->epair [i].value, "MOMENTUM_DAMAGE") == 0)
				self->spawnflags = self->spawnflags | MOMENTUM_DAMAGE;
			else if (stricmp (self->epair [i].value, "NO_ROTATION_ADJUST") == 0)
				self->spawnflags = self->spawnflags | NO_ROTATION_ADJUST;
			else if (stricmp (self->epair [i].value, "DROP_ONLY") == 0)
				self->spawnflags = self->spawnflags | DROP_ONLY;
			else if (stricmp (self->epair [i].value, "QUARTER_SIZE") == 0)
				self->spawnflags = self->spawnflags | QUARTER_SIZE;
		}
		i++;
	}

	if (hook->damage = -1)
	{
		hook->damage = 3;
		self->spawnflags = self->spawnflags | MOMENTUM_DAMAGE;
	}

//	self->spawnflags = self->spawnflags | QUARTER_SIZE;

	if (self->spawnflags & NO_ROTATE)
		hook->has_rotated = TRUE;

	self->movetype = MOVETYPE_NONE;
	self->takedamage = DAMAGE_NO;
	self->solid = SOLID_NOT;
	hook->modelName = strdup (self->modelName);

	org = (Vector (self->absmin) + Vector (self->absmax)) * 0.5;
	hook->last_water = gstate->PointContents (org.vec ());
	hook->dest = Vector (0, 0, 0);

	self->use = func_debris_use;

	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	gstate->SetModel (self, self->modelName);
	self->s.modelindex = 0;

	temp_count = 0;

	self->think = func_debris_find_target;
	//	self->nextthink = self->ltime + 0.2;
	self->nextthink = gstate->time + 0.1;
}

///////////////////////////////
//	func_debris_visible
//
//
///////////////////////////////

void	func_debris_visible (userEntity_t *self)
{
	debrisHook_t	*hook;
	int				i;
	Vector			org;

	// parse epairs
	self->userHook = com->malloc_hook (sizeof (debrisHook_t), MEM_HUNK);
	hook = (debrisHook_t *) self->userHook;
	hook->damage = -1;
	hook->has_rotated = FALSE;
	hook->mass_scale = 1.2;
	hook->rotation_scale = 0.9;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "fly_sound") == 0)
		{
			hook->fly_sound = self->epair [i].value;
			if (hook->fly_sound)
				gstate->Precache_Sound (hook->fly_sound);
		}
		else if (stricmp (self->epair [i].key, "hit_sound") == 0)
		{
			hook->hit_sound = self->epair [i].value;
			if (hook->hit_sound)
				gstate->Precache_Sound (hook->hit_sound);
		}
		else if (stricmp (self->epair [i].key, "damage") == 0)
			hook->damage = atof (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, "targetname") == 0)
			self->targetname = 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, "message") == 0)
			self->message = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "mass_scale") == 0)
			hook->mass_scale = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "rotation_scale") == 0)
			hook->rotation_scale = 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, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "GO_TO_ACTIVATOR") == 0)
				self->spawnflags = self->spawnflags | GO_TO_ACTIVATOR;
			else if (stricmp (self->epair [i].value, "NO_ROTATE") == 0)
				self->spawnflags = self->spawnflags | NO_ROTATE;
			else if (stricmp (self->epair [i].value, "MOMENTUM_DAMAGE") == 0)
				self->spawnflags = self->spawnflags | MOMENTUM_DAMAGE;
			else if (stricmp (self->epair [i].value, "NO_ROTATION_ADJUST") == 0)
				self->spawnflags = self->spawnflags | NO_ROTATION_ADJUST;
			else if (stricmp (self->epair [i].value, "DROP_ONLY") == 0)
				self->spawnflags = self->spawnflags | DROP_ONLY;
			else if (stricmp (self->epair [i].value, "QUARTER_SIZE") == 0)
				self->spawnflags = self->spawnflags | QUARTER_SIZE;
		}
		i++;
	}

	if (hook->damage = -1)
	{
		hook->damage = 3;
		self->spawnflags = self->spawnflags | MOMENTUM_DAMAGE;
	}

	if (self->spawnflags & NO_ROTATE)
		hook->has_rotated = TRUE;

	self->solid = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;

	self->use = func_debris_use;
	self->touch = NULL;

	hook->modelName = strdup (self->modelName);

	org = (Vector (self->absmin) + Vector (self->absmax)) * 0.5;
	hook->last_water = gstate->PointContents (org.vec ());
	hook->dest = Vector (0, 0, 0);

	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	gstate->SetModel (self, self->modelName);

	self->think = func_debris_find_target;
	//	self->nextthink = self->ltime + 0.2;
	self->nextthink = gstate->time + 0.2;
}

/*
==========
info_null_remove
==========
*/

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

/*
==========
info_null
==========
*/

void	info_null (userEntity_t *self)
{
	int		i;

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


	self->className = "info_null";

	self->nextthink = gstate->time + 0.2;
	self->think = info_null_remove;
}

///////////////////////////////////////////////////////////////////////////////
//	info_not_null
///////////////////////////////////////////////////////////////////////////////

void	info_not_null (userEntity_t *self)
{
	int		i;

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


	self->className = "info_not_null";

	self->s.modelindex = 0;
//	self->s.renderfx = RF_NODRAW;
}

/*
=============
sound_ambient
=============
*/

void	sound_ambient (userEntity_t *self)
{
	int		i, index;
	char	*sound;
	float	fade = 1.0;
	int		volume = 255;

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

		i++;
	}

	if (sound)
	{
		//	Q2KILL
		//	gstate->AmbientSound (self->s.origin, sound, 255, ATTN_STATIC, fade);
		index = gstate->SoundIndex (sound);
		gstate->PositionedSound (self->s.origin, self, 1, index, 1.0, ATTN_STATIC, 0);
	}
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void deco_debris (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	decoHook_t		*hook = (decoHook_t *) self->userHook;
	Vector			origin, size;
	Vector			chunkorigin, vel, scale;
	int				count;
	int				mass;

	size = Vector (self->size) * 0.5;
	origin = Vector (self->s.origin);

	self->takedamage = DAMAGE_NO;

	if (hook->damage)
		com->RadiusDamage (self, attacker, self->owner, hook->damage, DAMAGE_EXPLOSION);

	vel = origin - Vector (inflictor->s.origin);
	vel.Normalize ();
	vel = vel * 150;
	vel.set (self->velocity);

	// start chunks towards the center
	size = size;// * 0.5;

	mass = self->mass;
	if (!mass)
		mass = 75;

	// big chunks
	if (mass >= 100)
	{
		count = mass / 100;
		if (count > 8)
			count = 8;

		while(count--)
		{
			scale.x = 1.0 + rnd () * 2.0;
			scale.y = 1.0 + rnd () * 2.0;
			scale.z = 1.0 + rnd () * 2.0;

			chunkorigin.x  = origin.x + (rnd () - 0.5) * size.x;
			chunkorigin.y  = origin.y + (rnd () - 0.5) * size.y;
			chunkorigin.z = origin.z + (rnd () - 0.5) * size.z;

			if (self->spawnflags & DECO_WOOD)
				throw_debris (self, "models/gibs/wood1/wood1.md2", 1, chunkorigin, 0, scale);
			else if (self->spawnflags & DECO_METAL)
				throw_debris (self, "models/gibs/metal1/metal1.md2", 1, chunkorigin, 0, scale);
			else if (self->spawnflags & DECO_GLASS)
				throw_debris (self, "models/gibs/glass1/glass1.md2", 1, chunkorigin, RF_TRANSLUCENT, scale);
			else if (self->spawnflags & DECO_WHITE)
				throw_debris (self, "models/gibs/glass1/glass1.md2", 1, chunkorigin, 0, scale);
		}
	}

	// small chunks
	count = mass / 25;
	if (count > 16)
		count = 16;
	while(count--)
	{
		scale.x = 1.0 + rnd () * 1.0;
		scale.y = 1.0 + rnd () * 1.0;
		scale.z = 1.0 + rnd () * 1.0;

		chunkorigin.x  = origin.x + (rnd () - 0.5) * size.x;
		chunkorigin.y  = origin.y + (rnd () - 0.5) * size.y;
		chunkorigin.z = origin.z + (rnd () - 0.5) * size.z;

		if (self->spawnflags & DECO_WOOD)
			throw_debris (self, "models/gibs/wood2/wood2.md2", 1, chunkorigin, 0, scale);
		else if (self->spawnflags & DECO_METAL)
			throw_debris (self, "models/gibs/metal2/metal2.md2", 1, chunkorigin, 0, scale);
		else if (self->spawnflags & DECO_GLASS) 
			throw_debris (self, "models/gibs/glass2/glass2.md2", 1, chunkorigin, RF_TRANSLUCENT, scale);
		else if (self->spawnflags & DECO_WHITE)
			throw_debris (self, "models/gibs/glass2/glass2.md2", 1, chunkorigin, 0, scale);}

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

//	if (hook->damage)
//		BecomeExplosion1 (self);
//	else
		gstate->RemoveEntity (self);
}

///////////////////////////
//	deco_explode
//
///////////////////////////

void	deco_explode (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	self->takedamage = DAMAGE_NO;

	// blow up
	spawn_sprite_explosion (self, self->s.origin, true);

	// do the damage
	//	FIXME:	do damage based on epairs
//	com->RadiusDamage (inflictor, attacker, self, 120, DAMAGE_EXPLOSION);

	//	throw debris
	self->mass *= 75;

	deco_debris (self, inflictor, attacker, 0, point);
	
	// erase the poor little sucker whose only function in life is to go boom
	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//	deco_explode_think
//
///////////////////////////////////////////////////////////////////////////////

void	deco_explode_think (userEntity_t *self)
{
	decoHook_t	*hook = (decoHook_t *) self->userHook;
	Vector		org, rand_org;

	hook->explo_count++;
	if (hook->explo_count > hook->explo_max)
		deco_explode (self, hook->inflictor, hook->attacker, hook->damage_done, hook->point);
	else
	{
		self->groundEntity = NULL;
		org = Vector (rnd () * 40 - 20, rnd () * 40 - 20, rnd () * 40 - 20);
		org.set (self->velocity);

		rand_org.x = self->size [0] * rnd ();
		rand_org.y = self->size [1] * rnd ();
		rand_org.z = self->size [2] * rnd ();

		com->RadiusDamage (hook->inflictor, hook->attacker, self, 15, DAMAGE_EXPLOSION);

		// small explosions before big boom
		org = Vector (self->s.origin) + Vector (self->s.mins) + rand_org;
		spawn_small_sprite_explosion (self, org.vec (), true);
		self->nextthink = gstate->time + rnd () * 0.2 + 0.05;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	deco_explode_die
//
///////////////////////////////////////////////////////////////////////////////

void	deco_explode_die (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	decoHook_t	*hook = (decoHook_t *) self->userHook;

	hook->explo_max = rand () & 3;
	hook->attacker = attacker;
	hook->inflictor = inflictor;
	hook->damage_done = damage;
	VectorCopy (point, hook->point);

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

///////////////////////////
//	deco_die
//
//	FIXME: throw gibs depending on type of model
///////////////////////////

void	deco_die (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	self->takedamage = DAMAGE_NO;

	VectorSubtract (self->s.mins, self->s.maxs, self->size);
	self->mass *= 75;

	if (rand () % 2)
		gstate->sound (attacker, CHAN_AUTO, "misc/debris1.wav", 128, ATTN_NORM);
	else
		gstate->sound (attacker, CHAN_AUTO, "misc/debris1.wav", 128, ATTN_NORM);

	deco_debris (self, inflictor, attacker, 0, point);

	gstate->RemoveEntity (self);
}

///////////////////////////
//	deco_pain
//
///////////////////////////

void	deco_pain (edict_t *self, edict_t *other, float kick, int damage)
{
}

//////////////////////
//  deco info class.
//////////////////////

//	Nelno:	commented this out to compile

class deco_info {
public:
    deco_info();

    //the name of the model
    char name[256];
    //the path of the md2 file.
    char path[256];

    //the mass of the object
    float mass;

    //the movement type of the object.
    movetype_t move_type;

    //the solid type of the the object.
    solid_t solid_type;

    //true if we can explode, false otherwise.
    bool exploding;

    //the amound of hit points the deco has.
    float hit_points;

    //the size of the object.
    int min[3];
    int max[3];

    //takes the file pointer and deco name and fills in the data for the deco.
    //returns false if the info for the named deco is not found in the file.
    //file_start is the position in the file that marks the beginning of the data, used
    //when this file is part of a .pak file and the first byte is somewhere in the middle of the
    //file.
    qboolean ReadInfo(char *deco_name, FILE *file, int file_start, int file_len);
};

deco_info::deco_info() {
    //set default parameters.
    strcpy(name, "2shield1");
    strcpy(path, "models/deco/e2/2shield1/2shield1.md2");

    mass = 1.0f;

    move_type = MOVETYPE_TOSS;

    solid_type = SOLID_BBOX;

    exploding = true;
    hit_points = 20.0f;
}

//strips leading and trailing spaces from a string.
void StripSpaces(char *str) {
    if (str == NULL) return;

    //the src and dest positions we read/write to.
    int dest = 0;
    int src = 0;
    
    //the length of the string without the spaces.
    int len = 0;

    //skip past any leading spaces.
    while (str[src] == ' ') {
        src++;
    }

    //copy the string 
    while (str[src] != '\0') {
        //copy the character.
        str[dest] = str[src];

        //increment src and dest.
        dest++;
        src++;

        //increment the length
        len++;
    }

    //null terminate the string.
    str[len] = '\0';

    //remove any spaces at the end of the string.
    while (str[len - 1] == ' ') {
        //get rid of the space.
        str[len - 1] = '\0';
        //decrement the length.
        len--;
    }
}

//parses the hit points out of a string.
float GetHitPoints(char *str);
//parses the exploding flag out of a string.
qboolean GetExploding(char *str);
//parses the mass out of a string.
float GetMass(char *str);
//parses the solid type out of a string.
solid_t GetSolidType(char *str);
//parses the move type out of a string.
movetype_t GetMoveType(char *str);
//parses a size parameter out of a string.
int GetSize(char *str);

//////////////////////////////////////////////////////////////////////////////////////////////
//  reads through a decoinfo.txt file and finds the data for the deco with the given name.
//////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_DECO_LINE_LEN 256
#define NUM_DECO_FIELDS 13
qboolean deco_info::ReadInfo(char *deco_name, FILE *file, int file_start, int file_len) {
    if (file == NULL) return false;
    if (deco_name == NULL) return false;

    //seek to the start of the file
    gstate->FS_Seek(file, file_start, SEEK_SET);

    //the text fields that we will read out of each line of the file
    char fields[NUM_DECO_FIELDS][MAX_DECO_LINE_LEN];

    //the current position we are writing in the line
    int line_pos = 0;

    //the current field we are reading
    int cur_field = 0;

    //reset the first field.
    fields[0][0] = '\0';

    //flag to tell if we are reading through a comment.
    bool in_comment = false;

    while (file_len > 0) {
        //read in a character.
        char lastchar = gstate->FS_Getc(file);
        //decrement the file_len var.
        file_len--;

        //check if this line is a comment.
        if (line_pos == 0) {
            if (lastchar == ';') {
                in_comment = true;
            }
            else {
                in_comment = false;
            }
        }

        //check if we are reading through a comment.
        if (in_comment == true) {
            line_pos++;
            //check what character we read in last, if it was a \n, we are on the next line.
            if (lastchar == '\n') {
                //we are leaving the comment.
                in_comment = false;

                //reset our line position.
                line_pos = 0;
            }
            //read the next character, dont do any processing on this character.
            continue;
        }

        //put the character in the line.
        fields[cur_field][line_pos] = lastchar;

        //check which character we read.
        //check if we hit the end of the line or file.
        if (lastchar == '\r' || file_len < 1) {
            //the end of the line/file.
            //null terminate the current field.
            fields[cur_field][line_pos] = '\0';

            //reset our line position
            line_pos = 0;

            //null terminate the remainder of the fields.
            for (cur_field++; cur_field < NUM_DECO_FIELDS; cur_field++) {
                fields[cur_field][0] = '\0';
            }

            //read past the \n in the file.
            gstate->FS_Getc(file);
            file_len--;
        }
        //check if we are done with the current field.
        //either we find a comma or the field was too long.
        else if (lastchar == ',' || line_pos >= MAX_DECO_LINE_LEN - 1) {
            //we found the end of the current field.
            //null terminate the field string
            fields[cur_field][line_pos] = '\0';
            //go to the next field.
            cur_field++;
            //reset our line position.
            line_pos = 0;
        }
        else {
            //we didnt end our current field or our current line,
            //advance to the next line position
            line_pos++;
        }

        //check if we have read in all the fields.
        if (cur_field >= NUM_DECO_FIELDS) {
            //got all the fields.  

            //remove spaces from the fields.
            for (int i = 0; i < NUM_DECO_FIELDS; i++) {
                StripSpaces(&fields[i][0]);
            }

            //check the name of the model we read from the file.
            if (stricmp(&fields[0][0], deco_name) == 0) {
                //this is the deco we are looking for.
                //parse the fields.
                //get the deco name.
                strcpy(&name[0], &fields[0][0]);
                //get the md2 file path name.
                strcpy(&path[0], &fields[1][0]);
                //get the move type.
                move_type = GetMoveType(&fields[2][0]);
                //get the solid type
                solid_type = GetSolidType(&fields[3][0]);
                //get the mass.
                mass = GetMass(&fields[4][0]);
                //get our exploding flag.
                exploding = GetExploding(&fields[5][0]);
                //get our hit points
                hit_points = GetHitPoints(&fields[6][0]);
                //get the sizes.
                min[0] = GetSize(&fields[7][0]);
                min[1] = GetSize(&fields[8][0]);
                min[2] = GetSize(&fields[9][0]);
                max[0] = GetSize(&fields[10][0]);
                max[1] = GetSize(&fields[11][0]);
                max[2] = GetSize(&fields[12][0]);

                //we're done.
                return true;
            }

            //reset cur_field
            cur_field = 0;

            //reset our line position
            line_pos = 0;

            //read the next character.
            continue;
        }

        //read another character
    }

    //went through the whole file and couldnt find the line.
    return false;
}

//parses a size parameter out of a string.
int GetSize(char *str) {
    if (str == NULL) return -16;

    //get the integer value of the string.
    int val = atoi(str);

    //dont allow a size of zero.
    if (val == 0) {
        return -16;
    }

    return val;
}

//parses the hit points out of a string.
float GetHitPoints(char *str) {
    if (str == NULL) return 20.0f;

    float val = atof(str);

    if (val < 1.0f) {
        val = 20.0f;
    }

    return val;
}

//parses the exploding flag out of a string.
qboolean GetExploding(char *str) {
    if (str == NULL) return true;

    //get the integer value of the string.
    int val = atoi(str);

    //check if it is 0.
    if (val == 0) return false;

    //otherwise, it is exploding.
    return true;
}

//parses the mass out of a string.
float GetMass(char *str) {
    if (str == NULL) return 1.0f;

    return atof(str);
}

//parses the solid type out of a string.
solid_t GetSolidType(char *str) {
    if (str == NULL) return SOLID_BBOX;

    if (stricmp(str, "bbox") == 0) {
        return SOLID_BBOX;
    }

    if (stricmp(str, "not") == 0) {
        return SOLID_NOT;
    }

    return SOLID_BBOX;
}

//parses the move type out of a string.
movetype_t GetMoveType(char *str) {
    if (str == NULL) return MOVETYPE_TOSS;

    if (stricmp(str, "toss") == 0) {
        return MOVETYPE_TOSS;
    }

    if (stricmp(str, "none") == 0) {
        return MOVETYPE_NONE;
    }

    if (stricmp(str, "bounce") == 0) {
        return MOVETYPE_BOUNCE;
    }

    return MOVETYPE_TOSS;
}

//generic function that will spawn a deco in any of the episodes.
void SpawnDeco(userEntity_t *self, char *decoinfo_file_name) 
{
    char				*model = NULL;
	char				*name;
    int					frame = 0;
    float				scale = 1.0f;
	decoHook_t			*hook;

	self->userHook = com->malloc_hook (sizeof (decoHook_t), MEM_MALLOC);
	hook = (decoHook_t *) self->userHook;
	
	hook->damage = 0;
	self->health = 0;

    for (int i = 0; self->epair[i].key != NULL; i++) {
        if (stricmp (self->epair[i].key, "target") == 0) {
            self->target = self->epair[i].value;
		}
        else if (stricmp (self->epair[i].key, "targetname") == 0) {
            self->targetname = 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, "model") == 0) {
            model = self->epair[i].value;
        }
        else if (stricmp(self->epair[i].key, "frame") == 0) {
            frame = atoi(self->epair[i].value);
            //error check
            if (frame < 0) frame = 0;
        }
        else if (stricmp(self->epair[i].key, "scale") == 0) {
            scale = atof(self->epair[i].value);
            //make sure the scale is a reasonable value.
            if (scale < 0.01f) scale = 1.0f;
        }
        else if (!stricmp(self->epair[i].key, "damage")) 
		{
			//	FIXME:	damage based on damage epair... needs a hook
            hook->damage = atof(self->epair[i].value);
        }
        else if (!stricmp(self->epair[i].key, "health")) 
		{
			//	FIXME:	damage based on damage epair... needs a hook
            self->health = atof(self->epair[i].value);
        }
    }

	if (model == NULL) {
		//there was no model name in the entity
        return;
	}

	//open the file with our model 
    int file_len;
    int file_start;
	FILE *file;

    //for now, we cannot use the .pak file code.
    file_len = gstate->FS_Open(decoinfo_file_name, &file);

	//check if we got the file.
	if (file == NULL) return;

    gstate->FS_Seek(file, 0, SEEK_SET);
    //use ftell to get the start of the file
    file_start = gstate->FS_Tell(file);

    //make a deco info struct.
    deco_info info;
    //read the info from the file
    if (info.ReadInfo(model, file, file_start, file_len) == false) {
        //couldn't read the deco info from the file.
        return;
    }

    //close the file
    gstate->FS_Close(file);   

    //allocate memory for the name of the object 
    name = (char *) com->malloc (256, MEM_MALLOC);

    //read the data out of the deco_info.
    //get the path of the md2 file.
	strcpy(name, info.path);
	
	if (!(self->spawnflags & (DECO_WOOD | DECO_METAL | DECO_GLASS | DECO_WHITE)))
		self->spawnflags |= DECO_METAL;

	if (!self->health)
		self->health = info.hit_points;

	self->die = deco_die;
	self->pain = deco_pain;

    //check if the object should explode.
    if (info.exploding == true || self->spawnflags & DECO_EXPLODE) 
	{
        self->use = NULL;
        self->die = deco_explode_die;
		if (!hook->damage)
			hook->damage = 80;
    }

    //set the move type
    self->movetype = info.move_type;

    //set the solid type.
    self->solid = info.solid_type;

    //set the mass
    self->mass = info.mass;

	//	must be FL_PUSHABLE or damage won't apply velocity
    self->flags = FL_PUSHABLE;

	//	SVF_MONSTER so shots clip against it
	self->svflags |= SVF_MONSTER;
	//	DAMAGE_YES so that damage can be done
	self->takedamage = DAMAGE_YES;

    //get the model index.
    self->s.modelindex = gstate->ModelIndex(name);

    //set the size of the object.
    gstate->SetSize (self, info.min[0], info.min[1], info.min[2], 
                     info.max[0], info.max[1], info.max[2]);

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

    //set the frame
    self->s.frame = frame;

    //set the scale
    self->s.render_scale[0] = self->s.render_scale[1] = self->s.render_scale[2] = scale;

	VectorSubtract (self->s.mins, self->s.maxs, self->size);
}

///////////////////////////
// deco_e1
///////////////////////////

void deco_e1(userEntity_t *self) {
    SpawnDeco(self, "models\\deco\\e1\\decoinfo.txt");
}

///////////////////////////
// deco_e2
///////////////////////////

void deco_e2 (userEntity_t *self) {
    SpawnDeco(self, "models/deco/e2/decoinfo.txt");
}

///////////////////////////
// deco_e3
///////////////////////////

void deco_e3(userEntity_t *self) {
    SpawnDeco(self, "models\\deco\\e3\\decoinfo.txt");
}

///////////////////////////
//	deco_e4
///////////////////////////

void deco_e4(userEntity_t *self) {
    SpawnDeco(self, "models\\deco\\e4\\decoinfo.txt");
}

///////////////////////////
//  crate_pain
//
///////////////////////////

void	crate_pain (userEntity_t *self, userEntity_t *other, float kick, int damage)
{
	if (self->takedamage)
	{
		if (self->frags - self->health > 10.0)
		{
			self->frags = self->health;
			self->s.frame;
		}

		if (self->s.frame >= 8)
		{
			self->takedamage = DAMAGE_NO;
			self->deadflag = DEAD_DEAD;
			self->s.frame = 8;
		}
	}
}

///////////////////////////////////////
//  particlefield_XZ
///////////////////////////////////////

#define	USE_COUNT	1

void	particlefield_XZ (userEntity_t *self)
{
	Vector			pos, start, end, temp;
	particleHook_t	*hook = (particleHook_t *) self->userHook;
	userEntity_t	*ent;

	//	self->ltime = gstate->time + 0.25;

	ent = com->ClientVisible (self);
	if (!ent)
		return;

	start = hook->dest1 + Vector (self->s.origin);
	end   = hook->dest2 + Vector (self->s.origin);

	//	Q2FIXME:	change to Q2 network functions
/*
	gstate->WriteByte (self, MSG_BROADCAST, SVC_TEMPENTITY);
	gstate->WriteByte (self, MSG_BROADCAST, TE_FORCEFIELDXZ);

	gstate->WriteCoord (self, MSG_BROADCAST, start.x);
	gstate->WriteCoord (self, MSG_BROADCAST, start.y);
	gstate->WriteCoord (self, MSG_BROADCAST, start.z);

	gstate->WriteCoord (self, MSG_BROADCAST, end.x);
	gstate->WriteCoord (self, MSG_BROADCAST, end.y);
	gstate->WriteCoord (self, MSG_BROADCAST, end.z);

	gstate->WriteByte (self, MSG_BROADCAST, hook->color);	
*/
/*	
	pos.y = start.y;
	pos.z = start.z;

	while (pos.z <= end.z)
	{
		pos.x = start.x;
		while (pos.x <= end.x)
		{
			temp = Vector (0, 0, 0);
			gstate->particle (pos.vec (), temp.vec (), hook->color, 1);
			pos.x = pos.x + 16;
		}

		pos.z = pos.z + 16;
	}
*/
}

///////////////////////////////////////
//  particlefield_YZ
///////////////////////////////////////

void	particlefield_YZ (userEntity_t *self)
{
	Vector			pos, start, end, temp;
	particleHook_t	*hook = (particleHook_t *) self->userHook;
	userEntity_t	*ent;

	//	self->ltime = gstate->time + 0.25;

	ent = com->ClientVisible (self);
	if (!ent)
		return;
	start = hook->dest1 + Vector (self->s.origin);
	end   = hook->dest2 + Vector (self->s.origin);

	//	Q2FIXME:	change to Q2 network functions
	/*
	gstate->WriteByte (self, MSG_BROADCAST, SVC_TEMPENTITY);
	gstate->WriteByte (self, MSG_BROADCAST, TE_FORCEFIELDYZ);

	gstate->WriteCoord (self, MSG_BROADCAST, start.x);
	gstate->WriteCoord (self, MSG_BROADCAST, start.y);
	gstate->WriteCoord (self, MSG_BROADCAST, start.z);

	gstate->WriteCoord (self, MSG_BROADCAST, end.x);
	gstate->WriteCoord (self, MSG_BROADCAST, end.y);
	gstate->WriteCoord (self, MSG_BROADCAST, end.z);

	gstate->WriteByte (self, MSG_BROADCAST, hook->color);	
	*/

/*
	pos.x = start.x;
	pos.z = start.z;
   
	while(pos.z < end.z)
	{
		pos.y = start.y;
		while (pos.y < end.y)
		{
			temp = Vector (0, 0, 0);
			gstate->particle (pos.vec (), temp.vec (), hook->color, hook->count);
			pos.y = pos.y + 16;
		}
		
		pos.z = pos.z + 16;
	}
*/
}

///////////////////////////////////////
//  particlefield_XY
///////////////////////////////////////

void	particlefield_XY (userEntity_t *self)
{
	Vector			pos, start, end, temp;
	particleHook_t	*hook = (particleHook_t *) self->userHook;

	//	self->ltime = gstate->time + 0.25;

	// Only show particles if client is visible.
	// This helps to keep network traffic down to a minimum.
	if (!com->ClientVisible (self))
		return;

	start = hook->dest1 + Vector (self->s.origin);
	end   = hook->dest2 + Vector (self->s.origin);

	//	Q2FIXME:	change to Q2 network functions
	/*
	gstate->WriteByte (self, MSG_BROADCAST, SVC_TEMPENTITY);
	gstate->WriteByte (self, MSG_BROADCAST, TE_FORCEFIELDXY);

	gstate->WriteCoord (self, MSG_BROADCAST, start.x);
	gstate->WriteCoord (self, MSG_BROADCAST, start.y);
	gstate->WriteCoord (self, MSG_BROADCAST, start.z);

	gstate->WriteCoord (self, MSG_BROADCAST, end.x);
	gstate->WriteCoord (self, MSG_BROADCAST, end.y);
	gstate->WriteCoord (self, MSG_BROADCAST, end.z);
	
	gstate->WriteByte (self, MSG_BROADCAST, hook->color);	
	*/
/*
	pos.x = start.x;
	pos.z = start.z;

	while(pos.x < end.x)
	{
		pos.y = start.y;
		
		while (pos.y < end.y)
		{
			temp = Vector (0, 0, 0);
			gstate->particle (pos.vec (), temp.vec (), hook->color, hook->count);
			pos.y = pos.y + 16;
		}
		
		pos.x = pos.x + 16;
	}
*/
}

///////////////////////////////////////
//  particlefield_touch
///////////////////////////////////////

void particlefield_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	particleHook_t	*hook = (particleHook_t *) self->userHook;
	playerHook_t	*ohook;
	int				color;
	float			speed;
	Vector			temp;

	if (!(other->flags & FL_CLIENT) && !(other->flags & FL_BOT) &&
		!(other->flags & FL_MONSTER))
	{
		if (other->touch)
		{
			//	blow up projectiles

			//	Q2FIXME:	activator?!?!?
			other->touch (other, self, NULL, NULL);
		}
		else 
		{
			//	remove other stuff
			spawn_sprite_explosion (other, other->s.origin, true);
			gstate->RemoveEntity (other);
		}
		
		return;
	}
	else if (other->teleport_time > gstate->time)
		return;
	else
		ohook = (playerHook_t *) other->userHook;

	if (!hook->damage)
		return;
	
	if (hook->other == other && hook->hold_time > gstate->time)
	{
		com->ZeroVector (other->velocity);
		return;
	}

	// not holding anyone yet
	if (!hook->other)
	{
		if (other->flags & FL_DEBUG)
			gstate->bprint ("starting to hold %s\n", other->netname);
		
		hook->other = other;
		hook->last_color = hook->color;

		hook->color = gstate->global_white;
		self->use (self, other, other);

		// ignore client's input so they can't run through the field

		//	Q2FIXME:	change to Q2 network functions
		/*
		if (hook->other->flags & FL_CLIENT)
			gstate->WriteByte (other, MSG_ONE, SVC_IGNOREINPUT);
		else if (hook->other->flags & FL_POSSESSED)
			if (ohook->camera->owner)
				gstate->WriteByte (ohook->camera->owner, MSG_ONE, SVC_IGNOREINPUT);
		*/
			
		hook->impact_vel = Vector (other->velocity);
		hook->impact_vel.Normalize ();

		com->ZeroVector (other->velocity);

		hook->hold_time = gstate->time + 0.5;
		hook->attack_finished = 0.05;

		other->s.renderfx |= RF_MUZZLEFLASH;

		if (hook->trigger_sound)
			gstate->sound (other, CHAN_ITEM, hook->trigger_sound, 255, ATTN_NORM);

		//////////////////////////////////////////////////////////////
		//	if a monster, stop if from moving forward
		//////////////////////////////////////////////////////////////

		if (other->flags & (FL_BOT + FL_MONSTER))
		{
			self->die = (die_t) other->think;
			other->think = NULL;
		}
	}
	// already holding one person, so just throw this other entity off
	else
	{
		if (other == hook->other)
			return;

		if (other->flags & FL_DEBUG)
			gstate->bprint ("throwing %s\n", other->netname);
		
		temp = Vector (other->velocity);
		temp.Normalize ();

		temp = temp * -1 * 800.0;
		temp.set (other->velocity);
		other->velocity [2] = 10.0;
		other->flags = other->flags - (other->flags & FL_ONGROUND);

		other->s.renderfx |= RF_MUZZLEFLASH;
		other->teleport_time = gstate->time + 0.3;

		if (hook->touch_sound)
			gstate->sound (other, CHAN_ITEM, hook->touch_sound, 255, ATTN_NORM);
	}

	if (other->flags & FL_CLIENT)
		com->Damage (other, self, self, zero_vector, zero_vector, hook->damage, DAMAGE_NONE | DAMAGE_NO_BLOOD);
};

///////////////////////////////////////
//  particlefield_think
///////////////////////////////////////

void particlefield_think (userEntity_t *self)
{
	particleHook_t	*hook = (particleHook_t *) self->userHook;
	playerHook_t	*ohook;
	Vector			temp;
	userEntity_t	*ent;
	int				percent;

	// if hook->other is set then we're hold a victim momentarily
	// on the field
	if (hook->other)
	{
		particleHook_t	*hook = (particleHook_t *) self->userHook;
		
		ohook = (playerHook_t *) hook->other->userHook;

		if (hook->other->flags & FL_CLIENT && ohook->shake_time < gstate->time)
			ohook->shake_time = gstate->time + 0.75;

		if (hook->other->flags & FL_CLIENT || hook->other->flags & FL_POSSESSED)
			com->Damage (hook->other, self, self, zero_vector, zero_vector, hook->damage, DAMAGE_NONE | DAMAGE_NO_BLOOD);
		
		ent = hook->other;
		percent = (int)(rnd () * 128.0);

		if (hook->other->flags & FL_DEBUG)
			gstate->bprint ("holding %s\n", hook->other->netname);

		// finished holding, throw entity off
		if (hook->hold_time <= gstate->time)
		{
			/////////////////////////////////////////////////////////
			//	throw clients off based on their impact velocity
			//	throw bots/monsters off based on their facing direction
			/////////////////////////////////////////////////////////

			if (hook->other->flags & FL_CLIENT)	
				forward = hook->impact_vel;
			else
			{
				temp = Vector (hook->other->s.angles);
				temp.AngleVectors (forward, right, up);
			}

			temp = Vector (hook->other->s.origin) + forward * -16.0;
			temp.z = hook->other->s.origin [2];
			gstate->SetOrigin2 (hook->other, temp.vec ());

			temp = forward * -150.0;
			temp.set (hook->other->velocity);

			hook->other->teleport_time = gstate->time + 0.3;
			hook->other->velocity [2] = 250.0;
			hook->other->flags = hook->other->flags - (hook->other->flags & FL_ONGROUND);
	
			hook->other->s.effects |= RF_MUZZLEFLASH;

			if (hook->touch_sound)
				gstate->sound (hook->other, CHAN_ITEM, hook->touch_sound, 255, ATTN_NORM);
		
			self->max_speed = hook->attack_finished;
			if (hook->other->flags & FL_CLIENT)
			{
				gstate->sound (hook->other, CHAN_VOICE, "player/playerpain4.wav", 255, ATTN_NORM);
				//	Q2FIXME:	change to Q2 network functions
				/*
				if (ohook->camera)
					gstate->WriteByte (ohook->camera->owner, MSG_ONE, SVC_RESTOREINPUT);
				else
					gstate->WriteByte (hook->other, MSG_ONE, SVC_RESTOREINPUT);
				*/
			}
			else if (hook->other->flags & FL_POSSESSED)
			{
				//	Q2FIXME:	change to Q2 network functions
				/*
				if (ohook->camera->owner)
					gstate->WriteByte (ohook->camera->owner, MSG_ONE, SVC_RESTOREINPUT);
				*/
			}
			else if (hook->other->flags & (FL_BOT + FL_MONSTER))
			{
				//////////////////////////////////////////////////////////////
				//	if a monster, start it thinking again
				//////////////////////////////////////////////////////////////
				hook->other->nextthink = gstate->time + ohook->think_time;
				hook->other->think = (think_t) self->die;
				self->die = NULL;
			}

			if (hook->other->flags & FL_DEBUG)
				gstate->bprint ("threw %s\n", hook->other->netname);

			hook->other = NULL;
			hook->color = hook->last_color;
			hook->attack_finished = 0.1;
			
			percent = 255;
		}

		if (ent->flags & FL_CLIENT)
		{
			//	Q2FIXME:	change to Q2 network functions
			/*
			gstate->WriteByte (ent, MSG_ONE, SVC_TEMPENTITY);
			gstate->WriteByte (ent, MSG_ONE, TE_COLORFLASH);
			gstate->WriteByte (ent, MSG_ONE, 255);		//red component
			gstate->WriteByte (ent, MSG_ONE, 255);		//green component
			gstate->WriteByte (ent, MSG_ONE, 255);		//blue component
			gstate->WriteByte (ent, MSG_ONE, percent);	//percent
			gstate->WriteByte (ent, MSG_ONE, 1);			//wait
			gstate->WriteLong (ent, MSG_ONE, 128);		//decay
			*/
		}
	}

	//////////////////////////////////////////////////////////////
	//	send the message down to the client if client is visible
	//////////////////////////////////////////////////////////////
	hook->think_use (self);

	self->think = particlefield_think;
	self->nextthink = gstate->time + hook->attack_finished;
}

///////////////////////////////////////
//  particlefield_toggle
///////////////////////////////////////

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

	// if no one is on the field and it is active, toggle it off
	if (self->think && hook->other == NULL)
	{
		gstate->sound (self, CHAN_AUTO, "ambience/forcefieldoff.wav", 255, ATTN_NORM);
		self->think = NULL;
		self->touch = NULL;
	}
	else if (!self->think)
	{
		gstate->sound (self, CHAN_AUTO, "ambience/forcefieldon.wav", 255, ATTN_NORM);
		self->think = particlefield_think;
		self->nextthink = gstate->time + hook->attack_finished;
		self->touch = particlefield_touch;
	}
}

///////////////////////////////////////
//  func_particlefield
//
//	"cnt" is the count to activate on when USE_COUNT is set.
//	"color" is the color of the particles.  Default is 192 (yellow).
//	"count" is the density of the particles.  Default is 2.
//	"noise" is the sound to play when triggered.  Do not use a looping sound here.
//	"dmg" is the amount of damage to cause when touched.
///////////////////////////////////////

void	func_particlefield (userEntity_t *self)
{
	int				i;
	particleHook_t	*hook;
	Vector			temp;

	return;

	self->userHook = com->malloc_hook (sizeof (particleHook_t), MEM_HUNK);
	hook = (particleHook_t *) self->userHook;

	for (i = 0; self->epair [i].key != NULL; i++)
	{
		if (!stricmp (self->epair [i].key, "color"))
		{
			if (!stricmp (self->epair [i].value, "black"))
				hook->color = gstate->global_black;
			else if (!stricmp (self->epair [i].value, "blue"))
				hook->color = gstate->global_blue;
			else if (!stricmp (self->epair [i].value, "green"))
				hook->color = gstate->global_green;
			else if (!stricmp (self->epair [i].value, "cyan"))
				hook->color = gstate->global_cyan;
			else if (!stricmp (self->epair [i].value, "red"))
				hook->color = gstate->global_red;
			else if (!stricmp (self->epair [i].value, "purple"))
				hook->color = gstate->global_purple;
			else if (!stricmp (self->epair [i].value, "brown"))
				hook->color = gstate->global_brown;
			else if (!stricmp (self->epair [i].value, "ltgray"))
				hook->color = gstate->global_ltgray;
			else if (!stricmp (self->epair [i].value, "dkgray"))
				hook->color = gstate->global_dkgray;
			else if (!stricmp (self->epair [i].value, "ltblue"))
				hook->color = gstate->global_ltblue;
			else if (!stricmp (self->epair [i].value, "ltgreen"))
				hook->color = gstate->global_ltgrn;
			else if (!stricmp (self->epair [i].value, "ltcyan"))
				hook->color = gstate->global_ltcyan;
			else if (!stricmp (self->epair [i].value, "ltpurple"))
				hook->color = gstate->global_ltpurple;
			else if (!stricmp (self->epair [i].value, "yellow"))
				hook->color = gstate->global_yellow;
			else if (!stricmp (self->epair [i].value, "white"))
				hook->color = gstate->global_white;
		}
		else if (!stricmp (self->epair [i].key, "count"))
			hook->count = atoi (self->epair [i].value);
//		else if (!stricmp (self->epair [i].key, "touch_sound"))
//			hook->touch_sound = atoi (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "trigger_sound"))
			hook->trigger_sound = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "ambient_sound"))
			hook->amb_sound = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
	}

	if (hook->amb_sound)
		gstate->AmbientSound (self->s.origin, hook->amb_sound, 128, ATTN_STATIC, 1.0);

	if (!hook->touch_sound)
		hook->touch_sound = "doors/e1/forcerepel.wav";
	if (!hook->trigger_sound)
		hook->trigger_sound = "doors/e1/forcehold.wav";
	
	gstate->Precache_Sound (hook->touch_sound);
	gstate->Precache_Sound (hook->trigger_sound);

	gstate->Precache_Sound ("ambience/forcefieldoff.wav");
	gstate->Precache_Sound ("ambience/forcefieldon.wav");

	if (hook->color == 0)
		hook->color = gstate->global_ltblue;
//	if (hook->count == 0)
//		hook->count = 2;
	hook->count = temp_count++;

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

	temp = (Vector (self->s.mins) + Vector (self->s.maxs)) * 0.5;
	temp.set (self->s.origin);
//	gstate->SetOrigin2 (self, self->s.origin);
	hook->dest = Vector (self->s.maxs) - Vector (self->s.mins) - Vector (16, 16, 16);
	hook->dest1 = Vector (self->s.mins) + Vector (8, 8, 8) - Vector (self->s.origin);
	hook->dest2 = Vector (self->s.maxs) + Vector (7.9, 7.9, 7.9) - Vector (self->s.origin);
//	gstate->SetSize (self, self->mins [0], self->mins [1], self->mins [2], self->maxs [0], self->maxs [1], self->maxs [2]);
	self->touch = particlefield_touch;

	if (!hook->damage)
		hook->damage = 2.0;

	if (hook->dest.x > hook->dest.z)
	{
		if (hook->dest.y > hook->dest.z)
		{
			hook->think_use = particlefield_XY;
			hook->dest1.z = (hook->dest1.z + hook->dest2.z ) / 2;
		}
		else
		{
			hook->think_use = particlefield_XZ;
			hook->dest1.y = (hook->dest1.y + hook->dest2.y ) / 2;
		}
	}
	else
	{
		if (hook->dest.y > hook->dest.x)
		{
			hook->think_use = particlefield_YZ;
			hook->dest1.x = (hook->dest1.x + hook->dest2.x ) / 2;
		}
		else
		{
			hook->think_use = particlefield_XZ;
			hook->dest1.y = (hook->dest1.y + hook->dest2.y ) / 2;
		}
	}

	if (hook->trigger_sound)
		gstate->Precache_Sound (hook->trigger_sound);

	self->use = particlefield_toggle;

	if (!(self->spawnflags & USE_COUNT))
	{
		self->think = particlefield_think;
		self->nextthink = gstate->time + 0.1;
	}

	hook->attack_finished = 0.1;
	//	self->ltime = gstate->time;
}

/////////////////////////////
//	lavaball_glob_think
//
/////////////////////////////

#define	LAVABALL_MAXSCALE	15.0

void	lavaball_glob_think (userEntity_t *self)
{
	if (self->s.render_scale [0] > LAVABALL_MAXSCALE)
		self->delay = 0.92;
	else if (self->s.render_scale [0] < 2.0)
		self->delay = 1.08;

	if (self->s.render_scale [1] > LAVABALL_MAXSCALE)
		self->frags = 0.95;
	else if (self->s.render_scale [1] < 2.0)
		self->frags = 1.05;

	if (self->s.render_scale [2] > LAVABALL_MAXSCALE)
		self->team = 0.91;
	else if (self->s.render_scale [2] < 2.0)
		self->team = 1.09;

	self->s.render_scale [0] *= self->delay;
	self->s.render_scale [1] *= self->frags;
	self->s.render_scale [2] *= self->team;

	gstate->Con_Printf ("%.2f %.2f %.2f\n", self->s.render_scale [0], self->s.render_scale [1], self->s.render_scale [2]);
	gstate->Con_Printf ("%.2f %.2f %.2f\n", self->delay, self->frags, self->team);

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


///////////////////////////////////////
//	lavaball_shrink
//
///////////////////////////////////////

#define	SPLAT_MAXSCALE	10.0
#define	SPLAT_MINSCALE	1.0

void	lavaball_shrink (userEntity_t *self)
{
	int			pc_top, pc_mid, pc_bottom;
	Vector		org;

	self->velocity [2] *= 1.0125;

	if (self->s.render_scale [2] > SPLAT_MAXSCALE * 0.75)
	{
		self->delay *= self->frags;
		self->team = 1.0 - self->delay;
	}
	else if (self->s.render_scale [2] < 3.0)
	{
		self->delay *= self->frags;
		self->team = 1.0 + self->delay;
	}

	self->s.render_scale [2] *= self->team;

	//	width is inversly proportional to height
	self->s.render_scale [0] = (SPLAT_MAXSCALE * 3.0) / (self->s.render_scale [2]);
	self->s.render_scale [1] = (SPLAT_MAXSCALE * 3.0) / (self->s.render_scale [2]);

	gstate->Con_Printf ("shrink: %.2f %.2f %.2f\n", self->s.render_scale [0], self->s.render_scale [1], self->s.render_scale [2]);

	org = Vector (self->s.origin) + Vector (0, 0, 16.0);
	pc_top = gstate->PointContents (org.vec ());

	pc_mid = gstate->PointContents (self->s.origin);

	org = Vector (self->s.origin) - Vector (0, 0, fabs (self->velocity [2]) * 0.05);
	pc_bottom = gstate->PointContents (org.vec ());

	if (pc_top == CONTENTS_LAVA && pc_mid == CONTENTS_LAVA && pc_bottom == CONTENTS_LAVA)
		gstate->RemoveEntity (self);
}

/////////////////////////////
//	lavaball_velocity_deform_think
//
/////////////////////////////

#define	LAVABALL_DEFORM_MAXSCALE	20.0
#define	LAVABALL_DEFORM_MINSCALE	2.0
#define	LAVABALL_DELTA_MID			15.0

void	lavaball_velocity_deform_think (userEntity_t *self)
{
	Vector		dir, org, speed, deform;
	int			pc, i;
	float		scale_factor;

	org = Vector (self->s.origin) - Vector (0, 0, fabs (self->velocity [2]) * 0.05);
	pc = gstate->PointContents (org.vec ());

	if (pc == CONTENTS_LAVA)
	{
		org = Vector (self->s.origin);

		for (i = 1; i <= 16; i++)
		{
			org.z -= 1;
			if (gstate->PointContents (org.vec ()) != CONTENTS_LAVA)
				break;
		}

		org.z -= 4;
		gstate->SetOrigin2 (self, org.vec ());

		com->ZeroVector (self->velocity);

		self->delay = 0.25;
		self->frags = 0.85;
		self->team = 0.80;

		self->movetype = MOVETYPE_FLY;
		
		self->velocity [0] = 0;
		self->velocity [1] = 0;
		self->velocity [2] = -5.0;

		lavaball_shrink (self);
		self->prethink = lavaball_shrink;

		return;
	}

	//////////////////////////////////////////////////////////////////////////
	//	do a pretend deform based on "air friction" (direction of travel)
	//////////////////////////////////////////////////////////////////////////

	speed = Vector (self->velocity);

	//	will reach max elongation at speed of 1000
	scale_factor = 1000 / (LAVABALL_DEFORM_MAXSCALE - LAVABALL_DEFORM_MINSCALE);

	deform.x = fabs (speed.x) / scale_factor + LAVABALL_DEFORM_MINSCALE;
	deform.y = fabs (speed.y) / scale_factor + LAVABALL_DEFORM_MINSCALE;;
	deform.z = fabs (speed.z) / scale_factor + LAVABALL_DEFORM_MINSCALE;;

	self->s.render_scale [0] = fabs (deform.x);
	self->s.render_scale [1] = fabs (deform.y);
	self->s.render_scale [2] = fabs (deform.z);

//	gstate->Con_Printf ("vel_def: %.2f %.2f %.2f\n", self->s.render_scale [0], self->s.render_scale [1], self->s.render_scale [2]);
}

/////////////////////////////
//	lavaball_spawn
//
/////////////////////////////

void	lavaball_spawn (userEntity_t *self)
{
	userEntity_t	*ball;

	ball = gstate->SpawnEntity ();
	
	gstate->SetModel (ball, "models/lavaball.mdl");
	gstate->SetSize (ball, 0, 0, 0, 0, 0, 0);
	gstate->SetOrigin2 (ball, self->s.origin);

	ball->movetype = MOVETYPE_TOSS;
	ball->solid = SOLID_BBOX;
	ball->gravity = 0.75;
	
	ball->prethink = lavaball_velocity_deform_think;
//	ball->nextthink = gstate->time + 0.05;

	com->SetVector (ball->s.render_scale, 1.0, 1.0, 1.0);

	self->nextthink = gstate->time + 8.0;
	self->think = lavaball_spawn;
}

/////////////////////////////
//	lavaball_spawn_glob
//
/////////////////////////////

void	lavaball_spawn_glob (userEntity_t *self)
{
	userEntity_t	*ball;

	ball = gstate->SpawnEntity ();
	
	gstate->SetModel (ball, "models/lavaball.mdl");
	gstate->SetSize (ball, 0, 0, 0, 0, 0, 0);
	gstate->SetOrigin2 (ball, self->s.origin);

	ball->movetype = MOVETYPE_NONE;
	ball->solid = SOLID_NOT;
	
	ball->think = lavaball_glob_think;
	ball->nextthink = gstate->time + 0.05;

	ball->delay = 1.08;
	ball->frags = 1.05;
	ball->team = 1.09;
	
	com->SetVector (ball->s.render_scale, 2.0, 2.0, 2.0);
}

/////////////////////////////
//	misc_lavaball_drop
//
/////////////////////////////

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

	self->movetype = MOVETYPE_NONE;
	self->solid = SOLID_NOT;
	self->modelName = NULL;

	self->think = lavaball_spawn;
	self->nextthink = gstate->time + rnd () * 5.0;
}

////////////////////////////////////////////////////////////////////////////
//	lavaball_xy_vel
//
//	sets the correct forward velocity need to go distxy forward and
//	land distz units below current position
/////////////////////////////////////////////////////////////////////////

float	lavaball_xy_vel (userEntity_t *self, float distxy, float distz, float up_vel)
{
	float			t, g, y0, y, root;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	if (self->gravity != 0.0)
		g = gstate->GetCvar ("sv_gravity") * self->gravity;
	else
		g = gstate->GetCvar ("sv_gravity");

	y0 = self->s.origin [2];
	y = self->s.origin [2] + distz;

	root = y - y0;
	root = (up_vel * up_vel) - (2 * g * (y - y0));

	// negative value means we're trying to jump to a higher ledge!
	if (root < 0)
		return	root;

	t = (up_vel + sqrt (root)) / g;
	
	return	distxy / t;
}

/////////////////////////////
//	lavaball_touch
//
/////////////////////////////

void	lavaball_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	if (other->takedamage)
		com->Damage (other, self, self, self->s.origin, zero_vector, self->health, DAMAGE_INERTIAL | DAMAGE_INFLICTOR_VEL);
}

/////////////////////////////
//	lavaball_velocity_deform
//
/////////////////////////////

#define	LAVABALL_VEL_MAXSCALE	10.0
#define	LAVABALL_VEL_MINSCALE	2.0

void	lavaball_velocity_deform (userEntity_t *self)
{
	Vector		dir, ang, speed, deform;
	int			pc;
	float		scale_factor;

	pc = gstate->PointContents (self->s.origin);

	if (pc == CONTENTS_LAVA || gstate->time > self->teleport_time)
	{
		gstate->RemoveEntity (self);
		return;
	}

	//////////////////////////////////////////////////////////////////////////
	//	orient towards direction of travel
	//////////////////////////////////////////////////////////////////////////
/*
	dir = Vector (self->velocity);
	dir.Normalize ();
	com->VecToAngles (ang, dir);
//	ang.set (self->s.angles);
	self->s.angles [0] = ang.x;
//	self->s.angles [1] = ang.y;
	self->s.angles [2] = ang.z;
*/
	//////////////////////////////////////////////////////////////////////////
	//	do a pretend deform based on "air friction" (direction of travel)
	//////////////////////////////////////////////////////////////////////////

	speed = Vector (self->velocity);

	//	will reach max elongation at speed of 1000
	scale_factor = 500 / (self->delay - self->frags);

	//	deform x and y proportional to z

	deform.x = fabs (speed.x) / scale_factor + self->frags;
	deform.y = fabs (speed.y) / scale_factor + self->frags;
	deform.z = fabs (speed.z) / scale_factor + self->frags;

	self->s.render_scale [0] = fabs (deform.x);
	self->s.render_scale [1] = fabs (deform.y);
	self->s.render_scale [2] = fabs (deform.z);
}

/////////////////////////////
//	lavaball_random_toss
//
/////////////////////////////

void	lavaball_random_toss (userEntity_t *self)
{
	lavaballHook_t	*hook = (lavaballHook_t *) self->userHook;
	userEntity_t	*ball;

	ball = gstate->SpawnEntity ();
	
	ball->s.modelindex = gstate->ModelIndex ("models/lavaball2/lavaball2.md2");
	gstate->SetSize (ball, -8, -8, -8, 8, 8, 8);
//	gstate->SetSize (ball, 0, 0, 0, 0, 0, 0);
	gstate->SetOrigin2 (ball, self->s.origin);

	ball->movetype = MOVETYPE_BOUNCE;
	ball->solid = SOLID_BBOX;
	ball->gravity = rnd () * 0.5 + 0.25;
	ball->s.effects = 0;//EF_DIMLIGHT + EF_LAVATRAIL;	
	ball->teleport_time = gstate->time + 4.0;
	ball->health = hook->damage;
	ball->mass = 2.0;
	ball->flags = FL_PUSHABLE;

	ball->delay = rnd () * 10.0 + 10.0;
	ball->frags = rnd () * 2.0 + 2.0;

	ball->prethink = lavaball_velocity_deform;
	if (hook->damage)
		ball->touch = lavaball_touch;

	com->SetVector (ball->s.render_scale, 1.0, 1.0, 1.0);

	ball->velocity [0] = rnd () * 300 - 150.0;
	ball->velocity [1] = rnd () * 300 - 150.0;
	ball->velocity [2] = rnd () * hook->z_rng + hook->z_min;

	ball->avelocity [0] = rnd () * 300 - 150.0;
	ball->avelocity [1] = rnd () * 300 - 150.0;
	ball->avelocity [2] = rnd () * 300 - 150.0;

	self->nextthink = gstate->time + (rnd () * hook->time_rng) + hook->time_min;
	self->think = lavaball_random_toss;
}

/////////////////////////////
//	lavaball_targetted_toss
//
/////////////////////////////

void	lavaball_targetted_toss (userEntity_t *self)
{
	lavaballHook_t	*hook = (lavaballHook_t *) self->userHook;
	userEntity_t	*ball;
	float			dist, xy_vel, z_vel;
	Vector			dir, org, end;

	ball = gstate->SpawnEntity ();

	ball->s.modelindex = gstate->ModelIndex ("models/lavaball2/lavaball2.md2");
	gstate->SetSize (ball, -8, -8, -8, 8, 8, 8);
	gstate->SetOrigin2 (ball, self->s.origin);

	ball->movetype = MOVETYPE_BOUNCE;
	ball->solid = SOLID_BBOX;
	ball->gravity = rnd () * 0.5 + 0.25;
	ball->s.effects = 0;// EF_DIMLIGHT + EF_LAVATRAIL;	
	ball->health = hook->damage;
	ball->mass = 2.0;
	ball->flags = FL_PUSHABLE;

	ball->delay = rnd () * 5.0 + 10.0;
	ball->frags = rnd () * 1.0 + 2.0;

	com->SetVector (ball->s.render_scale, 1.0, 1.0, 1.0);

	ball->avelocity [0] = rnd () * 300 - 150.0;
	ball->avelocity [1] = rnd () * 300 - 150.0;
	ball->avelocity [2] = rnd () * 300 - 150.0;

	ball->teleport_time = gstate->time + 4.0;

	ball->prethink = lavaball_velocity_deform;
	if (hook->damage)
		ball->touch = lavaball_touch;

	/////////////////////////////////////////////////////////
	//	compute velocity needed to reach target
	//
	/////////////////////////////////////////////////////////

	org = Vector (self->s.origin);
	org.z = 0;
	end = Vector (hook->targ_origin);
	end.z = 0;

	dir = end - org;
	dist = dir.vlen ();
	dir.Normalize ();

	z_vel = (rnd () * hook->z_rng + hook->z_min) * ball->gravity / 0.5;

	xy_vel = lavaball_xy_vel (ball, dist, hook->targ_origin [2] - self->s.origin [2], z_vel);
	dir = dir * xy_vel;
	dir.z = z_vel;
	dir.set (ball->velocity);

	if (!self->targetname)
	{
		self->nextthink = gstate->time + (rnd () * hook->time_rng) + hook->time_min;
		self->think = lavaball_targetted_toss;
	}
}

/////////////////////////////
//	lavaball_findtarget
//
/////////////////////////////

void	lavaball_find_target (userEntity_t *self)
{
	lavaballHook_t	*hook = (lavaballHook_t *) self->userHook;
	userEntity_t	*target;

	target = com->FindTarget (self->target);
	
	if (target)
	{
		com->CopyVector (hook->targ_origin, target->s.origin);
		
		//	if not targetted, start throwing at random time
		if (!self->targetname)
		{
			self->think = lavaball_targetted_toss;
			self->nextthink = gstate->time + hook->time_min + rnd () * hook->time_rng;
		}
	}
}

/////////////////////////////
//	misc_lavaball_toss
//
/////////////////////////////

void	misc_lavaball_toss (userEntity_t *self)
{
	lavaballHook_t	*hook;
	int				i;
	float			max_time = 12.0, min_time = 4.0;
	float			max_up = 400.0, min_up = 400.0;

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

	for (i = 0; self->epair [i].key != NULL; i++)
	{
		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, "mintime"))
			min_time = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "maxtime"))
			max_time = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "damage"))
			hook->damage = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "upmax"))
			max_up = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "upmin"))
			min_up = atof (self->epair [i].value);
	}

	if (!hook->damage)
		hook->damage = 15.0;

	/////////////////////////////////////////////////////////
	//	toss timing
	/////////////////////////////////////////////////////////

	if (max_time >= min_time)
	{
		hook->time_rng = max_time - min_time;
		hook->time_min = min_time;
	}
	else
	{
		hook->time_rng = 8.0;
		hook->time_min = 4.0;
	}

	/////////////////////////////////////////////////////////
	//	z velocity
	/////////////////////////////////////////////////////////

	if (max_up >= min_up)
	{
		hook->z_rng = max_up - min_up;
		hook->z_min = min_up;
	}
	else
	{
		hook->z_rng = 0.0;
		hook->z_min = 400.0;
	}

	gstate->ModelIndex ("models/lavaball2/lavaball2.md2");

	self->movetype = MOVETYPE_NONE;
	self->solid = SOLID_NOT;
	self->modelName = NULL;

	if (!self->target)
	{
		self->think = lavaball_random_toss;
		self->nextthink = gstate->time + hook->time_min + rnd () * hook->time_rng;
	}
	else
	{
		self->think = lavaball_find_target;
		self->nextthink = gstate->time + 0.1;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST
//	Any brush that you want to explode or break apart.  If you want an
//	explosion, set dmg and it will do a radius explosion of that amount
//	at the center of the bursh.
//
//	If targeted it will not be shootable.
//
//	health defaults to 100.
//
//	mass defaults to 75.  This determines how much debris is emitted when
//	it explodes.  You get one large chunk per 100 of mass (up to 8) and
//	one small chunk per 25 of mass (up to 16).  So 800 gives the most.
///////////////////////////////////////////////////////////////////////////////

void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
	gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void throw_debris (edict_t *self, char *modelname, float speed, Vector origin, int renderfx, Vector scale)
{
	userEntity_t	*chunk;
	Vector			vel;

	chunk = gstate->SpawnEntity ();
	gstate->SetOrigin2 (chunk, origin.vec ());
	chunk->s.modelindex = gstate->ModelIndex (modelname);

	chunk->s.renderfx = renderfx;

	vel.x = 100 * (rnd () - 0.5);
	vel.y = 100 * (rnd () - 0.5);
	vel.z = 100 + 100 * (rnd () - 0.5);

	vel = Vector (self->velocity) + vel * speed;
	vel.set (chunk->velocity);

	chunk->movetype = MOVETYPE_BOUNCE;
	chunk->solid = SOLID_NOT;

	chunk->avelocity[0] = rnd ()*600;
	chunk->avelocity[1] = rnd ()*600;
	chunk->avelocity[2] = rnd ()*600;

	chunk->think = gstate->RemoveEntity;
	chunk->nextthink = gstate->time + 5 + rnd () * 5;
	
	chunk->s.frame = 0;
	chunk->flags = 0;
	chunk->className = "debris";
	chunk->takedamage = DAMAGE_YES;
	chunk->die = debris_die;

	scale.set (chunk->s.render_scale);
//	chunk->s.render_scale [0] = chunk->s.render_scale [1] = chunk->s.render_scale [2]  = 1.0;;

	gstate->LinkEntity (chunk);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void func_explosive_explode (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	funcExploHook_t	*hook = (funcExploHook_t *) self->userHook;
	Vector			origin, size;
	Vector			chunkorigin, vel, scale;
	int				count;
	int				mass;

	size = Vector (self->size) * 0.5;
	origin = size + Vector (self->absmin);

	self->takedamage = DAMAGE_NO;

	if (hook->damage)
		com->RadiusDamage (self, attacker, self->owner, hook->damage, DAMAGE_EXPLOSION);

	vel = origin - Vector (inflictor->s.origin);
	vel.Normalize ();
	vel = vel * 150;
	vel.set (self->velocity);

	// start chunks towards the center
	size = size;// * 0.5;

	mass = self->mass;
	if (!mass)
		mass = 75;

	// big chunks
	if (mass >= 100)
	{
		count = mass / 100;
		if (count > 8)
			count = 8;

		while(count--)
		{
			scale.x = 1.0 + rnd () * 2.0;
			scale.y = 1.0 + rnd () * 2.0;
			scale.z = 1.0 + rnd () * 2.0;

			chunkorigin.x  = origin.x + (rnd () - 0.5) * size.x;
			chunkorigin.y  = origin.y + (rnd () - 0.5) * size.y;
			chunkorigin.z = origin.z + (rnd () - 0.5) * size.z;

			if (self->spawnflags & EXPL_WOOD)
				throw_debris (self, "models/gibs/wood1/wood1.md2", 1, chunkorigin, 0, scale);
			else if (self->spawnflags & EXPL_METAL)
				throw_debris (self, "models/gibs/metal1/metal1.md2", 1, chunkorigin, 0, scale);
			else 
				throw_debris (self, "models/gibs/glass1/glass1.md2", 1, chunkorigin, RF_TRANSLUCENT, scale);
		}
	}

	// small chunks
	count = mass / 25;
	if (count > 16)
		count = 16;
	while(count--)
	{
		scale.x = 1.0 + rnd () * 1.0;
		scale.y = 1.0 + rnd () * 1.0;
		scale.z = 1.0 + rnd () * 1.0;

		chunkorigin.x  = origin.x + (rnd () - 0.5) * size.x;
		chunkorigin.y  = origin.y + (rnd () - 0.5) * size.y;
		chunkorigin.z = origin.z + (rnd () - 0.5) * size.z;

		if (self->spawnflags & EXPL_WOOD)
			throw_debris (self, "models/gibs/wood2/wood2.md2", 1, chunkorigin, 0, scale);
		else if (self->spawnflags & EXPL_METAL)
			throw_debris (self, "models/gibs/metal2/metal2.md2", 1, chunkorigin, 0, scale);
		else 
			throw_debris (self, "models/gibs/glass2/glass2.md2", 1, chunkorigin, RF_TRANSLUCENT, scale);
	}

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

//	if (hook->damage)
//		BecomeExplosion1 (self);
//	else
		gstate->RemoveEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void func_explosive_use (edict_t *self, edict_t *other, edict_t *activator)
{
	func_explosive_explode (self, self, other, self->health, zero_vector);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator)
{
	self->solid = SOLID_BSP;
	self->svflags &= ~SVF_NOCLIENT;
	self->use = NULL;

//	KillBox (self);

	gstate->LinkEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

void func_explosive (userEntity_t *self)
{
	funcExploHook_t	*hook;
	int				i;
	
	self->userHook = com->malloc_hook (sizeof (funcExploHook_t), MEM_MALLOC);
	hook = (funcExploHook_t *) self->userHook;

	for (i = 0; self->epair [i].key; i++)
	{
		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, "damage"))
			hook->damage = atoi (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "mass"))
			self->mass = atoi (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "health"))
			self->health = atoi (self->epair [i].value);
	}

	self->movetype = MOVETYPE_PUSH;

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

	if (self->spawnflags & EXPL_TRIGGER_SPAWN)
	{
		self->svflags |= SVF_NOCLIENT;
		self->solid = SOLID_NOT;
		self->use = func_explosive_spawn;
	}
	else
	{
		self->solid = SOLID_BSP;
		if (self->targetname)
			self->use = func_explosive_use;
	}

	if (self->spawnflags & EXPL_ANIM)
		self->s.effects |= EF_ANIM_ALL;
	if (self->spawnflags & EXPL_ANIMFAST)
		self->s.effects |= EF_ANIM_ALLFAST;

	if (self->use != func_explosive_use)
	{
		if (!self->health)
			self->health = 100;
		self->die = func_explosive_explode;
		self->takedamage = DAMAGE_YES;
	}

	gstate->LinkEntity (self);
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////