//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/turret.cpp                       $
// $Revision:: 35                                                             $
//   $Author:: Aldie                                                          $
//     $Date:: 11/06/98 9:26p                                                 $
//
// 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/turret.cpp                            $
// 
// 35    11/06/98 9:26p Aldie
// Moved spawning of explosion to centroid of turret
// 
// 34    10/21/98 12:10a Aldie
// Added EV_Activate to the responses.
// 
// 33    10/15/98 5:02p Markd
// Fixed turret problems with invulnerability
// 
// 32    10/15/98 3:37p Markd
// Added forcefield to turrets with no health
// 
// 31    10/14/98 12:13a Aldie
// Don't need to mess with AttackFinished for turrets
// 
// 30    10/13/98 7:14p Aldie
// Fixed a warning when no thread was found
// 
// 29    10/13/98 2:30p Aldie
// Fix attack_finished to update the weapon as well.
// 
// 28    10/09/98 4:54p Markd
// Changed script code to ExecuteThread
// 
// 27    9/30/98 5:39p Aldie
// Added showinfo command
// 
// 26    9/30/98 4:36p Aldie
// Fixed some weirdness with turrets.
// 
// 25    9/30/98 3:42p Markd
// put in bulletproofing for start thread functions
// 
// 24    9/23/98 5:19p Markd
// Put DAMAGE_NO in killed functions of these classes
// 
// 23    9/22/98 4:57p Aldie
// Moved lagtime check right before firing
// 
// 22    9/20/98 9:09p Aldie
// Fixed patience and lagtime for turrets
// 
// 21    9/20/98 5:13p Aldie
// Changed some stuff with the thread and targeting when the turret sees a
// player
// 
// 20    9/19/98 6:14p Aldie
// Added trigger targets and thread to turrets
// 
// 19    9/15/98 6:45p Aldie
// Fixed some sound aliases
// 
// 18    9/01/98 3:05p Markd
// Rewrote explosion code
// 
// 17    8/29/98 9:46p Jimdose
// Added call info to G_Trace
// 
// 16    8/29/98 5:27p Markd
// added specialfx, replaced misc with specialfx where appropriate
// 
// 15    8/18/98 11:08p Markd
// Added new Alias System
// 
// 14    7/26/98 6:54a Aldie
// Removed setting of takedamage.  This is now the responsibility of the
// subclass
// 
// 13    7/25/98 7:19p Aldie
// Client side explosion
// 
// 12    7/23/98 7:12p Aldie
// Update turret stuff so it triggers it's targets
// 
// 11    7/23/98 12:17p Aldie
// Removed takedamage=no from the down event
// 
// 10    7/22/98 8:40p Aldie
// Deactivate a turret will make it go down.
// 
// 9     7/22/98 5:16p Aldie
// Changed general behavior
// 
// 8     7/17/98 4:42p Aldie
// Made "fire" call an event when it's through
// 
// 7     7/10/98 5:39p Aldie
// Moved some settings from base class to subclasses
// 
// 6     7/10/98 3:07p Aldie
// Activate and deactivate turrets.
// 
// 5     7/07/98 4:11p Aldie
// New version of turret
//
// DESCRIPTION:
// Generic turret

#include "g_local.h"
#include "turret.h"
#include "explosion.h"
#include "misc.h"
#include "weapon.h"
#include "specialfx.h"

Event EV_Turret_GoUp( "raise" );
Event EV_Turret_GoDown( "lower" );
Event EV_Turret_Down( "down" );
Event EV_Turret_Seek( "seek" );
Event EV_Turret_Turn( "turn" );
Event EV_Turret_EndSeek( "endseek" );
Event EV_Turret_Attack( "attack" );
Event EV_Turret_CheckVicinity( "checkvicinity" );
Event EV_Turret_AttackStart( "attack_start" );
Event EV_Turret_AttackFinished( "attack_finished" );
Event EV_Turret_Activate( "activate" );
Event EV_Turret_Deactivate( "deactivate" );
Event EV_Turret_Lagtime( "lagtime" );
Event EV_Turret_SetSightTime( "setsighttime" );

CLASS_DECLARATION( Sentient, Turret, NULL )

ResponseDef Turret::Responses[] =
	{
	   { &EV_Turret_GoUp,				( Response )&Turret::GoUp },
	   { &EV_Turret_GoDown,				( Response )&Turret::GoDown },
		{ &EV_Turret_Down,				( Response )&Turret::Down },
		{ &EV_Turret_Seek,				( Response )&Turret::Seek },
		{ &EV_Turret_Turn,				( Response )&Turret::Turn },
		{ &EV_Turret_EndSeek,			( Response )&Turret::EndSeek },
		{ &EV_Turret_Attack,				( Response )&Turret::Fire },
		{ &EV_Turret_CheckVicinity,	( Response )&Turret::CheckVicinity },
		{ &EV_Turret_AttackStart,		( Response )&Turret::AttackStart },
		{ &EV_Turret_AttackFinished,	( Response )&Turret::AttackFinished },
      { &EV_Turret_Activate,	      ( Response )&Turret::Activate },
      { &EV_Turret_Deactivate,	   ( Response )&Turret::Deactivate },
      { &EV_Turret_Lagtime,	      ( Response )&Turret::Lagtime },
      { &EV_Turret_SetSightTime,    ( Response )&Turret::SetSightTime },
		{ &EV_Pain,							( Response )&Turret::Pain },
		{ &EV_Killed,						( Response )&Turret::Killed },
		{ &EV_Activate,					( Response )&Turret::Activate },
      { NULL, NULL }
	};

void Turret::Lagtime
   (
   Event *ev
   )

   {
   lagtime = ev->GetFloat( 1 );
   }

Turret::Turret
	(
	void
	)

	{
   str            hstr;

	base				= 0;
	enemy				= 0;
	lastSightTime	= 0;
	attacking		= false;
	turning			= false;
	active			= false;
	turntime			= 0;

   thread         = G_GetStringArg( "sightthread", "" );
   sight_target   = G_GetStringArg( "sighttarget", "" );
   patience       = G_GetFloatArg( "patience", 1 );
   lagtime        = G_GetFloatArg( "lagtime", 0 );
   firetime       = 0;

   takedamage		= DAMAGE_YES;

   hstr = G_GetStringArg( "health" );
   if ( hstr != "" )
      {
      health = G_GetFloatArg( "health" );
      if ( health )
         {
         flags |= FL_SPARKS;
         }
      else
         {
         flags |= FL_FORCEFIELD;
         }
      }
   else
      {
      flags |= FL_SPARKS;
      }

   if ( Targeted() )
      activated = false;
   else
      activated = true;
	}

void Turret::Activate
   (
   Event *ev
   )

   {
   activated = true;
   firetime = level.time + lagtime;
   }

void Turret::Deactivate
   (
   Event *ev
   )

   {
   activated = false;
   ProcessEvent( EV_Turret_GoDown );
   }

void Turret::Killed
	(
	Event *ev
	)

	{
   Entity      *attacker;
   Vector      dir;
   int         power;
   const char  *name;
   int         num;
   Entity      *ent;
   Event       *event;

   takedamage = DAMAGE_NO;
   attacker		= ev->GetEntity( 1 );
	power    	= ev->GetInteger( 2 );
	if ( power < 40 )
		{
		power = 40;
		}

	ProcessEvent( EV_BreakingSound );

   dir = worldorigin - attacker->worldorigin;
	CreateExplosion( centroid, 30, 0.5, true, this, attacker, this );
   //
   // kill the killtargets
   //
	name = KillTarget();
	if ( name && strcmp( name, "" ) )
		{
		num = 0;
		do
			{
			num = G_FindTarget( num, name );
			if ( !num )
				{
				break;
				}

			ent = G_GetEntity( num );
			ent->PostEvent( EV_Remove, 0 );
			} 
		while ( 1 );
		}
		
   //
   // fire targets
   //
	name = Target();
	if ( name && strcmp( name, "" ) )
		{
		num = 0;
		do
			{
			num = G_FindTarget( num, name );
			if ( !num )
				{
				break;
				}

			ent = G_GetEntity( num );
		
			event = new Event( EV_Activate );
			event->AddEntity( attacker );
			ent->PostEvent( event, 0 );
			}
		while ( 1 );
		}

	ProcessEvent( EV_Turret_GoDown );
   PostEvent( EV_Remove, 0 );
	}

qboolean Turret::CanSee
	(
	Entity *ent
	)

	{
	Vector delta;
	Vector start;
	Vector end;
	trace_t trace;

	start = worldorigin + gunoffset;
	end = ( ent->absmin + ent->absmax ) * 0.5;

	// Check if he's visible
	trace = G_Trace( start, vec_zero, vec_zero, end, this, MASK_OPAQUE, "Turret::CanSee" );
	if ( trace.fraction == 1.0 || trace.ent == ent->edict )
		{
		return true;
		}

	return false;
	}

int Turret::Range
	( 
	float dist
	)

   {
	if ( dist > wakeupdistance )
		{
		return TURRET_OUTOFRANGE;
		}
	if ( dist > firingdistance )
		{
		return TURRET_WAKEUPRANGE;
		}
	return TURRET_FIRERANGE;
	}

float Turret::Distance
	( 
	Entity *targ 
	)

   {
	Vector delta;
	
	delta = worldorigin + gunoffset - ( targ->absmin + targ->absmax ) * 0.5;
	return delta.length();
	}

void Turret::Pain
	(
	Event *ev
	)

	{
	Entity	*attacker;
	Vector	dir;

	if ( takedamage == DAMAGE_NO )
		{
		return;
		}

	attacker		= ev->GetEntity( 2 );

   dir = worldorigin - attacker->worldorigin;

   if ( attacker != this )
	   enemy = attacker->entnum;
	}

qboolean Turret::FindTarget
	( 
	void 
	)

   {
   Entity	*ent;
	edict_t	*ed;
	int		i;
	float		dist;
	float		bestdist;
	Entity	*bestent;

	bestent = NULL;
	bestdist = wakeupdistance + 1;
	for( i = 0; i < game.maxclients; i++ )
		{
		ed = &g_edicts[ 1 + i ];
		if ( !ed->inuse || !ed->entity )
			{
			continue;
			}

		ent = ed->entity;
	   if ( ( ent->health < 0 ) || ( ent->flags & FL_NOTARGET ) )
			{
			continue;
			}

		dist = Distance( ent );
		if ( Range( dist ) == TURRET_OUTOFRANGE )
			{
			continue;
			}

		if ( ( dist < bestdist ) && CanSee( ent ) )
			{
			bestent = ent;
			bestdist = dist;
			}
		}
	
	if ( bestent )
		{
		enemy = bestent->entnum;
		return true;
		}

	enemy = 0;
	return false;
	}

//FIXME
// make this a common function
float Turret::AdjustAngle
	(
	float maxadjust,
	float currangle,
	float targetangle
	)

   {
   float dangle;
	float magangle;

	dangle = currangle - targetangle;

	if ( dangle )
		{
		magangle = ( float )fabs( dangle );
		if ( magangle < maxadjust )
			{
			currangle = targetangle;
			}
		else 
			{
			if ( magangle > 180.0f  )
				{
				maxadjust = -maxadjust;
				}
			if ( dangle > 0 )
				{
				maxadjust = -maxadjust;
				}
			currangle += maxadjust;
			}
		}

	while( currangle >= 360.0f )
		{
		currangle -= 360.0f;
		}

	while( currangle < 0.0f )
		{
		currangle += 360.0f;
		}

	return currangle;
	}

void Turret::Seek
	(
	Event *ev
	)

	{
	Entity	*ent;
	Vector	v;
	Vector	s;
	float		t;
	int		range;
	Vector	f;
	
	active = true;
	ent = NULL;
	if ( enemy )
		{
		ent = G_GetEntity( enemy );
		if ( ( !ent ) || ( ent->health <= 0 ) || ( ent->flags & FL_NOTARGET ) )
			{
			enemy = 0;
			ent = NULL;
			}
		else
			{
			range = Range( Distance( ent ) );
			}
		}

	if ( ( lastSightTime ) && ( ( lastSightTime + patience ) < level.time ) )
		{
		ProcessEvent( EV_Turret_GoDown );
		}

	if ( !enemy )
		{
		FindTarget();
		PostEvent( EV_Turret_Seek, FRAMETIME * 2 );
		return;
		}

	if ( ( range != TURRET_OUTOFRANGE ) && ent && CanSee( ent ) )
		{
		lastSightTime = level.time;

		v = ent->worldorigin - worldorigin;
		neworientation = v.toYaw();
		}

	if ( ( angles.y != neworientation ) && !turning )
		{
		ProcessEvent( EV_Turret_Turn );
		}

	t = worldorigin.z + gunoffset.z;
	if ( ( !attacking ) && ( t < ent->absmax.z ) && ( t > ent->absmin.z ) && 
		( range == TURRET_FIRERANGE ) )
		{
      // Allow some freetime to let player get somewhere before turret shoots
      if ( level.time < firetime )
         {
    	   PostEvent( EV_Turret_Seek, FRAMETIME * 2 );
         return;
         }
		ProcessEvent( EV_Turret_Attack );
		}

	PostEvent( EV_Turret_Seek, FRAMETIME * 2 );
	}

void Turret::Fire
	(
	Event *ev
	)

	{
   RandomAnimate( "fire", EV_Turret_AttackFinished );
	}

void Turret::Turn
	(
	Event *ev
	)

	{
	if ( angles[ 1 ] != neworientation )
		{
		turntime = level.time + 0.2;
		if ( !turning )
			{
			ProcessEvent( EV_MachineSound );
			RandomSound( "snd_seek", 1 );
			turning = true;
			}
		}
	else if ( turntime < level.time )
		{
		turning = false;
		ProcessEvent( EV_MachineSound );
		RandomSound( "snd_stop", 1 );
		return;
		}

	angles[ 1 ] = AdjustAngle( 12, angles[ 1 ], neworientation );	
	setAngles( angles );
	PostEvent( EV_Turret_Turn, FRAMETIME );
	}

void Turret::EndSeek
	(
	Event *ev
	)

	{
   RandomSound( "snd_stop", 1 );
	CancelEventsOfType( EV_Turret_Seek );
	CancelEventsOfType( EV_Turret_Turn );
   CancelEventsOfType( EV_Turret_Attack );
   ProcessEvent( EV_Turret_AttackFinished );
	turning = false;
	lastSightTime = 0;
	enemy = false;
	active = false;
	}

void Turret::CheckVicinity
	(
	Event *ev
	)

	{
	Event *e;

   if ( activated  && FindTarget() )
      {
		e = new Event( EV_Turret_GoUp );
		e->AddEntity( G_GetEntity( enemy ) );
		ProcessEvent( e );
      }
	}

void Turret::SetSightTime
   (
   Event *ev 
   )

   {
   lastSightTime = level.time;
   }

void Turret::GoUp
	(
	Event *ev
	)

	{
	Entity      *b;
	Entity      *ent;
   const char  *name;
   int         num;
   Event       *event;

	lastSightTime = level.time;
	ent = ev->GetEntity( 1 );
	enemy = ent->entnum;

	if ( !active )
		{
		active = true;
		}

   if ( base )
      {
   	b = G_GetEntity( base );
	   assert( b );
	   if ( b )
		   {
		   b->ProcessEvent( EV_Turret_GoUp );
	   	}
      }
   
   firetime = level.time + lagtime;

   RandomAnimate( "raise", EV_Turret_SetSightTime );

   // Turret just woke up, trigger targets and call thread.
 	name = sight_target.c_str();
	if ( name && strcmp( name, "" ) )
		{
		num = 0;
		do
			{
			num = G_FindTarget( num, name );
			if ( !num )
				{
				break;
				}

			ent = G_GetEntity( num );
		
			event = new Event( EV_Activate );
			event->AddEntity( G_GetEntity( enemy ) );
			ent->PostEvent( event, 0 );
			}
		while ( 1 );
		}

   if ( thread.length() && !ExecuteThread( thread ) )
      {
      warning( "GoUp", "%s not found.", thread.c_str() );
      }
	}

void Turret::GoDown
	(
	Event *ev
	)

	{
	Entity *b;

	if ( base )
      {
      b = G_GetEntity( base );
	   assert( b );
	   if ( b )
		   {
		   b->ProcessEvent( EV_Turret_GoDown );
		   }
      }
	ProcessEvent( EV_Turret_EndSeek );
	RandomAnimate( "lower", EV_Turret_Down );
	}

void Turret::Down
	(
	Event *ev
	)

	{
	}

void Turret::AttackStart
	(
   Event *ev
	)

	{
	attacking = true;
	}

void Turret::AttackFinished
	(
	Event *ev
	)

	{
	attacking = false;
   }

