//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/spidermine.cpp                      $
// $Revision:: 50                                                             $
//   $Author:: Markd                                                          $
//     $Date:: 6/07/99 2:29p                                                  $
//
// 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/spidermine.cpp                           $
// 
// 50    6/07/99 2:29p Markd
// fixed null pointer bug
// 
// 49    11/18/98 1:27a Jimdose
// Fixed sliding and orientation issues with gravaxis
// 
// 48    11/16/98 9:44p Jimdose
// fixed sticking on alternate gravaxis
// 
// 47    11/16/98 3:12a Jimdose
// mine takes gravity of owner
// 
// 46    11/15/98 10:31p Jimdose
// view of spidermine is now towards direction it's moving
// 
// 45    11/13/98 8:00p Markd
// Fixed spider mine bug with owner being reset to world too soon
// 
// 44    11/13/98 3:30p Markd
// put in more precaching on weapons
// 
// 43    11/13/98 2:35a Aldie
// Lowered the rank of spidermines
// 
// 42    11/11/98 5:12p Aldie
// Spidermine fix when player dies - don't drop it
// 
// 41    11/09/98 2:52a Aldie
// increase health
// 
// 40    11/09/98 12:12a Aldie
// Made it so you can reclaim your mines when you die, and mines are now shootable
// 
// 39    10/26/98 2:50p Aldie
// Fixed a bug with checking of NULL owners
// 
// 38    10/24/98 12:42a Markd
// changed origins to worldorigins where appropriate
// 
// 37    10/22/98 7:57p Markd
// put in proper pre-caching in all the classes
// 
// 36    10/22/98 4:33p Aldie
// Fix for spidermine and detonators
// 
// 35    10/21/98 12:09a Aldie
// Removed friction
// 
// 34    10/20/98 8:26p Markd
// Added Attacker to DamageSurface stuff
// 
// 33    10/19/98 12:07a Jimdose
// made all code use fast checks for inheritance (no text lookups when possible)
// isSubclassOf no longer requires ::_classinfo()
// 
// 32    10/16/98 10:22p Aldie
// Updated single player damage settings
// 
// 31    10/16/98 2:30p Aldie
// Check for NULL detonator
// 
// 30    10/10/98 9:14p Aldie
// Damage tweak and detonator tweak
// 
// 29    10/10/98 7:14p Aldie
// Tweaked damage
// 
// 28    10/08/98 12:03a Jimdose
// Got rid of unused variables speed and direction from Mine
// 
// 27    10/05/98 10:20p Aldie
// Changed damage a bit
// 
// 26    10/02/98 5:47p Aldie
// Add MOD to RadiusDamage
// 
// 25    9/18/98 8:14p Markd
// rewrote surface system so that surfaces are now damaged by surface name instead
// of by surfinfo
// 
// 24    9/13/98 8:37p Aldie
// Don't autoswitch to detonator
// 
// 23    9/11/98 3:19p Aldie
// Make mines bounce of entities that take damage.
// 
// 22    9/05/98 12:13p Aldie
// Made explosion driven by animation of the spidermine.
// 
// 21    9/01/98 7:53p Aldie
// Removed sound error
// 
// 20    9/01/98 7:46p Aldie
// Added area checking for spidermines
// 
// 19    9/01/98 3:05p Markd
// Rewrote explosion code
// 
// 18    8/29/98 9:46p Jimdose
// Added call info to G_Trace
// 
// 17    8/27/98 2:31p Aldie
// Fix for dead owner
// 
// 16    8/26/98 6:19p Aldie
// Update fov to 120 and remove the proximity stuff
// 
// 15    8/25/98 7:39p Aldie
// Made spidermine immediately switch to detonator.
// 
// 14    8/25/98 5:29p Aldie
// New version of the spidermine which uses cameras
// 
// 13    8/22/98 9:36p Jimdose
// Added support for alternate gravity axis
// 
// 12    8/18/98 11:08p Markd
// Added new Alias System
// 
// 11    7/25/98 7:10p Markd
// Put in EV_Removes for demo
// 
// 10    7/22/98 9:57p Markd
// Defined weapon type
// 
// 9     6/27/98 7:58p Aldie
// Minor adjustments to stuff
// 
// 8     6/19/98 9:30p Jimdose
// Moved gun orientation code to Weapon
// 
// 7     6/10/98 2:10p Aldie
// Updated damage function.
// 
// 6     6/08/98 9:04p Aldie
// Updated doneFiring event
// 
// 5     6/08/98 8:43p Aldie
// Added ReadyToUse support
// 
// 4     6/08/98 8:21p Aldie
// Updated spidermines with new anims and logic
// 
// 3     6/08/98 11:34a Aldie
// New version of spidermines.
// 
// 2     5/11/98 11:24a Markd
// First time
// 
// 1     5/11/98 11:10a Markd
// 
// 1     5/11/98 10:24a Markd
// 
// 1     5/11/98 10:20a Markd
// 
// 1     5/11/98 10:18a Markd
// 
// 1     5/11/98 9:55a Markd
// 
// DESCRIPTION:
// Spider Mine
// 

#include "g_local.h"
#include "spidermine.h"
#include "explosion.h"
#include "player.h"
#include "surface.h"

CLASS_DECLARATION( Projectile, Mine, "Mine" );

Event EV_Mine_Explode( "mine_explode" );
Event EV_Mine_CheckForTargets( "mine_targets" );
Event EV_Mine_Run( "mine_run" );

ResponseDef Mine::Responses[] =
	{
	   { &EV_Touch,				         ( Response )&Mine::SlideOrStick },
      { &EV_Killed,				         ( Response )&Mine::Explode },
	   { &EV_Mine_Explode,	            ( Response )&Mine::Explode },
      { &EV_Trigger_Effect,				( Response )&Mine::SlideOrStick },
      { &EV_Mine_CheckForTargets,   	( Response )&Mine::CheckForTargets },
      { &EV_Mine_Run,                	( Response )&Mine::Run },
		{ NULL, NULL }
	};


void Mine::Run
   (
   Event *ev
   )

   {
   RandomAnimate( "run", NULL );
   }

void Mine::CheckForTargets
   (
   Event *ev
   )
   
   {
   Entity      *ent;
   Event       *event;
   trace_t     trace;

   // Playtest
   return;

   if (detonate)
      return;

   ent = findradius( NULL, worldorigin, 150 );
	while( ent )
		{
		if ( ( ent != this ) && 
           ( !ent->deadflag ) && 
           ( ent->entnum != owner ) &&
           ( ent->takedamage ) && 
           ( !(ent->flags & FL_NOTARGET) ) && 
           ( strcmp(ent->getClassname(),"Mine") ) )
			{
 			detonate = true;
         trace = G_Trace( worldorigin, vec_zero, vec_zero, ent->worldorigin, ent, MASK_PROJECTILE, "Mine::CheckForTargets" );
         if (trace.fraction != 1.0f)
            detonate = false;
         else
            break;
			}
		ent = findradius( ent, worldorigin, 150 );
		}

   if (detonate)
      {
      RandomGlobalSound("spider_arm",1);
      event = new Event(EV_Mine_Explode);
      PostEvent(event,0.5f);
      }
   else
      {
      event = new Event(EV_Mine_CheckForTargets);
      PostEvent(event,0.5f);
      }
   }


void Mine::SlideOrStick
   (
   Event *ev
   )

   {
   Entity   *other;
   Event    *event;
   Vector   norm;
   Vector   addSpeed;

   if (detonate)
      return;

	other = ev->GetEntity( 1 );
	assert( other );

   if ( other->takedamage )
		{
      setMoveType(MOVETYPE_BOUNCE);
      return;
		}
    
   // Check to see if we hit the ground, if so then slide along,
   // otherwise stick to the wall.
   if (( (level.impact_trace.plane.normal[ gravity_axis[ gravaxis ].z ] * gravity_axis[ gravaxis ].sign ) > 0.7 ) && 
     !sticky)
      {
      setMoveType(MOVETYPE_SLIDE);
      event = new Event(EV_Mine_CheckForTargets);
      PostEvent(event,2.5);
      }
   else
      {
      // So that we can shoot our own mines
	   edict->owner = world->edict;

      CancelEventsOfType(EV_Mine_Run);
      setMoveType(MOVETYPE_NONE);
      RandomAnimate( "stick", NULL );
      norm = level.impact_trace.plane.normal;
      angles = norm.toAngles();
      angles.x = -angles.x;
      setAngles(angles);
      velocity = "0 0 0";

      // Check to see if we are on the floor and adjust origin
      if (!sticky)
         {
         CheckGround();
         if (groundentity)
            setOrigin(origin + "0 0 12" );
         }

      setOrigin(origin);
      event = new Event(EV_Mine_CheckForTargets);
      PostEvent(event,0.7f);
      }
   }


void Mine::Explode
   (
   Event *ev
   )
   {
	int			damg;
	Vector		v;
   Vector		norm;
   Player      *client;
   Camera      *cam;
   Entity      *owner;


   detonate = true;
   takedamage = DAMAGE_NO;

   stopsound( CHAN_VOICE );
	setSolidType( SOLID_NOT );

   if ( HitSky() )
		{
		PostEvent( EV_Remove, 0 );
		return;
		}

   owner = G_GetEntity( this->owner );

   if ( !owner )
      owner = world;

   damg = 100;

   if ( !deathmatch->value && owner->isClient() )
      damg *= 1.5;

   surfaceManager.DamageSurface (&level.impact_trace, damg, owner );

   v = velocity;
	v.normalize();
   v = worldorigin - v * 24;

   RadiusDamage( this, owner, damg, this, MOD_SPIDERSPLASH );

   if ( owner->isSubclassOf( Player ) )
      {
      client = ( Player * )owner;
      cam = client->CurrentCamera();
   
      // If we are in the camera, get out of it
      if ( cam == ( Camera * )this )
         client->SetCamera( NULL );
      }

   // Remove the mine from the detonator
   if ( detonator )
      detonator->RemoveMine( this );

   RandomAnimate( "explode", NULL );
   PostEvent( EV_Remove, 0.2 );
   }

void Mine::SetDetonator
   (
   Detonator *det
   )

   {
   detonator = det;
   }

qboolean Mine::IsOwner
   (
   Sentient *sent
   )

   {
   return ( sent == G_GetEntity( this->owner ) );
   }

void Mine::Setup
   (
   Entity *owner,
   Vector pos,
	Vector dir
   )

   {
   Event *ev;
	Vector t[ 3 ];
	Vector forward;
	Vector right;
	Vector up;

   this->owner  = owner->entnum;

	edict->owner = owner->edict;

   setModel( "spidermine.def" );
   
   showModel();
   setMoveType( MOVETYPE_SLIDE );
   setSolidType( SOLID_BBOX );
   edict->clipmask = MASK_PROJECTILE;
   RandomAnimate( "open", EV_Mine_Run );

   SetGravityAxis( owner->gravaxis );
   velocity = dir * 600;
   sticky = false;

   const gravityaxis_t &grav = gravity_axis[ gravaxis ];

   setAngles( Vector( 0, dir.toYaw(), 0 ) );

   angles.AngleVectors( &t[0], &t[1], &t[2] );
	forward[ grav.x ] = t[ 0 ][ 0 ];
	forward[ grav.y ] = t[ 0 ][ 1 ] * grav.sign;
	forward[ grav.z ] = t[ 0 ][ 2 ] * grav.sign;
	right[ grav.x ] = t[ 1 ][ 0 ];
	right[ grav.y ] = t[ 1 ][ 1 ] * grav.sign;
	right[ grav.z ] = t[ 1 ][ 2 ] * grav.sign;
	up[ grav.x ] = t[ 2 ][ 0 ];
	up[ grav.y ] = t[ 2 ][ 1 ] * grav.sign;
	up[ grav.z ] = t[ 2 ][ 2 ] * grav.sign;
	VectorsToEulerAngles( forward.vec3(), right.vec3(), up.vec3(), angles.vec3() );
   setAngles( angles );
   
 	ev = new Event( EV_Mine_Explode );
	ev->AddEntity( world );
	PostEvent( ev, 180 );

 	takedamage = DAMAGE_YES;
	health = 150;
   edict->svflags |= ( SVF_SHOOTABLE );
   setSize("-4 -4 -4", "4 4 4");
   
   setOrigin( pos );
   worldorigin.copyTo(edict->s.old_origin);
   detonate = false;

   fov = 120;

	if ( owner->isClient() && owner->client->ps.pmove.pm_flags & PMF_DUCKED )
      {
      setMoveType( MOVETYPE_TOSS );
      sticky = true;
      }
   }

CLASS_DECLARATION( Weapon, SpiderMine, NULL);

ResponseDef SpiderMine::Responses[] =
	{
	   { &EV_Weapon_Shoot,              ( Response )&SpiderMine::Shoot },
      { &EV_Weapon_DoneFiring,         ( Response )&SpiderMine::DoneFiring },
      { &EV_Weapon_SecondaryUse,       ( Response )&SpiderMine::ChangeToDetonator },
      { NULL, NULL }
	};

SpiderMine::SpiderMine
   (
   )

	{
#ifdef SIN_DEMO
   PostEvent( EV_Remove, 0 );
   return;
#endif
	SetModels( "mines.def", "view_mines.def" );
	SetAmmo( "SpiderMines", 1, 0 );
	SetRank( 60, 5 );
   SetType( WEAPON_1HANDED );
   modelIndex( "spidermine.def" );
   modelIndex( "mine.def" );

   currentMine = 0;
	}

qboolean SpiderMine::IsDroppable
	(
	void
	)
	{
	return false;
   }

void SpiderMine::SetOwner
	(
	Sentient *sent
	)

	{
   Detonator   *detonator;
   int         num;

   assert( sent );
	if ( !sent )
		{
		// return to avoid any buggy behaviour
		return;
		}

	Item::SetOwner( sent );

	setOrigin( vec_zero );
   setAngles( vec_zero );

	if ( !viewmodel.length() )
		{
		error( "setOwner", "Weapon without viewmodel set" );
		}

	setModel( viewmodel );

   // Give the owner a detonator and setup the link

   detonator = ( Detonator * )sent->FindItem( "Detonator" );
   
   if ( !detonator )
      {
      detonator = ( Detonator * )sent->giveWeapon( "Detonator" );
      }

   SetDetonator( detonator );


   // Check the world for any spidermines in existence and 
   // if the player owns them, add them to the minelist.
   num = 0;
	while( ( num = G_FindClass( num, "Mine" ) ) != NULL )
		{
		Mine *mine = ( Mine * )G_GetEntity( num );

      if ( mine->IsOwner( sent ) )
         {
         detonator->AddMine( mine );
         mine->SetDetonator( detonator );
         }
      }
	}

void SpiderMine::DoneFiring
   (
   Event *ev
   )

   {
   assert( owner );
	
   weaponstate = WEAPON_HOLSTERED;
   DetachGun();
	StopAnimating();

	if ( owner )
		{
		owner->SetCurrentWeapon( detonator );
		}
   }

void SpiderMine::ChangeToDetonator
   (
   Event *ev
   )

   {   
   if ( owner && detonator->mineList.NumObjects() )
      owner->ChangeWeapon( detonator );
   }

qboolean SpiderMine::ReadyToUse
	(
	void
	)

	{
   Event *ev;

   if ( HasAmmo() )
      {
      return true;
      }
   else
      {
      if ( owner->isClient() )
         {    
         // Check to see if detonator has mines active
         if ( detonator && detonator->mineList.NumObjects() )
            {
            ev = new Event( "use" );
            ev->AddString( "Detonator" );
            owner->PostEvent( ev, 0 );
            }
         }
      return false;
      }
	}

void SpiderMine::SetDetonator
   ( 
   Detonator *det
   )

   {
   detonator = det;
   }

void SpiderMine::Shoot
	(
	Event *ev
	)

	{
 	Vector	pos;
	Vector	dir;
   Mine     *mine;
   Event    *event;

	assert( owner );
	if ( !owner )
		{
		return;
		}

	GetMuzzlePosition( &pos, &dir );


   if ( currentMine )
      {
      event = new Event(EV_Mine_Explode);
      currentMine->ProcessEvent( event );
      }
   else
      {
     	mine = new Mine;
   	mine->Setup( owner, pos, dir );
      mine->SetDetonator( detonator );
      detonator->AddMine( mine );   
      }

	NextAttack( 1.8f );
   }

CLASS_DECLARATION( Weapon, Detonator, NULL);

ResponseDef Detonator::Responses[] =
	{
	   { &EV_Weapon_Shoot,              ( Response )&Detonator::Shoot },
      { &EV_Weapon_DoneFiring,         ( Response )&Detonator::DoneFiring },
      { &EV_Weapon_SecondaryUse,       ( Response )&Detonator::CycleCamera },
      { NULL, NULL }
	};

Detonator::Detonator
   (
   )

   {
#ifdef SIN_DEMO
   PostEvent( EV_Remove, 0 );
   return;
#endif
   SetModels( NULL, "view_detonator.def" );
   SetType( WEAPON_1HANDED );
   currentMine = 0;
   }

void Detonator::RemoveMine
   (
   Mine *mine
   )

   {
   mineList.RemoveObject( MinePtr( mine ) );
   }

void Detonator::AddMine
   (
   Mine *mine
   )

   {
   mineList.AddObject( MinePtr( mine ) );
   }

void Detonator::CycleCamera
   ( 
   Event *ev 
   )

   {
   Player      *client;
   int         numMines;
   Mine        *mine;
   
   assert( owner );
	if ( !owner )
		{
		return;
		}

   numMines = mineList.NumObjects();

   if ( !numMines )
      return;

   currentMine++;
   
   mine = mineList.ObjectAt( ( currentMine % numMines ) + 1 );
   if ( !mine )
      {
      mineList.RemoveObjectAt( ( currentMine % numMines ) + 1 );
      return;
      }
   
   client = ( Player * )( Entity * ) owner;
   if ( client->edict->areanum == mine->edict->areanum )
      client->SetCamera( mine );
   else
      {
      gi.cprintf(client->edict, PRINT_HIGH, "Spidermine %d out of range\n", ( currentMine % numMines ) + 1 );
      }
   }

void Detonator::DoneFiring
   (
   Event *ev
   )

   {
   Weapon *weapon;

	weaponstate = WEAPON_READY;

	if ( owner )
		{
		owner->ProcessEvent( EV_Sentient_WeaponDoneFiring );
		}

   // Change back to spidermines if there is ammo, otherwise change weapons.

   weapon = ( Weapon * )owner->FindItem( "SpiderMine" );
   
   if ( !weapon || !weapon->HasAmmo() )
      {
      weapon = owner->BestWeapon();
      }

   owner->ChangeWeapon( weapon );
   }

void Detonator::Shoot
   (
   Event *ev
   )

   {
   int      num,i;
   Player   *player;
   Entity   *cameraMine;
   Event    *event;
   
   // If the owner is in a camera, the only detonate that mine, 
   // otherwise detonate them all

 	NextAttack( 0.1 );

   if ( owner->isClient() )
      {
      player = ( Player * )( Entity * )owner;

      cameraMine = player->CurrentCamera();
      
      if ( cameraMine && cameraMine->isSubclassOf( Mine ) )
         {
         event = new Event(EV_Mine_Explode);
         cameraMine->PostEvent( event, 0 );
         return;
         }
      }
   
   // Go through all the mines and detonate them 

   num = mineList.NumObjects();

	for( i = num; i >= 1; i-- )
      {
      Event *explodeEvent;
      Mine  *mine;

      mine = mineList.ObjectAt( i );
      explodeEvent = new Event(EV_Mine_Explode);
      mine->PostEvent( explodeEvent, 0 );
      }
   }

qboolean Detonator::IsDroppable
	(
	void
	)
	{
	return false;
   }

qboolean Detonator::AutoChange
   (
   void
   )

   {
   return false;
   }
