//////////////////////////////////////////////////////////////////////
//	ai_frames
//
//	routines for extracting and animating frames in actor entities
//////////////////////////////////////////////////////////////////////

#include	"world.h"
#include	"ai_frames.h"
#include	"floater.h"

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

char	*gib_names [] =   {"models/gibs/gib_torso/gib_torso.md2",
							"models/gibs/gib_shldr/gib_shldr.md2",
							"models/gibs/gib_leg/gib_leg.md2",
							"models/gibs/gib_arm/gib_arm.md2",
							"models/gibs/gib_chunk/gib_chunk.md2"};

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

#define	GIB_FOLLOW_SPEED	40.0
#define	GIB_MAX_SPIN		180.0
#define	GIB_GOAL_DIST		32.0

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

typedef	struct	gibHook_s
{
	float		target_z;		//	what height the gib should float up to

	float		expire_time;	//	how long the gib should wait before shrinking away
} gibHook_t;

//////////////////////////////////////////////
//	ai_get_frames
//
//	queries the model for start and end frames
//
//	returns TRUE if a sequence with that name exists
//	returns FALSE if no matching sequence was found
//////////////////////////////////////////////

int	ai_get_frames (userEntity_t *self, char *seq_name, int *startFrame, int *endFrame)
{
	int				i, j, len, frameNum;
	int				_frameFirst, _frameLast, found_sequence;
	char			frameName [16];
	char			baseName[100];

	found_sequence = FALSE;
	_frameFirst = 10000; _frameLast = -1;

	// go through all frames and get start and stop info
	for (i = 0;; i++)
	{
		gstate->GetFrameName (self->s.modelindex, i, frameName);
		if (!frameName || frameName [0] == 0x00)
			break;

		len = strlen (frameName);

		// separate base and frame number
		for (j = 0; j < len; j++)
		{
			if ((frameName [j] >= '0') && (frameName [j] <= '9'))
			{
				break;
			}
		}

		// make sure the frame has a name
		if (j >= len) continue;

		strcpy (baseName, frameName);

		baseName [j] = 0x00;
		frameNum    = atoi (&frameName [j]);

		if (!stricmp (baseName, seq_name))
		{
			found_sequence = TRUE;

			if (frameNum < _frameFirst) 
	        {
				* startFrame = i;
				_frameFirst = frameNum;
			}
			if (frameNum > _frameLast)  
			{
				* endFrame = i;
				_frameLast = frameNum;
			}
		}
	}

	return	found_sequence;
}
/*
int	ai_get_frames (userEntity_t *self, char *seq_name, int *startFrame, int *endFrame)
{
	// int ret;
	int				i, j, len, frameNum;
	int				_frameFirst, _frameLast, found_sequence;
	userFrameInfo_t frameInfo;
	char			baseName[100];

	found_sequence = FALSE;
	_frameFirst = 10000; _frameLast = -1;

	// go through all frames and get start and stop info
	for (i = 0;; i++)
	{
	    if (!gstate->FrameInfo(self->modelIndex, i, &frameInfo)) break;

		len = strlen(frameInfo.frameName);

		// separate base and frame number
		for (j = 0; j < len; j++)
		{
			if ((frameInfo.frameName[j] >= '0') && (frameInfo.frameName[j] <= '9'))
			{
				break;
			}
		}

		// make sure frame has a name
		if (j >= len) continue;

		strcpy (baseName, frameInfo.frameName);

		baseName [j] = 0x00;
		frameNum    = atoi (&frameInfo.frameName[j]);

		if (!stricmp (baseName, seq_name))
		{
			found_sequence = TRUE;

			if (frameNum < _frameFirst) 
	        {
				* startFrame = i;
				_frameFirst = frameNum;
			}
			if (frameNum > _frameLast)  
			{
				* endFrame = i;
				_frameLast = frameNum;
			}
		}
	}

	return	found_sequence;
}
*/

///////////////////////
//	ai_get_sequence
//
//	grabs a single sequence from a model if that sequence exists
///////////////////////

int	ai_get_sequence (userEntity_t *self, char *name, frameData_t **sequence)
{
	int			first, last;
	frameData_t	*seq;

	if (com->GetFrames (self, name, &first, &last))
	{
		// don't bother freeing old data, because common routines will demalloc it at the end of a level anyway
		// this will screw you, of course, if you keep pulling different sequences out for the same entity
		if (* sequence)
			com->Error ("Frame sequence for %s was not NULL!", self->className);
		* sequence = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		seq = * sequence;

		// init this memory
		memset (seq, 0x00, sizeof (frameData_t));

		seq->first = first;
		seq->last = last;

		seq->soundframe1 = -1;
		seq->soundframe2 = -1;

		seq->trans_pre = -1;
		seq->trans_post = -1;

		return	TRUE;
	}
	else 
		return	FALSE;
}


///////////////////////
//	ai_get_sequences
//
//	grabs the standard frame sequences from a monster's model
///////////////////////

void	ai_get_sequences (userEntity_t *self)
{
	int				i;
	char			name [12];
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	// get walk sequences
	strcpy (name, "walka");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->walk [i]); i++)
		name [4]++;
	hook->sequence_max [SEQUENCE_WALK] = (char) i;

	// get stand sequences
	strcpy (name, "amba");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->stand [i]); i++)
		name [3]++;
	hook->sequence_max [SEQUENCE_STAND] = (char) i;

	// get run sequences
	strcpy (name, "runa");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->run [i]); i++)
		name [3]++;
	hook->sequence_max [SEQUENCE_RUN] = (char) i;

	// get death sequences
	strcpy (name, "diea");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->death [i]); i++)
		name [3]++;
	hook->sequence_max [SEQUENCE_DEATH] = (char) i;

	// get pain sequences
	strcpy (name, "hita");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->pain [i]); i++)
		name [3]++;
	hook->sequence_max [SEQUENCE_PAIN] = (char) i;

	// get attack sequences
	strcpy (name, "ataka");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->atak [i]); i++)
		name [4]++;
	hook->sequence_max [SEQUENCE_ATAK] = (char) i;

	// get jump sequences
	strcpy (name, "jumpa");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->jump [i]); i++)
		name [4]++;
	hook->sequence_max [SEQUENCE_JUMP] = (char) i;

	// get death sequences
	strcpy (name, "turna");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->turn [i]); i++)
		name [4]++;
	hook->sequence_max [SEQUENCE_TURN] = (char) i;

	// get fly sequences
	strcpy (name, "flya");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->fly [i]); i++)
		name [3]++;
	hook->sequence_max [SEQUENCE_FLY] = (char) i;

	// get swim sequences
	strcpy (name, "swima");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->swim [i]); i++)
		name [3]++;
	hook->sequence_max [SEQUENCE_SWIM] = (char) i;

	// get transition sequences
	strcpy (name, "transa");
	for (i = 0; ai_get_sequence (self, name, &hook->frames->trans [i]); i++)
		name [5]++;
	if (i == 0)
	{
		// get alternately named transition sequences
		strcpy (name, "tranza");
		for (i = 0; ai_get_sequence (self, name, &hook->frames->trans [i]); i++)
			name [5]++;
	}
	hook->sequence_max [SEQUENCE_TRANS] = (char) i;

	////////////////////////////////////////////////////////////////////
	// make sure at least 1 sequence is malloced for everything
	////////////////////////////////////////////////////////////////////

	if (!hook->frames->walk [0])
	{
		//	make walk frames the run frames if run frames exist
		if (hook->frames->run [0])
			hook->frames->walk [0] = hook->frames->run [0];
		else
		{
			hook->frames->walk [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
			memset (hook->frames->walk [0], 0x00, sizeof (frameData_t));
		}
		hook->sequence_max [SEQUENCE_WALK] = 1;
	}
	if (!hook->frames->run [0])
	{
		//	make run frames the walk frames if walk frames exist
		if (hook->frames->walk [0])
			hook->frames->run [0] = hook->frames->walk [0];
		else
		{
			hook->frames->run [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
			memset (hook->frames->run [0], 0x00, sizeof (frameData_t));
		}
		hook->sequence_max [SEQUENCE_RUN] = 1;
	}
	if (!hook->frames->stand [0])
	{
		hook->frames->stand [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->stand [0], 0x00, sizeof (frameData_t));
	}
	if (!hook->frames->death [0])
	{
		hook->frames->death [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->death [0], 0x00, sizeof (frameData_t));
	}
	if (!hook->frames->pain [0])
	{
		hook->frames->pain [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->pain [0], 0x00, sizeof (frameData_t));
	}
	if (!hook->frames->atak [0])
	{
		if (hook->frames->run [0])
			hook->frames->atak [0] = hook->frames->run [0];
		else
		{
			hook->frames->atak [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
			memset (hook->frames->atak [0], 0x00, sizeof (frameData_t));
		}
	}
	if (!hook->frames->jump [0])
	{
		hook->frames->jump [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->jump [0], 0x00, sizeof (frameData_t));
		hook->frames->jump [0]->first = hook->frames->run [0]->first;
		hook->frames->jump [0]->last = hook->frames->run [0]->last;
	}
	if (!hook->frames->turn [0])
	{
		hook->frames->turn [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->turn [0], 0x00, sizeof (frameData_t));
	}
	if (!hook->frames->fly [0])
	{
		hook->frames->fly [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->fly [0], 0x00, sizeof (frameData_t));
	}
	if (!hook->frames->swim [0])
	{
		hook->frames->swim [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->swim [0], 0x00, sizeof (frameData_t));
	}
	if (!hook->frames->trans [0])
	{
		hook->frames->trans [0] = (frameData_t *) com->malloc (sizeof (frameData_t), MEM_MALLOC);
		memset (hook->frames->trans [0], 0x00, sizeof (frameData_t));
	}
}

//////////////////////////////
// ai_set_sequence_sounds
//
//////////////////////////////

void	ai_set_sequence_sounds (userEntity_t *self, int frame1, char *sound1, int frame2, char *sound2)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	// frames on which we want to play a sound
	hook->sound1Frame = frame1;
	hook->sound2Frame = frame2;
	hook->sound1 = sound1;
	hook->sound2 = sound2;

	hook->frame_flags -= (hook->frame_flags & FRAME_PLAYSOUND1);
	hook->frame_flags -= (hook->frame_flags & FRAME_PLAYSOUND2);
}


////////////////////////////////
//	ai_set_sequence_data
//
////////////////////////////////

void	ai_set_sequence_data (userEntity_t *self, frameData_t *data, int sf1, char *name1, int sf2, char *name2, int pret_type, int pret, int post_type, int postt, unsigned long int flags)
{
	data->soundframe1 = sf1 + data->first;
	data->sound1 = name1;
	data->soundframe2 = sf2 + data->first;
	data->sound2 = name2;
	
	data->trans_pre = pret;
	data->trans_post = postt;

	data->flags = flags;
}

//////////////////////////////
//	ai_set_trans
//
//	sets up a transition that occurs
//	during another type of think other than a transition think (during attack, for example)
//////////////////////////////

void	ai_set_trans (userEntity_t *self, int start, int main, int main_num, int end)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	hook->start_seq_num = start;

	hook->main_seq_type = main;
	hook->main_seq_num = main_num;

	hook->end_seq_num = end;

	self->s.frame = hook->startFrame = hook->frames->trans [start]->first;
	hook->endFrame = hook->frames->trans [start]->last;
	hook->frame_flags = FRAME_ONCE + FRAME_TRANS;
}

////////////////////////////////
//	ai_init_sequence
//
////////////////////////////////

void	ai_init_sequence (userEntity_t *self, frameData_t *data, int seq_type, int seq_num)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	frameData_t		*old_data;

	old_data = NULL;

	if (data == NULL)
	{
		gstate->Con_Printf ("Missing frame data for %s.\n", self->className);
		hook->cur_seq_type = SEQUENCE_WALK;
		hook->cur_seq_num = 0;
		hook->frame_flags = FRAME_ONCE;
		hook->startFrame = hook->frames->walk [0]->first;
		hook->endFrame = hook->frames->walk [0]->last;
		self->s.frame = hook->startFrame;
		hook->frameInc = 1;
		return;
	}

	// if this sequence has to start with a transition, then set it up instead
	if (data->trans_pre >= 0)
	{
		hook->frame_flags = FRAME_ONCE + FRAME_TRANS;

		old_data = data;
		data = hook->frames->trans [data->trans_pre];

		hook->cur_seq_type = SEQUENCE_TRANS;
		hook->cur_seq_num = data->trans_pre;

		hook->cur_sequence = hook->frames->trans [data->trans_pre];
	}
	else
	{
		hook->frame_flags = data->flags;
		hook->cur_seq_type = seq_type;
		hook->cur_seq_num = seq_num;

		hook->cur_sequence = data;
	}

	ai_set_sequence_sounds (self, data->soundframe1, data->sound1, data->soundframe2, data->sound2);

	hook->startFrame = data->first;
	hook->endFrame = data->last;

	//	don't reset animation if already in this sequence
	if (self->s.frame < hook->startFrame || self->s.frame >= hook->endFrame)
		self->s.frame = hook->startFrame;

	if (old_data)
		data = old_data;
		
	hook->main_seq_num = seq_num;
	hook->main_seq_type = seq_type;

	hook->start_seq_num = data->trans_pre;
	hook->end_seq_num = data->trans_post;

	hook->frameInc = 1;
}

///////////////////////////////
//	ai_post_trans
//
///////////////////////////////

void	ai_post_trans (userEntity_t *self, unsigned long flags)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	frameData_t		*data;

	switch	(hook->main_seq_type)
	{
		case	SEQUENCE_ATAK:
			data = hook->frames->atak [hook->main_seq_num];

			hook->startFrame = self->s.frame = data->first;
			hook->endFrame = data->last;
			ai_set_sequence_sounds (self, data->soundframe1, data->sound1, data->soundframe2, data->sound1);
			break;

		default:
			com->Error ("Tried to transition to unknown sequence type.");
	}

	hook->frame_flags = flags;
}

////////////////////////////////
//	ai_set_frames
//
//	sets hook->startFrame and hook->endFrame according to
//	cur_seq_type and cur_seq_num
////////////////////////////////

void	ai_set_frames (userEntity_t *self, int cur_seq_type, int cur_seq_num)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	switch	(cur_seq_type)
	{
		case	SEQUENCE_ATAK:
			hook->cur_sequence = hook->frames->atak [cur_seq_num];
//			hook->startFrame = hook->frames->atak [cur_seq_num]->first;
//			hook->endFrame = hook->frames->atak [cur_seq_num]->last;
			break;
		case	SEQUENCE_RUN:
			hook->cur_sequence = hook->frames->run [cur_seq_num];
//			hook->startFrame = hook->frames->run [cur_seq_num]->first;
//			hook->endFrame = hook->frames->run [cur_seq_num]->last;
			break;
		case	SEQUENCE_WALK:
			hook->cur_sequence = hook->frames->walk [cur_seq_num];
//			hook->startFrame = hook->frames->walk [cur_seq_num]->first;
//			hook->endFrame = hook->frames->walk [cur_seq_num]->last;
			break;
		case	SEQUENCE_STAND:
			hook->cur_sequence = hook->frames->stand [cur_seq_num];
//			hook->startFrame = hook->frames->stand [cur_seq_num]->first;
//			hook->endFrame = hook->frames->stand [cur_seq_num]->last;
			break;
		case	SEQUENCE_DEATH:
			hook->cur_sequence = hook->frames->death [cur_seq_num];
//			hook->startFrame = hook->frames->death [cur_seq_num]->first;
//			hook->endFrame = hook->frames->death [cur_seq_num]->last;
			break;
		case	SEQUENCE_PAIN:
			hook->cur_sequence = hook->frames->pain [cur_seq_num];
//			hook->startFrame = hook->frames->pain [cur_seq_num]->first;
//			hook->endFrame = hook->frames->pain [cur_seq_num]->last;
			break;
		case	SEQUENCE_JUMP:
			hook->cur_sequence = hook->frames->jump [cur_seq_num];
//			hook->startFrame = hook->frames->jump [cur_seq_num]->first;
//			hook->endFrame = hook->frames->jump [cur_seq_num]->last;
			break;
		case	SEQUENCE_FLY:
			hook->cur_sequence = hook->frames->fly [cur_seq_num];
//			hook->startFrame = hook->frames->fly [cur_seq_num]->first;
//			hook->endFrame = hook->frames->fly [cur_seq_num]->last;
			break;
		case	SEQUENCE_SWIM:
			hook->cur_sequence = hook->frames->swim [cur_seq_num];
//			hook->startFrame = hook->frames->swim [cur_seq_num]->first;
//			hook->endFrame = hook->frames->swim [cur_seq_num]->last;
			break;
		case	SEQUENCE_TRANS:
			hook->cur_sequence = hook->frames->trans [cur_seq_num];
//			hook->startFrame = hook->frames->trans [cur_seq_num]->first;
//			hook->endFrame = hook->frames->trans [cur_seq_num]->last;
			break;
		case	SEQUENCE_SPECIAL:
			hook->cur_sequence = hook->frames->special [cur_seq_num];
//			hook->startFrame = hook->frames->trans [cur_seq_num]->first;
//			hook->endFrame = hook->frames->trans [cur_seq_num]->last;
			break;
	}

	hook->startFrame = hook->cur_sequence->first;
	hook->endFrame = hook->cur_sequence->last;

	self->s.frame = hook->startFrame;
}

////////////////////////////////
//	ai_force_sequence
//
//	initializes a sequence using the passed vars rather than
//	pulling information from the frameData_t
//
//
////////////////////////////////

void	ai_force_sequence (userEntity_t *self, int seq_type, int seq_num, 
						   int trans_pre_type, int trans_pre, 
						   int trans_post_type, int trans_post, int sf1, 
						   char *sname1, int sf2, char *sname2)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	// if this sequence has to start with a transition, then set it up instead
	if (trans_pre >= 0)
	{
		hook->frame_flags = FRAME_ONCE + FRAME_TRANS;

		hook->cur_seq_type = SEQUENCE_TRANS;
		hook->cur_seq_num = trans_pre;
	}
	else
	{
		hook->frame_flags = FRAME_ONCE;
		hook->cur_seq_type = seq_type;
		hook->cur_seq_num = seq_num;
	}

	ai_set_sequence_sounds (self, sf1, sname1, sf2, sname2);

	ai_set_frames (self, hook->cur_seq_type, hook->cur_seq_num);

	hook->main_seq_num = seq_num;
	hook->main_seq_type = seq_type;

	hook->start_seq_type = trans_pre_type;
	hook->start_seq_num = trans_pre;

	hook->end_seq_type = trans_post_type;
	hook->end_seq_num = trans_post;
}


///////////////////////////////////////////////////////////////
//	ai_frame_update
//
//	called during prethinks for entities with a playerHook
//	plays back frames at percieved correct rate, even if
//	engine is not reaching maximum frame rate of 20 fps
//	sets flags for playing sounds during a sequence
//	sets flags for last frame of a looping sequence and end
//	of a non-looping sequence
///////////////////////////////////////////////////////////////

void	ai_frame_update (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	float			frame_remainder, temp;
	int				add, new_frame;
	int				test;

	// no frame changes if FRAME_STATIC is set
	if (!(hook->frame_flags & FRAME_STATIC))
	{
		// increment frames according to length of last frame
		hook->next_frameTime = hook->next_frameTime + gstate->frametime;
		
		if (hook->next_frameTime > hook->frameTime)
		{
			temp = floor (hook->next_frameTime / hook->frameTime);
			add = (int)(hook->frameInc * temp);
			new_frame = self->s.frame + add;

			///////////////////////////////////////////////////////////////////////
			//	set/clear FRAME_LAST flag if on last frame
			///////////////////////////////////////////////////////////////////////

			if (hook->endFrame <= new_frame && hook->endFrame > self->s.frame)
				hook->frame_flags |= FRAME_LAST;

//			if (self->flags & FL_DEBUG)
//				gstate->printxy (0, 80, "sf=%i ef=%i fi=%i add=%i\n", hook->startFrame, hook->endFrame, self->s.frame, add);

			///////////////////////////////////////////////////////////////////////
			//	set FRAME_PLAYSOUND flags if correct frame
			///////////////////////////////////////////////////////////////////////

			if (hook->sound1Frame >= self->s.frame && hook->sound1Frame < new_frame)
				hook->frame_flags = hook->frame_flags | FRAME_PLAYSOUND1;
			if (hook->sound2Frame >= self->s.frame &&  hook->sound2Frame < new_frame)
				hook->frame_flags = hook->frame_flags | FRAME_PLAYSOUND2;

			self->s.frame += add;

			// are we currently looping within a larger sequence?
			if (hook->frame_flags & FRAME_REPEAT)
			{
				// determine if sequence needs to loop back to loopStart
				if (self->s.frame > hook->endLoop)
				{
					self->s.frame = hook->startLoop;
					hook->loopCount--;
					// if count is <= 0, stop the looping of this partial sequence
					if (hook->loopCount <= 0)
					{
						hook->frame_flags -= FRAME_REPEAT;
						hook->startLoop = hook->startFrame;
						hook->endLoop = hook->endFrame;
					}
				}
			}
			// going through an entire sequence
			else
			{
				// determine if sequence needs to loop back to start
				if (self->s.frame > hook->endFrame)
				{
					if (hook->frame_flags & FRAME_ONCE)
					{
						// we've gone through once, so set frames to static (no updates)
						hook->frame_flags |= FRAME_STATIC;
						self->s.frame = hook->endFrame;
					}
					else
						//	looping through entire sequence
						self->s.frame = hook->startFrame;
				}
			}
			
			hook->next_frameTime = hook->next_frameTime - (temp * hook->frameTime);
		}
	}

//	com->Showbbox (self, Vector (self->s.origin), Vector (self->mins), Vector (self->mins));
}
///////////////////////////////////////////////////////////////////////////////
//	gib_goal_dir
//
//	returns a vector pointing towards goal which has a zero z 
//	component (direction in x/y plane only).
///////////////////////////////////////////////////////////////////////////////

void	gib_goal_dir (userEntity_t *self, userEntity_t *goal, Vector &dir)
{
	Vector		s_org, g_org;

	s_org = Vector (self->s.origin);
	s_org.z = 0;

	g_org = Vector (goal->s.origin);
	g_org.z = 0;

	dir = g_org - s_org;
	dir.Normalize ();
}

///////////////////////////////////////////////////////////////////////////////
//	gib_throw_blood
//
//	spawns blood particles traveling in direction of self->s.angles at a velocity
//	based on spin rate (self->avelocity [1]).
///////////////////////////////////////////////////////////////////////////////

void	gib_throw_blood (userEntity_t *self)
{
	Vector	ang;
	
	ang = Vector (self->s.angles);
	ang.AngleVectors (forward, right, up);

	forward = forward * self->avelocity [1] * 0.025;

//	gstate->particle (self->s.origin, forward.vec (), gstate->global_red, 32);
}

///////////////////////////////////////////////////////////////////////////////
//	gib_follow
//
//	moves towards goal in the x/y plane, doesnt affect z velocity
//	returns distance to goal in x/y plane
///////////////////////////////////////////////////////////////////////////////

float	gib_follow (userEntity_t *self, userEntity_t *goal, float &speed)
{
	Vector	dir;

	gib_goal_dir (self, goal, dir);

	dir = dir * speed;
	
	self->velocity [0] = dir.x;
	self->velocity [1] = dir.y;

	speed = speed * self->button0;	// increase speed

	return	xy_dist (self->s.origin, goal->s.origin);
}

///////////////////////////////////////////////////////////////////////////////
//	gib_shrink
//
//	gib has reached desired height and is spinning and shrinking into nothing
//	spinning grows faster and faster as gib shrinks
///////////////////////////////////////////////////////////////////////////////

void	gib_shrink (userEntity_t *self)
{
	float		speed;
	gibHook_t	*gibHook = (gibHook_t *) self->userHook;

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

	gib_follow (self, self->goalentity, self->button1);

	//	gib has reached desired height and is spinning and shrinking into nothing
	com->MultVector (self->s.render_scale, self->delay, self->s.render_scale);

	gib_throw_blood (self);

	if (self->s.render_scale [0] < 0.1)
		gstate->RemoveEntity (self);
	else
	{
		self->think = gib_shrink;
		self->nextthink = gstate->time + 0.05;//gstate->frametime;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	gib_scale_spin
///////////////////////////////////////////////////////////////////////////////

void	gib_scale_spin (userEntity_t *self)
{
	//	spin faster
	com->MultVector (self->avelocity, self->team, self->avelocity);
	
	if (self->avelocity [0] > GIB_MAX_SPIN)
		self->avelocity [0] = GIB_MAX_SPIN;
	else if (self->avelocity [0] < - GIB_MAX_SPIN)
		self->avelocity [0] = - GIB_MAX_SPIN;

	if (self->avelocity [1] > GIB_MAX_SPIN)
		self->avelocity [1] = GIB_MAX_SPIN;
	else if (self->avelocity [1] < - GIB_MAX_SPIN)
		self->avelocity [1] = - GIB_MAX_SPIN;

	if (self->avelocity [2] > GIB_MAX_SPIN)
		self->avelocity [2] = GIB_MAX_SPIN;
	else if (self->avelocity [2] < - GIB_MAX_SPIN)
		self->avelocity [2] = - GIB_MAX_SPIN;
}

///////////////////////////////////////////////////////////////////////////////
//	gib_spin
//
//	gib has reached desired height and is spinning until expire_time is reached
///////////////////////////////////////////////////////////////////////////////

void	gib_spin (userEntity_t *self)
{
	float		speed, dist;
	gibHook_t	*gibHook = (gibHook_t *) self->userHook;

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

	dist = gib_follow (self, self->goalentity, self->button1);

	//	gib has reached desired height and is spinning 
	gib_scale_spin (self);

	gib_throw_blood (self);

	if (gibHook->expire_time > gstate->time && dist > GIB_GOAL_DIST)
	{
		self->think = gib_spin;
		self->nextthink = gstate->time + 0.05;	//gstate->frametime;
	}
	else
	{
		self->think = gib_shrink;
		self->nextthink = gstate->time + 0.05;	//gstate->frametime;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	gib_rise
//
//	gib is rising to target_z height
///////////////////////////////////////////////////////////////////////////////

void	gib_rise (userEntity_t *self)
{
	float		speed;
	gibHook_t	*gibHook = (gibHook_t *) self->userHook;

//	gstate->Con_Printf ("gib_rise\n");
	
	gib_follow (self, self->goalentity, self->button1);

	gib_scale_spin (self);

	gib_throw_blood (self);

	//	gib is rising up
	if (self->s.origin [2] < gibHook->target_z && gstate->time < gibHook->expire_time)
	{
		self->think = gib_rise;
		self->nextthink = gstate->time + 0.05;//gstate->frametime;
	}
	else
	{
		com->ZeroVector (self->velocity);

		self->movetype = MOVETYPE_FLY;
		self->gravity = 1.0;

		gibHook->expire_time = gstate->time + 3.5;	//	spin time before shrinking away

		self->think = gib_spin;
		self->nextthink = gstate->time + 0.05;//gstate->frametime;
	}
}

//////////////////////////////////////////////////
//	gib_percent_above_below
//
//////////////////////////////////////////////////

void	gib_percent_above_below (userEntity_t *self, int &contents, float &percent_submerged)
{
	vec3_t		top, bottom, move;
	float		bbox_height, water_height;				
	gibHook_t	*gibHook = (gibHook_t *) self->userHook;

	com->CopyVector (top, self->s.origin);
	top [2] += self->s.maxs [2];

	com->CopyVector (bottom, self->s.origin);
	bottom [2] += self->s.mins [2];

	bbox_height = (self->s.maxs [2] - self->s.mins [2]);

	water_height = gibHook->target_z - bottom [2];

	if (water_height <= 0)
		percent_submerged = 0.0;
	else if (water_height >= bbox_height)
		percent_submerged = 1.0;
	else
		percent_submerged = 1 / (bbox_height / water_height);

	contents = CONTENTS_EMPTY;

//	gstate->Con_Printf ("%s water_height = %.2f bb_h = %.2f p_s = %.2f\n", self->netname, water_height, bbox_height, percent_submerged);
}

/////////////////////////////////
//	gib_touch_stopped
//
//	determines if the toucher can pick the gib up
/////////////////////////////////

void	gib_touch_stopped (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	Vector		avel;
	float		speed;
	gibHook_t	*gibHook;
	weapon_t	*weapon;

	if (other->flags & (FL_CLIENT + FL_BOT))// + FL_MONSTER))
	{
		weapon = (weapon_t *) other->curWeapon;

		if (weapon && weapon->name && !stricmp (weapon->name, "weapon_nightmare"))
		{
			//	FIXME: FL_MONSTER only picks up gibs if of TYPE_NHARRE
			//	FIXME: FL_CLIENT only picks up gibs if holding Nharre's Nightmare
			//	FIXME: FL_BOT only picks up gibs if Nharre's Nightmare is in inventory

			self->userHook = com->malloc_hook (sizeof (gibHook_t), MEM_MALLOC);
			gibHook = (gibHook_t *) self->userHook;

			//	float up to player's view_ofs height
			//	FIXME: make sure height is always the same above floor when Q2 bounding boxes are implemented
			gibHook->target_z = self->s.origin [2] + 32 + rnd () * 24 - 16;
			gibHook->expire_time = gstate->time + 2.0;

			self->delay = 0.5;			//	shrink rate
			self->team	= 1.05;			//	rate of spin increase
			self->button0 = 1.025;		//	velocity scale
			self->button1 = GIB_FOLLOW_SPEED;	//	speed

			self->movetype = MOVETYPE_TOSS;
			//	pre-floater way of making gibs rise
	//		self->gravity = -0.05;
			self->flags -= (self->flags & FL_ONGROUND);

			self->goalentity = other;

			avel.x = rnd () * 2.0 - 1.0;
			avel.y = rnd () * 2.0 + 2.0;
			if (rnd () < 0.5)
				avel.y = -avel.y;
			avel.z = rnd () * 2.0 - 1.0;
			avel.Normalize ();

			avel = avel * 30.0;

			avel.set (self->avelocity);

			self->touch = NULL;

			self->think = gib_rise;
			self->nextthink = gstate->time + 0.1;

			float_init (self, 1.0, 2.0, 1.0, 0.9, 24.0, gib_percent_above_below);
		}
	}
}

/////////////////////////////////
//	gib_touch_moving
//
//	changes gib angular velocity each time it hits
/////////////////////////////////

void	gib_touch_moving (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	float		speed;
	gibHook_t	*gibHook;

	speed = qv_len (self->velocity);

	self->avelocity [0] = (rnd () * speed * 2.0) - speed;
	self->avelocity [1] = (rnd () * speed * 2.0) - speed;
	self->avelocity [2] = (rnd () * speed * 2.0) - speed;
}

///////////////////////////////////////////////////////////////////////////////
//	gib_think
//
//	temp routine for trailing blood
///////////////////////////////////////////////////////////////////////////////

void	gib_think (userEntity_t *self)
{
	if (qv_len (self->velocity) > 0)
	{
//		gstate->particle (self->s.origin, zero_vector, gstate->global_red, 32);

		self->think = gib_think;
		self->nextthink = gstate->time + 0.05;
	}
	else
	{
		self->solid = SOLID_TRIGGER;
		self->movetype = MOVETYPE_NONE;

		gstate->LinkEntity (self);
//		gstate->SetSize (self, -16, -16, -16, 16, 16, 16);

		com->ZeroVector (self->avelocity);

		self->touch = gib_touch_stopped;

		self->think = gstate->RemoveEntity;
		self->nextthink = gstate->time + 10.0 + rnd () * 30.0;
	}
}

//////////////////////////////////////////////////////////////////////////////
//	ai_throw_gib
//
//	throws a gib more or less in the direction of damage_vector
//////////////////////////////////////////////////////////////////////////////

userEntity_t	*ai_throw_gib (userEntity_t *self, char *model, Vector offset)
{
	userEntity_t	*gib;
	Vector			dir;
	float			damage_mult, speed;

	gib = gstate->SpawnEntity ();

	gib->movetype = MOVETYPE_BOUNCE;
	gib->solid = SOLID_BBOX;
	gib->owner = self;
	gib->className = "gib";
	gib->s.effects = EF_GIB;
	gib->s.modelindex = gstate->ModelIndex (model);

	//	FIXME:	hack for E3 demo
	if (gib->s.modelindex == 0)
	{
		gstate->RemoveEntity (gib);
		return	NULL;
	}

	dir = Vector (self->s.angles);
	dir.AngleVectors (forward, right, up);
	dir = Vector (self->s.origin) + offset.x * right + offset.y * forward + offset.z * up;
	gstate->SetOrigin2 (gib, dir.vec ());
	gstate->SetSize (gib, -8, -8, -8, 8, 8, 8);

	dir = gstate->damage_vector;

//	gstate->Con_Printf ("damage_inflicted = %.2f\n", gstate->damage_inflicted);
//	gstate->Con_Printf ("damage_vector = %.2f %.2f %.2f\n", dir.x, dir.y, dir.z);

	//	damage_inflicted of 50 points is a multiplier of 1.0
	damage_mult = (gstate->damage_inflicted / 50) * 250.0;

//	gstate->Con_Printf ("damage_mult = %.2f\n", damage_mult);

	dir.x *= rnd () * damage_mult;
	dir.y *= rnd () * damage_mult;
	if (gstate->damage_flags & DAMAGE_EXPLOSION)
		dir.z *= rnd () * damage_mult * (2.0 * rnd () + 1.0);
	else
		dir.z *= rnd () * damage_mult;

	if (dir.z < 0 && dir.z > -100) 
		dir.z -= 100.0;
	else if (dir.z > 0 && dir.z < 200) 
		dir.z += 200.0;

//	dir = dir * damage_mult;

	dir.set (gib->velocity);

	speed = qv_len (self->velocity);

	if (speed)
	{
		gib->avelocity [0] = (rnd () * speed * 2.0) - speed;
		gib->avelocity [1] = (rnd () * speed * 2.0) - speed;
		gib->avelocity [2] = (rnd () * speed * 2.0) - speed;
	}
//	dir = Vector (rnd () * damage_mult + 100, rnd () * damage_mult + 100, rnd () * damage_mult + 100);
//	dir.set (self->avelocity);

	gib->think = gib_think;
	gib->nextthink = gstate->time + 0.05;

	gib->touch = gib_touch_moving;

	return	gib;
}

///////////////////////////////
//  ai_pain_sound
//
//	plays the correct pain sound for a dying creature
///////////////////////////////

void	ai_pain_sound (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	char			*sound [2];
	int				i, max_sounds;

	max_sounds = 2;

	switch	(hook->type)
	{
		//
		//	Episode 1
		//
		case	TYPE_BATTLEBOAR:
			sound[0] = "monsters/battleboar/battleboarpain1.wav";
			sound[1] = "monsters/battleboar/battleboarpain2.wav";
			break;

		case	TYPE_RAGEMASTER:
			sound[0] = "monsters/ragemaster/ragemasterpain.wav";
			sound[1] = "monsters/ragemaster/ragemasterpain1.wav";
			break;

		case	TYPE_SLUDGEMINION:
			sound[0] = "monsters/sludge/sludgepain1.wav";
			sound[1] = "monsters/sludge/sludgepain2.wav";
			break;

		case	TYPE_FROGINATOR:
			max_sounds = 1;
			sound[0] = "monsters/froginator/frogpain.wav";
			break;

		case	TYPE_VERMIN:
			max_sounds = 1;
			sound[0] = "monsters/vermin/verminpain.wav";
			break;

		case	TYPE_PRISONER:
			max_sounds = 3;
			sound[0] = "monsters/e1prisoner/e1prisonerpain1.wav";
			sound[1] = "monsters/e1prisoner/e1prisonerpain2.wav";
			sound[2] = "monsters/e1prisoner/e1prisonerpain3.wav";
			break;

		//
		//	Episode 2
		//
		case	TYPE_SKELETON:
			max_sounds = 1;
			sound[0] = "monsters/skeleton/skeletonpain.wav";
			break;

		case	TYPE_CEREBUS:
			max_sounds = 1;
			sound[0] = "monsters/cerebus/cerebuspain1.wav";
			break;

		case	TYPE_SPIDER:
			sound [0] = "monsters/spider/spiderpain1.wav";
			sound [1] = "monsters/spider/spiderpain2.wav";
			break;

		case	TYPE_GRIFFON:
			max_sounds = 3;
			sound [0] = "monsters/griffon/griffonpain1.wav";
			sound [1] = "monsters/griffon/griffonpain2.wav";
			sound [2] = "monsters/griffon/griffonpain3.wav";
			break;

		case	TYPE_SATYR:
			max_sounds = 0;
			break;

		case	TYPE_COLUMN:
			max_sounds = 0;
			break;

		//
		//	Episode 3
		//
		case	TYPE_LYCANTHIR:
			sound [0] = "monsters/lycanthir/lycanthirpain1.wav";
			sound [1] = "monsters/lycanthir/lycanthirpain2.wav";
			break;

		case	TYPE_FLETCHER:
			max_sounds = 0;
			break;

		case	TYPE_BUBOID:
			sound [0] = "monsters/buboid/buboidpain1.wav";
			sound [1] = "monsters/buboid/buboidpain2.wav";
			break;

		case	TYPE_DOOMBAT:
			sound [0] = "monsters/doombat/doombatpain1.wav";
			sound [1] = "monsters/doombat/doombatpain2.wav";
			break;

		case	TYPE_DWARF:
			sound [0] = "monsters/dwarf/dwarfpain1.wav";
			sound [1] = "monsters/dwarf/dwarfpain2.wav";
			break;

		//
		//	Episode 4
		//
		case	TYPE_CHAINGANG:
			sound [0] = "monsters/cgang/cgangpain.wav";
			sound [1] = "monsters/cgang/cgangpain.wav";
			break;

		case	TYPE_FEMGANG:
			sound [0] = "monsters/femgang/femalepain.wav";
			sound [1] = "monsters/femgang/femalepain.wav";
			break;

		case	TYPE_ROCKETGANG:
			sound [0] = "monsters/rocketdude/rocketpain.wav";
			sound [1] = "monsters/rocketdude/rocketpain.wav";
			break;

		case	TYPE_LABMONKEY:
			max_sounds = 3;
			sound [0] = "monsters/labmonkey/labmonkypain1.wav";
			sound [1] = "monsters/labmonkey/labmonkypain2.wav";
			sound [2] = "monsters/labmonkey/labmonkypain3.wav";
			break;

		case	TYPE_BLACKPRIS:
			max_sounds = 1;
			sound [0] = "monsters/blackpris/blackprisonerpain.wav";
			break;


		default:
			max_sounds = 0;
			break;
	}

	if (max_sounds)
	{
		i = rand () % max_sounds;

		gstate->sound (self, CHAN_AUTO, sound [i], 255.0, ATTN_NORM);
	}
}

///////////////////////////////
//  ai_die_sound
//
//	plays the correct death sound for a dying creature
///////////////////////////////

void	ai_die_sound (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	char			*sound [2];
	int				i, max_sounds;

	max_sounds = 2;

	switch	(hook->type)
	{
		//
		//	Episode 1
		//
		case	TYPE_BATTLEBOAR:
			sound[0] = "monsters/battleboar/battleboardies1.wav";
			sound[1] = "monsters/battleboar/battleboardies2.wav";
			break;

		case	TYPE_RAGEMASTER:
			max_sounds = 1;
			sound[0] = "monsters/ragemaster/ragemasterdie1.wav";
			break;

		case	TYPE_SLUDGEMINION:
			sound[0] = "monsters/sludge/sludgedeath1.wav";
			sound[1] = "monsters/sludge/sludgedeath2.wav";
			break;

		case	TYPE_FROGINATOR:
			max_sounds = 1;
			sound[0] = "monsters/froginator/frogdeath.wav";
			break;

		case	TYPE_VERMIN:
			max_sounds = 1;
			sound[0] = "monsters/vermin/vermindie.wav";
			break;

		case	TYPE_PRISONER:
			max_sounds = 1;
			sound[0] = "monsters/e1prisoner/e1prisonerdies1.wav";
			break;

		//
		//	Episode 2
		//
		case	TYPE_SKELETON:
			sound[0] = "monsters/skeleton/skeletondeath.wav";
			sound[1] = "monsters/skeleton/skeletondeath2.wav";
			break;

		case	TYPE_CEREBUS:
			max_sounds = 1;
			sound[0] = "monsters/cerebus/cerebusdies1.wav";
			break;

		case	TYPE_SPIDER:
			sound [0] = "monsters/spider/spiderdeath1.wav";
			sound [1] = "monsters/spider/spiderdeath2.wav";
			break;

		case	TYPE_GRIFFON:
			max_sounds = 1;
			sound [0] = "monsters/griffon/griffondeath1.wav";
			break;

		case	TYPE_SATYR:
			max_sounds = 0;
			break;

		case	TYPE_COLUMN:
			max_sounds = 0;
			break;

		//
		//	Episode 3
		//
		case	TYPE_LYCANTHIR:
			max_sounds = 1;
			sound [0] = "monsters/lycanthir/lycanthirdeath1.wav";
			break;
		
		case	TYPE_FLETCHER:
			max_sounds = 0;
			break;

		case	TYPE_BUBOID:
			sound [0] = "monsters/buboid/buboiddeath1.wav";
			sound [1] = "monsters/buboid/buboiddeath2.wav";
			break;

		case	TYPE_DOOMBAT:
			sound [0] = "monsters/doombat/doombatdeath1.wav";
			sound [1] = "monsters/doombat/doombatdeath2.wav";
			break;

		case	TYPE_DWARF:
			sound [0] = "monsters/dwarf/dwarfdeath1.wav";
			sound [1] = "monsters/dwarf/dwarfdeath2.wav";
			break;

		//
		//	Episode 4
		//
		case	TYPE_CHAINGANG:
			sound [0] = "monsters/cgang/cgangdies.wav";
			sound [1] = "monsters/cgang/cgangdies.wav";
			break;

		case	TYPE_FEMGANG:
			sound [0] = "monsters/femgang/femaledeath.wav";
			sound [1] = "monsters/femgang/femaledeath.wav";
			break;

		case	TYPE_ROCKETGANG:
			sound [0] = "monsters/rocketdude/rocketdeath.wav";
			sound [1] = "monsters/rocketdude/rocketdeath.wav";
			break;

		case	TYPE_LABMONKEY:
			max_sounds = 1;
			sound [0] = "monsters/labmonkey/labmonkeydies1.wav";
			break;

		case	TYPE_BLACKPRIS:
			max_sounds = 1;
			sound [0] = "monsters/blackpris/blackprisonerdies.wav";
			break;


		default:
			max_sounds = 0;
			break;
	}

	if (max_sounds)
	{
		i = rand () % max_sounds;

		gstate->sound (self, CHAN_AUTO, sound [i], 255.0, ATTN_NORM);
	}
}

///////////////////////////////////
//	ai_sight_sound
//
//	plays an appropriate sight sound
//	based on actor type
///////////////////////////////////

void	ai_sight_sound (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	char			*sound [2];
	int				i, max_sounds;

	max_sounds = 2;

	switch	(hook->type)
	{
		//
		//	Episode 1
		//
		case	TYPE_BATTLEBOAR:
			sound [0] = "monsters/battleboar/battleboarsight1.wav";
			sound [1] = "monsters/battleboar/battleboarsight2.wav";
			break;

		case	TYPE_INMATER:
			sound [0] = "monsters/inmater/inmatersight1.wav";
			sound [1] = "monsters/inmater/inmatersight2.wav";
			break;

		case	TYPE_RAGEMASTER:
			max_sounds = 1;
			sound[0] = "monsters/ragemaster/ragemastersight.wav";
			break;

		case	TYPE_SLUDGEMINION:
			sound[0] = "monsters/sludge/sludgesight1.wav";
			sound[1] = "monsters/sludge/sludgesight2.wav";
			break;

		case	TYPE_FROGINATOR:
			max_sounds = 1;
			sound[0] = "monsters/froginator/frogsight.wav";
			break;

		case	TYPE_VERMIN:
			max_sounds =1 ;
			sound[0] = "monsters/vermin/verminsight.wav";
			break;

		case	TYPE_PRISONER:
			max_sounds = 3;
			sound[0] = "monsters/e1prisoner/e1prisonersight1.wav";
			sound[1] = "monsters/e1prisoner/e1prisonersight2.wav";
			sound[2] = "monsters/e1prisoner/e1prisonersight3.wav";
			break;

		//
		//	Episode 2
		//
		case	TYPE_SKELETON:
			max_sounds = 1;
			sound [0] = "monsters/skeleton/skeletonsight.wav";
			break;

		case	TYPE_CEREBUS:
			sound[0] = "monsters/cerebus/cerebussight1.wav";
			sound[1] = "monsters/cerebus/cerebussight2.wav";
			break;

		case	TYPE_SPIDER:
			max_sounds = 1;
			sound [0] = "monsters/spider/spidersight1.wav";
			break;

		case	TYPE_GRIFFON:
			sound [0] = "monsters/griffon/griffonsight1.wav";
			sound [1] = "monsters/griffon/griffonsight2.wav";
			break;

		case	TYPE_SATYR:
			max_sounds = 0;
			break;

		case	TYPE_COLUMN:
			max_sounds = 0;
			break;

		//
		//	Episode 3
		//
		case	TYPE_BUBOID:
			max_sounds = 3;
			sound [0] = "monsters/buboid/buboidsight1.wav";
			sound [1] = "monsters/buboid/buboidsight2.wav";
			sound [2] = "monsters/buboid/buboidsight3.wav";
			break;

		case	TYPE_FLETCHER:
			sound [0] = "monsters/fletcher/fletchersight1.wav";
			sound [1] = "monsters/fletcher/fletchersight2.wav";
			break;

		case	TYPE_LYCANTHIR:
			sound [0] = "monsters/lycanthir/lycanthirsight1.wav";
			sound [1] = "monsters/lycanthir/lycanthirsight2.wav";
			break;

		case	TYPE_DOOMBAT:
			sound [0] = "monsters/doombat/doombatsight1.wav";
			sound [1] = "monsters/doombat/doombatsight2.wav";
			break;

		case	TYPE_DWARF:
			sound [0] = "monsters/dwarf/dwarfsight2.wav";
			sound [1] = "monsters/dwarf/dwarfsight3.wav";
			break;

		//
		//	Episode 4
		//
		case	TYPE_CHAINGANG:
			sound [0] = "monsters/cgang/cgangsight.wav";
			sound [1] = "monsters/cgang/cgangsight.wav";
			break;

		case	TYPE_FEMGANG:
			sound [0] = "monsters/femgang/femalesight.wav";
			sound [1] = "monsters/femgang/femalesight.wav";
			break;

		case	TYPE_ROCKETGANG:
			sound [0] = "monsters/rocketdude/rocketsight.wav";
			sound [1] = "monsters/rocketdude/rocketsight.wav";
			break;

		case	TYPE_LABMONKEY:
			sound [0] = "monsters/labmonkey/labmonkeysight1.wav";
			sound [1] = "monsters/labmonkey/labmonkeysight2.wav";
			break;

		case	TYPE_BLACKPRIS:
			max_sounds = 1;
			sound [0] = "monsters/blackpris/blackprisonersight.wav";
			break;

		default:
			max_sounds = 0;
			break;
	}

	if (max_sounds)
	{
		i = rand () % max_sounds;

		gstate->sound (self, CHAN_AUTO, sound [i], 255.0, ATTN_NORM);
	}
}

///////////////////////////////////
//	ai_wander_sound
//
//	plays an appropriate wandering sound
//	based on actor type
///////////////////////////////////

void	ai_wander_sound (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	char			*sound	[4];
	char			*play;
	int				i, max_sounds;

	if (hook->sound_time >= gstate->time)
		return;

	max_sounds = 2;

	switch	(hook->type)
	{
		//
		//	Episode 1
		//
		case	TYPE_BATTLEBOAR:
			max_sounds = 3;
			sound [0] = "monsters/battleboar/battleboarwander1.wav";
			sound [1] = "monsters/battleboar/battleboarwander2.wav";
			sound [2] = "monsters/battleboar/battleboarwander2.wav";
			break;

		case	TYPE_INMATER:
			sound [0] = "monsters/inmater/inmaterwander1.wav";
			sound [1] = "monsters/inmater/inmaterwander2.wav";
			break;

		case	TYPE_RAGEMASTER:
			sound[0] = "monsters/ragemaster/ragemasterwander.wav";
			sound[1] = "monsters/ragemaster/ragemasterwander1.wav";
			break;

		case	TYPE_SLUDGEMINION:
			sound[0] = "monsters/sludge/sludgewander1.wav";
			sound[1] = "monsters/sludge/sludgewander2.wav";
			break;

		case	TYPE_FROGINATOR:
			sound[0] = "monsters/froginator/frogwander1.wav";
			sound[1] = "monsters/froginator/frogwander2.wav";
			break;

		case	TYPE_VERMIN:
			max_sounds = 0;
//			sound[0] = "monsters/vermin/verminwander1.wav";
//			sound[0] = "monsters/vermin/verminwander2.wav";
			break;

		case	TYPE_PRISONER:
			max_sounds = 3;
			sound[0] = "monsters/e1prisoner/e1prisonerwander1.wav";
			sound[1] = "monsters/e1prisoner/e1prisonerwander2.wav";
			sound[2] = "monsters/e1prisoner/e1prisonerwander3.wav";
			break;

		//
		//	Episode 2
		//
		case	TYPE_SKELETON:
			sound [0] = "monsters/skeleton/skeletonwander1.wav";
			sound [1] = "monsters/skeleton/skeletonwander2.wav";
			break;

		case	TYPE_CEREBUS:
			sound [0] = "monsters/cerebus/cerebuswander1.wav";
			sound [1] = "monsters/cerebus/cerebuswander2.wav";
			break;

		case	TYPE_SPIDER:
			sound [0] = "monsters/spider/spiderwander1.wav";
			sound [1] = "monsters/spider/spiderwander2.wav";
			break;

		case	TYPE_GRIFFON:
			max_sounds = 1;
			sound [0] = "monsters/griffon/griffonwander1.wav";
			break;

		case	TYPE_SATYR:
			max_sounds = 0;
			break;

		case	TYPE_COLUMN:
			max_sounds = 0;
			break;

		//
		//	Episode 3
		//
		case	TYPE_BUBOID:
			max_sounds = 4;
			sound [0] = "monsters/buboid/buboidwander1.wav";
			sound [1] = "monsters/buboid/buboidwander2.wav";
			sound [2] = "monsters/buboid/buboidwander3.wav";
			sound [3] = "monsters/buboid/buboidwander4.wav";
			break;

		case	TYPE_FLETCHER:
			sound [0] = "monsters/fletcher/fletcherwander1.wav";
			sound [1] = "monsters/fletcher/fletcherwander2.wav";
			break;

		case	TYPE_LYCANTHIR:
			max_sounds = 3;
			sound [0] = "monsters/lycanthir/lycanthirwander1.wav";
			sound [1] = "monsters/lycanthir/lycanthirwander2.wav";
			sound [2] = "monsters/lycanthir/lycanthirwander3.wav";
			break;

		case	TYPE_DOOMBAT:
			sound [0] = "monsters/doombat/doombatwander1.wav";
			sound [1] = "monsters/doombat/doombatwander2.wav";
			break;

		case	TYPE_DWARF:
			sound [0] = "monsters/dwarf/dwarfwander1.wav";
			sound [1] = "monsters/dwarf/dwarfwander2.wav";
			break;

		//
		//	Episode 4
		//
		case	TYPE_CHAINGANG:
			sound [0] = "monsters/cgang/cgangwander.wav";
			sound [1] = "monsters/cgang/cgangwander.wav";
			break;

		case	TYPE_FEMGANG:
			sound [0] = "monsters/femgang/femalewander.wav";
			sound [1] = "monsters/femgang/femalewander.wav";
			break;

		case	TYPE_ROCKETGANG:
			sound [0] = "monsters/rocketdude/rocketwander.wav";
			sound [1] = "monsters/rocketdude/rocketwander.wav";
			break;

		case	TYPE_LABMONKEY:
			sound [0] = "monsters/labmonkey/labmonkeywander1.wav";
			sound [1] = "monsters/labmonkey/labmonkeywander2.wav";
			break;

		case	TYPE_BLACKPRIS:
			sound [0] = "monsters/blackpris/blackprisonerwander.wav";
			sound [1] = "monsters/blackpris/blackprisonerwander1.wav";
			break;


		default:
			max_sounds = 0;
			break;
	}

	if (max_sounds)
	{
		i = rand () % max_sounds;

		gstate->sound (self, CHAN_AUTO, sound [i], 255.0, ATTN_NORM);

		hook->sound_time = gstate->time + 10.0 + rnd () * 30.0;
	}
}
