#include "weapons.h"
#include "shotcycler6.h"

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

static	long			shell_count;
static	userEntity_t	*collect_targ;
static	float			damage_total;

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

DllExport	void	weapon_shotcycler6 (userEntity_t *self);

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

void			shotcycler6_select (userEntity_t *self);
userInventory_t	*shotcycler6_give(userEntity_t *self);
void			shotcycler6_use (userInventory_s *ptr, userEntity_t *self);

///////////////////////////////////////////////////////////////////
// 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
///////////////////////////////////////////////////////////////////

typedef struct
{
	// first seven fields must be the same
	char    *name;
	void	(*use)(struct userInventory_s *ptr, userEntity_t *user);
	void	*(*command)(struct userInventory_s *ptr, char *commandStr, void *data);
	void	*precache;
	void	*dummy3;
	void	*dummy4;
	
	ammo_t	*ammo;

	// can add anything after this point
	float	wait_time;
	int		rotations;
} shotcycler6_t;

typedef struct
{
	float	remove_time;
	float	last_touch_time;
	float	last_water;
	float	change_time;
	float   rotation_scale;
		
	Vector	last_pos;
	Vector	hit_water_pos;
	Vector  forward;
	Vector  right;
	Vector  up;

	int		my_count;
	int     shell_stop;
	float   rotate_x;
} weapHook_t;

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

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];

/////////////////////////////////
// shotcycler6_load_init
//
// should be called from dll_Load
/////////////////////////////////

int shotcycler6_load_init (void)
{
	weapon_name = "weapon_shotcycler6";
	weapon_netname = "shotcycler6";
	weapon_model = "models/shotcycler/shotcycler.md2";
	ammo_name = "ammo_shells";
	ammo_netname = "shells";
	ammo_model = "models/shotcycler/ammobox.md2";
	
	// add spawn command
	gstate->AddCommand ("select_shotcycler6", shotcycler6_select);
	gstate->AddCommand (weapon_name, shotcycler6_give);

	// we need to precache an item's resources here
	gstate->InventoryPrecache(weapon_name, "needicon",weapon_model);
	gstate->InventoryPrecache(ammo_name, "needicon",ammo_model);
	
	return (sizeof(userEntity_t) + sizeof(serverState_t));
}

/////////////////////////////////
// shotcycler6_level_init
//
// should be called from dll_LoadLevel
/////////////////////////////////

void shotcycler6_level_init (void)
{
	userEntity_t	*temp;
	
	gstate->Precache_Sound("weapons/shotcycler6/shotcyclerfire.wav");
	gstate->Precache_Sound("weapons/shotcycler6/barrelspin.wav");
	gstate->Precache_Model(weapon_model);

	shell_count = 0;

	////////////////////////////////////////////////////////////////////
	// spawn a temp entity to pull out the frame sequence data
	////////////////////////////////////////////////////////////////////

	temp = gstate->SpawnEntity ();
	gstate->SetModel (temp, weapon_model);
	
	com->GetFrames (temp, "goaway", &changeFirst [0], &changeLast [0]);
	com->GetFrames (temp, "reload", &reloadFirst [0], &reloadLast [0]);
	com->GetFrames (temp, "shoot", &shootFirst [0], &shootLast [0]);
	com->GetFrames (temp, "ready", &readyFirst [0], &readyLast [0]);
	
	gstate->RemoveEntity (temp);
}

///////////////////////////////////////////
//
// Every weapon should have these commands:
// death_msg
// suicide_msg - if you can kill yourself with it
// rating
//
///////////////////////////////////////////

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

		if (ki->victim->netname && ki->attacker->netname)
		{
			sprintf (msg, "%s takes %s's spray in the head.\n", ki->victim->netname, ki->attacker->netname);

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

		if (ki->victim->netname && ki->attacker->netname)
		{
			sprintf (msg, "%s puts a shell in his head.\n", ki->attacker->netname, ki->victim->netname);

			gstate->bprint (msg);
		}
	}
	else if (!stricmp (commandStr, "rating"))
	{
		// return a value between 0 and 1000
		*(int *) data = 395;
	}
	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
//  or adding weapons:
//		give_ammo
//		take_ammo
//		count
//////////////////////////////////

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;
	else if (!stricmp (commandStr, "take_ammo"))
	{
		if (ammo->count > 0)
		{
			ammo->count = ammo->count - *(int *) data;
			return (void *) 1;
		}
		else 
			return NULL;
	}
	else if (!stricmp (commandStr, "use_ammo"))
	{
		if (ammo->count > 0)
		{
			ammo->count--;
			return (void *) 1;
		}
		else 
			return NULL;
	}
	else if (!stricmp (commandStr, "count"))
	{
		// returns ammount of ammo in data
		*(int *) data = ammo->count;
		return (void *) 1;
	}
	
	return NULL;
}


//////////////////////////////////
//ammo_create
//
//creates an inventory item of ammo type
//////////////////////////////////

static ammo_t *ammo_create (void)
{
	ammo_t	*ammo;
	ammo = (ammo_t *) com->malloc (sizeof (ammo_t), MEM_MALLOC);
	memset (ammo, 0x00, sizeof (ammo_t));
	ammo->name = strdup (ammo_name);
	ammo->use  = NULL;
	ammo->command = ammo_command;

	ammo->count = 0;

	return ammo;
}

//////////////////////////////////
//	shotcycler6_create	
//////////////////////////////////

static shotcycler6_t *shotcycler6_create (void)
{
	shotcycler6_t	*weapon;

	weapon = (shotcycler6_t *) com->malloc (sizeof (shotcycler6_t), MEM_MALLOC);
	memset (weapon, 0x00, sizeof (shotcycler6_t));
	weapon->name = strdup (weapon_name);
	weapon->use  = shotcycler6_use;
	weapon->command = shotcycler6_command;

	weapon->rotations = 0;

	return weapon;		
}
 
 /*
=============
shotcycler6_give

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

static	userInventory_t *shotcycler6_give (userEntity_t *self)
{
	shotcycler6_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->Con_Printf ("You already have a %s.\n", weapon_netname);
		return	invItem;
	}

	// create new inventory item
	if ((weapon = shotcycler6_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 weapon starts with
//	ammo_count = 18;
	ammo_count = 32767;

	// make sure that this ammo type isn't already in inventory
	ammo = (ammo_t *) gstate->InventoryFindItem (self->inventory, ammo_name);
	
	if (!ammo)
	{
		// ammo type has not been created so go create an ammo type in the inventory
		if ((ammo = ammo_create ()) == NULL)
		{
			gstate->Con_Printf ("Unable to malloc %s.\n", ammo_netname);
			return	NULL;
		}

		// ammo type already exists, 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;
	weapon->rotations = 6;

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

	return	(userInventory_t *) weapon;
}

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

/*
===============
shell_water_friction

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

static	void	shell_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);

	dir = Vector (self->velocity);		

	if (pc != CONTENTS_EMPTY)
	{
		if (dir.vlen () > 50)
		{
			dir = dir * 0.8;
			if (dir.vlen () < 50)
			{
				dir.Normalize ();
				dir = dir * 50;
			}
		}
	}

	dir.set (self->velocity);

	if (hook->last_water != pc && (pc == CONTENTS_WATER ||
		pc == CONTENTS_SLIME || pc == CONTENTS_LAVA))
	{
		//hook->change_time = gstate->time;
		//Insert water sound here
		hook->last_water = pc;
	}
}

/*
===============
shell_touch
===============
*/

static	void	shell_touch (userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
	int			pc;
	weapHook_t	*hook = (weapHook_t *) self->userHook;
	Vector		last_pos,start, forward, right, up;
	float		velocity;
	
	if (self->owner == other)
		return;
	start = Vector (last_pos) - Vector (self->s.origin);

	last_pos = Vector (self->s.origin);	

	if (start.vlen () < 1.0)
	{
		com->SetVector (self->velocity, 0.0, 0.0, 0.0);
		com->SetVector (self->avelocity, 0.0, 0.0, 0.0);
		self->s.angles [0] = 0.0;
		hook->shell_stop = 1;
	}
	velocity = qv_len (self->velocity);
	pc = gstate->PointContents (self->s.origin);
	if ((velocity != 0.0)&&(hook->shell_stop==0))
	{
		if (pc != CONTENTS_LAVA && pc != CONTENTS_SLIME && pc != CONTENTS_WATER)
		{
			com->SetVector (self->avelocity, hook->rotation_scale * (rnd () - 0.35) * 50.0, hook->rotation_scale * (rnd () - 0.35) * 50.0, hook->rotation_scale * (rnd () - 0.35) * 50.0);
			start = Vector (self->avelocity);
		
			start = Vector (self->size);		
		
		/*	if(velocity >290)
				gstate->sound (self, CHAN_AUTO, "weapons/", 120, ATTN_NORM);
			if((velocity>220)&&(velocity<=290))
				gstate->sound (self, CHAN_AUTO, "weapons/", 120, ATTN_NORM);
			if((velocity>160)&&(velocity<=220))
				gstate->sound (self, CHAN_AUTO, "weapons/", 120, ATTN_NORM);
			if((velocity>100)&&(velocity<=160))
				gstate->sound (self, CHAN_AUTO, "weapons/", 120, ATTN_NORM);
			if(velocity<=100)
				gstate->sound (self, CHAN_AUTO, "weapons/", 120, ATTN_NORM);
		*/
		}
	}

	hook->last_touch_time = gstate->time;
	
	//Reduce velocity like a shell bouncing
	if(self->velocity[2] > 50)
		self->velocity [2] = self->velocity [2] * 0.85;
	else if(self->velocity[2] > 25)
		self->velocity [2] = self->velocity [2] * 0.95;

	if(self->velocity[1] > 200)
		self->velocity [1] = self->velocity [1] * 0.85;
	else if(self->velocity[1] > 75)
		self->velocity [1] = self->velocity [1] * 0.95;

	if(self->velocity[0] > 40)
		self->velocity [0] = self->velocity [0] * 0.85;
	else if(self->velocity[0] > 20)
		self->velocity [0] = self->velocity [0] * 0.95;
}

//////////////////////////////////
// shell_think
//////////////////////////////////

void	shell_think (userEntity_t *self)
{
	weapHook_t		*hook = (weapHook_t *) self->userHook;
	
	shell_water_friction (self);
	if(hook->my_count < (shell_count-10))
	{
		com->free (self->userHook);
		gstate->RemoveEntity (self);
	}

	self->nextthink = gstate->time + 0.20;
	self->think = shell_think;
}

////////////////////////////
// shotcycler6_reload
////////////////////////////

int	shotcycler6_reload (userEntity_t *self)		  
{
	playerHook_t	*phook;
	shotcycler6_t	*invWeapon = (shotcycler6_t *) self->curWeapon;
	ammo_t			*ammo = invWeapon->ammo;	
	int				ammo_count;

	// get the number of total rounds that we have
	ammo->command ((userInventory_t *) ammo, "count", &ammo_count);
	if (ammo_count <= 0)
		return	FALSE;

	// cap ammo_count at clip size
	if (ammo_count > 6)
		ammo_count = 6;
	
	// subtract the ammo
	if (!weapon_check_ammo (self, ammo_name, ammo_count))
		return FALSE;

	// put the bullets in the clip
	invWeapon->rotations = ammo_count;
	
	phook = (playerHook_t *) self->userHook;	
	phook->attack_finished = gstate->time + (reloadLast [0] - reloadFirst [0] + 1) * 0.05;

	weapon_animate (self, reloadFirst [0], reloadLast [0], 0.05);

	return	TRUE;
}

/*
================
shotcycler6_use
================
*/

void	shotcycler6_use (struct userInventory_s *ptr, userEntity_t *self)
{
	userEntity_t	*shell;
	Vector			forward, right, up, start, end, dir, spread;
	weapHook_t      *hook;
	playerHook_t	*phook;
	userTrace_t     trace;
	shotcycler6_t	*curWeap;
	vec3_t          hit_spot;
	int				pc;
	
	
	phook = (playerHook_t *) self->userHook;
	phook->attack_finished = gstate->time + .35;

	curWeap = (shotcycler6_t *) self->curWeapon;

	if (curWeap->wait_time > gstate->time && self->button0 == 1)
		return;

	// are we still holding the mouse button down?
	if (self->button0)
	{
		if(curWeap->rotations == 0)
		{
			curWeap->wait_time = gstate->time + .50;

			shotcycler6_reload (self);
		
			return;
		}

		curWeap->rotations--;
		curWeap->wait_time = gstate->time + .20;

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

		if (pc == CONTENTS_WATER || pc == CONTENTS_LAVA || pc == CONTENTS_SLIME)
			gstate->sound (self, CHAN_AUTO, "weapons/shotcycler6/shotcyclerfire.wav", 190, ATTN_NORM);
		else
			gstate->sound (self, CHAN_AUTO, "weapons/shotcycler6/shotcyclerfire.wav", 190, ATTN_NORM);
			
		//Trace test
		
		weapon_trace_attack ( self, 20, 5, 2000);

		shell = gstate->SpawnEntity ();
		shell->owner = self;
		shell->movetype = MOVETYPE_BOUNCE;
		shell->solid = SOLID_BBOX;
		shell->className = "shell_shell";
      shell->svflags = SVF_NOTHUD | SVF_NOSPLASH;
//		shell->s.effects= EF_NOTHUD + EF_NOSPLASH;

		shell->userHook = com->malloc (sizeof (weapHook_t), MEM_MALLOC);
		hook = (weapHook_t *) shell->userHook;
	
		start = Vector (self->client->v_angle);
		start.AngleVectors (forward, right, up);
	
		hook->shell_stop = 0;	
	
//      self->s.renderfx |= RF_MUZZLEFLASH;
//		self->effects = (int)self->effects | EF_MUZZLEFLASH;	
		
		start = forward * 15.0 + up * 20.0 + right * 12 + Vector(self->s.origin);
		start.set (shell->s.origin);
	
		pc = gstate->PointContents (shell->s.origin);
		if (pc == CONTENTS_WATER || pc == CONTENTS_LAVA ||
			pc == CONTENTS_SLIME)
			start = forward * 50.0 + right * -40 + (up * (lim_rand(70)+50));
		else
			start = forward * (50 + lim_rand(25)) + right *(-50 - lim_rand(50)) + up * (lim_rand(50)+100);

		start.set (shell->velocity);

		com->SetVector (shell->avelocity, rnd () * 1000 - 500, rnd () * 1000 - 500, rnd () * 1000 - 500);

		com->VecToAngles (start, forward);
		start.set (shell->s.angles);

		shell->touch = (touch_t)shell_touch;
		shell->nextthink = gstate->time + 0.20;
		shell->think = shell_think;
		
		
		hook->last_water = CONTENTS_EMPTY;
		hook->hit_water_pos = Vector (shell->s.origin);

//		gstate->SetModel (shell, ammo_model);
//		gstate->SetSize (shell, -.25, -.25, -.25, .25, .25, .25);
//		gstate->SetOrigin2 (shell, shell->s.origin);
	
		shell_count++;
		hook->my_count = shell_count;

		// get rid of this damn thing for now
		com->free (shell->userHook);
		gstate->RemoveEntity (shell);
	}	

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

/*
=============
shotcycler6_select_finish

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

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

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

	if (!invItem)
		return;
	
	self->curWeapon = invItem;
	// this must be here to stop the sling (mousedown weapons) from screwing up the firing of other weapons
	// This weapon, however, is a ATTACKHOLD type weapon.
	self->flags |= FL_ATTACKHOLD;
	
	//place a delay in the next time to attack so the weapon
	//can cycle through its "select" sequence
	hook->attack_finished = gstate->time + 1.30;

	weapon_animate (self, readyFirst [0], readyLast [0], 0.05);
}

///////////////////////////////
//	shotcycler6_select
//
//	set the shotcycler6 as the players current weapon if it is in the 
//	inventory
///////////////////////////////

static	void	shotcycler6_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 = shotcycler6_select_finish;

		//	make sure we can't shoot!
		hook->attack_finished = gstate->time + seq_time;
	}
	else
		shotcycler6_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->UpdateClientAmmo (other);

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

////////////////////////////////////////
// ammo_shells
////////////////////////////////////////

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

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

	gstate->Precache_Model (ammo_model);	
	gstate->SetModel (self, ammo_model);
	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 = "shells";
	self->className = "ammo_shells";
	self->health = 24;
	self->touch = (touch_t)ammo_touch;
	com->SetVector (self->avelocity, 100, 50, 100);
}


#if 0
/*
=================
weapon_shotcycler6
=================
*/

void	weapon_shotcycler6 (userEntity_t *self)
{
	gstate->Precache_Model("models/shotcycler/g_rock2.md2");
	gstate->SetModel(self, "models/shotcycler/g_rock2.md2");

	self->touch   = (touch_t)weapon_touch;
	self->netname = "Shotcycler 6";
	self->className = weapon_name;
   self->client->ps.gunindex = gstate->ModelIndex(weapon_model);

	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)shotcycler6_give;
	self->die = (die_t)shotcycler6_select;
}
#endif