#include "weapons.h"
#include "meta.h"

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

#define	META_FIRE_RATE		1.0
#define	META_CHARGE_TIME	1.4
#define	META_TRACK_TIME		3.0
#define	META_MAX_TARGETS	16
#define META_MAX_CHARGES	120
#define	META_SEARCH_TIME	1.0

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

DllExport	void	weapon_metamaser (userEntity_t *self);
DllExport	void	ammo_meta (userEntity_t *self);

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

void			meta_select (userEntity_t *self);
void			meta_use (struct userInventory_s *ptr, userEntity_t *self);
userInventory_t	*meta_give (userEntity_t *self);
void			meta_wait_think (userEntity_t *self);
void			meta_search_think (userEntity_t *self);

/////////////////////////////////////////////////////////////////////
/// weapon_metamaser
/////////////////////////////////////////////////////////////////////

typedef	struct
{
	userEntity_t	*target;
	float			end_track_time;
	float			next_sound_time;
	float			next_damage_time;
} metaTarget_t;

typedef struct
{
	float	remove_time;

	float	last_touch_time;
	float	last_water;
	float	change_time;
	float	rotation_scale;
	float	mass_scale;

	Vector	hit_water_pos;

	int		charges;

	metaTarget_t	targets [16];
} weapHook_t;

///////////////////////////////////////////////////////////////////
// standard weapon interface routines
//
// each weapon must have these routines so that they can "plug-in"
// to Daikatana with minimum effort -- modularity is the goal
///////////////////////////////////////////////////////////////////

static	char			*weapon_name;
static	char			*weapon_netname;
static	char			*ammo_name;
static	char			*ammo_netname;

static	int	readyFirst		[1];
static	int	readyLast		[1];

static	int	shootFirst		[1];
static	int	shootLast		[1];

static	int	reloadFirst		[1];
static	int	reloadLast		[1];

static	int	changeFirst		[1];
static	int	changeLast		[1];

/////////////////////////////////
// metamaser_load_init
//
// should be called from dll_Load
/////////////////////////////////

int	metamaser_load_init (void)
{
	////////////////////////////////
	// set up names for this weapon
	////////////////////////////////

	weapon_name = "weapon_metamaser";
	weapon_netname = "metamaser";
	ammo_name = "ammo_meta";
	ammo_netname = "meta cubes";

	// host_initilized has not been set yet
	// so everything here is perm

	// add spawn command
	gstate->AddCommand ("select_metamaser", meta_select);
	gstate->AddCommand (weapon_name, meta_give);

	// we need to precache an item's resources here
	gstate->InventoryPrecache(weapon_name, "needicon", "models/metamaser/metamaser.md2");
	// we need to precache an item's resources here
	gstate->InventoryPrecache(ammo_name, "needicon", NULL);

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

/////////////////////////////////
// metamaser_level_init
//
// should be called from dll_LoadLevel
/////////////////////////////////

void metamaser_level_init (void)
{
	userEntity_t *temp;

	gstate->Precache_Model("models/metamaser/metacube.md2");

	gstate->Precache_Sound ("weapons/metamaser/metamaserfire.wav");
	gstate->Precache_Sound ("weapons/metamaser/metamaserwait.wav");
	gstate->Precache_Sound ("weapons/metamaser/metamasercharge.wav");
	gstate->Precache_Sound ("weapons/metamaser/metamaserpain.wav");

	gstate->Precache_Model ("models/metamaser/metamaser.md2");

	// spawn a temp entity to get the frames names from
	temp = gstate->SpawnEntity ();
	gstate->SetModel (temp, "models/metamaser/metamaser.md2");
	
	com->GetFrames (temp, "goaway", &changeFirst [0], &changeLast [0]);
	com->GetFrames (temp, "reload", &reloadFirst [0], &reloadLast [0]);
	com->GetFrames (temp, "shoota", &shootFirst [0], &shootLast [0]);
	com->GetFrames (temp, "readya", &readyFirst [0], &readyLast [0]);
	
	//pulserifle_getframes (temp);
	gstate->RemoveEntity (temp);
}

/*
=============
metamaser_command
=============
*/

void *metamaser_command (struct userInventory_s *ptr, char *commandStr, void *data)
{
	if (!stricmp (commandStr, "death_msg"))
	{
		killerInfo_t	*ki = (killerInfo_t *) data;
		char			msg [80];

		if (rnd () > 0.5)
			sprintf (msg, "%s gulps down %s's beam.\n", ki->victim->netname, ki->attacker->netname);
		else
			sprintf (msg, "%s's maser splits %s down the middle.\n", ki->attacker->netname, ki->victim->netname);

		gstate->bprint (msg);
	}
	else if (!stricmp (commandStr, "suicide_msg"))
	{
		killerInfo_t	*ki = (killerInfo_t *) data;
		char			msg [80];

		sprintf (msg, "%s burnt himself a new hole.\n", ki->attacker->netname, ki->victim->netname);

		gstate->bprint (msg);
	}
	else if (!stricmp (commandStr, "rating"))
	{
		// return a value between 0 and 1000
		*(int *) data = 975;
	}
	else if (!stricmp (commandStr, "change"))
	{
		//	play goaway sequence
		weapon_animate ((userEntity_t *)data, changeFirst [0], changeLast [0], 0.05);
	}
	else if (!stricmp (commandStr, "change_time"))
	{
		//	return length of time to play putaway sequence
		*(float *) data = (changeLast [0] - changeFirst [0]) * 0.05;
	}

	return NULL;
}

/*
=============
ammo_command

make sure you use standard command names when modifying/adding weapons:
  give_ammo
  take_ammo
=============
*/

static	void	*ammo_command (userInventory_t *ptr, char *commandStr, void *data)
{
	ammo_t	*ammo = (ammo_t *) ptr;

	if (!stricmp (commandStr, "give_ammo"))
	{
		ammo->count = ammo->count + *(int *) data;
			return (void *) 1;
	}
	else if (!stricmp (commandStr, "take_ammo"))
	{
		if (ammo->count > 0)
		{
			ammo->count = ammo->count - *(int *) data;
			return (void *) 1;
		}
		else 
			return NULL;
	}

	return NULL;
}

/*
=============
ammo_create

creates an inventory item of ammo type
=============
*/

static ammo_t *ammo_create (void)
{
	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 (ammo_name);
	ammo->use  = NULL;
	ammo->command = ammo_command;

	ammo->count = 0;

	return ammo;
}

/*
=============
weapon_create

creates a inventory item of weapon type
=============
*/

static weapon_t *weapon_create (void)
{
	weapon_t	*weapon;

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

	// init the inventory struct to 0's
	memset (weapon, 0x00, sizeof (weapon_t));
	weapon->name = strdup (weapon_name);
	weapon->use  = meta_use;
	weapon->command = metamaser_command;
	
	return weapon;
}

/*
=============
meta_give

puts a crossbow in the inventory
=============
*/

userInventory_t	*meta_give (userEntity_t *self)
{
	weapon_t		*weapon;
	ammo_t			*ammo;
	userInventory_t	*invItem;
	int				ammo_count;

	/////////////////////////////
	// add weapon to inventory
	/////////////////////////////

	// make sure weapon is not already in inventory	
	invItem = gstate->InventoryFindItem (self->inventory, weapon_name);

	if (invItem)
	{
		gstate->sprint (self, "You already have a %s.\n", weapon_netname);
		return	invItem;
	}

	// create new inventory item
	if ((weapon = weapon_create ()) == NULL)
	{
		gstate->Con_Printf("Unable to malloc %s.\n", weapon_netname);
		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", weapon_netname);
		return	NULL;
	}

	///////////////////////////////////
	// add ammo to inventory
	///////////////////////////////////

	// ammount of ammo
	ammo_count = 10;

	// make sure that this ammo type isn't already in inventory
	ammo = (ammo_t *) gstate->InventoryFindItem (self->inventory, ammo_name);
	
	if (!ammo)
	{
		// create ammo
		if ((ammo = ammo_create ()) == NULL)
		{
			gstate->Con_Printf ("Unable to malloc %s.\n", ammo_netname);
			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", ammo_netname);
			return	NULL;
		}
	}

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

	if (self->flags & FL_CLIENT)
		gstate->sprint (self, "You got the %s!\n", weapon_netname);

	return	(userInventory_t *) weapon;
}

//////////////////////////////////////////////////////////////////////////////
// end of weapon interface routines
//////////////////////////////////////////////////////////////////////////////

/*
===============
meta_water_friction

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

void	meta_water_friction (userEntity_t *self)
{
	weapHook_t	*hook = (weapHook_t *) self->userHook;
	Vector			forward, right, up, dir, ang;
	float			pc, wobble_speed, wander_speed;

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

		hook->last_water = pc;
		return;
	}

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

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

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

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

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

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

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

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

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

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

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

	hook->last_water = pc;
}

/*
===============
meta_target_init

initializes the metamasers list of targets
===============
*/

void	meta_target_init (userEntity_t *self)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	int				i;

	for (i = 0; i < META_MAX_TARGETS; i++)
	{
		hook->targets [i].target = NULL;
		hook->targets [i].end_track_time = 0.0;
		hook->targets [i].next_sound_time = 0.0;
		hook->targets [i].next_damage_time = 0.0;
	}
}

/*
===============
meta_target_add

adds entity other to the metamasers list of targets
===============
*/

void	meta_target_add (userEntity_t *self, userEntity_t *other)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	int				i;

	// make sure this target isn't already in the list
	for (i = 0; i < META_MAX_TARGETS; i++)
	{
		if (hook->targets [i].target == other)
			return;
	}

	// put the target in the first open space in the list
	for (i = 0; i < META_MAX_TARGETS; i++)
	{
		if (hook->targets [i].target == NULL)
		{
			hook->targets [i].target = other;
			hook->targets [i].end_track_time = gstate->time + META_TRACK_TIME;
			hook->targets [i].next_sound_time = gstate->time;
			hook->targets [i].next_damage_time = gstate->time;

			return;
		}
	}

	gstate->Con_Printf ("no free targets\n");
}

/*
===============
meta_target_remove

removes entity other from the metamasers list of targets
===============
*/

void	meta_target_remove (userEntity_t *self, userEntity_t *other)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	int				i;

	for (i = 0; i < META_MAX_TARGETS; i++)
	{
		if (hook->targets [i].target == other)
		{
			hook->targets [i].target = NULL;
			hook->targets [i].end_track_time = 0.0;
			hook->targets [i].next_sound_time = 0.0;
			hook->targets [i].next_damage_time = 0.0;			

			return;
		}
	}

	gstate->Con_Printf ("target not in list!\n");
}

/*
===============
meta_fire_laser
===============
*/

void	meta_fire_laser (userEntity_t *self, userEntity_t *other)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	userEntity_t	*laser;
	Vector			temp, dir;

//	gstate->Con_Printf ("firing laser at %s\n", other->netname);

	hook->charges--;

#if 0    // mdm q2fix
   gstate->WriteByte (self, MSG_BROADCAST, SVC_TEMPENTITY);
	gstate->WriteByte (self, MSG_BROADCAST, TE_LINKTO);

	gstate->WriteEntity (self, MSG_BROADCAST, self);

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

	temp = Vector (other->s.origin) + Vector (0.0, 0.0, 22.0);
	gstate->WriteCoord (self, MSG_BROADCAST, other->s.origin [0]);
	gstate->WriteCoord (self, MSG_BROADCAST, other->s.origin [1]);
	gstate->WriteCoord (self, MSG_BROADCAST, other->s.origin [2]);
	
	// send the target entity for this temporary entity
	gstate->WriteEntity (self, MSG_BROADCAST, other);
#endif

	// remove this entity from the list if it is dead
	if (other->deadflag)
		meta_target_remove (self, other);
}

/*
===============
meta_remove
===============
*/

void	meta_remove (userEntity_t *self)
{
	// add some cool effect here for when the metamaser grenade is removed
	gstate->Con_Printf ("removing %s\n", self->className);

	gstate->RemoveEntity (self);
	self->nextthink = -1;
	self->think = NULL;
}

/*
===============
meta_pain

initializes the metamasers list of targets
===============
*/

void	meta_pain (userEntity_t *self)
{
	gstate->sound (self, CHAN_AUTO, "weapons/metamaser/metamaserpain.wav", 255, ATTN_NORM);

	self->nextthink = gstate->time + META_SEARCH_TIME;
	self->think = meta_search_think;
}

/*
===============
meta_track_think

this is actually the prethink for the metamaser.
Once the metamaser starts firing it tracks each of the entities
in its entity list for up to 5 seconds, moving the beam onto them
during each frame
===============
*/

void	meta_track_think (userEntity_t *self)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	int				i, cur_targets;

	for (cur_targets = i = 0; i < META_MAX_TARGETS; i++)
	{
		// make sure this is not an empty target slot
		if (hook->targets [i].target)
		{
			if (hook->targets [i].end_track_time > gstate->time && 
				com->Visible (self, hook->targets [i].target))
			// we can still see the target, so fire at it
			{
				if (hook->targets [i].next_sound_time <= gstate->time)
				{
					gstate->sound (self, CHAN_AUTO, "weapons/metamaser/metamaserfire.wav", 255, ATTN_NORM);
					hook->targets [i].next_sound_time = gstate->time + 0.2;
				}

				if (hook->targets [i].next_damage_time <= gstate->time)
				{
					//	Nelno:	this is the new damage routine but it's not been tested... passing zero_vector
					//	for both vectors because it doesn't look like a trace is being done here at all
					//	maybe call to com->Damage should be done later??  Looks like com->Visible is used to
					//	determine this in meta_search_think
					com->Damage (hook->targets [i].target, self, self->owner, zero_vector, zero_vector, 10.0, DAMAGE_NONE);
					hook->targets [i].next_damage_time = gstate->time + 0.2;
				}

				meta_fire_laser (self, hook->targets [i].target);
				cur_targets++;
			}
			else
			// target has moved out of view, so remove it
				meta_target_remove (self, hook->targets [i].target);
		}

		// are we all out of charges?
		if (!hook->charges)
		{
			// stop tracking
			self->prethink = NULL;
			// go away
			meta_remove (self);
			
			return;
		}
	}


	if (!cur_targets)
	{
		// no targets are in view, so stop tracking and go to search mode
		self->prethink = NULL;

		// wait for more targets to appear
		self->think = meta_wait_think;
		self->nextthink = gstate->time + META_SEARCH_TIME;
	}
}

/*
===============
meta_search_think

at least one entity has been found and the laser has charged up
and is ready to begin firing

called every META_SEARCH_TIME second to acquire new targets, as long as at
least one target is still visible
===============
*/

void	meta_search_think (userEntity_t *self)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	userEntity_t	*head;

	gstate->Con_Printf ("searching - ");
	// search entity list
	head = gstate->FirstEntity ();
	
	while (head != NULL)
	{
		if (!hook->charges)
			break;

		if (head->flags & FL_CLIENT || head->flags & FL_BOT || head->flags & FL_MONSTER)
		{
			// should it not fire at owner and his pals?
			if (com->Visible (self, head))
			{
				// set up tracking of all entities in the list
				self->prethink = meta_track_think;
				meta_target_add (self, head);
				
				// on next frame, firing will begin on all entities currently in the target list
//				meta_fire_laser (self, head);
			}
			else
				gstate->Con_Printf ("%s not vis\n", head->className);
		}
		head = gstate->NextEntity (head);
	}

	if (gstate->time >= hook->remove_time || !hook->charges)
		meta_remove (self);
	else
	{
		self->nextthink = gstate->time + META_SEARCH_TIME;
		self->think = meta_search_think;
	}
}

/*
===============
meta_wait

stuck to a wall and waiting for an entity to come into view
searches for new targets every 1/2 second
===============
*/

void	meta_wait_think (userEntity_t *self)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	userEntity_t	*head;

	// search entity list
	head = gstate->FirstEntity ();
	
	while (head != NULL)
	{
		if (!hook->charges)
			break;

		if (head->flags & FL_CLIENT || head->flags & FL_BOT || head->flags & FL_MONSTER)
		{
			// should it not fire at owner and his pals?
			if (com->Visible (self, head))
			{
				// charge up the laser to fire
				gstate->sound (self, CHAN_AUTO, "weapons/metamaser/metamasercharge.wav", 255.0, ATTN_NORM);

				self->nextthink = gstate->time + META_CHARGE_TIME;
				self->think = meta_search_think;

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

	gstate->Con_Printf ("no entities found\n");

	if (gstate->time >= hook->remove_time || !hook->charges)
		meta_remove (self);
	else
	{
		self->nextthink = gstate->time + META_SEARCH_TIME;
		self->think = meta_wait_think;
	}
}

/*
===============
meta_touch
===============
*/

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

	if (other->flags & FL_BOT || other->flags & FL_CLIENT || other->flags & FL_MONSTER)
		return;

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

	// move the meta maser back a few units along it's velocity vector so that it's center 
	// is not stuck in a wall

	temp = Vector (self->velocity);
	temp.Normalize ();
	
	temp = Vector (self->s.origin) + temp * -8.0;
	temp.set (self->s.origin);

	// initialize this metamaser's target list
	meta_target_init (self);

	// set up the think for firing lasers at living targets
	self->think = meta_wait_think;
	// time between each search and fire
	self->nextthink = gstate->time + META_FIRE_RATE;
}

/*
===============
meta_launch_think
===============
*/

void	meta_launch_think (userEntity_t *self)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;

	meta_water_friction (self);

	if (gstate->time >= hook->remove_time)
		meta_remove (self);
	else
	{
		self->nextthink = gstate->time + 0.1;
		self->think = meta_launch_think;
	}
}

/*
===============
meta_use
===============
*/
void	meta_use (struct userInventory_s *ptr, userEntity_t *self)
{
	userEntity_t	*metacube;
	Vector			forward, right, up, temp;
	weapHook_t		*hook;
	playerHook_t	*phook;

	phook = (playerHook_t *) self->userHook;
	phook->attack_finished = gstate->time + 0.2;


	///////////////////////////////////
	// check for ammo for this weapon
	///////////////////////////////////

	if (!weapon_check_ammo (self, ammo_name, 1))
		return;

	metacube = gstate->SpawnEntity ();

	metacube->solid = SOLID_SLIDEBOX;
	metacube->movetype = MOVETYPE_BOUNCE;
	metacube->owner = self;
	metacube->className = "metacube";

	metacube->userHook = com->malloc_hook (sizeof (weapHook_t), MEM_MALLOC);
	hook = (weapHook_t *) metacube->userHook;

	// used to scale rotational velocity
	hook->rotation_scale = 0.8;
	hook->mass_scale = 1.1;

	temp = Vector (self->client->v_angle);
	temp.AngleVectors (forward, right, up);

	temp = forward * 8.0 + Vector (self->s.origin);
	temp.z = temp.z + 16.0;
	temp.set (metacube->s.origin);

	gstate->SetModel (metacube, "models/metamaser/metacube.md2");
//	metacube->origin_z_ofs = 0;
//	gstate->SetSize (metacube, -2.0, -2.0, -2.0, 2.0, 2.0, 2.0);
	gstate->SetSize (metacube, 0, 0, 0, 0, 0, 0);
	gstate->SetOrigin (metacube, metacube->s.origin [0], metacube->s.origin [1], metacube->s.origin [2]);

	temp = forward * 750.0;
	temp.set (metacube->velocity);

	com->VecToAngles (temp, forward);
	temp.set (metacube->s.angles);

	metacube->velocity [2] = metacube->velocity [2] + 200.0;

	hook->rotation_scale = hook->rotation_scale;
	com->SetVector (metacube->avelocity, hook->rotation_scale * (rnd () - 0.5) * 1400.0, hook->rotation_scale * (rnd () - 0.5) * 1400.0, hook->rotation_scale * (rnd () - 0.5) * 1400.0);

	metacube->touch = (touch_t)meta_touch;
	metacube->nextthink = gstate->time + 0.1;
	metacube->think = meta_launch_think;

	metacube->health = 500.0;
	metacube->die = (die_t)meta_remove;
	metacube->pain = (pain_t)meta_pain;
	metacube->takedamage = DAMAGE_YES;

	hook->remove_time = gstate->time + 60.0;
	hook->last_water = CONTENTS_EMPTY;
	hook->hit_water_pos = Vector (metacube->s.origin);

	gstate->sound (self, CHAN_WEAPON, "firegrn2.wav", 255, ATTN_NORM);

	hook->charges = META_MAX_CHARGES;

	weapon_animate (self, shootFirst [0], shootLast [0], 0.05);

	phook->fired++;

   gstate->LinkEntity(metacube);
}

/*
=============
meta_select
 
set the grenade launcher as the players current weapon if it is in the 
inventory
=============
*/

void	meta_select_finish (userEntity_t *self)
{
	playerHook_t		*hook = (playerHook_t *) self->userHook;
	userInventory_t		*invItem;
	userEntity_t		*temp;

	invItem = gstate->InventoryFindItem (self->inventory, weapon_name);

	if (!invItem)
		return;

	weapon_animate (self, -1, -1, 0.05);

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

///////////////////////////////
//	meta_select
//
//	set the meta as the players current weapon if it is in the 
//	inventory
///////////////////////////////

static	void	meta_select (userEntity_t *self)
{
	playerHook_t		*hook = (playerHook_t *) self->userHook;
	weapon_t			*weapon;
	userInventory_t		*invItem;
	float				seq_time;

	invItem = gstate->InventoryFindItem (self->inventory, weapon_name);

	if (!invItem)
		return;
	//	tell the user's current weapon to go away
	weapon = (weapon_t *) self->curWeapon;
	if (weapon)
	{
		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 = meta_select_finish;

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

//////////////////////////////////////////////////////
// ammo_touch
//
// generic touch function for all standard ammo types
//////////////////////////////////////////////////////

static	void	ammo_touch (userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
	playerHook_t	*ohook = (playerHook_t *) other->userHook;
	userInventory_t	*invItem;
	int				ammo_count;
	ammo_t			*ammo;

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

	///////////////////////////////////
	// add ammo to inventory
	///////////////////////////////////

	// ammount of ammo
	ammo_count = self->health;

	// make sure that this ammo type isn't already in inventory
	ammo = (ammo_t *) gstate->InventoryFindItem (other->inventory, self->className);
	
	if (!ammo)
	{
		// ammo type is not already in inventory, so create one create ammo
		if ((ammo = ammo_create ()) == NULL)
		{
			gstate->Con_Printf ("Unable to malloc %s.\n", self->className);
			return;
		}

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

	// ammo is in inventory, pointed to by ammo so add ammo to them
	ammo->command ((userInventory_t *) ammo, "give_ammo", &ammo_count);

	com->SetRespawn (self, 30, FALSE, 0);
	
	if (other->flags & FL_CLIENT)
		gstate->sprint (other, "You picked up some %s.\n", self->netname);
}

////////////////////////////////////////
// ammo_maser
////////////////////////////////////////

void	ammo_maser (userEntity_t *self)
{
	int				i;
	userFrameInfo_t frameInfo;

	self->solid = SOLID_TRIGGER;
	self->movetype = MOVETYPE_FLY;

	gstate->Precache_Model ("models/metamaser/ammobox.md2");	
	gstate->SetModel (self, "models/metamaser/ammobox.md2");
	gstate->SetSize (self, -16, -16, -24, 16, 16, 32);
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);

	self->netname = "meta cubes";
	self->className = ammo_name;
	self->health = 10;
	self->touch = (touch_t)ammo_touch;
	com->SetVector (self->avelocity, 100, 50, 100);
}

#if 0
////////////////////
// weapon_metamaser
////////////////////

void	weapon_metamaser (userEntity_t *self)
{

	gstate->Precache_Model("models/metamaser/g_rock2.md2");
	gstate->SetModel(self, "models/metamaser/g_rock2.md2");

	self->touch   = (touch_t)weapon_touch;
	self->netname = "metamaser";
	self->className = "weapon_metamaser";
   self->client->ps.gunindex = gstate->ModelIndex("models/metamaser/g_rock2.md2");

	gstate->SetSize(self, -16.0, -16.0, 0.0, 16.0, 16.0, 56.0);

	self->flags    = FL_ITEM;
	self->solid    = SOLID_TRIGGER;
	self->movetype = MOVETYPE_TOSS;

	// set this so weapon_touch knows what select function to call
	// when the weapon is picked up
	self->use = (use_t)meta_give;
	self->die = (die_t)meta_select;
}
#endif