//---------------------------------------------------------------------------
// RIPGUN
//---------------------------------------------------------------------------
#include "weapons.h"
#include "ripgun.h"

// model defs
#define MDL_GRENADE        MDL_SPECIAL+0

// sound defs
#define SND_LASERFIRE      0
#define SND_CORDITEFIRE    1
#define SND_BOUNCE1        2
#define SND_BOUNCE2        3
#define SND_BOUNCE3        4
#define SND_BOUNCE4        5
#define SND_BOUNCE5        6
#define SND_BOUNCE6        7
#define SND_EXPLODE        8
#define SND_CHANGE1        9
#define SND_CHANGE2        10
#define SND_PICKUP         11

// frame defs
#define ANIM_READY2        ANIM_SPECIAL+0
#define ANIM_SHOOT2        ANIM_SPECIAL+1

// weapon info def
weaponInfo_t ripgunWeaponInfo = {
      // names, net names, icon
      "weapon_ripgun",
      "Ripgun",
      "ammo_cordite",
      "Cordite Grenades",
      "iconname",

      // model files
      {"models/ripgun/ripgun.md2",
       "models/grenade/grenade.md2",
       NULL},

      // sound files
      {"weapons/ripgun/ripgunfire2.wav",
       "weapons/ripgun/ripgunfire.wav",
       "weapons/ripgun/corditebounce2.wav",
       "weapons/ripgun/corditebounce3.wav",
       "weapons/ripgun/corditebounce4.wav",
       "weapons/ripgun/corditebounce5.wav",
       "weapons/ripgun/corditebounce6.wav",
       "weapons/ripgun/corditeexplode.wav",
       "weapons/ripgun/change1.wav",
       "weapons/ripgun/change2.wav",
       "weapons/ripgun/ripgunpickup.wav",
       NULL},

      // frame names
      {MDL_WEAPON_STD,"ready",0,0,
               -1,"away",0,0,
               -1,"shoota",0,0,
               -1,"amba",0,0,
               -1,"ready",0,0,
               -1,"shootb",0,0,
       0,NULL},

      // commands
      {"weapon_ripgun",(void (*)())ripgun_give,
       "select_ripgun",(void (*)())ripgun_select,
       NULL},

   ripgun_use,
   weapon_command,
   sizeof(weaponPulse_t),

   150,
};

// local weapon info access
static weaponInfo_t *winfo=&ripgunWeaponInfo;

static   float flash_distance;
static   float flash_delta_yaw;  // difference between entities view angle and angle to explosion

//---------------------------------------------------------------------------
// ripgun_give()
//---------------------------------------------------------------------------
static userInventory_t *ripgun_give (userEntity_t *self)
{
   weaponPulse_t *ripgun;

   ripgun=(weaponPulse_t *)weaponGive(self,winfo,100);

	ripgun->mode = 0;

	return((userInventory_t *)ripgun);
}

//---------------------------------------------------------------------------
// ripgun_select()
//---------------------------------------------------------------------------
static void ripgun_select(userEntity_t *self)
{
	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;

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

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

//---------------------------------------------------------------------------
// ripgun_select_finish()
//---------------------------------------------------------------------------
static	void	ripgun_select_finish (userEntity_t *self)
{
	playerHook_t		*hook = (playerHook_t *) self->userHook;
	userInventory_t		*invItem;
	weaponPulse_t			*ripgun;
	ammo_t				*ammo;
	userEntity_t		*temp;
	int					already_selected = FALSE;

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

	if (self->curWeapon == invItem)
		already_selected = TRUE;

   if (!already_selected)
   {
      if (!selectWeapon(self,winfo->weaponName,winfoModel(MDL_WEAPON_STD)))
         return;
      winfoAnimDelay(self,winfo,ANIM_READY_STD,.05);
   }

	self->curWeapon = invItem;
	ripgun = (weaponPulse_t *) self->curWeapon;

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

	//Check to see which mode we are in:
	// 1 = ripgun mode
	// 0 = cordite cluster mode
	// when we switch modes sent the weapon's ammo pointer to the correct ammo type!
	if (ripgun->mode > 0 && already_selected)
	{
		// find the ammo_cordite inventory item I'm not checking for failure because it should ALWAYS be there if
		// the player has the ripgun
		ripgun->ammo = (ammo_t *)gstate->InventoryFindItem (self->inventory, winfo->ammoName);
		if (!ripgun->ammo)
			gstate->Con_Printf ("no %s in inventory!\n", winfo->ammoName);
		
		ripgun->mode = 0;

      if (!already_selected)
         winfoAnimDelay(self,winfo,ANIM_AWAY_STD,.05);

      winfoPlay(SND_CHANGE1,255);
	}
	else if (already_selected)
	{
		// find the ammo_pulse inventory item I'm not checking for failure because it should ALWAYS be there if
		// the player has the ripgun
		ripgun->ammo  = (ammo_t *)gstate->InventoryFindItem (self->inventory,winfo->ammoName);
		if (!ripgun->ammo)
			gstate->Con_Printf ("no %s in inventory!\n", winfo->ammoName);
		
		ripgun->mode = 1;

      winfoPlay(SND_CHANGE2,255);

      if (!already_selected)
         winfoAnimDelay(self,winfo,ANIM_AWAY_STD,.05);
	}

	if (!already_selected)
      winfoPlay(SND_PICKUP,255);
}

//---------------------------------------------------------------------------
// weapon_command()
//---------------------------------------------------------------------------
static void *weapon_command(struct userInventory_s *ptr, char *commandStr, void *data)
{
	weaponPulse_t	*weapon = (weaponPulse_t *) ptr;
   userEntity_t *self=(userEntity_t *)data;
   playerHook_t *phook=(playerHook_t *)self->userHook;
	
	if (!stricmp (commandStr, "death_msg"))
	{
		killerInfo_t	*ki = (killerInfo_t *) data;
		char			msg [80];

		sprintf (msg, "%s sucks down some of %s's lead!\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];

		sprintf (msg, "%s's own cordite smacks him down!\n", ki->attacker->netname);
	
		gstate->bprint (msg);
	}
	else if (!stricmp (commandStr, "force_mode"))
	{
		//	force the mode of the ripgun
		weapon->mode = *(int *) data;
	}
	else if (!stricmp (commandStr, "set_ammo"))
	{
		weapon->ammo = (ammo_t *) data;
	}
   else
      winfoGenericCommand(winfo,ptr,commandStr,data);

	return(NULL);
}

//---------------------------------------------------------------------------
// entity_isin_fov()
//---------------------------------------------------------------------------
static float entity_isin_fov(userEntity_t *self, userEntity_t *target)
{
	Vector		dir, temp;
	float		target_yaw, dir_yaw, result;
	corditeHook_t	*hook = (corditeHook_t *) self->userHook;

	// can the entity see the grenade at all?
	gstate->TraceLine(self->s.origin, target->s.origin, TRUE, self, &trace);
	
	// can't see it, so leave
	if (trace.fraction != 1.0)
		return FALSE;

	dir = Vector (self->s.origin) - Vector (target->s.origin);
	flash_distance = dir.vlen ();
	dir.Normalize ();

	// if 250 units or more away, no effect
	dir_yaw = com->VecToYaw (dir);
	target_yaw = com->AngleMod (target->s.angles [1]);

	flash_delta_yaw = target_yaw - dir_yaw;

	// keep angles relative and within 180 degrees
	while (flash_delta_yaw >= 180.0)
		flash_delta_yaw -= 360.0;
	
	flash_delta_yaw = fabs (flash_delta_yaw);

	return TRUE;
}

//---------------------------------------------------------------------------
// cordite_explode()
//---------------------------------------------------------------------------
static void cordite_explode(userEntity_t *self)
{
	float			cansee, temp;
	userEntity_t	*ent;
	int				percent, wait, decay;
	playerHook_t	*phook = (playerHook_t *) self->owner->userHook;
	corditeHook_t	*hook = (corditeHook_t *) self->userHook;

	// traverse entity list
   winfoPlay(SND_EXPLODE,255);

	//The cordite does hurt but not that much.
	com->RadiusDamage (self, self->owner, self, 80, DAMAGE_EXPLOSION);
   spawn_sprite_explosion(self,self->s.origin,false);

#if 0
   gstate->WriteByte(SVC_TEMP_ENTITY);
   gstate->WriteByte(TE_BLOOD);
   gstate->WritePosition(self->s.origin);
   gstate->WriteDir(self->s.angles);
   gstate->UniCast(self->owner,false);
#endif

	for (ent = gstate->FirstEntity (); ent != NULL ;ent = gstate->NextEntity (ent))
	{
		if (ent->flags & FL_CLIENT)
		{
			//check to see if the client can see the flash
			if (!entity_isin_fov (self, ent))
				continue;

			if (flash_distance > 600.0)
				flash_distance = 600;
			
			temp = (600.0 - flash_distance) / 2.36 + 1.0; // max percent base of 255
			percent = 255 - (int) (temp * (flash_delta_yaw / 180.0));
			
			wait = (int) ((600.0 - flash_distance) / 7.5 + 1.0); // max wait of 80
			wait = wait - (80.0 * (flash_delta_yaw / 180.0));
			if (wait < 0) wait = 1
				;
			decay = 2 + (int) (flash_distance * (flash_delta_yaw / 180.0));

			if (!gstate->deathmatch && self->owner == ent)
			{
				decay >>= 1;
				wait >>= 1;
				percent >>= 1;
			}

#if 0
         gstate->WriteByte(SVC_TEMPENTITY);
			gstate->WriteByte(TE_COLORFLASH);
			gstate->WriteByte(255);		         //red componant
			gstate->WriteByte(255);		         //green componant
			gstate->WriteByte(255);		         //blue componant
			gstate->WriteByte(percent);	      //percent
			gstate->WriteByte(wait);		      //wait
			gstate->WriteLong(decay);		      //decay
         gstate->UniCast(ent,false);
#endif
      }

	}
   
	// free the friction hook
	com->friction_remove (hook->fhook);

	com->free (self->userHook);
	gstate->RemoveEntity (self);
}

//---------------------------------------------------------------------------
// cordite_touch()
//---------------------------------------------------------------------------
static void cordite_touch(userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
	corditeHook_t	*hook = (corditeHook_t *) self->userHook;
	frictionHook_t	*fhook = (frictionHook_t *) hook->fhook;
	Vector		temp, forward, right, up;
	float		velocity;

	if (other->flags & FL_CLIENT || other->flags & FL_BOT || other->flags & FL_MONSTER)
	{
		cordite_explode (self);
		return;
	}

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

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

	// grenade is coming to a rest
	if (temp.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;

		if (other == self->owner)
		{
			temp = Vector (other->s.angles);
			temp.AngleVectors (forward, right, up);

			temp = forward * 32.0 + Vector (other->s.origin);
	
			gstate->SetOrigin (self, temp.x, temp.y, temp.z);

			temp = forward * qv_len (other->velocity) * 2.2;
			temp.set (self->velocity);
			self->velocity [2] = self->velocity [2] + qv_len (other->velocity) / 2 + 100.0;
		
			return;
		}
	}

	// we just touched less than 1/10 of a second ago, so don't play bounce noise again
	if (gstate->time < hook->last_touch_time + 0.1)
		return;

	if (qv_len (self->velocity) != 0.0)
	{
		// change angular velocity because grenade just bounced
		com->SetVector (self->avelocity, fhook->size_scale * (rnd () - 0.5) * 1400.0, fhook->size_scale * (rnd () - 0.5) * 1400.0, fhook->size_scale * (rnd () - 0.5) * 1400.0);

		temp = Vector (self->avelocity);
		
		temp = Vector (self->size);
		
		velocity = qv_len (self->velocity);
		
		if(velocity>850)
         winfoPlay(SND_BOUNCE4,125);
		if((velocity > 750)&&(velocity <= 850))
         winfoPlay(SND_BOUNCE2,125);
		if((velocity > 600)&&(velocity <= 800))
         winfoPlay(SND_BOUNCE5,100);
		if((velocity > 400)&&(velocity <= 600))
         winfoPlay(SND_BOUNCE6,100);
		if(velocity <= 400)
         winfoPlay(SND_BOUNCE3,75);
	}

	hook->last_touch_time = gstate->time;
}

//---------------------------------------------------------------------------
// cordite_think()
//---------------------------------------------------------------------------
static void cordite_think(userEntity_t *self)
{
	corditeHook_t		*hook = (corditeHook_t *) self->userHook;
	
	com->friction_apply (self, hook->fhook);

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

//---------------------------------------------------------------------------
// ripgun_use_think()
//---------------------------------------------------------------------------
static	void	ripgun_use_think (userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->owner->userHook;	
	weaponPulse_t	*ripgunp;
	ammo_t			*ammo;
	Vector			org, end, dir;
	trace_t			tr;

	ripgunp = (weaponPulse_t *) self->owner->curWeapon;
	ammo = (ammo_t *)ripgunp->ammo;

	//Check to see if we have any cordite bombs to throw
	//if not deny them the right! Those freaky Bastards!
	if(ammo->count == 0)
		return;
		
	ammo->command ((userInventory_t *) ammo, "use_ammo", NULL);

	// for stat keeping, all five shots logged
	phook->fired++;

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

	org = Vector (self->owner->s.origin) + forward * 8.0 + up * 16.0;	
	end = org + forward * 2000.0;

	tr = gstate->trace_q2 (org.vec (), NULL, NULL, end.vec (), self->owner, 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)
		{
			//If we hit them then hurt them!
			com->Damage (tr.ent, self->owner, self->owner, tr.endpos, forward.vec (), 1, DAMAGE_INERTIAL);
				
			phook->hit++;
		}
		if (tr.ent->solid == SOLID_BSP || trace.ent->takedamage == DAMAGE_NO)
		{
#if 0    // mdm q2fix
			//Place the particles and create the ricochet noise
			gstate->WriteByte (self->owner, MSG_BROADCAST, SVC_TEMPENTITY);
			gstate->WriteByte (self->owner, MSG_BROADCAST, TE_RICOCHET);
			gstate->WriteCoord (self->owner, MSG_BROADCAST, trace.endpos [0]);
			gstate->WriteCoord (self->owner, MSG_BROADCAST, trace.endpos [1]);
			gstate->WriteCoord (self->owner, MSG_BROADCAST, trace.endpos [2]);
#endif
		}
//      self->s.effects |= RF_MUZZLEFLASH;
//		self->effects = (int)self->effects | EF_MUZZLEFLASH;
	}

	self->team--;

	if (self->team)
	{
		self->nextthink = gstate->time + 0.06;
		self->think = ripgun_use_think;
	}
	else
		gstate->RemoveEntity (self);
}

//---------------------------------------------------------------------------
// cordite_fire_grenade()
//---------------------------------------------------------------------------
static void cordite_fire_grenade(userEntity_t *self)
{
	userEntity_t	*grenade;
	Vector			forward, right, up, temp, end;
	corditeHook_t      *hook;
	playerHook_t	*phook=(playerHook_t *)self->userHook;
	weaponPulse_t		*grenadep;
	ammo_t			*ammo;
	vec3_t          hit_spot;
	float			r_scale;

	int				pc;
	userEntity_t	*rifle;

	grenadep = (weaponPulse_t *) self->curWeapon;
	ammo = (ammo_t *)grenadep->ammo;

	//Check to see if we have any cordite bombs to throw
	//if not deny them the right! Those freaky Bastards!
	if(ammo->count == 0)
		return;
		
	ammo->command ((userInventory_t *) ammo, "use_ammo", NULL);

	temp = Vector (14, 12, 13);
	grenade = weapon_spawn_projectile (self, temp, 1000.0, 200.0, "projectile_cordite");
	grenade->movetype = MOVETYPE_BOUNCE;
	
	// set up userHook
	grenade->userHook = com->malloc_hook (sizeof (corditeHook_t), MEM_MALLOC);
	hook = (corditeHook_t *) grenade->userHook;

	// set up frictionHook
	hook->fhook = com->friction_init (self, hook->fhook);
	com->friction_set_physics (self, hook->fhook, 1.0, 1.0, 500.0, 0);

	grenade->s.angles[0] = self->s.angles[0];
	
   winfoSetModel(grenade,MDL_GRENADE);
//	grenade->origin_z_ofs = 0;
	gstate->SetSize (grenade,0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
	gstate->SetOrigin (grenade, grenade->s.origin [0], grenade->s.origin [1], grenade->s.origin [2]);

	grenade->touch = (touch_t)cordite_touch;
	grenade->nextthink = gstate->time + 0.08;
	grenade->think = cordite_think;

	hook->remove_time = gstate->time + 1.25;     //2.5;
	//Need to change model
		
   winfoPlay(SND_CORDITEFIRE,255);

   phook->fxFrameFunc=NULL;
}

//---------------------------------------------------------------------------
// ripgun_use()
//---------------------------------------------------------------------------
static	void	ripgun_use (struct userInventory_s *ptr, userEntity_t *self)
{
	userEntity_t	*ripgun;
	Vector			forward, right, up, temp, end;
	corditeHook_t      *hook;
	playerHook_t	*phook;
	weaponPulse_t		*ripgunp;
	ammo_t			*ammo;
	vec3_t          hit_spot;
	float			r_scale;

	int				pc;
	userEntity_t	*rifle;

	phook = (playerHook_t *) self->userHook;

	ripgunp = (weaponPulse_t *) self->curWeapon;
	ammo = (ammo_t *)ripgunp->ammo;

	if (phook->attack_finished > gstate->time)
		return;

	if (ripgunp->mode == 1)
	{
      winfoAnimDelay(self,winfo,ANIM_SHOOT_STD,.05);
      phook->fxFrameNum=winfoFirstFrame(ANIM_SHOOT_STD)+2;
      phook->fxFrameFunc=shootLaser;
	}
	else
	{
		winfoAnimDelay(self,winfo,ANIM_SHOOT2,.05);
      phook->attack_finished=gstate->time + (winfoNumFrames(ANIM_SHOOT2)-5) * .05;
      phook->fxFrameNum=winfoFirstFrame(ANIM_SHOOT2)+2;
      phook->fxFrameFunc=cordite_fire_grenade;
	}

//	self->s.renderfx = RF_MUZZLEFLASH;
}	
	
//---------------------------------------------------------------------------
// shootLaser()
//---------------------------------------------------------------------------
static void shootLaser(userEntity_t *self)
{
   playerHook_t *phook=(playerHook_t *)self->userHook;
   userEntity_t *laser;
   trackInfo_t tinfo;
   Vector vel,pos;

   winfoPlay(SND_LASERFIRE,255);

   // spawn laser
   laser=gstate->SpawnEntity();
   laser->className="laser_shot";
   laser->think=laser_think;
   laser->nextthink=gstate->time+.1;
   laser->touch=laser_touch;
   laser->owner=self;
	laser->s.frame=2;                // beam diameter
	laser->s.skinnum=0xa1a2a3a4;     // beam color
	laser->solid=SOLID_SLIDEBOX;
   laser->clipmask=MASK_SHOT;
   laser->movetype=MOVETYPE_FLY;
   laser->s.renderfx=RF_BEAM|RF_TRANSLUCENT;
   laser->s.modelindex=1;
   laser->s.alpha=.8;
   gstate->LinkEntity(laser);

   vel=Vector(self->client->v_angle);
   vel.AngleVectors(forward,right,up);
   vel=forward*2000;
   vel.set(laser->velocity);

   pos=Vector(self->s.origin)+Vector(0,0,20.6)+weaponHandVector(self,forward*12,right*6);
   pos.set(laser->s.origin);

   laser_think(laser);

   phook->fxFrameFunc=NULL;
}

//---------------------------------------------------------------------------
// laser_think()
//---------------------------------------------------------------------------
static void laser_think(userEntity_t *self)
{
   Vector vel=Vector(self->velocity),angles,endpos,pos;

   com->VecToAngles(angles,vel);
   angles.AngleVectors(forward,right,up);

//   pos=Vector(self->s.origin);
//   pos=pos+forward*10;
//   pos.set(self->s.origin);

   endpos=Vector(self->s.origin)+forward;
   endpos.set(self->s.old_origin);

   self->nextthink=gstate->time+.1;
}

//---------------------------------------------------------------------------
// laser_touch()
//---------------------------------------------------------------------------
static void laser_touch(userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
   if (!surf || !(surf->flags & SURF_SKY))
   {
      gstate->WriteByte(SVC_TEMP_ENTITY);
      gstate->WriteByte(TE_LASER_SPARKS);
      gstate->WriteByte(25);
      gstate->WritePosition(self->s.origin);
      gstate->WriteDir(zero_vector);
      gstate->WriteByte(rnd() > .5 ? 56 : 72);
      gstate->MultiCast(self->s.origin,MULTICAST_ALL);

      if (other->takedamage != DAMAGE_NO)
         com->Damage(other,self,self,other->s.origin,zero_vector,20,DAMAGE_INERTIAL);
   }

   gstate->RemoveEntity(self);
}

