#include	"weapons.h"
#include "..\..\base\qcommon\qfiles.h"
#include "..\..\base\qcommon\qcommon.h"
#include "io.h"
#include "fcntl.h"

int mike;
float mikef;

weaponInfo_t *weaponCurrentWinfo=NULL;

////////////////////////////////////////////
// crnd
//
// returns a random value between -1 and 1
///////////////////////////////////////////

float crnd(void)
{
	return 2*(rnd() - 0.5);
}

//////////////////////////////////////////////////////
// lim_rand
//
// Return a pseudo-random value between 0 and limit
//////////////////////////////////////////////////////

float lim_rand(int limit)
{
	 
	return  ((float)(limit*(((float)rand())/0x7fff)));
}

///////////////////////////////////////////
// old_weapon_animate          DELETE ME SOON
//
// sets up the animations sequence and
// calls com->ClientWeaponFrames to start
// the sequence playing
//////////////////////////////////////////
void	old_weapon_animate (userEntity_t *self, int start, int end, float frametime)
{
	playerHook_t		*hook  = (playerHook_t *) self->userHook;

	//	no weapon frames for bots or monsters
	if (!(self->flags & FL_CLIENT))
   		return;
#ifdef	OLDCRAP
	hook->viewmodel_start_frame = start;
	hook->viewmodel_end_frame = end;
	hook->viewmodel_frame_time = frametime;
#endif

	hook->vm_startFrame = start;
	hook->vm_endFrame = end;
	hook->vm_frameTime = frametime;
	hook->vm_frameFlags = FRAME_ONCE;
	hook->vm_frameInc = 1;
	self->client->ps.gunframe = start;
//	hook->vm_next_frameTime = 0;

#ifdef	OLDCRAP
	com->ClientWeaponFrames (self);
#endif
}

////////////////////////////////////////////
// weapon_trace_attack
// returns an int depending on what was hit
// 0 - nothing was hit
// 1 - living thing was hit
// 2 - bmodel/world was hit
////////////////////////////////////////////

int	weapon_trace_attack ( userEntity_t *self, int damage, int num_bullets, int distance)
{
	int				per_partical,hit_a_monster;
	Vector			forward, right, up, temp, end, spread, dir;
    vec3_t          hit_spot;
	trace_t			tr;
	
	temp = Vector (self->client->v_angle);
	temp.AngleVectors (forward, right, up);

	temp = Vector (self->s.origin) + forward * 8.0 + up * 16;	
	hit_a_monster=0;
	if(damage && num_bullets)
	{
		///////SPREAD RATIOS, IF MORE BULLETS MORE SPREAD//////
		if(num_bullets >= 2 && num_bullets <= 4)
			spread = Vector (.01, .01, 0.0);
		else if( num_bullets >= 5 && num_bullets <= 7)
			spread = Vector(.02,.02,.0.0);
		else if(num_bullets >= 8 && num_bullets <= 12)
			spread = Vector(.03,.03,.0.0);
		else if(num_bullets >= 13)
			spread = Vector(.04,.04,.0.0);
		else{}  /// 1 shot////	
		
		//////// DAMAGE PER PARTICAL//////
		
		per_partical = damage / num_bullets;
		
		for(int i =0; i <num_bullets; i++)
		{	
			if(num_bullets == 1)
				end = temp + forward * distance;
					
			if(num_bullets > 1)
			{
				dir = forward + right * crnd () * spread.x + up * crnd () * spread.y;
				end = temp + dir * distance;
			}
			
			///////TRACE ME/////
			tr = gstate->trace_q2 (temp.vec(), NULL, NULL, end.vec(), self, MASK_SHOT);
			
			//See if we actually hit something
			if(tr.fraction!= 1.0)
			{
				//Make sure we are damaging something that is meant to be damaged
				if (tr.ent->takedamage != DAMAGE_NO)
				{
					dir = end - temp;
					dir.Normalize ();

					//start color red particles...will have an actual blood particle function
					//sometime soon!
//					gstate->particle (trace.endpos, hit_spot, gstate->global_red, 25);
					com->Damage (tr.ent, self, self, tr.endpos, dir.vec (), per_partical, DAMAGE_INERTIAL);
					hit_a_monster=2;
				
				}
				if (tr.ent->solid == SOLID_BSP || tr.ent->takedamage == DAMAGE_NO)
				{
               vec3_t v3dir;

               dir.set(v3dir);

					//Place the particles and create the ricochet noise
					gstate->WriteByte(SVC_TEMP_ENTITY);
					gstate->WriteByte(TE_SHIELD_SPARKS);
               gstate->WritePosition(tr.endpos);
               gstate->WriteDir(dir.vec());
               gstate->MultiCast(self->s.origin,MULTICAST_ALL);
					hit_a_monster = 1;
				}
			}	
		}
	}

	return	hit_a_monster;	
}

//////////////////////////////////////////////////////
// weapon_touch
//
// generic touch function for all standard weapons
//////////////////////////////////////////////////////

void	weapon_touch (userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
	playerHook_t	*ohook = (playerHook_t *) other->userHook;
	userInventory_t	*invItem, *thisWeapon;
	int				this_weapons_rating;
	int				current_weapons_rating;
	think_t			weapon_select;
	weaponGive_t	weapon_give;

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

	weapon_give = (weaponGive_t)self->use;
	weapon_select = (think_t)self->die;

	invItem = gstate->InventoryFindItem (other->inventory, self->className);
	thisWeapon = weapon_give (other);

	//	weapon wasn't given for some reason -- perhaps inventory wasn't initialized
	if (!thisWeapon)
		return;

	// determine if this weapon is more powerful than the one that the player is
	// currently holding, if it is, then switch to it.

	current_weapons_rating = 0;

	if (other->curWeapon)
		other->curWeapon->command (other->curWeapon, "rating", &current_weapons_rating);
	
	thisWeapon->command (other->curWeapon, "rating", &this_weapons_rating);

	if (!invItem && this_weapons_rating > current_weapons_rating) weapon_select (other);

	// if FL_NORESPAWN bit is clear, then set up for respawning
	if (!(self->flags & FL_NORESPAWN))
		com->SetRespawn (self, 30, FALSE, 0);
}

////////////////////////////////////////////////////////////////
// weapon_check_ammo
//
// check for a valid pointer to an ammo type in self->curWeapon
// if pointer to ammo is valid, then the ammo is sent a command
// to decrement its rounds ammo_count ammount
////////////////////////////////////////////////////////////////

ammo_t	*weapon_check_ammo (userEntity_t *self, char *ammo_name, int ammo_count)
{
	ammo_t			*ammo, *temp;
	weapon_t		*invWeapon = (weapon_t *) self->curWeapon;

	// make sure we have ammo for the weapon
	// don't search through the inventory list for ammo each time unless we have to
	if (!invWeapon)
	{
		gstate->Con_Printf ("ammo pointer is invalid.\n");
		ammo = (ammo_t *) gstate->InventoryFindItem (self->inventory, ammo_name);
		if (!ammo)	
		{
			gstate->Con_Printf ("no bolts found in inventory\n");
			return	NULL;
		}
	}
	else
		ammo = (ammo_t *) invWeapon->ammo;

	if (ammo)
	{
		// we have an ammo item for the weapon
		if (ammo_count == 0) ammo_count = 1;

		// try to decrement the rounds in the ammo item
		// if command returns NULL, then the ammo item holds < ammo_count rounds
		temp = (ammo_t *) ammo->command ((userInventory_t *) ammo, "take_ammo", &ammo_count);
		if (!temp)
			return	NULL;
	
		// send the new ammo count down to the server
		com->UpdateClientAmmo (self);
		return	temp;
	}
	else
		return	NULL;
}

///////////////////////////////////////////////////////////////////////////////////////
// smoke & explosions 
///////////////////////////////////////////////////////////////////////////////////////

void smoke_trail_think (userEntity_t *self)
{
	if (self->frameIndex > 5)
		gstate->RemoveEntity (self);

	self->frameIndex++;

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

void spawn_smoke_trail (userEntity_t *self, vec3_t org, int play_sound)
{
	userEntity_t	*smoke;

	smoke = gstate->SpawnEntity ();

	smoke->movetype = MOVETYPE_NONE;
	smoke->solid = SOLID_NOT;

	gstate->SetModel (smoke, "models/smoke.spr");
	gstate->SetOrigin2 (smoke, org);
	gstate->SetSize (smoke, 0, 0, 0, 0, 0, 0);

	smoke->frameIndex = 0;
	smoke->nextthink = gstate->time + 0.1;
	smoke->think = smoke_trail_think;
	smoke->translucent = 2;
	smoke->owner = self;
}

//////////////////////////////
// explosion_ring_think
//////////////////////////////

void	ring_explosion_think (userEntity_t *self)
{

	if (self->frameIndex == 20)
	{
		gstate->RemoveEntity (self);
		return;
	}

	self->frameIndex++;

	com->RadiusDamage (self, self->owner, self, self->frameIndex * 6, DAMAGE_EXPLOSION);

	self->nextthink = gstate->time + 0.025;
	self->think = ring_explosion_think;
}

//////////////////////////////
// spawn_ring_explosion
//////////////////////////////

void	spawn_ring_explosion (userEntity_t *self, vec3_t org, int play_sound)
{
	userEntity_t	*explo;

	explo = gstate->SpawnEntity ();

	explo->movetype = MOVETYPE_NONE;
	explo->solid = SOLID_NOT;

	org[2] += 25;

	gstate->SetModel (explo, "models/ring.mdl");
	gstate->SetOrigin2 (explo, org);
	gstate->SetSize (explo, 0, 0, 0, 0, 0, 0);

	if (play_sound)
		gstate->sound (explo, CHAN_AUTO, "explosions/explode1.wav", 255.0, ATTN_NORM);

	explo->frameIndex = 0;
	explo->nextthink = gstate->time + 0.025;
	explo->think = ring_explosion_think;
	explo->s.effects = EF_DIMLIGHT;
	explo->translucent = 2;
	explo->owner = self;
}

//////////////////////////////
// explosion_think
//////////////////////////////

void	explosion_think (userEntity_t *self)
{
	self->s.frame++;

	if (self->s.frame > 9)
	{
		gstate->RemoveEntity (self);
		return;
	}

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

//////////////////////////////
// spawn_sprite_explosion
//////////////////////////////

void	spawn_sprite_explosion (userEntity_t *self, vec3_t org, int play_sound)
{
	userEntity_t	*explo;
	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_EXPLOSION1);

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

/*
/////////////////////////////////////////////////////////////////////////////////////
// list functions -- NOW IN com->DLL
//
// manages a linked list of an ammo type, ie. c4 packs that have been shot or
// crossbow bolts that have been fired
/////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////
// weapon_list_init
//
// initialize a linked list
///////////////////////////////

weaponListHeader_t	*weapon_list_init (weaponListHeader_t *list)
{
	list = (weaponListHeader_t *) com->malloc (sizeof (weaponListHeader_t));

	if (!list)
		gstate->Error ("Failed to malloc weaponListHeader!");

	list->head = NULL;
	list->tail = NULL;
	list->count = 0;

	return	list;
}

///////////////////////////////
// weapon_list_new_node
//
// create a new node and return
// a pointer to it
///////////////////////////////

weaponListNode_t	*weapon_list_new_node (serverState_t *state)
{
	weaponListNode_t	*node;

	node = (weaponListNode_t *) com->malloc (sizeof (weaponListNode_t));
	if (!node)
		gstate->Error ("Failed to malloc weaponListNode.");

	memset (node, 0x00, sizeof (weaponListNode_t));

	return	node;
}

///////////////////////////////
// weapon_list_add
//
// add a node to the list which
// points to self
///////////////////////////////

void	weapon_list_add (userEntity_t *self, weaponListHeader_t *list)
{
	weaponListNode_t	*node;

	if (!list)
		gstate->Error ("Attempted to add to uninitialized weapon list!");

	// create a new node
	node = weapon_list_new_node (state);
	
	// point this node to self
	node->ent = self;

	if (list->head == NULL)
	{
		// list is empty
		list->head = node;
		list->tail = node;
		node->prev = NULL;
		node->next = NULL;
	}
	else
	{
		// one or more nodes in list
		
		// make the tail to point to the new node
		list->tail->next = node;

		// point new node back to tail
		node->prev = list->tail;
		// new node is last in list, so point next to NULL
		node->next = NULL;

		// make the new node the tail
		list->tail = node;
	}

	list->count++;
}

///////////////////////////////
// weapon_list_delete_node
//
// remove node from list
///////////////////////////////

void	weapon_list_delete_node (weaponListHeader_t *list, weaponListNode_t *node)
{
	if (!node)
		gstate->Error ("Attempted to remove a null node from a weaponList!");

	if (node == list->head)
	{
		// node is the head of the list
		list->head = node->next;
	}
	if (node == list->tail)
	{
		// node is the tail of the list
		list->tail = node->prev;
	}

	if (node->next)
		node->next->prev = node->prev;
	if (node->prev)
		node->prev->next = node->next;

	free (node);
	list->count--;
}

///////////////////////////////
// weapon_list_remove
//
// remove all nodes from the list
// which point to self
///////////////////////////////

void	weapon_list_remove (userEntity_t *self, weaponListHeader_t *list)
{
	weaponListNode_t	*cur_node, *last_node;

	if (!list || !list->head)
		gstate->Error ("Attempted to remove from empty weaponList!");

	cur_node = list->head;

	while (cur_node)
	{
		if (cur_node->ent == self)
		{
			last_node = cur_node;
			cur_node = weapon_list_node_next (cur_node);
			weapon_list_delete_node (list, last_node);
		}
		else
			cur_node = weapon_list_node_next (cur_node);
	}
}

///////////////////////////////
// weapon_list_ent
//
// returns the entity pointed to
// by node
///////////////////////////////

userEntity_t	*weapon_list_ent (weaponListNode_t *node)
{
	return	node->ent;
}

///////////////////////////////
// weapon_list_next
//
// returns a pointer to the node
// after node
///////////////////////////////

weaponListNode_t	*weapon_list_node_next (weaponListNode_t *node)
{
	return	node->next;
}

///////////////////////////////
// weapon_list_prev
//
// returns a pointer to the node
// preceding node
///////////////////////////////

weaponListNode_t	*weapon_list_node_prev (weaponListNode_t *node)
{
	return	node->prev;
}

/////////////////////////////////////////////////////////////////////////////////////
// end of list functions
/////////////////////////////////////////////////////////////////////////////////////
*/

/////////////////////////////////
//	weapon_face_velocity
/////////////////////////////////

void	weapon_face_velocity (userEntity_t *self)
{
	Vector	temp, dir;

	dir = Vector (self->velocity);
	dir.Normalize ();

	com->VecToAngles (temp, dir);
	temp.set (self->s.angles);
}

/////////////////////////////////
// weapon_spawn_projectile
//
/////////////////////////////////

userEntity_t *weapon_spawn_projectile (userEntity_t *self, Vector &org, float xy_vel, float z_vel, char *className)
{
	userEntity_t	*ent;
	Vector			vel, origin;

	ent = gstate->SpawnEntity ();

	ent->owner = self;
	ent->movetype = MOVETYPE_TOSS;
	ent->solid= SOLID_BBOX;
	ent->className = className;

	ent->clipmask = MASK_SHOT;
	ent->svflags = SVF_SHOT;	//	won't clip against any ents with clipmask == MASK_SHOT
	// copy for use with delayed launchings
//	com->CopyVector (ent->v_angle, self->client->v_angle);

	// calculate the velocity of the projectile
	vel = Vector (self->client->v_angle);
	vel.AngleVectors (forward, right, up);
	
	vel = forward * xy_vel;
   vel.z += z_vel;
	vel.set (ent->velocity);

	origin=Vector(self->s.origin)+weaponHandVector(self,forward*org.y,right*org.x);
	origin.z += org.z;
	origin.set (ent->s.origin);
	gstate->LinkEntity (ent);
	
	weapon_face_velocity (ent);

	return	ent;
}

//---------------------------------------------------------------------------
// weaponCreateInv()
//---------------------------------------------------------------------------
weapon_t *weaponCreateInv(weaponInfo_t *winfo, void (*use_func)(), void *(*cmd_func)(), short struct_size)
{
	weapon_t	*weapon;

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

	// init the inventory struct to 0's
	memset(weapon,0x00,struct_size);    //sizeof(weapon_t));
	weapon->name=strdup(winfo->weaponName);
	weapon->use=(void (*)(userInventory_s *ptr, userEntity_t *self))use_func;
	weapon->command=(void *(*)(struct userInventory_s *ptr, char *commandStr, void *data))cmd_func;
	
	return(weapon);
}

//---------------------------------------------------------------------------
// weaponCreateInv2() - save as above, different parameter declaration
//---------------------------------------------------------------------------
weapon_t *weaponCreateInv2(weaponInfo_t *winfo, 
                           void (*use_func)(userInventory_s *ptr, userEntity_t *self),
                           void *(*cmd_func)(struct userInventory_s *inv, char *commandStr, void *data),
                           short struct_size)
{
	weapon_t	*weapon;

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

	// init the inventory struct to 0's
	memset(weapon,0x00,struct_size);    //sizeof(weapon_t));
	weapon->name=strdup(winfo->weaponName);
	weapon->use=(void (*)(userInventory_s *ptr, userEntity_t *self))use_func;
	weapon->command=(void *(*)(struct userInventory_s *ptr, char *commandStr, void *data))cmd_func;
	
	return(weapon);
}

//---------------------------------------------------------------------------
// ammoCreate()
//---------------------------------------------------------------------------
ammo_t *ammoCreate(weaponInfo_t *winfo)
{
	ammo_t *ammo;

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

	// init the inventory struct to 0's
	memset(ammo,0x00,sizeof(ammo_t));
	ammo->name=strdup(winfo->ammoName);
	ammo->use=NULL;
	ammo->command=ammoGenericCommand;

	ammo->count=0;

	return(ammo);
}

//---------------------------------------------------------------------------
// ammoAdd()
//---------------------------------------------------------------------------
ammo_t *ammoAdd(userEntity_t *self,int amount,weaponInfo_t *winfo)
{
	ammo_t *ammo;

	// make sure that this ammo type isn't already in inventory
	if (!(ammo=(ammo_t *)gstate->InventoryFindItem(self->inventory,winfo->ammoName)))
	{
		// create ammo
		if (!(ammo=ammoCreate(winfo)))
		{
			gstate->Con_Printf("Unable to malloc %s.\n",winfo->ammoNetName);
			return(NULL);
		}

		// add the ammo to the player's inventory list
		if (!gstate->InventoryAddItem(self->inventory,(userInventory_t *)ammo))
		{
			gstate->Con_Printf("Could not add %s to inventory.\n",winfo->ammoNetName);
			return(NULL);
		}
	}

	// bolts are in inventory, pointed to by invItem so add ammo to them
	ammo->command((userInventory_t *)ammo,"give_ammo",&amount);

   return(ammo);
}

//---------------------------------------------------------------------------
// weaponGenericCommand()
//---------------------------------------------------------------------------
void *weaponGenericCommand(userInventory_t *inv,char *commandStr,void *data)
{
   weapon_t *weapon=(weapon_t *)inv;

   if (!stricmp(commandStr,"ammo_count"))
   {
      if (weapon->ammo)
         weapon->ammo->command((userInventory_t *)weapon->ammo,"count",data);
   }

   return(NULL);
}

//---------------------------------------------------------------------------
// winfoGenericCommand()
//---------------------------------------------------------------------------
void *winfoGenericCommand(weaponInfo_t *winfo,userInventory_t *inv,char *commandStr,void *data)
{
   userEntity_t *self=(userEntity_t *)data;
   playerHook_t *phook=(playerHook_t *)self->userHook;
   weapon_t *weapon=(weapon_t *)inv;

   if (!stricmp(commandStr,"ammo_count"))
   {
      if (weapon->ammo)
         weapon->ammo->command((userInventory_t *)weapon->ammo,"count",data);
   }
	else if (!stricmp(commandStr, "change"))
	{
		//	play goaway sequence
      winfoAnimDelay((userEntity_t *)data,winfo,ANIM_AWAY_STD,.05);
	}
	else if (!stricmp(commandStr, "change_time"))
	{
		//	return length of time to play putaway sequence
		*(float *)data=(winfoNumFrames(ANIM_AWAY_STD)) * 0.05;
	}
   else if (!stricmp(commandStr,"ambient"))
   {
      userEntity_t *went=(userEntity_t *)self->client->ps.weapon;
      float animtime=winfoNumFrames(ANIM_AMBIENT_STD)*.05;

      winfoAnim((userEntity_t *)data,ANIM_AMBIENT_STD,.05);
      if (went->flags & FL_LOOPAMBIENT)
	  {
         phook->ambient_time=gstate->time + animtime;
		 // Play the looping ambient sound if it exists
		 if (winfo->flags2)
			 winfoPlay (winfo->flags2, 255);
	  }
      else
         phook->ambient_time=gstate->time+animtime+2+(rnd()*6);
   }
	else if (!stricmp (commandStr, "rating"))       // return a value between 0 and 1000
		*(int *)data=winfo->rating;

   return(NULL);
}

//---------------------------------------------------------------------------
// ammoGenericCommand()
//---------------------------------------------------------------------------
void *ammoGenericCommand(userInventory_t *ptr,char *commandStr,void *data)
{
	ammo_t *ammo=(ammo_t *)ptr;

	if (!stricmp (commandStr, "give_ammo"))            // adds (*data) units of ammo
		ammo->count = ammo->count + *(int *)data;
	else if (!stricmp (commandStr, "take_ammo"))       // uses (*data) units of ammo
	{
		if (ammo->count > 0)
		{
			ammo->count = ammo->count - *(int *)data;
			return ((void *)1);
		}
		else 
			return (NULL);
	}
	else if (!stricmp (commandStr, "use_ammo"))        // uses 1 unit of ammo
	{
		if (ammo->count > 0)
		{
			ammo->count--;
			return ((void *)1);
		}
		else 
			return (NULL);
	}
	else if (!stricmp (commandStr, "count"))           // returns ammo count
	{
		*(int *)data=ammo->count;
		return ((void *)ammo->count);
	}
	
	return NULL;
}

//---------------------------------------------------------------------------
// weaponDllInit()
//---------------------------------------------------------------------------
int weaponDllInit(weaponInfo_t *winfo)
{
   weaponCmd_t *cmd;

//
// i think this func just needs to return ent size info
// and should let weaponInitLevel() handle the rest...
//
// will probably change later...
//

   // add commands
   cmd=&winfo->command[0];
   while (cmd->text)
   {
      gstate->AddCommand(cmd->text,cmd->func);
      cmd++;
   }

   // precache inventory
	gstate->InventoryPrecache(winfo->weaponName, "needicon", winfo->models[0]);
	gstate->InventoryPrecache(winfo->ammoName, "needicon", NULL);

	return(sizeof(userEntity_t)+sizeof(serverState_t));
}

//---------------------------------------------------------------------------
// weaponLevelInit()
//---------------------------------------------------------------------------
void weaponLevelInit(weaponInfo_t *winfo)
{
   userEntity_t *ent;
   weaponFrame_t *fptr;
   char **ptr;

   // cache models
   ptr=&winfo->models[0];
   while (*ptr)
   {
      gstate->Precache_Model(*ptr);
      ptr++;
   }

   // cache sounds
   ptr=&winfo->sounds[0];
   while (*ptr)
   {
      gstate->Precache_Sound(*ptr);
      ptr++;
   }

   // get frame info
   ent=gstate->SpawnEntity();
   fptr=winfo->frames;
   while (fptr->name)
   {
      dk2SetModel(ent,winfo->models[fptr->model]);

      if (!strcmp(fptr->name,"sp2"))         // sprites
      {
         dsprite_t sprite;
         FILE *h;

	      gstate->gi->FS_Open(winfo->models[fptr->model],&h);
         if (h)
         {
            gstate->gi->FS_Read(&sprite,sizeof(dsprite_t)-sizeof(dsprframe_t),h);
            gstate->gi->FS_Close(h);
            fptr->first=0;
            fptr->last=sprite.numframes-1;
         }
         fptr++;
      }
      else                                   // models
         do {
            com->GetFrames(ent,fptr->name,&fptr->first,&fptr->last);
            fptr++;
         } while (fptr->model == -1);
   }
   gstate->RemoveEntity(ent);
}

//---------------------------------------------------------------------------
// weaponHandVector()
//---------------------------------------------------------------------------
Vector weaponHandVector(userEntity_t *ent,Vector &fpos,Vector &hpos)
{
   Vector dofs;

   ent->client->pers.hand=gstate->GetCvar("hand");

	if (ent->client->pers.hand==LEFT_HANDED)
      hpos=-hpos;
   else if (ent->client->pers.hand==CENTER_HANDED)
      hpos=fpos=Vector(0,0,-4);

   if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
      dofs=Vector(0,0,25);
   else
      dofs=zero_vector;

   return(fpos+hpos-dofs);
}

//---------------------------------------------------------------------------
// selectWeapon()
//---------------------------------------------------------------------------
userEntity_t *selectWeapon(userEntity_t *owner,char *weaponName,char *modelName)
{
   playerHook_t *phook=(playerHook_t *)owner->userHook;
   trackInfo_t tinfo;
   userEntity_t *ent;
   userInventory_t *inv;
   Vector vec,dir;
   short weapon_spawned=false;

   // set owner's current weapon
   if (!(inv=gstate->InventoryFindItem(owner->inventory,weaponName)))
      return(NULL);
   owner->curWeapon=inv;

   // spawn weapon entity (if not already there)
   if (!owner->client->ps.weapon)
   {
      owner->client->ps.weapon=gstate->SpawnEntity();
      weapon_spawned=true;
   }

   // setup weapon entity
   ent=(userEntity_t *)owner->client->ps.weapon;
   ent->owner = owner;
   ent->movetype = MOVETYPE_FLY;
   ent->solid = SOLID_BBOX;
   ent->className = weaponName;
   ent->s.effects=256;
   ent->s.modelindex=gstate->ModelIndex(modelName);
	ent->svflags = SVF_OWNERONLY;                      // only send to owner's machine
   ent->s.renderfx = RF_TRACKENT;                     // special draw
   ent->flags &= ~FL_LOOPAMBIENT;

   ent->think=ent->prethink=NULL;

   com->CopyVector(ent->s.origin,owner->s.origin);
   gstate->LinkEntity(ent);		
   if (weapon_spawned)
      gstate->hr_InitRelative (owner,ent,0);

   // this doesn't affect the size of the weapon!!!
   // i've traced it and i don't know why!!
   com->SetVector(ent->s.render_scale,8,8,8);

   // send 'track entity' msg to client
   tinfo.ent=ent;
   tinfo.srcent=owner;
   tinfo.fru[0]=tinfo.fru[1]=tinfo.fru[2]=0;
   tinfo.dstent=0;
   tinfo.flags=RF_MINLIGHT|RF_DEPTHHACK|RF_WEAPONMODEL|TEF_SRCINDEX|TEF_FRU;
   trackEntity(&tinfo,UNICAST_ENT);

   // init ambient_time
   phook->ambient_time=gstate->time+2.0+(rnd()*6);

   return(ent);
}

//---------------------------------------------------------------------------
// weapon_animate()
//---------------------------------------------------------------------------
void weapon_animate (userEntity_t *self, int start, int end, float frametime)
{
	playerHook_t *hook=(playerHook_t *)self->userHook;
   userEntity_t *weapon;

   weapon=(userEntity_t *)self->client->ps.weapon;
   if (!weapon)
      return;

	//	no weapon frames for bots or monsters
	if (!(self->flags & FL_CLIENT))
      return;

	hook->vm_startFrame = start;
	hook->vm_endFrame = end;
	hook->vm_frameTime = frametime;
	hook->vm_next_frameTime = 0;
	hook->vm_frameFlags = FRAME_ONCE;
	hook->vm_frameInc = 1;
   weapon->s.frame=start;
}

//---------------------------------------------------------------------------
// winfoAnimDelay()
//---------------------------------------------------------------------------
void winfoAnimDelay(userEntity_t *self,weaponInfo_t *winfo,short animNum,float delay)
{
	playerHook_t *hook=(playerHook_t *)self->userHook;

	winfoAnim(self,animNum,delay);
	int num=winfoNumFrames(animNum);
	hook->attack_finished = gstate->time + winfoNumFrames(animNum)*delay;
}

//---------------------------------------------------------------------------
// trackEntity()
//---------------------------------------------------------------------------
void trackEntity(trackInfo_t *tinfo,short broadcasttype)
{
   gstate->WriteByte(SVC_TEMP_ENTITY);                            // evaluated with temp entities
   gstate->WriteByte(TE_TRACK_ENTITY);                            // tracking msg
   gstate->WriteLong(tinfo->ent->s.number);                       // track index / id
   gstate->WriteByte(1);                                          // track mode   (1=new/update   2=kill)
   gstate->WriteLong(tinfo->flags);                               // flags

   if (tinfo->flags & TEF_SRCINDEX)
      gstate->WriteLong(tinfo->srcent->s.number);                    // src index

   if (tinfo->flags & TEF_FRU)
      gstate->WritePosition(tinfo->fru);                             // fwd, rt, up offsets

   if (tinfo->flags & TEF_DSTINDEX)
      gstate->WriteLong((int)(tinfo->dstent-gstate->g_edicts));   // dst index

   if (tinfo->flags & TEF_DSTPOS)
      gstate->WritePosition(tinfo->dstpos);                       // dst position

   if (tinfo->flags & TEF_LENGTH)
      gstate->WriteFloat(tinfo->length);                          // length

   switch (broadcasttype)
   {
      case UNICAST_ENT:       gstate->UniCast(tinfo->srcent,false);                                      break;
      default:                gstate->MultiCast(tinfo->srcent->s.origin, (multicast_t)broadcasttype);    break;
   }
}

//---------------------------------------------------------------------------
// untrackEntity()
//---------------------------------------------------------------------------
void untrackEntity(userEntity_t *ent,userEntity_t *to,short broadcasttype)
{
   gstate->WriteByte(SVC_TEMP_ENTITY);                         // evaluated with temp entities
   gstate->WriteByte(TE_TRACK_ENTITY);                         // tracking msg
   gstate->WriteLong(ent->s.number);                           // track index / id
   gstate->WriteByte(2);                                       // track mode   (1=new/update   2=kill)

   switch (broadcasttype)
   {
      case UNICAST_ENT:       gstate->UniCast(to,true);                                         break;
      default:                gstate->MultiCast(ent->s.origin, (multicast_t)broadcasttype);     break;
   }
}

//---------------------------------------------------------------------------
// weaponUseAmmo()
//---------------------------------------------------------------------------
short weaponUseAmmo(userInventory_t *weapon,int count)
{
   weapon_t *weap=(weapon_t *)weapon;
   short unlimited_ammo;

   // cvar check is for 'runtime', #define is for 'builds' (like E3)
   if (gstate->GetCvar("g_unlimited_ammo") || WEAPON_UNLIMITED_AMMO)
      return(99);

   if (!count)
   {
      weap->ammo->command((userInventory_t *)weap->ammo,"count",&count);
      return(count);
   }

   return((short)weap->ammo->command((userInventory_t *)weap->ammo,"take_ammo",&count));
}

//---------------------------------------------------------------------------
// weaponGive()
//---------------------------------------------------------------------------
userInventory_t *weaponGive(userEntity_t *self,weaponInfo_t *winfo,short ammoCount)
{
	weapon_t *weapon;
	userInventory_t *invItem;
   playerHook_t *phook=(playerHook_t *)self->userHook;

	// make sure weapon is not already in inventory	
	if (invItem=gstate->InventoryFindItem(self->inventory,winfo->weaponName))
	{
		gstate->Con_Printf("You already have a %s.\n",winfo->weaponNetName);
		return(invItem);
	}

	// create new inventory item
	if (!(weapon=weaponCreateInv2(winfo,winfo->use_func,winfo->cmd_func,winfo->weapon_t_size)))
	{
		gstate->Con_Printf("Unable to malloc %s.\n",winfo->weaponNetName);
		return(NULL);
	}

	// add the weapon to the player's inventory list
	if (!gstate->InventoryAddItem(self->inventory,(userInventory_t *)weapon))
	{
		gstate->Con_Printf("Could not add %s to inventory.\n",winfo->weaponNetName);
		return(NULL);
	}

	if (self->flags & FL_CLIENT)
		gstate->sprint(self,"You picked up the %s!\n",winfo->weaponNetName);

   weapon->ammo=ammoAdd(self,ammoCount,winfo);           // give ammo with weapon
   phook->ambient_time=gstate->time+2.0+(rnd()*6);       // starting ambient time

	return((userInventory_t *)weapon);
}

//---------------------------------------------------------------------------
// weaponSelect()
//---------------------------------------------------------------------------
void weaponSelect(userEntity_t *self,weaponInfo_t *winfo)
{
	playerHook_t *hook=(playerHook_t *)self->userHook;
	weapon_t *weapon;
	userInventory_t *invItem;
	float seq_time;

	if (!(invItem=gstate->InventoryFindItem(self->inventory,winfo->weaponName)))
      return;

   // set this up for call to weaponSelectFinish()
   weaponCurrentWinfo=winfo;                          // MAY WANT TO DO THIS A DIFFERENT WAY

	//	tell the user's current weapon to go away
	if ((weapon=(weapon_t *)self->curWeapon))
	{
      if (invItem==self->curWeapon)
         return;

		weapon->command(self->curWeapon,"change",self);
		weapon->command(self->curWeapon,"change_time",&seq_time);

		//	make sure the client's frame flags tell us to go to next weapon
		hook->vm_frameFlags |= FRAME_WEAPONSWITCH;
	
		//	set client's next weapon to this weapon
		hook->weapon_next=weaponSelectFinish;

		//	make sure we can't shoot!
		hook->attack_finished=gstate->time + seq_time;
	}
	else
		weaponSelectFinish(self);
}

//---------------------------------------------------------------------------
// weaponSelectFinish()
//---------------------------------------------------------------------------
void weaponSelectFinish(userEntity_t *self)
{
   weaponInfo_t *winfo=weaponCurrentWinfo;
   userEntity_t *went;
   playerHook_t *phook=(playerHook_t *)self->userHook;

   if (!winfo)
      return;

   if (!selectWeapon(self,winfo->weaponName,winfoModel(MDL_WEAPON_STD)))
      return;

   went=(userEntity_t *)self->client->ps.weapon;

	// this must be here to stop the sling from screwing up the firing of other weapons
	self->flags=self->flags - (self->flags & FL_ATTACKHOLD);

   winfoAnimDelay(self,winfo,ANIM_READY_STD,0.05);

   weaponCurrentWinfo=NULL;
   went->flags = winfo->flags;

   if (went->flags & FL_LOOPAMBIENT)
      phook->ambient_time=gstate->time+.1;
}

//---------------------------------------------------------------------------
// entityHuman() - better way to determine human from mech???
//---------------------------------------------------------------------------
qboolean entityHuman(userEntity_t *self)
{
   char **ptr;
   char *mechEnts[] = {"monster_sludgeminion",
                       "monster_ragemaster",
                       "monster_battleboar",
                       NULL};

   ptr=mechEnts;
   while (*ptr)
   {
      if (!stricmp(self->className,*ptr))
         return(FALSE);

      ptr++;
   }

   return(TRUE);
}

//---------------------------------------------------------------------------
// winfoClientExplosion()
//---------------------------------------------------------------------------
void winfoClientExplosion(weaponInfo_t *winfo,Vector pos,Vector angle,float scale,short model,short anim)
{
	gstate->WriteByte(SVC_TEMP_ENTITY);
	gstate->WriteByte(TE_EXPLOSION);
	gstate->WritePosition(pos.vec());
	gstate->WriteAngle(angle.x);
	gstate->WriteAngle(angle.y);
	gstate->WriteAngle(angle.z);
   gstate->WriteFloat(scale);
   gstate->WriteByte(winfoNumFrames(anim));
   gstate->WriteLong(gstate->ModelIndex(winfoModel(model)));
	gstate->MultiCast(pos.vec(),MULTICAST_PVS);
}

//---------------------------------------------------------------------------
// trackLight()
//---------------------------------------------------------------------------
void trackLight(userEntity_t *ent,float size,float r,float g,float b)
{
   trackInfo_t tinfo;

   tinfo.flags=0;
   tinfo.srcent=ent;
   tinfo.fru[0]=r;
   tinfo.fru[1]=g;
   tinfo.fru[2]=b;
   tinfo.length=size;
   tinfo.flags |= TEF_SRCINDEX|TEF_FRU|TEF_LENGTH|TEF_ADDLIGHT;
   tinfo.ent=ent;
   trackEntity(&tinfo,MULTICAST_ALL);
}

//---------------------------------------------------------------------------
// untrackLight()
//---------------------------------------------------------------------------
void untrackLight(userEntity_t *ent)
{
   untrackEntity(ent,NULL,MULTICAST_ALL);
}
