#define	DEBUGINFO	1
#define markers

//////////////////////////////////////////////////////////////////////////////////
//	ai_weapons:
//
//	Each monster has at least one weapon in its inventory.  These weapons have
//	to be useable by both monsters and clients (for possesion).  In order for
//	a client to use a weapon, the direction of attack must be based on self->client->v_angle.
//
//	For monsters, self->v_angle is set in ai_fire_curWeapon, which then calls
//	the weapon's use function.  Clients only need to directly call the use function.
//
//	Each weapon's use function converts self->client->v_angle to a normalized directional
//	vector and then calculates the aiming vector based on that weapon's spread_x
//	and spread_z and the origin of the firing entity.
//
//////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////
//	generalized functions for monster attacks
//////////////////////////////////////////////////////////////////////////////////

// user interface file between katana and dlls
#include	"world.h"

#include	"ai.h"
#include	"ai_utils.h"
#include	"ai_weapons.h"
#include	"thinkFuncs.h"
#include	"ai_frames.h"
#include	"misc.h"

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

//	used so that ai_check_projectile_attack always checks the exact same direction
//	as ai_fire_projectile will fire
typedef	struct	aimData_s
{
	Vector			org;
	Vector			end;			//	ending position at weapon distance with spread adjustments
	Vector			spread_end;		//	ending position at MAX_WEAPON_TRACE with spread adjustments
	Vector			angles;			//	self->client->v_angle with pitch negated
	Vector			dir;			//	actual direction of attack after spread adjustments
	
	char			flag;			//	set when aiming is done
									//	cleared once an attack has been executed
} aimData_t;

////////////////////////////////////////////////////////////////////////////////
//	globals
////////////////////////////////////////////////////////////////////////////////

aimData_t	aim_data;
static	int				trace_damage;

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

#define	AIM_CLEAR	0
#define	AIM_VALID	1

#define	MAX_WEAPON_TRACE	2000.0

////////////////////////////////////////////////////////////////////////////////
// prototypes
////////////////////////////////////////////////////////////////////////////////

void ai_fire_bullet (userEntity_t *self, userEntity_t *target, float spread_x, float spread_z, int damage);

//////////////////////////////////
//	ai_lead_target
//
//////////////////////////////////

void	ai_lead_target (userEntity_t *targ, Vector org, float speed, Vector &predicted_org)
{
	float	time_to_target, dist, targ_speed;

	//	FIXME: base accuracy on skill

	/////////////////////////////////////////////////////////
	//	find time from self to target at speed
	/////////////////////////////////////////////////////////

	dist = qv_dist (org.vec (), targ->s.origin);
	time_to_target = dist / speed;

	//	don't lead up in the air if enemy is just jumping
	if (targ->groundEntity == NULL && targ->velocity [2] > 0)
	{
		predicted_org = Vector (targ->s.origin) + Vector (targ->velocity [0], targ->velocity [1], 0) * time_to_target;
	}
	else
		predicted_org = Vector (targ->s.origin) + Vector (targ->velocity) * time_to_target;
}

///////////////////////////////
//	ai_aim
//
//	using self->client->v_angle, find the origin and ending position of an attack
///////////////////////////////

void	ai_aim (userEntity_t *self, ai_weapon_t *weapon)
{
	float		sx, sz;

	aim_data.angles = Vector (self->client->v_angle);
//	aim_data.angles.x = -aim_data.angles.x;
	aim_data.angles.AngleVectors (forward, right, up);

	aim_data.org = Vector (self->s.origin) + weapon->ofs.x * right + 
		weapon->ofs.y * forward + weapon->ofs.z * up;

	sx = weapon->spread_x * rnd () * 2.0 - weapon->spread_x;
	sz = weapon->spread_z * rnd () * 2.0 - weapon->spread_z;

	aim_data.spread_end = aim_data.org + forward * MAX_WEAPON_TRACE + 
		(right * sx * MAX_WEAPON_TRACE) + (up * sz * MAX_WEAPON_TRACE);

	aim_data.dir = aim_data.spread_end - aim_data.org;
	aim_data.dir.Normalize ();

	aim_data.end = aim_data.org + aim_data.dir * weapon->distance;

//	com->DrawLine (self, aim_data.org, aim_data.end, 3.0);
}

///////////////////////////////
//	ai_aim_bullet
//
//	sets up v_angle to aim a bullet
///////////////////////////////

void	ai_aim_bullet (userEntity_t *self)
{
	Vector		ang, dir;

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

	com->VecToAngles (ang, dir);

	ang.x = -ang.x;					//	negate pitch so that it matches client pitch
	ang.y = self->s.angles [1];		//	always fire the direction monster is facing, because movetogoal can turn us around
	ang.z = 0;					

	ang.set (self->client->v_angle);

	aim_data.flag = AIM_VALID;
}

///////////////////////////////
//	ai_fire_bullet
//
//	Spread is the distance to right or left of the target
//
//	Fires from aim_data.org to aim_data.spread_end (ignores weapon->distance)
///////////////////////////////

void ai_fire_bullet (userEntity_t *self, userEntity_t *target, ai_weapon_t *weapon)
{
	Vector		dir;

	ai_aim (self, weapon);

	tr = gstate->trace_q2 (aim_data.org.vec (), NULL, NULL, aim_data.spread_end.vec (), self, MASK_SHOT);

	if (tr.ent->takedamage != DAMAGE_NO)
	{
		dir = aim_data.spread_end - aim_data.org;
		dir.Normalize ();

		com->Damage (tr.ent, self, self, tr.endpos, dir.vec (), (float) weapon->base_damage + rnd () * float (weapon->rnd_damage), DAMAGE_INERTIAL);
	}
	else if (tr.fraction != 1.0)
	{
		/*
		gstate->WriteByte (self, MSG_BROADCAST, SVC_TEMPENTITY);
		gstate->WriteByte (self, MSG_BROADCAST, TE_GUNSHOT);
		gstate->WriteCoord (self, MSG_BROADCAST, trace.endpos [0]);
		gstate->WriteCoord (self, MSG_BROADCAST, trace.endpos [1]);
		gstate->WriteCoord (self, MSG_BROADCAST, trace.endpos [2]);
		*/
	}

	aim_data.flag = AIM_CLEAR;
}

///////////////////////////////
//	ai_aim_trace
//
//	sets up v_angle to aim a bullet
///////////////////////////////

void	ai_aim_trace (userEntity_t *self)
{
	Vector		ang, dir;

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

	com->VecToAngles (ang, dir);

	ang.x = -ang.x;					//	negate pitch so that it matches client pitch
	ang.y = self->s.angles [1];		//	always fire the direction monster is facing, because movetogoal can turn us around
	ang.z = 0;					

	ang.set (self->client->v_angle);

	aim_data.flag = AIM_VALID;
}

////////////////////////////////////////////
//	ai_trace_attack
//
//	Standard melee attack.
//	Fires from aim_data.org to aim_data.end.  Attacks reach max of weapon->distance units.
////////////////////////////////////////////

userEntity_t	*ai_trace_attack (userEntity_t *self, ai_weapon_t *weapon)
{
	Vector		dir;

	ai_aim (self, weapon);

	aim_data.flag = AIM_CLEAR;

	tr = gstate->trace_q2 (aim_data.org.vec(), NULL, NULL, aim_data.end.vec(), self, MASK_SHOT);

	if (tr.fraction!= 1.0)
	{
		if (tr.ent->takedamage != DAMAGE_NO)
		{
			dir = aim_data.spread_end - aim_data.org;
			dir.Normalize ();

			trace_damage = (float) weapon->base_damage + rnd () * float (weapon->rnd_damage);
			com->Damage (tr.ent, self, self, tr.endpos, dir.vec (), trace_damage, DAMAGE_NONE);

			return	tr.ent;
		}
		else if (tr.ent->solid == SOLID_BSP || tr.ent->takedamage == DAMAGE_NO)
		{
			/*
			gstate->WriteByte (self, MSG_BROADCAST, SVC_TEMPENTITY);
			gstate->WriteByte (self, MSG_BROADCAST, TE_GUNSHOT);
			gstate->WriteCoord (self, MSG_BROADCAST, trace.endpos[0]);
			gstate->WriteCoord (self, MSG_BROADCAST, trace.endpos[1]);
			gstate->WriteCoord (self, MSG_BROADCAST, trace.endpos[2]);
			*/
			
			return	tr.ent;
		}
	}

	return	NULL;
}

///////////////////////////////
//	ai_aim_projectile
//
//	sets up v_angle to aim a bullet
///////////////////////////////

void	ai_aim_projectile (userEntity_t *self, ai_weapon_t *weapon)
{
	Vector			ang, org, dir, lead_org;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	//	won't fire up in the air if entity is not on the ground
	ai_lead_target (self->enemy, Vector (self->s.origin), weapon->speed, lead_org);

	if (weapon->flags & WEAPON_EXPLOSIVE)
	{
		//	if difficulty level is is high, or monster intelligence is high
		//	then monster will aim for splash damage
		if (ai_skill_rand () || hook->intQuota > INT_HIGH)
			//	aim at ground in front of target
			lead_org.z -= 32.0;
	}

	com->SpawnMarker (self, lead_org, "models/debug/marker/marker.md2", 2.0);

	//	calculate angle to target from actual firing position of projectile
	ang = Vector (self->s.angles);
	ang.AngleVectors (forward, right, up);

	org = Vector (self->s.origin) + weapon->ofs.x * right + 
		weapon->ofs.y * forward + weapon->ofs.z * up;

	dir = Vector (lead_org) - org;
	dir.Normalize ();

	com->VecToAngles (ang, dir);

	//	try to change yaw to match direction to lead_org.  
	self->ideal_ang [1] = ang.y;
	com->ChangeYaw (self);

	ang.x = ang.x;
	ang.y = self->s.angles [1];		//	always fire the direction monster is facing, because movetogoal can turn us around
	ang.z = 0;					

	ang.set (self->client->v_angle);
	
	//	debugging
//	ang.Normalize ();
//	com->DrawLine (self, org, lead_org, 3.0);
}

////////////////////////////////////////////
//	ai_check_projectile_attack
//
//	aims a projectile attack and determines if it will hit the desired target
//
//	returns true if a trace from the attacker to the target's predicted position
//	is not obstructed
//
//	if a trace from the attacker to the target's predicted position is obstructed,
//	the true is returned if the trace went farther than min_dist units, otherwise
//	false is returned.
////////////////////////////////////////////

int	ai_check_projectile_attack (userEntity_t *self, userEntity_t *target, ai_weapon_t *weapon, float min_dist)
{
	Vector		end;
	float		dist;

	if (min_dist == 0)
		//	explosive weapons have a radius == damage
		min_dist = weapon->base_damage + 32;

	ai_aim_projectile (self, weapon);

	ai_aim (self, weapon);

	aim_data.flag = AIM_VALID;

	dist = qv_dist (self->s.origin, target->s.origin);

	end = aim_data.org + aim_data.dir * dist;
	tr = gstate->trace_q2 (aim_data.org.vec (), NULL, NULL, end.vec (), self, MASK_SHOT);

	if (tr.fraction == 1.0)
		//	trace made it all the way to predicted position
		return	TRUE;
	else if (tr.ent == target)
		//	trace hit the target
		return	TRUE;
	else if (tr.ent->flags & (FL_CLIENT + FL_BOT))
		//	trace hit a client or bot, so fire anyway
		return	TRUE;
	else if (tr.fraction * dist > min_dist)
		//	check to see if trace travelled far enough before impact
		return	TRUE;

	return	FALSE;
}

////////////////////////////////////////////
//	ai_fire_projectile
//
////////////////////////////////////////////

userEntity_t *ai_fire_projectile (userEntity_t *self, userEntity_t *target, ai_weapon_t *weapon,
						 char *model, touch_t touch, unsigned long fx)
{
	userEntity_t	*temp;
	Vector			org, end, dir, ang, p_org;

	if (aim_data.flag != AIM_VALID)
		ai_aim (self, weapon);

	temp = gstate->SpawnEntity ();
	temp->movetype = MOVETYPE_FLYMISSILE;
	temp->solid = SOLID_BBOX;
	temp->s.effects = fx;
	temp->owner = self;
	temp->s.frame = 0;

	temp->clipmask = MASK_SHOT;

	temp->s.modelindex = gstate->ModelIndex (model);
	gstate->SetSize (temp, 0, 0, 0, 0, 0, 0);

	gstate->SetOrigin2 (temp, aim_data.org.vec ());

	//	align model in direction of travel
	com->VecToAngles (ang, aim_data.dir);
	ang.set (temp->s.angles);

	dir = aim_data.dir * weapon->speed;
	dir.set (temp->velocity);

	temp->touch = touch;
	temp->think = NULL;
	temp->nextthink = -1;

	aim_data.flag = AIM_CLEAR;

	return	temp;
}

///////////////////////////////
//	ai_muzzle_flash_think
//
///////////////////////////////

void	ai_muzzle_flash_think (userEntity_t *self)
{
	Vector	org, ang, dir;
	float	i;
	vec3_t	vert [4];

	self->s.frame++;
	if (self->s.frame > 3)
		self->s.frame = 0;
/*
	//	track without locking to a surface
	if (!self->message)
	{
NoSurface:
		ang = Vector (self->owner->s.angles);
		ang.set (self->s.angles);
		ang.AngleVectors (forward, right, up);

		org = Vector (self->owner->s.origin);
		org = org + forward * self->view_ofs [1] + right * self->view_ofs [0] + up * self->view_ofs [2];

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

		gstate->Con_Printf ("No surface for muzzle flash!\n");
	}
	else
	{
		//	lock to surface self->message
		i = gstate->SurfInfo (self->owner->modelIndex, self->message);
		
		//	ooh, Nelno bad!
		if (i <= 0) goto NoSurface;

		gstate->VertInfo (self->owner, i, self->owner->s.frame, 0, vert [0]);
		gstate->VertInfo (self->owner, i, self->owner->s.frame, 1, vert [1]);
		gstate->VertInfo (self->owner, i, self->owner->s.frame, 2, vert [2]);
		gstate->VertInfo (self->owner, i, self->owner->s.frame, 3, vert [3]);

		com->CopyVector (self->s.origin, vert [3]);
		self->s.origin [2] -= 24.0;

//		com->Normal (vert [0], vert [1], vert [2], dir);
//		com->VecToAngles (ang, dir);

		ang = Vector (self->owner->s.angles);
		ang.set (self->s.angles);
		ang.AngleVectors (forward, right, up);

		org = Vector (self->s.origin) + forward * 30.0;
		gstate->SetOrigin2 (self, org.vec ());
	}
*/
	if (gstate->time >= self->delay)
	{
		if (self->owner)
			gstate->hr_RemoveChild (self->owner, self, false);
		gstate->RemoveEntity (self);
	}
	else
	{
		self->think = ai_muzzle_flash_think;
		self->nextthink = gstate->time + 0.05;
	}
}

///////////////////////////////
//	ai_muzzle_flash
//
///////////////////////////////

void	ai_muzzle_flash (userEntity_t *owner, Vector ofs, float time, char *surf1, char* surf2, unsigned long flags)
{
	userEntity_t	*flash;

	flash = gstate->SpawnEntity ();
	flash->movetype = MOVETYPE_NONE;
	flash->solid = SOLID_NOT;
	flash->owner = owner;
	flash->delay = gstate->time + time;
	flash->message = surf1;
	flash->className = "muzzle_flash";

	gstate->SetModel (flash, "models/muzflash.mdl");
	gstate->SetSize (flash, 0, 0, 0, 0, 0, 0);
	
//	ofs.set (flash->view_ofs);

	gstate->hr_InitSurface (owner, flash, surf1, 0, flags);

	ai_muzzle_flash_think (flash);
}

//////////////////////////////////////////////////////////////////////////////////////
//	monster weapon types
//
//	usuable just like player weapons when possessing an enemy
//////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////
//	ai_aim_curWeapon
//
/////////////////////////////

void	ai_aim_curWeapon (userEntity_t *self, ai_weapon_t *weapon)
{
	if (weapon->flags & WEAPON_PROJECTILE)
		ai_aim_projectile (self, weapon);
	else if (weapon->flags & WEAPON_BULLET)
		ai_aim_bullet (self);
	else if (weapon->flags & WEAPON_TRACE)
		ai_aim_trace (self);
}

/////////////////////////////
//	ai_fire_curWeapon
//
//	fires curWeapon in direction from org to end
/////////////////////////////

void	ai_fire_curWeapon (userEntity_t *self, Vector org, Vector end, float spread_x, float spread_z)
{
	Vector		old_angles, dir, ang;
	ai_weapon_t	*weapon = (ai_weapon_t *) self->curWeapon;

	if (!self->curWeapon)
		return;

	//	save current view angles
	old_angles = Vector (self->client->v_angle);

	//	set self->client->v_angle to direction of attack
	ai_aim_curWeapon (self, weapon);

	//	fire the weapon
	self->curWeapon->use (self->curWeapon, self);

	old_angles.set (self->client->v_angle);
}

/* ************************************ psiblast ************************************** */

/////////////////////////////
//	psiblast_fire_think
//
/////////////////////////////

void	psiblast_firethink (userEntity_t *self)
{
	Vector			mins, maxs;
	playerHook_t	*hook;

	mins = Vector (-16, -16, -24);
	maxs = Vector (16, 16, 32);

	//	gstate->TraceBox (self->s.origin, self->s.origin, mins.vec (), maxs.vec (), FALSE, self, &trace);
	tr = gstate->trace_q2 (self->s.origin, self->s.origin, mins.vec (), maxs.vec (), self, MASK_MONSTERSOLID);
	
	if (tr.ent)
	{
		if (tr.ent->takedamage != DAMAGE_NO)
		{
			com->Damage (tr.ent, self->owner, self->owner, self->s.origin, zero_vector, rnd () * 5 + 5, DAMAGE_INERTIAL | DAMAGE_INFLICTOR_VEL);
			if (tr.ent->flags & FL_CLIENT)
			{
				hook = (playerHook_t *) tr.ent->userHook;
				hook->shake_time = gstate->time + 0.25;
			}
		}
	}

	com->MultVector (self->s.render_scale, 1.25, self->s.render_scale);

	if (self->delay <= gstate->time)
		gstate->RemoveEntity (self);
	else
	{
		self->think = psiblast_firethink;
		self->nextthink = gstate->time + 0.1;
	}
}

/////////////////////////////
//	psiblast_fire
//
/////////////////////////////

void	psiblast_fire (userInventory_s *ptr, userEntity_t *self)
{
	userEntity_t	*blast;
	Vector			dir, org;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;

	hook->attack_finished = gstate->time + 1.0;

	blast = ai_fire_projectile (self, self->enemy, weapon, NULL/*"models/psiblast.mdl"*/, NULL, 0);
		//Vector (0, 16, 6), weapon->spread_x, weapon->spread_z, 250.0, "models/psiblast.mdl", NULL, 0);

	blast->movetype = MOVETYPE_NOCLIP;
	blast->solid = SOLID_NOT;
	
	com->SetVector (blast->s.render_scale, 1.0, 1.0, 1.0);
	
	blast->translucent = 3;
	blast->delay = gstate->time + 2.0;

	psiblast_firethink (blast);	
}

/* ************************************ melee ************************************** */

/////////////////////////////
//	melee_punch
//
//	"fires" a standard melee attack
//	which reaches hook->attack_dist
//
//	only plays hook->sound2 if target is hit
//
//	hook->sound1 can be used in the monster's code to
//	play a swinging sound
/////////////////////////////

void	melee_punch (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;

	if (ai_trace_attack (self, weapon))
		if (hook->sound2)
			gstate->sound (self, CHAN_AUTO, hook->sound2, 128, ATTN_NORM);
}

/////////////////////////////
//	melee_swing
//
//	"fires" a standard melee attack
//	which reaches hook->attack_dist
//
//	plays hook->sound1
/////////////////////////////

void	melee_swing (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;

	ai_trace_attack (self, weapon);

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

/* ************************************ chaingun ************************************** */

/////////////////////////////
//	chaingun_think
//
//	a thinkFunc for chaingun firing
//	self is a fake entity
//	self->owner is the entity that is shooting
/////////////////////////////

void	chaingun_think (userEntity_t *self)
{
	playerHook_t	*hook;
	ai_weapon_t		*weapon;

	if (!self->owner || !self->owner->enemy)
	{
		thinkFunc_remove (self);		
		return;
	}

	hook = (playerHook_t *)self->owner->userHook;
	weapon = (ai_weapon_t *) self->owner->curWeapon;

	if (!(self->owner->flags & FL_POSSESSED))
		ai_aim_bullet (self->owner);
	ai_fire_bullet (self->owner, self->owner->enemy, weapon);//weapon->spread_x, weapon->spread_z, (float) weapon->base_damage * rnd () + (float) weapon->rnd_damage);

	self->delay++;

	if (self->delay >= (hook->endFrame - hook->startFrame) / 2)
	{
		thinkFunc_remove (self);
		return;
	}
	else
		thinkFunc_set_think (self, chaingun_think, 0.1);
}

/////////////////////////////
//	chaingun_fire
//
//	fires the chaingunner's chaingun
//	which fires several shots one after another
/////////////////////////////

void	chaingun_fire (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	userEntity_t	*think_ent;

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

	think_ent = thinkFunc_add (self, chaingun_think, 0.1);
	think_ent->owner = self;
	
	//	use think_ent->delay as a counter for the chaingun
	think_ent->delay = 0;
	think_ent->curWeapon = self->curWeapon;
	com->CopyVector (think_ent->client->v_angle, think_ent->owner->client->v_angle);
	com->CopyVector (think_ent->s.origin, think_ent->owner->s.origin);
	com->CopyVector (think_ent->view_ofs, think_ent->owner->view_ofs);
	chaingun_think (think_ent);

	//	spawn muzzle flash
//	ai_muzzle_flash (self, Vector (-38, 0, -14), ((hook->endFrame - hook->startFrame - 2) / 2) * 0.1, "flash forward", "flash right", STICKY_FLIPFORWARD + STICKY_FLIPRIGHT);// + STICKY_OFFSET);
}

/////////////////////////////
//	boargun_think
//
//	a thinkFunc for boargun firing
//	self is a fake entity
//	self->owner is the entity that is shooting
/////////////////////////////

void	boargun_think (userEntity_t *self)
{
	playerHook_t	*hook;
	ai_weapon_t		*weapon;

	if (!self->owner || !self->owner->enemy)
	{
		thinkFunc_remove (self);		
		return;
	}

	hook = (playerHook_t *)self->owner->userHook;
	weapon = (ai_weapon_t *) self->owner->curWeapon;

	if (!(self->owner->flags & FL_POSSESSED))
		ai_aim_bullet (self->owner);

	ai_fire_bullet (self->owner, self->owner->enemy, weapon);

	self->delay++;

	if (self->delay >= 3)
	{
		thinkFunc_remove (self);
		return;
	}
	else
		thinkFunc_set_think (self, boargun_think, 0.1);
}

/////////////////////////////
//	boargun_fire
//
//	fires the battle boar's machine gun
//	which fires 2 machine guns, several shots one after another
/////////////////////////////

void	boargun_fire (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	userEntity_t	*think_ent;

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

	think_ent = thinkFunc_add (self, chaingun_think, 0.1);
	think_ent->owner = self;
	
	//	use think_ent->delay as a counter for the boargun
	think_ent->delay = 0;
	think_ent->curWeapon = self->curWeapon;
	com->CopyVector (think_ent->client->v_angle, think_ent->owner->client->v_angle);
	com->CopyVector (think_ent->s.origin, think_ent->owner->s.origin);
	com->CopyVector (think_ent->view_ofs, think_ent->owner->view_ofs);
	boargun_think (think_ent);

	//	spawn muzzle flash
//	ai_muzzle_flash (self, Vector (-38, 0, -14), ((hook->endFrame - hook->startFrame - 2) / 2) * 0.1, "flash forward", "flash right", STICKY_FLIPFORWARD + STICKY_FLIPRIGHT);// + STICKY_OFFSET);
}

/* ************************************ poison bite ************************************** */

/////////////////////////////
//	poison_bite_mild
//
//	"fires" a standard melee attack
//	which reaches hook->attack_dist
//
//	only plays hook->sound2 if target is hit
//
//	hook->sound1 can be used in the monster's code to
//	play a swinging sound
//
//	if target was hit, it is poisoned with a mild poison:
//	60 seconds, 1 hit every 3 seconds
/////////////////////////////

void	poison_bite_mild (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*target;
	float			dist = hook->attack_dist;

	if (dist > 64.0)
		dist = 64.0;

	target = ai_trace_attack (self, weapon);

	if (target)
	{
		com->Poison (target, 1.0, 15.0, 3.0);
		if (hook->sound2)
			gstate->sound (self, CHAN_AUTO, hook->sound2, 128, ATTN_NORM);
	}
}

/* ************************************ dart ************************************** */

/////////////////////////////
//	dart_touch
//
/////////////////////////////

void	dart_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	com->Damage (other, self, self->owner, self->s.origin, zero_vector, self->health, DAMAGE_INERTIAL | DAMAGE_INFLICTOR_VEL);
	com->Poison (other, 1.0, 60.0, 3.0);

	gstate->RemoveEntity (self);
}

/////////////////////////////
//	dart_fire_think
//
//	a for dart firing
/////////////////////////////

void	dart_fire_think (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->owner->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) self->owner->curWeapon;
	userEntity_t	*dart;

	dart = ai_fire_projectile (self->owner, self->owner->enemy, weapon, "models/dart.mdl", dart_touch, 0);
		//Vector (0, 16, 10), 0, 0, 350.0, "models/dart.mdl", dart_touch, 0);
	dart->think = NULL;
	dart->nextthink = -1;
	dart->health = (float) weapon->base_damage * rnd () + (float) weapon->rnd_damage;

	self->delay++;

	if (self->delay >= (hook->endFrame - hook->startFrame) / 4)
	{
		thinkFunc_remove (self);
		return;
	}
	else
		thinkFunc_set_think (self, dart_fire_think, 0.1);
}

/////////////////////////////
//	dart_fire
//
//	fires multiple poison darts
/////////////////////////////

void	dart_fire (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*think_ent;

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

	think_ent = thinkFunc_add (self, dart_fire_think, 0.1);
	
	//	use self->delay as a counter for the chaingun
	think_ent->delay = 0;

	dart_fire_think (think_ent);
}

/////////////////////////////
//	dart_strafe_fire_think
//
//	a thinkFunc for dart firing
/////////////////////////////

void	dart_strafe_fire_think (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->owner->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) self->owner->curWeapon;
	userEntity_t	*dart;
	float			yaw;

	yaw = self->owner->s.angles [1];
	self->owner->s.angles [1] = (self->owner->s.angles [1] - 12) + (self->delay * 6);

	dart = ai_fire_projectile (self->owner, self->owner->enemy, weapon, "models/dart.mdl", dart_touch, 0);
		//Vector (0, 16, 10), 0, 0, 350.0, "models/dart.mdl", dart_touch, 0);
	dart->think = NULL;
	dart->nextthink = -1;
	dart->health = (float) weapon->base_damage * rnd () + (float) weapon->rnd_damage;

	self->owner->s.angles [1] = yaw;
	self->delay++;

	if (self->delay >= (hook->endFrame - hook->startFrame) / 4)
	{
		thinkFunc_remove (self);
		return;
	}
	else
		thinkFunc_set_think (self, dart_strafe_fire_think, 0.1);
}

/////////////////////////////
//	dart_strafe_fire
//
//	fires multiple poison darts
/////////////////////////////

void	dart_strafe_fire (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*think_ent;

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

	think_ent = thinkFunc_add (self, dart_strafe_fire_think, 0.1);
	
	//	use self->delay as a counter for the chaingun
	think_ent->delay = 0;

	dart_strafe_fire_think (think_ent);
}

/* ************************************ poison bubble ************************************** */

/////////////////////////////////////////
//	bubble_pop
//	
//	poison's anything in the vicinity that
//	is a bot, monster or client and isn't
//	TYPE_VERMIN
/////////////////////////////////////////

void	bubble_pop (userEntity_t *self)
{
	userEntity_t	*head;
	playerHook_t	*hook;

	self->s.frame;
	if (self->s.frame > 4)
	{
		for (head = gstate->FirstEntity (); head; head = gstate->NextEntity (head))
		{
			if (head->flags & (FL_BOT + FL_MONSTER + FL_CLIENT))
			{
				hook = (playerHook_t *) head->userHook;

				if (hook->type != TYPE_VERMIN & !(hook->items & IT_POISON))
				{
					if (qv_dist (self->s.origin, head->s.origin) <= 64.0)
						com->Poison (head, 3.0, 30.0, 3.0);
				}
			}
		}

		gstate->RemoveEntity (self);
		return;
	}

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

/////////////////////////////////////////
//	bubble_touch
//
//
/////////////////////////////////////////

void	bubble_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	bubble_pop (self);
}

/////////////////////////////////////////
//	bubble_think
//
//
/////////////////////////////////////////

void	bubble_think (userEntity_t *self)
{
	Vector	temp;

	if (qv_len (self->velocity) > 20)
	{
		temp = Vector (self->velocity) * 0.75;
		temp.set (self->velocity);
	}
	else if (self->delay <= gstate->time)
	{
		temp = Vector (0, self->s.angles [1] + rnd () * 45, 0);
		temp.set (self->s.angles);

		temp.AngleVectors (forward, right, up);
		temp = Vector (self->velocity) + forward * (rnd () * 10.0 - 5) + up * (rnd () * 10 - 5);
		temp.set (self->velocity);

		self->delay = gstate->time + rnd () * 5.0 + 1.0;
	}

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

	/////////////////////////////////////////////////////////////////
	//	expand bubble size
	/////////////////////////////////////////////////////////////////

	com->MultVector (self->s.render_scale, 1.025, self->s.render_scale);

	if (self->s.render_scale [0] > 10.0)
		bubble_pop (self);
}

/////////////////////////////////////////
//	poison_bubble
//
//	fires a poison bubble that slows down
//	quickly, floats and expands, and then
//	pops, poisoning whoever is near.
/////////////////////////////////////////

void	poison_bubble (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*bubble;

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

	bubble = ai_fire_projectile (self, self->enemy, weapon, "models/bubble.mdl", bubble_touch, 0);
//		Vector (0, 16, 10), weapon->spread_x, weapon->spread_z, 350.0, "models/bubble.mdl", bubble_touch, 0);

	com->SetVector (bubble->s.render_scale, 2.0, 2.0, 2.0);
	bubble->translucent = 3;

	bubble->think = bubble_think;
	bubble->nextthink = gstate->time + 0.1;
}

/* ************************************ rocket ************************************** */

/////////////////////////////////////////
//	rocket_explode
//
/////////////////////////////////////////

void	rocket_explode (userEntity_t *self)
{
	spawn_sprite_explosion (self, self->s.origin, true);

	com->RadiusDamage (self, self->owner, self->owner, self->health, DAMAGE_EXPLOSION);

	gstate->RemoveEntity (self);
}

/////////////////////////////////////////
//	rocket_think
//
/////////////////////////////////////////

void	rocket_think (userEntity_t *self)
{
	self->s.frame++;
	if (self->s.frame > 2)
		self->s.frame = 0;

	if (self->delay <= gstate->time)
		gstate->RemoveEntity (self);
	else
		self->nextthink = gstate->time + 0.1;
}

/////////////////////////////////////////
//	rocket_touch
//
/////////////////////////////////////////

void	rocket_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	rocket_explode (self);
}

/////////////////////////////////////////
//	rocket_attack
//
/////////////////////////////////////////

void	rocket_attack (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*rocket;

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

	rocket = ai_fire_projectile (self, self->enemy, weapon, "models/missile/missile.md2", rocket_touch, 0);
	rocket->s.effects = EF_ROCKET;

	com->SetVector (rocket->s.render_scale, 2.0, 2.0, 2.0);

	rocket->health = weapon->base_damage;

	rocket->delay = gstate->time + 3.0;

	rocket->think = rocket_think;
	rocket->nextthink = gstate->time + gstate->frametime;
}

/* ********************************* magic arrow ************************************** */

/////////////////////////////////////////
//	magic_arrow_think
//
/////////////////////////////////////////

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

/////////////////////////////////////////
//	magic_arrow_touch
//
/////////////////////////////////////////

void	magic_arrow_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	Vector		vel;

	vel = Vector (self->velocity);
	com->Damage (other, self, self->owner, self->s.origin, vel.vec (), self->health, DAMAGE_INERTIAL);

	gstate->RemoveEntity (self);
}

/////////////////////////////////////////
//	magic_arrow_attack
//
/////////////////////////////////////////

void	magic_arrow_attack (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*arrow;

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

	arrow = ai_fire_projectile (self, self->enemy, weapon, "models/flares/blue/blue.sp2", magic_arrow_touch, 0);
	arrow->s.effects = EF_MAGIC_ARROW;
	arrow->s.renderfx |= (SPR_ALPHACHANNEL | RF_TRANSLUCENT);

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

	arrow->health = weapon->base_damage;

	arrow->delay = gstate->time + 3.0;

	arrow->think = magic_arrow_think;
	arrow->nextthink = gstate->time + 3.0;
}

/* ********************************* magic arrow ************************************** */

/////////////////////////////////////////
//	sludge_think
//
/////////////////////////////////////////

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

/////////////////////////////////////////
//	sludge_touch
//
/////////////////////////////////////////

void	sludge_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	Vector		vel;

	vel = Vector (self->velocity);
	com->Damage (other, self, self->owner, self->s.origin, vel.vec (), self->health, DAMAGE_INERTIAL);

	gstate->RemoveEntity (self);
}

/////////////////////////////////////////
//	sludge_attack
//
/////////////////////////////////////////

void	sludge_attack (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*sludge;

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

	sludge = ai_fire_projectile (self, self->enemy, weapon, "models/sludgeball/sludgeball.md2", sludge_touch, 0);
	sludge->s.effects = EF_SLUDGE;
	sludge->s.renderfx |= (SPR_ALPHACHANNEL | RF_TRANSLUCENT);

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

	sludge->health = weapon->base_damage;

	sludge->delay = gstate->time + 3.0;

	sludge->think = sludge_think;
	sludge->nextthink = gstate->time + 3.0;
}

/* ******************************** weapon item routines ****************************** */

/////////////////////////////////////////
//	laser_touch
//
/////////////////////////////////////////

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

	gstate->RemoveEntity (self);
}

/////////////////////////////////////////
//	laser_fire
//
/////////////////////////////////////////

void	laser_fire (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*laser;

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

	laser = ai_fire_projectile (self, self->enemy, weapon, "models/ta_laser.mdl", laser_touch, 0);

	com->SetVector (laser->s.render_scale, 2.0, 2.0, 2.0);

	laser->health = (float) weapon->base_damage * rnd () + (float) weapon->rnd_damage;

	laser->delay = gstate->time + 3.0;

//	laser->think = laser_think;
//	laser->nextthink = gstate->time + gstate->frametime;
}

/* ********************************* wack ************************************** */


void	wack_back (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*hit;
	Vector			vel;

	hit = ai_trace_attack (self, weapon);

	if (hit)
	{
		if (hit->flags & (FL_CLIENT | FL_MONSTER | FL_BOT))
		{
			vel = aim_data.dir * (trace_damage * 6);
			vel.z = 0;

			vel = vel + Vector (hit->velocity);
			vel.set (hit->velocity);
		}
		
		if (hook->sound2)
			gstate->sound (self, CHAN_AUTO, hook->sound2, 128, ATTN_NORM);
	}
}

void	wack_left (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*hit;
	Vector			vel, ang;

	hit = ai_trace_attack (self, weapon);

	if (hit)
	{
		if (hit->flags & (FL_CLIENT | FL_MONSTER | FL_BOT))
		{
			ang = Vector (self->s.angles);
			ang.AngleVectors (vel, up, right);
			vel =  - (right * trace_damage * 6);
			vel.z = 0;

			vel = vel + Vector (hit->velocity);
			vel.set (hit->velocity);
		}
		
		if (hook->sound2)
			gstate->sound (self, CHAN_AUTO, hook->sound2, 128, ATTN_NORM);
	}
}

void	wack_right (userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;
	ai_weapon_t		*weapon = (ai_weapon_t *) ptr;
	userEntity_t	*hit;
	Vector			vel, ang;

	hit = ai_trace_attack (self, weapon);

	if (hit)
	{
		if (hit->flags & (FL_CLIENT | FL_MONSTER | FL_BOT))
		{
			ang = Vector (self->s.angles);
			ang.AngleVectors (vel, up, right);
			vel = (right * trace_damage * 6);
			vel.z = 0;

			vel = vel + Vector (hit->velocity);
			vel.set (hit->velocity);
		}
		
		if (hook->sound2)
			gstate->sound (self, CHAN_AUTO, hook->sound2, 128, ATTN_NORM);
	}
}
/* ******************************** weapon item routines ****************************** */

/////////////////////////////////////////
//	ai_weapon_command
//
//	standard command procedure for a monster weapon
//
//	default commands:
//		base_damage - sets the damage weapon does
//		rnd_damage	- the amount of random damage a weapon does
/////////////////////////////////////////

void *ai_weapon_command (struct userInventory_s *ptr, char *commandStr, void *data)
{
	ai_weapon_t	*weapon = (ai_weapon_t *) ptr;
	
	if (!stricmp (commandStr, "base_damage"))
	{
		weapon->base_damage = *(int *) data;
	}
	else if (!stricmp (commandStr, "rnd_damage"))
	{
		weapon->rnd_damage = *(int *) data;
	}
	else if (!stricmp (commandStr, "spread_x"))
	{
		weapon->spread_x = *(float *) data;
	}
	else if (!stricmp (commandStr, "spread_z"))
	{
		weapon->spread_z = *(float *) data;
	}
	else if (!stricmp (commandStr, "speed"))
	{
		weapon->speed = *(float *) data;
	}
	else if (!stricmp (commandStr, "distance"))
	{
		weapon->distance = *(float *) data;
	}
	else if (!stricmp (commandStr, "ofs"))
	{
		weapon->ofs = *(Vector *) data;
	}

	return NULL;

}

/////////////////////////////////////////
//	ai_weapon_create
//
//	creates an inventory object for a monster weapon
/////////////////////////////////////////

userInventory_t	*ai_weapon_create (char	*weapon_name, weaponUse_t use_func, weaponCommand_t command_func)
{
	ai_weapon_t	*weapon;

	// use malloc since the inventory is here for good
	weapon = (ai_weapon_t *) com->malloc (sizeof (ai_weapon_t), MEM_MALLOC);

	// init the inventory struct to 0's
	memset (weapon, 0x00, sizeof (weapon_t));
	weapon->name = strdup (weapon_name);
	weapon->use  = use_func;
	weapon->command = command_func;
	
	weapon->base_damage = 10;
	weapon->rnd_damage = 0;

	return (userInventory_t *) weapon;
}

/////////////////////////////////////////
//	ai_init_weapons
//
/////////////////////////////////////////

void	ai_init_weapons (void)
{
	gstate->InventoryPrecache ("punch", NULL, NULL);
	gstate->InventoryPrecache ("psiblast", NULL, NULL);
	gstate->InventoryPrecache ("dart", NULL, NULL);
	gstate->InventoryPrecache ("dart strafe", NULL, NULL);
	gstate->InventoryPrecache ("chaingun", NULL, NULL);
	gstate->InventoryPrecache ("poison bite mild", NULL, NULL);
	gstate->InventoryPrecache ("poison bubble", NULL, NULL);
	gstate->InventoryPrecache ("rocket", NULL, NULL);
	gstate->InventoryPrecache ("rocket kneeling", NULL, NULL);
	gstate->InventoryPrecache ("boargun", NULL, NULL);
	gstate->InventoryPrecache ("laser", NULL, NULL);
	gstate->InventoryPrecache ("magic arrow", NULL, NULL);
	gstate->InventoryPrecache ("sludge left", NULL, NULL);
	gstate->InventoryPrecache ("sludge right", NULL, NULL);
	gstate->InventoryPrecache ("wack left", NULL, NULL);
	gstate->InventoryPrecache ("wack right", NULL, NULL);
	gstate->InventoryPrecache ("wack back", NULL, NULL);
}

/////////////////////////////////////
//	ai_init_weapon
//
//	set up a monster weapon with all of its attributes
/////////////////////////////////////

userInventory_t	*ai_init_weapon (userEntity_t *self, float base_damage, float rnd_damage,
						float spread_x, float spread_z, float speed, float dist, Vector ofs, 
						char *name, weaponUse_t use_func, unsigned long flags)
{
	userInventory_t	*w;
	ai_weapon_t		*weapon;
	int				i;
	float			f;
	Vector			v;

	w = ai_weapon_create (name, use_func, ai_weapon_command);

/*
	i = base_damage;	ai_weapon_command (w, "base_damage", &i);
	i = rnd_damage;		ai_weapon_command (w, "rnd_damage", &i);
	f = spread_x;		ai_weapon_command (w, "spread_x", &f);
	f = spread_z;		ai_weapon_command (w, "spread_z", &f);
	f = speed;			ai_weapon_command (w, "speed", &f);
	f = dist;			ai_weapon_command (w, "distance", &f);
	v = ofs;			ai_weapon_command (w, "ofs", &v);
*/
	weapon = (ai_weapon_t *) w;

	weapon->base_damage = base_damage;
	weapon->rnd_damage = rnd_damage;
	weapon->spread_x = spread_x;
	weapon->spread_z = spread_z;
	weapon->speed = speed;
	weapon->distance = dist;
	weapon->ofs = ofs;
	weapon->flags = flags;

	return	w;
}