//---------------------------------------------------------------------------
// BOLTER
//---------------------------------------------------------------------------
#include "weapons.h"
#include "bolter.h"

// model defs
#define MDL_BOLT           MDL_SPECIAL+0

// sound defs
#define SND_FIRE           0
#define SND_IMPACT1        1
#define SND_IMPACT2        2
#define SND_IMPACT3        3
#define SND_IMPACT4        4
#define SND_IMPACT5        5
#define SND_LOAD           6
#define SND_FIREWATER      7
#define SND_WETIMPACT1     8
#define SND_WETIMPACT2     9
#define SND_WETIMPACT3     10
#define SND_WETIMPACT4     11
#define SND_ARROWDROP1     12
#define SND_ARROWDROP2     13
#define SND_ARROWDROP3     14
#define SND_RELOAD         15

// frame defs
#define ANIM_RELOAD        ANIM_SPECIAL+0

// weapon info def
weaponInfo_t bolterWeaponInfo = {
      // names, net names, icon
      "weapon_bolter",
      "Bolter",
      "ammo_bolt",
      "Bolts",
      "iconname",

      // model files
      {"models/bolter/bolter.md2",
       "models/bolt/bolt.md2",
       NULL},

      // sound files
      {"weapons/bolter/bolterfire2.wav",
       "weapons/bolter/arrowimpact1.wav",
       "weapons/bolter/arrowimpact2.wav",
       "weapons/bolter/arrowimpact3.wav",
       "weapons/bolter/arrowimpact4.wav",
       "weapons/bolter/arrowimpact5.wav",
       "weapons/bolter/bowload.wav",
       "weapons/bolter/bolterwaterfire.wav",
       "weapons/bolter/wetimpact1.wav",
       "weapons/bolter/wetimpact2.wav",
       "weapons/bolter/wetimpact3.wav",
       "weapons/bolter/wetimpact4.wav",
       "weapons/bolter/arrowdrop1.wav",
       "weapons/bolter/arrowdrop2.wav",
       "weapons/bolter/arrowdrop3.wav",
       "weapons/bolter/bolterreload.wav",
       NULL},

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

      // commands
      {"weapon_bolter",(void (*)())bolter_give,
       "select_bolter",(void (*)())bolter_select,
       NULL},

      bolter_use,
      bolter_command,
      sizeof(bolterWeapon_t),

      150,
};

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

listHeader_t	*bolt_list;

//---------------------------------------------------------------------------
// bolter_give()
//---------------------------------------------------------------------------
userInventory_t *bolter_give (userEntity_t *self)
{
   bolterWeapon_t *bolter=(bolterWeapon_t *)self->curWeapon;

   if (!bolt_list)
	   bolt_list=com->list_init(bolt_list);

   bolter=(bolterWeapon_t *)weaponGive(self,winfo,30);
	bolter->reload_count = 2;

   return((userInventory_t *)bolter);
}

//---------------------------------------------------------------------------
// bolter_select()
//---------------------------------------------------------------------------
static	void	bolter_select (userEntity_t *self)
{
   weaponSelect(self,winfo);
}

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

		if (ki->victim->netname && ki->attacker->netname)
		{
			if (ki->attacker == ki->victim)
				sprintf (msg, "%s blows himself apart!\n", ki->attacker->netname, ki->victim->netname);
			else
				sprintf (msg, "%s is shredded to pieces by %s's bolter explosion!\n", ki->victim->netname, ki->attacker->netname);

			gstate->bprint (msg);
		}
	}
	else if (!stricmp (commandStr, "bolter"))
	{
		//Return the value of bolter so we know we have it readied
		buf = (char*) data;
		sprintf(buf,"bolter");
	}
   else
      winfoGenericCommand(winfo,ptr,commandStr,data);

	return(NULL);
}

//---------------------------------------------------------------------------
// bolt_remove_oldest()
//---------------------------------------------------------------------------
void	bolt_remove_oldest (userEntity_t *self)
{
	weaponListNode_t	*node;
	boltHook_t			*hook;

	// point to the first entity in the bolt_list
	self = (userEntity_t *) bolt_list->head->ptr;
	
	// remove this bolter bolt from the list of active bolter bolts
	com->list_remove_node (self, bolt_list);

	// free up all of it's memory
	hook = (boltHook_t *) self->userHook;
	
	// remove the friction hook if it has one
	com->friction_remove (hook->fhook);

	// bye-bye
	com->free (self->userHook);

//	if (self->flags & FL_STICKYCHILD)                              STICKY
//		gstate->StickyRemove (self->owner, self);	
	
	gstate->RemoveEntity (self);
}

//---------------------------------------------------------------------------
// bolt_remove()
//---------------------------------------------------------------------------
void	bolt_remove (userEntity_t *self)
{
	weaponListNode_t	*node;
	boltHook_t			*hook;

	// remove this bolter bolt from the list of active bolter bolts
	com->list_remove_node (self, bolt_list);

	// free up all of it's memory
	hook = (boltHook_t *) self->userHook;
	
	// remove the friction hook if it has one
	com->friction_remove (hook->fhook);

	// bye-bye
	com->free (self->userHook);

//	if (self->flags & FL_STICKYCHILD)                                 STICKY
//		gstate->StickyRemove (self->owner, self);	

	gstate->RemoveEntity (self);
}

//---------------------------------------------------------------------------
// bolt_think()
//---------------------------------------------------------------------------
void	bolt_think (userEntity_t *self)
{
	boltHook_t		*hook = (boltHook_t *) self->userHook;
	
	// are we sticking to something?
//	if (self->flags & FL_STICKYCHILD)                  STICKY
	{
		// remove the bolts if the entity their stuck to 
		// is dead or dying

		// FIXME:what will this do if sticky is stuck to another sticky???
		if (self->owner->deadflag != DEAD_NO)
		{
			self->think = bolt_remove;
			self->nextthink = gstate->time + 0.05;
			return;
		}
	}

	if (hook->remove_time <= gstate->time)
	{
		self->think = bolt_remove;
		self->nextthink = gstate->time + 0.05;
		return;
	}

	self->think = bolt_think;
	self->nextthink = gstate->time + 0.05;
}

//---------------------------------------------------------------------------
// bolt_touch()
//---------------------------------------------------------------------------
void	bolt_touch (userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
	boltHook_t	*hook = (boltHook_t *) self->userHook;
	int				r;
	char			sound_name [40];

	if (self->owner == other)
		return;

   if (surf && (surf->flags & SURF_SKY))
   {
		self->think = bolt_remove;
		self->nextthink = gstate->time + 0.05;
      return;
   }

	self->s.renderfx -= (self->s.renderfx & RF_FULLBRIGHT);

	// if other is not the world, then set up sticking
	if (other != gstate->FirstEntity ())
	{
		// set up the sticky code
//		gstate->StickyInit (other, self, 0);            STICKY
	}
	else
		self->movetype = MOVETYPE_NONE;

	// set max time for arrow to remain before removal
	hook->remove_time = gstate->time + 120.0;

	// start thinking now that we've hit
	self->think = bolt_think;
	self->nextthink = gstate->time + 0.05;

	// if too many bolter bolts are sticking, remove the oldest one
	if (bolt_list->count > 16)
		bolt_remove_oldest (self);

	//Can we damage the object we hit?
	if (other->takedamage != DAMAGE_NO)
	{
		// make sure we pass the bolt's OWNER as the attacker!
		//	pass zero_vector as dir because DAMAGE_INFLICTOR_VEL will override it
		com->Damage (other, self, self->owner, self->s.origin, zero_vector, 30, DAMAGE_INERTIAL | DAMAGE_INFLICTOR_VEL);
	}
	
	//Make sticking noise
   winfoPlay(SND_IMPACT1 + rnd()*5,255);

   //	Nelno:	clear touch function so we can't damage stuff when we're stuck in the floor
   self->touch = NULL;
}

//---------------------------------------------------------------------------
// bolt_flight_think()
//---------------------------------------------------------------------------
void	bolt_flight_think (userEntity_t *self)
{
	boltHook_t	*hook = (boltHook_t *) self->userHook;

	if (gstate->time >= hook->remove_time)
	{
		self->think = bolt_remove;
		self->nextthink = gstate->time + 0.05;

		return;
	}

	com->friction_apply (self, hook->fhook);

	self->think = bolt_flight_think;
	self->nextthink = gstate->time + 0.05;
}

//---------------------------------------------------------------------------
// bolt_launch()
//---------------------------------------------------------------------------
void	bolt_launch (userEntity_t *self)
{
	boltHook_t	*hook = (boltHook_t *) self->userHook;
	Vector			vel, origin, ang;

	// make the model visible
//	self->origin_z_ofs = 0;
   winfoSetModel(self,MDL_BOLT);
	gstate->SetSize (self, 0, 0, 0, 0, 0, 0);

	self->movetype = MOVETYPE_FLYMISSILE;

//	self->s.effects |= EF_MAGIC_ARROW;
	self->s.renderfx |= RF_FULLBRIGHT;

	// calculate the velocity of the projectile
	vel = Vector (self->owner->client->v_angle);
	vel.AngleVectors (forward, right, up);
	vel = forward * 1000;
	vel.set (self->velocity);

   com->VecToAngles(ang,vel);
   ang.set(self->s.angles);

	// place the projectile in the world at the correct location
//	origin = Vector (self->owner->s.origin) + forward*16 + right*16;
//	origin.z += 16.0;
//	gstate->SetOrigin2 (self, origin.vec ());

	// set up maximum flight time before bolt removal
	self->think = bolt_flight_think;
	self->nextthink = gstate->time + 0.05;
	self->touch = (touch_t)bolt_touch;

	hook->remove_time = gstate->time + 3.0;
}

//---------------------------------------------------------------------------
// bolter_use()
//---------------------------------------------------------------------------
void	bolter_use (struct userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t		*phook = (playerHook_t *) self->userHook;
	boltHook_t			*hook;
	bolterWeapon_t	*bolter = (bolterWeapon_t *) self->curWeapon;
	Vector				org;
	userEntity_t		*bolt;
	ammo_t				*ammo = bolter->ammo;
	int					ammo_count;

	// have we finished our last attack?    (IS THIS NEEDED???)
	if (phook->attack_finished >= gstate->time)
		return;

	bolter->reload_count--;

	// start the firing frame sequence
	if (bolter->reload_count == 1)
	{
		// do we have any ammo left?
		if (!weaponUseAmmo(self->curWeapon,1))
			return;

      winfoAnimDelay(self,winfo,ANIM_SHOOT_STD,.05);
	}
	else if (bolter->reload_count == 0)
	{
		// do we have any ammo left?
		if (!weaponUseAmmo(self->curWeapon,1))
			return;

      winfoAnimDelay(self,winfo,ANIM_SHOOT_STD,.05);
	}
	else
	{
		// find out how much ammo we have
		ammo->command ((userInventory_t *) ammo, "count", &ammo_count);
		
		if (ammo_count >= 2)
			bolter->reload_count = 2;
		else 
			bolter->reload_count = ammo_count;

		// only reload if we have some ammo
		if (ammo_count > 0)
      {
         winfoAnimDelay(self,winfo,ANIM_RELOAD,.05);
      }

//      winfoPlay(SND_LOAD,255);

		return;
	}

//   winfoPlay(SND_RELOAD,255);
   winfoPlay(SND_FIRE,255);
	
	/////////////////////////////////////////////////////////////////
	// create projectile
	/////////////////////////////////////////////////////////////////

	// spawn a new bolter bolt
	org.x = 10;
	org.y = 15;
   org.z = 14.0;

	bolt = weapon_spawn_projectile (self, org, 800.0, 0, "projectile_bolter");

	// don't move until bolt_launch is called
	bolt->movetype = MOVETYPE_NONE;
	bolt->modelName = NULL;

	// set up the userHook for the bolter bolt
	bolt->userHook = com->malloc_hook (sizeof (boltHook_t), MEM_MALLOC);
	hook = (boltHook_t *) bolt->userHook;

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

	// set up touch and think functions for the bolt
	bolt->touch = NULL;
	bolt->think = bolt_launch;
	bolt->nextthink = gstate->time + 0.10;
   bolt->owner=self;
   com->SetVector(bolt->s.render_scale,2,2,2);
	gstate->SetSize(bolt,-4.0,-4.0,-4.0,4.0,4.0,4.0);
//	gstate->SetOrigin(shot,shot->s.origin[0],shot->s.origin[1],shot->s.origin[2]);

	// add this bolt to the global list of bolter bolts
	com->list_add_node (bolt, bolt_list, 0);
}

//---------------------------------------------------------------------------
// bolter_exit_level()
//---------------------------------------------------------------------------
void	bolter_exit_level (userEntity_t *self)
{
	listNode_t		*cur_node, *last_node;
	userEntity_t	*bolt;
	boltHook_t		*bhook;

	// traverse the list of bolts and remove all of their userhooks,
	// friction hooks and sticky hooks
	cur_node = bolt_list->head;

	while (cur_node)
	{
		bolt = (userEntity_t *) cur_node->ptr;
		bhook = (boltHook_t *) bolt->userHook;

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

//		if (self->flags & FL_STICKYCHILD)                        // STICKY
//			gstate->StickyRemove (self->owner, self);       
		gstate->RemoveEntity (self);
		

		// go to the next node
		last_node = cur_node;
		cur_node = com->list_node_next (cur_node);
		
		// now remove the node
		com->list_delete_node (bolt_list, last_node);
	}
}
