//---------------------------------------------------------------------------
// SHOCKWAVE
//---------------------------------------------------------------------------
#include "weapons.h"
#include "shockwave.h"

//---------------------------------------------------------------------------
// weapon specific info
//---------------------------------------------------------------------------
#define SHOCKWAVE_NUM_WAVES         3        // # of shockwave rings
#define SHOCKWAVE_MAX_DAMAGE        100      // strongest ring's damage
#define SHOCKWAVE_MIN_DAMAGE        10       // all other ring's damage
#define SHOCKWAVE_BASE_VELOCITY     200      // shot velocity

#define SHOCKWAVE_SIZE_START        4.0
#define SHOCKWAVE_SIZE_END          16.0

#define SHOCKWAVE_SCALE_START       .3
#define SHOCKWAVE_SCALE_END         .8

#define SHOCKWAVE_GROW_STEPS        30.0

#define SHOCKRING_MAX_FRAMES        6
#define SHOCKRING_MAX_SCALE         6

// model defs
#define MDL_SPHERE            MDL_SPECIAL+0
#define MDL_SHOCKRING         MDL_SPECIAL+1
#define MDL_SPHEREEXPLODE     MDL_SPECIAL+2
#define MDL_BOUNCE            MDL_SPECIAL+3

// sound defs
#define SND_SHOOT             0
#define SND_EXPLODE           1
#define SND_BOUNCE            2
#define SND_SPINUP            3
#define SND_GLOBEEXPAND       4
#define SND_RING              5

// frame defs
#define ANIM_SPHERE           ANIM_SPECIAL+0
#define ANIM_BOUNCE           ANIM_SPECIAL+1

// weapon info def
weaponInfo_t shockwaveWeaponInfo = {
      // names, net names, icon
      "weapon_shockwave",
      "Shockwave",
      "ammo_shocksphere",
      "Shock-Sphere",
      "iconname",

      // model files
      {"models/swave/swave.md2",
       "models/swaveshot/swaveshot.sp2",
       "models/bluering/bluering.sp2",
       "models/shkexpl/shkexpl.sp2",
       "models/bounce_swave/bounce_swave.sp2",
       NULL},

      // sound files
      {"weapons/shockwave/shockwavefire2.wav",
       "weapons/shockwave/globeexplode.wav",
       "weapons/shockwave/globehit.wav",
       "weapons/shockwave/shockwavewarmup1.wav",
       "weapons/shockwave/globeexpand4.wav",
       "weapons/shockwave/ringsound.wav",
       NULL},

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

      // commands
      {"weapon_shockwave",(void (*)())weapon_give,
       "select_shockwave",(void (*)())weapon_select,
       NULL},

   weapon_use,
   weapon_command,
   sizeof(weapon_t),

   600,
};

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

//---------------------------------------------------------------------------
// weapon_give()
//---------------------------------------------------------------------------
static userInventory_t *weapon_give(userEntity_t *self)
{
   return(weaponGive(self,winfo,100));
}

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

//---------------------------------------------------------------------------
// weapon_command()
//---------------------------------------------------------------------------
static void *weapon_command(struct userInventory_s *ptr, char *commandStr, void *data)
{
	ammo_t *ammo=(ammo_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];

		if (rnd () < 0.5)
			sprintf (msg, "%s takes %s's axe to the head.\n", ki->victim->netname, ki->attacker->netname);
		else
			sprintf (msg, "%s shoves an axe up %s's yam! (and he likes it! HEHE) .\n", ki->attacker->netname, ki->victim->netname);
	}
   else
      winfoGenericCommand(winfo,ptr,commandStr,data);

	return(NULL);
}

//---------------------------------------------------------------------------
// weapon_use()
//---------------------------------------------------------------------------
static void weapon_use(userInventory_s *ptr, userEntity_t *self)
{
	userEntity_t *shot;
	Vector vec;
	shockwaveHook_t *hook;
	playerHook_t *phook=(playerHook_t *)self->userHook;
   float zvel;
	int pc;

   // any ammo?
 	if (!weaponUseAmmo(self->curWeapon,1))
		return;

   winfoPlay(SND_SPINUP,255);
   winfoAnimDelay(self,winfo,ANIM_SHOOT_STD,.05);

   phook->fxFrameNum=winfoFirstFrame(ANIM_SHOOT_STD)+20;
   phook->fxFrameFunc=shoot_func;

	phook->fired++;
}

//---------------------------------------------------------------------------
// shoot_func()
//---------------------------------------------------------------------------
static void shoot_func(userEntity_t *self)
{
	userEntity_t *shot;
	Vector vec;
	shockwaveHook_t *hook;
	playerHook_t *phook=(playerHook_t *)self->userHook;
   float zvel,size,scale;
	int pc;
	
   winfoPlay(SND_SHOOT,255);

   phook->fxFrameFunc=NULL;

   // adjust z velocity (based on shot angle)
	vec=Vector(self->client->v_angle);
	vec.AngleVectors(forward,right,up);
   zvel=(1.0-fabs(forward.z)) * SHOCKWAVE_BASE_VELOCITY;
   if (forward.z < 0)
      zvel = -zvel;

   // spawn projectile
   vec=Vector(0,12,18.0);       // must spawn behind player because sphere is so damn big!!
   shot=weapon_spawn_projectile(self,vec,SHOCKWAVE_BASE_VELOCITY,0,"projectile_shockwave");

   // alloc/define hook
   shot->userHook=com->malloc_hook(sizeof(shockwaveHook_t),MEM_MALLOC);
   hook=(shockwaveHook_t *)shot->userHook;

   // set hook vars
   hook->pc=hook->lastpc=gstate->PointContents(shot->s.origin);
	hook->bounce_count=0;

   // set shot attributes
   shockwave_set_physics(shot);
   shot->movetype=MOVETYPE_BOUNCE;
   shot->flags=FL_EXPLOSIVE;
   shot->s.effects=0;
   shot->s.renderfx=SPR_ALPHACHANNEL|RF_TRANSLUCENT;

   // set func ptrs
	shot->touch=(touch_t)weapon_touch;
	shot->think=shockwave_bouncing;
	shot->nextthink=gstate->time+.1;

   // more attrs
   size=SHOCKWAVE_SIZE_START;
   scale=SHOCKWAVE_SCALE_START;

   winfoSetModel(shot,MDL_SPHERE);
	gstate->SetSize(shot,-size,-size,-size,size,size,size);        // final s/b 24
	gstate->SetOrigin(shot,shot->s.origin[0],shot->s.origin[1],shot->s.origin[2]);
   com->SetVector(shot->s.render_scale,scale,scale,scale);

   // set size / scale change rate
   hook->size_change=(SHOCKWAVE_SIZE_END-SHOCKWAVE_SIZE_START) / SHOCKWAVE_GROW_STEPS;
   hook->scale_change=(SHOCKWAVE_SCALE_END-SHOCKWAVE_SCALE_START) / SHOCKWAVE_GROW_STEPS;
}

//---------------------------------------------------------------------------
// weapon_touch()
//---------------------------------------------------------------------------
static void weapon_touch(userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
   shockwaveHook_t *hook=(shockwaveHook_t *)self->userHook;
	Vector ang,pos,normal;

   hook->bounce_count++;
   switch (hook->pc)
   {
      case CONTENTS_WATER:
      case CONTENTS_SLIME:
      case CONTENTS_LAVA:
      break;

      default:
         winfoPlay(SND_BOUNCE,255);

      if (plane)
      {
         normal=Vector(plane->normal);
         pos=Vector(self->s.origin);

      	normal.Normalize();
	      com->VecToAngles(ang,normal);
      	pos=pos+normal*0.125;

         winfoClientExplosion(winfo,pos,ang,.75,MDL_BOUNCE,ANIM_BOUNCE);
      }
   }

   // 3 bounces? or sitting on floor?
   if (hook->bounce_count==3 || other->takedamage!=DAMAGE_NO)
   {
      hook->bounce_count=3;
      self->movetype=MOVETYPE_NONE;
      winfoPlay(SND_EXPLODE,255);
   }
}

//---------------------------------------------------------------------------
// shockwave_bouncing()
//---------------------------------------------------------------------------
static void shockwave_bouncing(userEntity_t *self)
{
   playerHook_t *phook=(playerHook_t *)self->owner->userHook;
   shockwaveHook_t *hook=(shockwaveHook_t *)self->userHook;
   float animPct,len,damage;
   short damageType;
   Vector vel,nvel;

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

   shockwave_set_physics(self);

   // grow ball as it moves
   if (hook->bounce_count)
   {
      float size=self->s.maxs[0],scale=self->s.render_scale[0];

      if (hook->bounce_count==1)
         winfoPlay(SND_GLOBEEXPAND,255);

      if (size <= SHOCKWAVE_SIZE_END)
      {
   	   size += hook->size_change;
         scale += hook->scale_change;
         gstate->SetSize(self,-size,-size,-size,size,size,size);        // final s/b 24
         com->SetVector(self->s.render_scale,scale,scale,scale);
         gstate->LinkEntity(self);
      }
   }

   // clip horizontal movement
   vel=Vector(self->velocity);
   vel.z=0;
   len=vel.vlen();
   if (len > SHOCKWAVE_BASE_VELOCITY*.8)
      vel = vel * .8;

   vel.z=self->velocity[2];
   vel.set(self->velocity);

   // in case projectile isn't bouncing
   if (self->flags & FL_ONGROUND)
      weapon_touch(self,NULL,NULL,NULL);

   // animate sphere as it bounces
   self->s.frame--;
   if (self->s.frame < 0)
      self->s.frame=winfoNumFrames(ANIM_SPHERE);

   // explode after 3 bounces or no velocity
   if (hook->bounce_count >= 3 || !len)
   {
      shockwave_explode(self,1);

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

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

//---------------------------------------------------------------------------
// shockwave_exploding()
//---------------------------------------------------------------------------
static void shockwave_exploding(userEntity_t *self)
{
   playerHook_t *phook=(playerHook_t *)self->owner->userHook;
   shockwaveHook_t *hook=(shockwaveHook_t *)self->userHook;
   float animPct,len,damage,push_extra,scale;
   short damageType;
   Vector vel,nvel;

   if (gstate->time >= hook->explode_time)
   {
      shockwave_explode(self,hook->spawn+1);
      hook->explode_time=gstate->time+9999;
   }

   scale=self->s.render_scale[0]+.15;
   com->SetVector(self->s.render_scale,scale,scale,scale);

   hook->min=hook->max-18;
   hook->max += 18;

   if (hook->spawn==SHOCKWAVE_NUM_WAVES)
   {
      damageType=DAMAGE_EXPLOSION;
      damage=SHOCKWAVE_MAX_DAMAGE;
      push_extra=2500;
   }
   else
   {
      damageType=DAMAGE_NONE;
      damage=SHOCKWAVE_MIN_DAMAGE;
      push_extra=50;
   }

   if (shockwave_damage(self,self->owner,self,damage,550,damageType,hook->min,hook->max,push_extra))
      phook->hit++;

   // anim finished?
   if (scale > SHOCKRING_MAX_SCALE)
   {
      com->free(self->userHook);
      gstate->RemoveEntity(self);
      return;
   }

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

//---------------------------------------------------------------------------
// shockwave_explode()
//---------------------------------------------------------------------------
static userEntity_t *shockwave_explode(userEntity_t *self,short explosionNum)
{
   userEntity_t *shock;
   playerHook_t *phook=(playerHook_t *)self->owner->userHook;
   shockwaveHook_t *hook;
   short damageType;
   float damage,push_extra;
   Vector vtemp;

   explodeSphere(self);

   vtemp=Vector(0,0,0);
   shock=weapon_spawn_projectile(self->owner, vtemp, 0, 0, "projectile_shockwave");

   shock->userHook=com->malloc_hook(sizeof(shockwaveHook_t),MEM_MALLOC);
   hook=(shockwaveHook_t *)shock->userHook;

   shock->owner=self->owner;
   shock->flags=FL_EXPLOSIVE;
   shock->gravity=.6;
   shock->elasticity=2.0;
   shock->mass=1.0;     //.5;

	shock->think=shockwave_exploding;
	shock->nextthink = gstate->time + 0.1;

   // change to shockwave explosion model and set attributes
   winfoSetModel(shock,MDL_SHOCKRING);
	gstate->SetSize(shock, -4.0, -4.0, -4.0, 4.0, 4.0, 4.0);
   com->SetVector(shock->s.render_scale,1,1,1);
   gstate->SetOrigin(shock,self->s.origin[0],self->s.origin[1],self->s.origin[2]);

   hook->spawn=explosionNum;
   hook->min=0;
   hook->max=10;
	hook->bounce_count=0;

   if (explosionNum < SHOCKWAVE_NUM_WAVES)
      hook->explode_time=gstate->time+.8;
   else
      hook->explode_time=gstate->time+9999;

   if (hook->spawn==SHOCKWAVE_NUM_WAVES)
   {
      damageType=DAMAGE_EXPLOSION;
      damage=SHOCKWAVE_MAX_DAMAGE;
      push_extra=1500;
   }
   else
   {
      damageType=DAMAGE_NONE;
      damage=SHOCKWAVE_MIN_DAMAGE;
      push_extra=50;
   }

   if (shockwave_damage(self, self->owner, self, damage,550, damageType,hook->min,hook->max,push_extra))
      phook->hit++;

   shock->s.angles[0]=90;
   shock->s.angles[1]=shock->s.angles[2]=0;
   shock->movetype=0;
   shock->s.effects=EF_BRIGHTLIGHT;
   shock->s.frame=0;
   shock->s.renderfx=SPR_ALPHACHANNEL|RF_TRANSLUCENT|SPR_ORIENTED;
   shock->s.alpha=.8;

   return(shock);
}

//---------------------------------------------------------------------------
// explodeSphere()
//---------------------------------------------------------------------------
static void explodeSphere(userEntity_t *self)
{
   userEntity_t *ent;
   Vector angles,vel;

   ent=gstate->SpawnEntity();
   ent->className="sphere_center_exp";
	ent->solid= SOLID_NOT;
   ent->movetype=MOVETYPE_NONE;
	ent->clipmask=MASK_SHOT;
   ent->think=center_think;
   ent->nextthink=gstate->time+.1;
   ent->s.renderfx=RF_TRANSLUCENT;
   ent->s.frame=0;
   ent->s.alpha=.9;

   com->CopyVector(ent->s.origin,self->s.origin);

   gstate->SetSize(ent,-24,-24,-24,24,24,24);
   com->SetVector(ent->s.render_scale,1,1,1);
   gstate->LinkEntity(ent);

   winfoSetModel(ent,MDL_SPHEREEXPLODE);
}

//---------------------------------------------------------------------------
// center_think()
//---------------------------------------------------------------------------
static void center_think(userEntity_t *self)
{
   float scale;

   scale=self->s.render_scale[0]+.2;
   com->SetVector(self->s.render_scale,scale,scale,scale);

   if (++self->s.frame >= winfoNumFrames(ANIM_SPHERE))
      gstate->RemoveEntity(self);
   else
      self->nextthink=gstate->time+.1;
}

//---------------------------------------------------------------------------
// shockwave_set_physics()
//---------------------------------------------------------------------------
static void shockwave_set_physics(userEntity_t *self)
{
   shockwaveHook_t *hook=(shockwaveHook_t *)self->userHook;

   switch (hook->pc)
   {
      case CONTENTS_WATER:
      case CONTENTS_SLIME:
      case CONTENTS_LAVA:
         self->gravity=0.4;
         self->elasticity=1.1;
         self->mass=.5;

         if (hook->pc == hook->lastpc)
         {
            self->velocity[0] *= .9 + rnd() * .1;
            self->velocity[1] *= .8 + rnd() * .2;
            self->velocity[2] *= .3 + rnd() * .3;
         }
         else
         {
	         gstate->sound(self,CHAN_AUTO,"player/playerlandinwater.wav",50,ATTN_NORM);
//            SV_StartSound(ent, 0, "engine/waterhit.wav", 255, 1);
            self->velocity[2] *= .2 + rnd() * .2;
         }
      break;

      default:
         self->gravity=.4;
         self->elasticity=2.1;
         self->mass=.5;
   }
}

//---------------------------------------------------------------------------
// shockwave_damage
//
// - entities are only damaged if they are within curmin & curmax
// - damage is based on distance from explosion
//---------------------------------------------------------------------------
static int shockwave_damage(userEntity_t *inflictor, userEntity_t *attacker, userEntity_t *ignore, float maxdamage,float maxdist, int damage_type, float curmin, float curmax, float push_extra)
{
	userEntity_t	*head;
	float			damage_points,dist,perc;
	Vector			org,pushdir,vel,extra;
	int				hit_something;

   if (curmin < 0)
      curmin=0;

	hit_something = FALSE;
	head = gstate->FirstEntity ();

	while (head)
	{
		if (head->className)
			if (stricmp (head->className, "trigger_once") == 0)
				gstate->Con_Printf ("trigger_once\n");
				
		// find the distance to this entity
		if (head != ignore && head->takedamage)
		{
			org = Vector(head->s.origin) + (Vector(head->s.mins) + Vector(head->s.maxs)) * 0.5;
			dist = org.vdist(Vector(inflictor->s.origin));

         if (dist>=curmin && dist<=curmax)
         {
   			perc=(maxdist-dist)/maxdist;
            damage_points=perc*maxdamage;

   			if (damage_points < 0)
	   			damage_points = 0;

   			// half damage from own weapon
   			if (head==attacker)
	   			damage_points=damage_points*.5;

   			if (com->CanDamage(head,inflictor))
	   		{
		   		hit_something=TRUE;

               pushdir=Vector(head->s.origin)-Vector(inflictor->s.origin);
               pushdir.Normalize();
               if (pushdir.z < .4 && pushdir.z > -0.1)
                  pushdir.z = .4;

               com->Damage(head,inflictor,attacker,head->s.origin,pushdir.vec(),damage_points,DAMAGE_EXPLOSION|DAMAGE_DIR_TO_INFLICTOR);

               vel=Vector(head->velocity);
               extra=pushdir*push_extra;
               extra=extra*perc;
               vel=vel+extra;
               vel.set(head->velocity);

               head->flags &= ~FL_ONGROUND;
   			}
	   	}
      }

		head = gstate->NextEntity (head);
	}

	return	hit_something;
}
