//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/pulserifle.cpp                   $
// $Revision:: 68                                                             $
//   $Author:: Jimdose                                                        $
//     $Date:: 11/16/98 3:11a                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/pulserifle.cpp                        $
// 
// 68    11/16/98 3:11a Jimdose
// pulse now takes gravity of owner
// 
// 67    11/16/98 2:11a Jimdose
// removed unused variable in Shoot
// 
// 66    11/15/98 12:22a Markd
// Fixed pre-caching issues
// 
// 65    11/13/98 3:30p Markd
// put in more precaching on weapons
// 
// 64    10/26/98 2:50p Aldie
// Fixed a bug with checking of NULL owners
// 
// 63    10/24/98 12:42a Markd
// changed origins to worldorigins where appropriate
// 
// 62    10/22/98 7:57p Markd
// put in proper pre-caching in all the classes
// 
// 61    10/20/98 8:26p Markd
// Added Attacker to DamageSurface stuff
// 
// 60    10/19/98 12:07a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
// 
// 59    10/16/98 10:22p Aldie
// Updated single player damage settings
// 
// 58    10/16/98 9:50p Aldie
// Added SecondaryAmmo command
// 
// 57    10/15/98 2:32p Aldie
// Updated model name for pulsebomb
// 
// 56    10/10/98 9:18p Aldie
// Tweak damage
// 
// 55    10/10/98 5:58p Aldie
// More quantumdestab fixes
// 
// 54    10/09/98 11:56p Markd
// Added GenericPulseRifle
// 
// 53    10/06/98 2:49p Aldie
// Tweak NextAttack
// 
// 52    10/05/98 10:39p Aldie
// Fixed rank and updated damage.
// 
// 51    10/04/98 10:28p Aldie
// Added multiple weapon changes.  Damage, flashes, quantum stuff
// 
// 50    10/03/98 1:09p Aldie
// Tweak the effects again
// 
// 49    10/02/98 7:19p Aldie
// Orient the shockwave
// 
// 48    10/02/98 5:47p Aldie
// New effects - yet again
// 
// 47    10/01/98 4:01p Markd
// Added Archive and Unarchive functions
// 
// 46    9/29/98 11:45p Aldie
// New effect for mode1
// 
// 45    9/21/98 5:31p Markd
// took out owner out of projectile subclasses, working on archive and
// unarchive
// 
// 44    9/18/98 8:14p Markd
// rewrote surface system so that surfaces are now damaged by surface name
// instead of by surfinfo
// 
// 43    9/16/98 8:58p Aldie
// Added ability to do a hold down weapon charge
// 
// 42    9/09/98 3:56p Aldie
// New lightning effect
// 
// 41    9/01/98 3:05p Markd
// Rewrote explosion code
// 
// 40    8/31/98 7:45p Aldie
// Updated surface data structure and removed surfinfo field
// 
// 39    8/29/98 9:45p Jimdose
// Added call info to G_Trace
// 
// 38    8/29/98 5:27p Markd
// added specialfx, replaced misc with specialfx where appropriate
// 
// 37    8/18/98 8:12p Aldie
// Added dual mode weapons to base class
// 
// 36    8/01/98 3:03p Aldie
// Client side muzzle flash (dynamic light)
// 
// 35    7/25/98 7:10p Markd
// Put in EV_Removes for demo
// 
// 34    7/23/98 6:17p Aldie
// Updated damage system and fixed some damage related bugs.  Also put tracers
// back to the way they were, and added gib event to funcscriptmodels
// 
// 33    7/22/98 9:57p Markd
// Defined weapon type
// 
// 32    7/22/98 5:14p Aldie
// Fixed NextAttack time
// 
// 31    7/21/98 1:10p Aldie
// Added meansofdeath to obituaries
// 
// 30    7/01/98 4:39p Aldie
// Put Beaus' model explosion back in
// 
// 29    6/30/98 6:48p Aldie
// Removed a printf
// 
// 28    6/30/98 12:40p Aldie
// Fixed some bugs with vid_restart issue
// 
// 27    6/29/98 8:19p Aldie
// Updated shockwave
// 
// 26    6/25/98 7:32p Aldie
// Adding damage wall 
// 
// 25    6/19/98 9:30p Jimdose
// Moved gun orientation code to Weapon
// 
// 24    6/17/98 6:30p Aldie
// Updated ammo requirements
// 
// 23    6/17/98 5:42p Aldie
// Updated lots and made the beam do some damage.
// 
// 22    6/15/98 9:09p Aldie
// Updated the special fx for beams trailing from the pulse.
// 
// 21    6/15/98 12:25p Aldie
// Updated pulse beam parms
// 
// 20    6/15/98 10:38a Aldie
// New version of pulse stuff
// 
// 19    6/10/98 2:10p Aldie
// Updated damage function.
// 
// 18    5/14/98 9:14p Markd
// Changed models
// 
// 17    5/13/98 6:21p Markd
// Made pulse rifle burn wall as well as retrigger more often
// 
// 16    5/03/98 4:36p Jimdose
// Changed Vector class
// 
// 15    4/20/98 1:56p Markd
// SINED decelration is now in def file
// 
// 14    4/18/98 3:12p Markd
// Changed view weapon naming convention
// 
// 13    4/08/98 12:06a Jimdose
// Added temporary beam effect
// 
// 12    4/07/98 6:41p Jimdose
// Rewrote weapon code.
// Added order to rank
// 
// 11    4/02/98 4:20p Jimdose
// Tweaked for DM
// 
// 10    3/30/98 9:55p Jimdose
// Changed location of .def files
// 
// 9     3/30/98 2:34p Jimdose
// Moved firing to BulletWeapon to make more general
// Added Ammo
// Added world models
// 
// 8     3/28/98 8:55p Jimdose
// Created file
// 
// DESCRIPTION:
// Pulse rifle
// 

#include "g_local.h"
#include "bullet.h"
#include "pulserifle.h"
#include "specialfx.h"
#include "misc.h"
#include "explosion.h"
#include "surface.h"

#define PULSE_MODE 0

// Projectile portion of the pulse rifle

template class EXPORT_FROM_DLL SafePtr<Beam>;
typedef SafePtr<Beam> BeamPtr;

class EXPORT_FROM_DLL Pulse : public Projectile
   {
	public:
      CLASS_PROTOTYPE( Pulse );

                     Pulse();
      virtual void   Setup( Entity *owner, Vector pos, Vector dir );
      virtual void   Explode( Event *ev );
  	};

CLASS_DECLARATION( Projectile, Pulse, NULL );

Event EV_Pulse_UpdateBeams("update_beams");
Event EV_Pulse_Remove("remove_beams");

ResponseDef Pulse::Responses[] =
	{
      { &EV_Touch,                           ( Response )Pulse::Explode },
      { NULL, NULL }
	};

Pulse::Pulse()
   {
   }

void Pulse::Explode
   (
   Event *ev
   )

   {
   Entity      *other;
   Entity      *owner;
   int         damg;
   Vector      v;
   Vector      norm;
   Vector      shockangles;
   Entity      *pulseexpl;

   other = ev->GetEntity( 1 );
	assert( other );

	if ( other->isSubclassOf( Teleporter ) )
		{
		return;
		}

	if ( other->entnum == this->owner )
		{
		return;
		}

   owner = G_GetEntity( this->owner );

   if ( !owner )
      owner = world;

   stopsound( CHAN_VOICE );
	setSolidType( SOLID_NOT );

   // Hit the shy, so remove everything
	if ( HitSky() )
		{
		PostEvent( EV_Remove, 0 );
		return;
		}

  	damg = 50 + ( int )G_Random( 25 );
	
   // Single player packs a bigger punch
   if ( !deathmatch->value && owner->isClient() )
      damg *= 1.5;
   
   if ( other->takedamage )
   	other->Damage( this, owner, damg, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_PULSE, -1, -1, 1.0f );

   // Damage the surface
   surfaceManager.DamageSurface ( &level.impact_trace, damg, owner );

   damg = 100;

   // Single player packs a bigger punch
   if ( !deathmatch->value && owner->isClient() )
      damg *= 1.5;
   
   // Do an explosion but don't hurt other, since he already took damage.
   RadiusDamage( this, owner, damg, other, MOD_PULSE );
   
   v = velocity;
	v.normalize();
   v = worldorigin - v * 36;
  
   norm           = level.impact_trace.plane.normal;
   norm.x         = -norm.x;
   norm.y         = -norm.y;
   shockangles    = norm.toAngles();
	shockangles.z  = G_Random( 360 );

   // Start up the shockwave effect

   pulseexpl = new Entity;
   pulseexpl->setModel( "pulseshock.def" );
   pulseexpl->setOrigin( v );
   pulseexpl->worldorigin.copyTo(pulseexpl->edict->s.old_origin);
   pulseexpl->setAngles( shockangles );
   pulseexpl->setMoveType( MOVETYPE_NONE );
   pulseexpl->setSolidType( SOLID_NOT );
   pulseexpl->RandomAnimate( "pulseexplode", NULL );
   pulseexpl->PostEvent( EV_Remove, 0.1f );
   FlashPlayers(v, 0.1, 0.1, 1, 0.5, 500 );

   PostEvent( EV_Remove, 0 );
   }
     
void Pulse::Setup
   (
   Entity *owner,
   Vector pos, 
   Vector dir
   )

   {
   Event *ev1, *ev2;
   Vector forward, right, up;
   Vector end, delta;

	this->owner = owner->entnum;
	edict->owner = owner->edict;

   // Align the projectile
  	angles = dir.toAngles();
	angles[ PITCH ] = -angles[ PITCH ];
   setAngles( angles );
   edict->s.angles[ROLL] = rand() % 360;

   // Flies like a rocket
	setMoveType( MOVETYPE_TOSS );
	setSolidType( SOLID_BBOX );
	edict->clipmask = MASK_PROJECTILE;
   setModel( "sprites/pulsebomb.spr" );
	edict->s.effects |= EF_AUTO_ANIMATE;
     
   // Set the flying velocity
   velocity = dir * 1000;
   gravity = 0.7;

   SetGravityAxis( owner->gravaxis );

	takedamage = DAMAGE_NO;

   // Set the light and effects
   edict->s.renderfx |= RF_DLIGHT;
   edict->s.effects  |= EF_PULSE;
   edict->s.radius    = 100;
   edict->s.color_r   = 0.1f;
   edict->s.color_g   = 0.1f;
   edict->s.color_b   = 0.9f;

   // setup ambient thrust sound
	ev1 = new Event( EV_RandomEntitySound );
	ev1->AddString( "thrust" );
   ProcessEvent( ev1 ); 

   // Set size and origin
   setSize( "-1 -1 -1", "1 1 1" );
	setOrigin( pos );
   worldorigin.copyTo(edict->s.old_origin);
   
   // Remove the projectile in the future
	ev2 = new Event( EV_Pulse_Remove );
	PostEvent( ev2, 30 );
   }

CLASS_DECLARATION( BulletWeapon, PulseRifle, "weapon_pulserifle" );

ResponseDef PulseRifle::Responses[] =
	{
	   { &EV_Weapon_Shoot,              ( Response )PulseRifle::Shoot },
		{ NULL, NULL }
	};

PulseRifle::PulseRifle()
	{
#ifdef SIN_DEMO
   PostEvent( EV_Remove, 0 );
   return;
#endif
	SetModels( "pulse2.def", "view_pulse2.def" );
	SetAmmo( "BulletPulse", 10, 50 );
	SetSecondaryAmmo( "BulletPulse", 5, 50 );
   SetRank( 80, 80 );	
   SetType( WEAPON_2HANDED_LO );
   dualmode = true;
	modelIndex( "pulse_ammo.def" );
	modelIndex( "beam.def" );
   modelIndex( "pulseshock.def" );
   modelIndex( "sprites/pulsebomb.spr" );
   modelIndex( "sprites/pulse_muzzle.spr" );
	}


// This is the trace that the laser portion of this weapon does.
void PulseRifle::TraceAttack
   (	   
   Vector	start,
	Vector	end,
	int		damage,
	trace_t	*trace,
   int      numricochets,
   int      kick,
   int      dflags
	)
	
	{
	Vector   org;
	Vector   dir;
   int      surfflags;
   int      surftype;
   Entity    *ent;

	if ( HitSky( trace ) )
		{
		return;
		}

	dir = end - start;
	dir.normalize();

	org = end - dir * 4;

	if ( !trace->surface )
		{
      surfflags = 0;
      surftype = 0;
		}
   else
      {
      surfflags = trace->surface->flags;
      surftype = SURFACETYPE_FROM_FLAGS( surfflags );
      surfaceManager.DamageSurface( trace, damage, owner );
      }

	ent = trace->ent->entity;

	if ( ent && ent->takedamage )
		{
      if ( trace->intersect.valid )
         {
   	   // We hit a valid group so send in location based damage
         ent->Damage( this,
                      owner,
                      damage,
                      trace->endpos,
                      dir,
                      trace->plane.normal,
                      kick,
                      dflags,
                      MOD_PULSELASER,
                      trace->intersect.parentgroup,
                      -1,
                      trace->intersect.damage_multiplier );
         }
      else
         {
         // We didn't hit any groups, so send in generic damage
   		ent->Damage( this,
                      owner,
                      damage,
                      trace->endpos,
                      dir,
                      trace->plane.normal,
                      kick,
                      dflags,
                      MOD_PULSELASER,
                      -1,
                      -1,
                      1 );
         }
      }
   }


void PulseRifle::PulseExplosion
   (
   trace_t *trace
   )

   {
   Vector      org, v;
  	float       damg;
   Entity      *ent;   
   float       kick = 250;
   Vector      shockangles;
   Vector      norm;
   Vector      tempvec;
   float       points;
   Vector      endpos;
   float       radius;

   damg = 50 + ( int )G_Random( 20 );
	RadiusDamage( this, owner, damg, NULL, MOD_PULSE );

   VectorCopy( trace->dir, tempvec.vec3() );
   endpos = trace->endpos - ( tempvec * 8 );

   norm           = trace->plane.normal;
   norm.x         = -norm.x;
   norm.y         = -norm.y;
   shockangles    = norm.toAngles();
	shockangles.z  = G_Random( 360 );

   RandomPositionedSound( endpos, "impact_smallexplosion", 1.0, CHAN_AUTO, ATTN_NORM );
  
   radius = damg + 50;
   ent = findradius( NULL, endpos, radius );
	while( ent )
		{
		if ( ( ent->takedamage ) &&
           ( ent->movetype != MOVETYPE_NONE ) && 
		     ( ent->movetype != MOVETYPE_BOUNCE ) && 
		     ( ent->movetype != MOVETYPE_PUSH ) && 
		     ( ent->movetype != MOVETYPE_STOP ) )
			{
			org = ent->centroid;
			v = org - endpos;
         points = v.length();
         v.normalize();
         ent->velocity += (v * kick);

         points *= (float)0.5;

         if ( points < 0 )
				{
				points = 0;
				}

			points = damg - points;
			
         if ( points > 0 )
				{
				if ( this->CanDamage( ent ) )
					{
               ent->Damage(this, owner, points,
                           org, v, vec_zero, points, 
                           DAMAGE_RADIUS, MOD_PULSE,
                           -1, -1, 1.0f );
					}
				}
         }
		ent = findradius( ent, endpos, radius );
      }
   }

void PulseRifle::Shoot
	(
	Event *ev
	)

	{

   Vector   pos;
   Vector   end;
   Vector   dir;
   Vector   delta;
   trace_t  trace;
   float    dist;
   float    length;  
   float    damg;

	assert( owner );
	if ( !owner )
		{
		return;
      }      

	GetMuzzlePosition( &pos, &dir );

   if ( weaponmode == PRIMARY )
      {
      Pulse *pulse;

      pulse = new Pulse;
      pulse->Setup( owner, pos, dir );
      NextAttack( 0.5 );
      }
   else
      {
      // Fire the beam
      length = ev->GetInteger( 1 );
      end   = pos + dir * length;
 	   trace = G_Trace( pos, vec_zero, vec_zero, end, owner, MASK_SHOT, "PulseRifle::Shoot" );
      delta = trace.endpos - pos;
      dist  = delta.length();

      // Set the pitch of this weapon so the client can use it to fire bullets in the right directions   
      dir = Vector( owner->orientation[ 0 ] );
      angles = dir.toAngles();
      setAngles( angles );
      
      damg = 15;
      TraceAttack( pos, trace.endpos, damg, &trace, 0, 0, 0 );
      NextAttack( 0 );
      }
	}

CLASS_DECLARATION( PulseRifle, GenericPulseRifle, "weapon_genericpulserifle" );

ResponseDef GenericPulseRifle::Responses[] =
	{
		{ NULL, NULL }
	};

GenericPulseRifle::GenericPulseRifle()
	{
	SetModels( NULL, "view_pulse2.def" );
	}

