//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/vehicle.cpp                      $
// $Revision:: 56                                                             $
//   $Author:: Aldie                                                          $
//     $Date:: 3/17/99 4:00p                                                  $
//
// Copyright (C) 1997 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/vehicle.cpp                           $
// 
// 56    3/17/99 4:00p Aldie
// CTF Update
// 
// 55    3/11/99 9:24p Markd
// don't let alternate gravaxis people enter vehicle
// 
// 54    3/05/99 6:36p Markd
// Fixed Tire issues
// 
// 53    3/05/99 5:51p Markd
// made vehicles respawn in CTF, fixed up turrets a bit more
// 
// 52    2/26/99 5:53p Markd
// fixed some stuff for turrets
// 
// 51    2/19/99 7:49p Markd
// implemented turret for CTF
// 
// 50    11/16/98 8:26p Jimdose
// made vehicles check for water and lava
// 
// 49    10/23/98 12:26a Markd
// Took out some vehicle coasting
// 
// 48    10/22/98 4:45p Markd
// made vehicles more responsive
// 
// 47    10/20/98 7:52p Markd
// Fixed drivable vehicles
// 
// 46    10/19/98 12:07a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
// 
// 45    10/18/98 9:01p Markd
// Added ShowWeapon method and showweapon variable
// 
// 44    10/17/98 8:14p Jimdose
// Changed Damage to DamgeEvent
// 
// 43    10/15/98 11:53p Markd
// added jumping ability to vehicles
// 
// 42    10/08/98 7:17p Markd
// added steerinplace and stuff
// 
// 41    10/06/98 10:17p Markd
// changed some vehicle physics stuff
// 
// 40    10/05/98 12:28a Jimdose
// Moved angledist to q_shared
// 
// 39    9/22/98 5:20p Markd
// Put in new consolidated gib function
// 
// 38    9/20/98 7:01p Markd
// made vehicle auto rotate its bounding box
// 
// 37    9/16/98 10:07p Markd
// working on vehicles
// 
// 36    9/16/98 4:29p Markd
// Put in scaled bounding boxes for vehicles
// 
// 35    9/16/98 11:01a Markd
// Fixed some vehicle stuff that broke the intro and non-drivable vehicles
// 
// 34    9/15/98 4:38p Markd
// Removed auto-jumping and other stuff
// 
// 33    9/15/98 4:30p Markd
// Updated vehicle code
// 
// 32    9/14/98 9:56p Markd
// put in sounds
// 
// 31    9/14/98 6:13p Markd
// working on vehicles
// 
// 30    9/14/98 3:30p Markd
// intermediate checkin
// 
// 29    9/09/98 3:04p Markd
// Added IsDrivable also setup some default pm_types etc.
// 
// 28    8/29/98 9:47p Jimdose
// Changed calls to gi.trace to G_Trace
// 
// 27    7/25/98 4:39p Jimdose
// Added Driver
// 
// 26    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
// 
// 25    7/21/98 1:10p Aldie
// Added meansofdeath to obituaries
// 
// 24    7/20/98 12:09p Markd
// Added vehicleanim support and driveroffset stuff
// 
// 23    7/19/98 7:34p Markd
// Added proper vehicle exit 
// 
// 22    7/19/98 5:41p Markd
// Forced changing of weapons
// 
// 21    7/19/98 3:43p Markd
// Took out special damage handling
// 
// 20    7/18/98 11:26p Markd
// Fixed damage, forgot to register Damage Event
// 
// 19    7/18/98 11:15p Markd
// Added damage ability
// 
// 18    7/18/98 3:57p Markd
// Put in InVehicle support
// 
// 17    7/17/98 4:03p Markd
// Added a bunch of functionality to the vehicle class
// 
// 16    6/23/98 11:45p Jimdose
// Got rid of some warnings
// 
// 15    6/23/98 2:58p Jimdose
// Better (but still bad) driving code
// 
// 14    6/22/98 2:04p Jimdose
// Reintegrated into sin
// 
// 12    11/19/97 8:07p Jimdose
// Fixed bug where vehicle would "heal" monsters if the player backed into them
// with a vehicle.
// 
// 11    11/12/97 5:11p Jimdose
// Enter and Exit events are now defined in player
// 
// 10    11/01/97 2:09p Jimdose
// Fixed rover1
// 
// 9     11/01/97 2:02p Jimdose
// Worked on vehicles
// 
// 8     10/31/97 9:03p Jimdose
// Worked on vehicles
// 
// 7     10/30/97 11:48p Jimdose
// Worked on vehicle code
// 
// 6     10/29/97 8:49p Jimdose
// Began work on steering
// 
// 5     10/27/97 3:29p Jimdose
// Removed dependency on quakedef.h
// 
// 4     10/09/97 4:26p Jimdose
// Worked on vehicle code
// 
// 3     10/08/97 9:30p Jimdose
// Refined Vehicle movement.
// 
// 2     10/08/97 6:03p Jimdose
// Began vehicle support.
// 
// DESCRIPTION:
// Script controlled Vehicles.
// 

#include "g_local.h"
#include "scriptslave.h"
#include "vehicle.h"
#include "player.h"
#include "specialfx.h"
#include "explosion.h"
#include "earthquake.h"
#include "gibs.h"
#include "item.h"

Event EV_Vehicle_Start( "start" );
Event EV_Vehicle_Enter( "enter" );
Event EV_Vehicle_Exit( "exit" );
Event EV_Vehicle_Drivable( "drivable" );
Event EV_Vehicle_UnDrivable( "undrivable" );
Event EV_Vehicle_Jumpable( "canjump" );
Event EV_Vehicle_Lock( "lock" );
Event EV_Vehicle_UnLock( "unlock" );
Event EV_Vehicle_SeatAnglesOffset( "seatanglesoffset" );
Event EV_Vehicle_SeatOffset( "seatoffset" );
Event EV_Vehicle_DriverAnimation( "driveranim" );
Event EV_Vehicle_SetWeapon( "setweapon" );
Event EV_Vehicle_SetSpeed( "vehiclespeed" );
Event EV_Vehicle_SetTurnRate( "turnrate" );
Event EV_Vehicle_SteerInPlace( "steerinplace" );
Event EV_Vehicle_ShowWeapon( "showweapon" );


CLASS_DECLARATION( ScriptModel, VehicleBase, NULL );

ResponseDef VehicleBase::Responses[] =
	{
		{ NULL, NULL }
	};

VehicleBase::VehicleBase()
	{
   takedamage = DAMAGE_NO;
	showModel();
   setSolidType( SOLID_NOT );
	setMoveType( MOVETYPE_NONE );
   setOrigin( origin + Vector( "0 0 30") );

   //
   // we want the bounds of this model auto-rotated
   //
   flags |= FL_ROTATEDBOUNDS;

   //
   // rotate the mins and maxs for the model
   //
   setSize( mins, maxs );

	vlink = NULL;
	offset = "0 0 0";
	}

CLASS_DECLARATION( VehicleBase, BackWheels, "script_wheelsback" );

ResponseDef BackWheels::Responses[] =
	{
		{ NULL, NULL }
	};

CLASS_DECLARATION( VehicleBase, FrontWheels, "script_wheelsfront" );

ResponseDef FrontWheels::Responses[] =
	{
		{ NULL, NULL }
	};

CLASS_DECLARATION( VehicleBase, Vehicle, "script_vehicle" );

ResponseDef Vehicle::Responses[] =
	{
	   { &EV_Blocked,					      ( Response )&Vehicle::VehicleBlocked },
	   { &EV_Touch,					      ( Response )&Vehicle::VehicleTouched },
	   { &EV_Use,						      ( Response )&Vehicle::DriverUse },
	   { &EV_Vehicle_Start,			      ( Response )&Vehicle::VehicleStart },
	   { &EV_Vehicle_Drivable,		      ( Response )&Vehicle::Drivable },
	   { &EV_Vehicle_UnDrivable,	      ( Response )&Vehicle::UnDrivable },
	   { &EV_Vehicle_Jumpable,	         ( Response )&Vehicle::Jumpable },
	   { &EV_Vehicle_SeatAnglesOffset,  ( Response )&Vehicle::SeatAnglesOffset },
	   { &EV_Vehicle_SeatOffset,        ( Response )&Vehicle::SeatOffset },
	   { &EV_Vehicle_Lock,		         ( Response )&Vehicle::Lock },
	   { &EV_Vehicle_UnLock,	         ( Response )&Vehicle::UnLock },
      { &EV_Vehicle_SetWeapon,         ( Response )&Vehicle::SetWeapon },
      { &EV_Vehicle_DriverAnimation,   ( Response )&Vehicle::DriverAnimation },
      { &EV_Vehicle_SetSpeed,          ( Response )&Vehicle::SetSpeed },
      { &EV_Vehicle_SetTurnRate,       ( Response )&Vehicle::SetTurnRate },
      { &EV_Vehicle_SteerInPlace,      ( Response )&Vehicle::SteerInPlace },
      { &EV_Vehicle_ShowWeapon,        ( Response )&Vehicle::ShowWeaponEvent },
		{ NULL, NULL }
	};

Vehicle::Vehicle()
	{
   takedamage = DAMAGE_YES;
   seatangles = vec_zero;
   driveroffset = vec_zero;
   seatoffset = vec_zero;
	driver = 0;
	lastdriver = 0;
	currentspeed = 0;
	turnangle = 0;
	turnimpulse = 0;
	moveimpulse = 0;
	jumpimpulse = 0;
   conesize = 75;
   hasweapon = false;
   locked = false;
   steerinplace = false;
   drivable = false;
   jumpable = false;
   showweapon = false;
   setSolidType( SOLID_BBOX );
   flags |= FL_SPARKS|FL_DIE_TESSELATE|FL_DIE_EXPLODE|FL_DARKEN;
   gravity = 1;
   mass = size.length() * 10;

   // 
   // we use this to signify the init state of the angles
   //
	health = G_GetFloatArg( "health", 1000 );
	speed = G_GetFloatArg( "speed", 600 );
	maxturnrate = G_GetFloatArg( "maxturnrate", 40.0f );
	PostEvent( EV_Vehicle_Start, 0.2f );
	}

void Vehicle::VehicleStart
	(
	Event *ev
	)

	{
	Entity *ent;
	VehicleBase *last;
   vec3_t	trans[ 3 ];
   vec3_t   orient;
   Vector   drivemins, drivemaxs;
   int		groupindex;
   int		tri_num;
   float    max;
   float    width,height;


	last = this;

	for( ent = G_NextEntity( world ); ent != NULL; ent = G_NextEntity( ent ) )
		{
		if ( ( ent != this ) && ( ent->isSubclassOf( VehicleBase ) ) )
			{
			if ( ( ent->absmax.x >= absmin.x ) && ( ent->absmax.y >= absmin.y ) && ( ent->absmax.z >= absmin.z ) && 
				( ent->absmin.x <= absmax.x ) && ( ent->absmin.y <= absmax.y ) && ( ent->absmin.z <= absmax.z ) )
				{
				last->vlink = ( VehicleBase * )ent;
				last = ( VehicleBase * )ent;
				last->offset = last->worldorigin - worldorigin;
            last->offset = getLocalVector( last->offset );
            last->edict->s.scale *= edict->s.scale;
				}
			}
		}

	last->vlink = NULL;

   //
   // get the seat offset
   //
   if ( gi.GetBoneInfo( edict->s.modelindex, "seat", &groupindex, &tri_num, orient ) )
		{
      gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim, edict->s.frame, 
			edict->s.scale, trans, driveroffset.vec3() );
      }
   driveroffset += seatoffset * edict->s.scale;
   SetDriverAngles( worldangles + seatangles );

   max_health = health;

   //
   // calculate drive mins and maxs
   //
   max = 0;
   if ( fabs( mins[ 0 ] ) > max )
      max = fabs( mins[ 0 ] );
   if ( fabs( maxs[ 0 ] ) > max )
      max = fabs( maxs[ 0 ] );
   if ( fabs( mins[ 1 ] ) > max )
      max = fabs( mins[ 1 ] );
   if ( fabs( maxs[ 1 ] ) > max )
      max = fabs( maxs[ 1 ] );
   drivemins = Vector( -max, -max, mins[ 2 ] ) * edict->s.scale;
   drivemaxs = Vector( max, max, maxs[ 2 ] ) * edict->s.scale;

   width = maxs[ 1 ] - mins[ 1 ];
   height = maxs[ 0 ] - mins[ 0 ];

   maxtracedist = height;

   Corners[ 0 ][ 0 ] = -(width/4); 
   Corners[ 0 ][ 1 ] = (height/4);
   Corners[ 0 ][ 2 ] = 0; 

   Corners[ 1 ][ 0 ] = (width/4); 
   Corners[ 1 ][ 1 ] = (height/4);
   Corners[ 1 ][ 2 ] = 0; 

   Corners[ 2 ][ 0 ] = -(width/4); 
   Corners[ 2 ][ 1 ] = -(height/4);
   Corners[ 2 ][ 2 ] = 0; 

   Corners[ 3 ][ 0 ] = (width/4); 
   Corners[ 3 ][ 1 ] = -(height/4);
   Corners[ 3 ][ 2 ] = 0; 
   if ( drivable )
      {
      // drop everything back to the floor
      droptofloor( 64 );
      Postthink();
      }
   last_origin = worldorigin;
   setSize( drivemins, drivemaxs );
   startorigin = worldorigin;
	}

void Vehicle::Drivable
	(
	Event *ev
	)

	{
	setMoveType( MOVETYPE_NONE );
   drivable = true;
	}

void Vehicle::UnDrivable
	(
	Event *ev
	)

	{
	setMoveType( MOVETYPE_PUSH );
   drivable = false;
	}

void Vehicle::Jumpable
	(
	Event *ev
	)

	{
   jumpable = true;
	}

void Vehicle::Lock
	(
	Event *ev
	)

	{
   locked = true;
	}

void Vehicle::UnLock
	(
	Event *ev
	)

	{
   locked = false;
	}

void Vehicle::SteerInPlace
	(
	Event *ev
	)

	{
   steerinplace = true;
	}

void Vehicle::SeatAnglesOffset
	(
	Event *ev
	)

	{
   seatangles = ev->GetVector( 1 );
	}

void Vehicle::SeatOffset
	(
	Event *ev
	)

	{
   seatoffset = ev->GetVector( 1 );
	}

void Vehicle::SetWeapon
	(
	Event *ev
	)

	{
   showweapon = true;
   hasweapon = true;
   weaponName = ev->GetString( 1 );
	}

void Vehicle::ShowWeaponEvent
	(
	Event *ev
	)

	{
   showweapon = true;
	}

void Vehicle::DriverAnimation
	(
	Event *ev
	)

	{
   driveranim = ev->GetString( 1 );
	}

qboolean Vehicle::HasWeapon
	(
   void
	)

	{
   return hasweapon;
	}

qboolean Vehicle::ShowWeapon
	(
   void
	)

	{
   return showweapon;
	}

void Vehicle::SetDriverAngles
	(
   Vector angles
   )
   {
   int i;

   if ( !driver )
      return;

	for( i = 0; i < 3; i++ )
		{
		driver->client->ps.pmove.delta_angles[ i ] = ANGLE2SHORT( angles[ i ] - driver->client->resp.cmd_angles[ i ] );
		}
   }

/*
=============
CheckWater
=============
*/
void Vehicle::CheckWater
   (
   void
   )

   {
	Vector  point;
	int	  cont;
	int	  sample1;
	int	  sample2;
	VehicleBase *v;
	const gravityaxis_t &grav = gravity_axis[ gravaxis ];

   unlink();
	v = this;
	while( v->vlink )
		{
		v = v->vlink;
      v->unlink();
		}

   if ( driver )
      {
      driver->unlink();
      }

   //
   // get waterlevel
   //
	waterlevel = 0;
	watertype = 0;

	sample2 = maxs[ grav.z ] - mins[ grav.z ];
	sample1 = sample2 / 2;

	point[ grav.x ] = worldorigin[ grav.x ];
	point[ grav.y ] = worldorigin[ grav.y ];
	point[ grav.z ] = worldorigin[ grav.z ] + mins[ grav.z ] + grav.sign;
	cont = gi.pointcontents( point.vec3() );

	if ( cont & MASK_WATER )
	   {
		watertype = cont;
		waterlevel = 1;
		point[ grav.z ] = worldorigin[ grav.z ] + mins[ grav.z ] + sample1;
		cont = gi.pointcontents( point.vec3() );
		if ( cont & MASK_WATER )
		   {
			waterlevel = 2;
			point[ grav.z ] = worldorigin[ grav.z ] + mins[ grav.z ] + sample2;
			cont = gi.pointcontents( point.vec3() );
			if ( cont & MASK_WATER )
            {
				waterlevel = 3;
            }
		   }
	   }

   link();
	v = this;
	while( v->vlink )
		{
		v = v->vlink;
      v->link();
		}

   if ( driver )
      {
      driver->link();
      driver->waterlevel = waterlevel;
      driver->watertype = watertype;
      }
   }
   
/*
=============
WorldEffects
=============
*/
void Vehicle::WorldEffects
	(
	void
	)

	{
   //
   // Check for earthquakes
   //
   if ( groundentity && ( level.earthquake > level.time ) )
      {
      velocity += Vector(EARTHQUAKE_STRENGTH*G_CRandom(),EARTHQUAKE_STRENGTH*G_CRandom(),fabs(50*G_CRandom()));
      }

   //
   // check for lava
   //
   if ( watertype & CONTENTS_LAVA )
      {
   	Damage( world, world, 20 * waterlevel, worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_LAVA, -1, -1, 1.0f );
      }
	}

void Vehicle::DriverUse
	(
	Event *ev
	)

	{
	Event *event;
	Entity *other;	
   Sentient *sent;

	other = ev->GetEntity( 1 );
	if ( !other || !other->isSubclassOf( Sentient ) )
		{
		return;
		}

   if ( other->gravaxis != gravaxis )
      {
      return;
      }

   sent = ( Sentient * )other;
	if ( driver )
		{
      int height;
      int ang;
      Vector angles;
      Vector forward;
      Vector pos;
      float ofs;
      trace_t trace;

		if ( other != driver )
			{
			return;
			}

      if ( locked )
         return;

      //
      // place the driver on the ground
      //
      ofs = size.length() * 0.5f;
      for ( height = 0; height < 100; height += 16 )
         {
         for ( ang = 0; ang < 360; ang += 30 )
            {
            angles[ 1 ] = driver->worldangles[ 1 ] + ang + 90;
         	angles.AngleVectors( &forward, NULL, NULL );
            pos = worldorigin + (forward * ofs);
            pos[2] += height;
      		trace = G_Trace( pos, driver->mins, driver->maxs, pos, NULL, MASK_PLAYERSOLID, "Vehicle::DriverUse 1" );
            if ( !trace.allsolid )
               {
               Vector end;

               end = pos;
               end[ 2 ] -= 128;
         		trace = G_Trace( pos, driver->mins, driver->maxs, end, NULL, MASK_PLAYERSOLID, "Vehicle::DriverUse 2" );
               if ( trace.fraction < 1.0f )
                  {
                  driver->setOrigin( pos );
                  goto foundpos;
                  }
               }
            }
         }

      // if we are in CTF choose a place by default
      if ( ctf->value )
         {
         pos = worldorigin;
         pos[ 2 ] += 192;
         driver->setOrigin( pos );
         goto foundpos;
         }
      return;

foundpos:

		turnimpulse = 0;
		moveimpulse = 0;
		jumpimpulse = 0;

		//driver->unbind();
		event = new Event( EV_Vehicle_Exit );
		event->AddEntity( this );
		driver->ProcessEvent( event );
      if ( hasweapon )
         {
         driver->takeWeapon( weaponName.c_str() );
         }
      if ( drivable )
         {
         edict->s.sound = 0;
         RandomSound( "snd_dooropen", 1, CHAN_BODY );
         RandomSound( "snd_stop", 1, CHAN_VOICE );
         driver->setSolidType( SOLID_BBOX );

		 /* FS: Restore the FOV */
         ev = new Event( EV_Player_RestoreFov );
         other->PostEvent( ev, 0.1f );
         }

		driver = NULL;
      //if ( drivable )
   	//   setMoveType( MOVETYPE_NONE );
		}
	else
		{
		driver = ( Sentient * )other;
		lastdriver = driver;

      if ( drivable )
   	   setMoveType( MOVETYPE_VEHICLE );
      if ( hasweapon )
         {
         Weapon *weapon;

         weapon = driver->giveWeapon( weaponName.c_str() );
         if ( weapon )
            {
            driver->ForceChangeWeapon( weapon );
            }
         else
            {
            return;
            }
         }
      if ( drivable )
         {
         RandomSound( "snd_doorclose", 1, CHAN_BODY );
         RandomSound( "snd_start", 1, CHAN_VOICE );
         driver->setSolidType( SOLID_NOT );

		 /* FS: Most annoying shit of all time.  If you got crazy fov, you get crazy (really crazy!) FOV for the vehicles */
         ev = new Event( EV_Player_SaveFov );
         other->ProcessEvent( ev );

         ev = new Event( EV_Player_Fov );
         ev->AddFloat( 90.0f );
         other->ProcessEvent( ev );
         }

		event = new Event( EV_Vehicle_Enter );
		event->AddEntity( this );
      if ( driveranim.length() )
         event->AddString( driveranim );
		driver->ProcessEvent( event );

		//driver->bind( this );
		//offset = other->origin;
		offset = other->worldorigin - worldorigin;

		flags	|= FL_POSTTHINK;
      SetDriverAngles( worldangles + seatangles );
		}
	}

qboolean Vehicle::Drive
	(
	usercmd_t *ucmd
	)

	{
	if ( !driver || !driver->isClient() )
		{
		return false;
		}

   if ( !drivable )
      {
   	driver->client->ps.pmove.pm_type = PM_INVEHICLE;
      ucmd->forwardmove = 0;
      ucmd->sidemove = 0;
      return false;
      }

	driver->client->ps.pmove.pm_type = PM_LOCKVIEW;
   driver->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;

	moveimpulse = ( ( float )ucmd->forwardmove ) * 3;
	turnimpulse = ( ( float )-ucmd->sidemove ) * 0.5;
	jumpimpulse = ( ( float )ucmd->upmove * gravity ) / 350;
   if ( ( jumpimpulse < 0 ) || ( !jumpable ) )
      jumpimpulse = 0;

   buttons = ucmd->buttons;

	turnimpulse += 2*angledist( SHORT2ANGLE( ucmd->angles[ 1 ] ) - driver->client->resp.cmd_angles[ 1 ] );

   cmd_angles.x = 2*angledist( SHORT2ANGLE( ucmd->angles[ 0 ] ) - driver->client->resp.cmd_angles[ 0 ] );
   cmd_angles.y = 2*angledist( SHORT2ANGLE( ucmd->angles[ 1 ] ) - driver->client->resp.cmd_angles[ 1 ] );
   cmd_angles.z = 0;

	return true;
	}

void Vehicle::Postthink
	(
	void
	)

	{
	float			turn;
	Vector		i, j, k;
   int         index;
   trace_t     trace;
   Vector      normalsum;
   int         numnormals;
   Vector      temp;
   Vector      pitch;
   Vector      roll;
	VehicleBase *v;
	VehicleBase *last;
   float       drivespeed;


   if ( drivable )
      {
      currentspeed = moveimpulse / 10;

	   turnangle = turnangle * 0.25f + turnimpulse;
	   if ( turnangle > maxturnrate )
		   {
		   turnangle = maxturnrate;
		   }
	   else if ( turnangle < -maxturnrate )
		   {
		   turnangle = -maxturnrate;
		   }
	   else if ( fabs( turnangle ) < 2 )
		   {
		   turnangle = 0;
		   }
      temp[ PITCH ] = 0;
      temp[ YAW ] = angles[ YAW ];
      temp[ ROLL ] = 0;
      temp.AngleVectors( &i, &j, &k );
      j = vec_zero - j;

      //
      // figure out what our orientation is
      //
      numnormals = 0;
      for ( index = 0; index < 4; index++ )
         {
         Vector start, end;
         Vector boxoffset;

         boxoffset = Corners[ index ];
	      start = worldorigin + i * boxoffset[0] + j * boxoffset[1] + k * boxoffset[2];
         end = start;
         end[ 2 ] -= maxtracedist;
         trace = G_Trace( start, vec_zero, vec_zero, end, NULL, MASK_SOLID, "Vehicle::PostThink Corners" );
         if ( trace.fraction != 1.0f && !trace.startsolid )
            {
            normalsum += Vector( trace.plane.normal );
            numnormals++;
            }
         }
      if ( numnormals > 1 )
         {
         temp = normalsum * ( 1.0f/ numnormals );
         temp.normalize();
         i = temp.CrossProduct( temp, j );
         pitch = i;
         // determine pitch
         angles[ 0 ] = (pitch.toPitch());
         }

	   turn = turnangle * ( 1.0f / 200.0f );

      if ( groundentity )
         {
         float dot;
         Vector newvel;
         Vector flatvel;

         velocity[ 0 ] *= 0.925f;
         velocity[ 1 ] *= 0.925f;
         flatvel = Vector( orientation[ 0 ] );
         velocity += flatvel * currentspeed;
         flatvel[ 2 ] = 0;
         dot = velocity * flatvel;
	      if ( dot > speed )
		      {
		      dot = speed;
		      }
	      else if ( dot < -speed )
		      {
		      dot = -speed;
		      }
	      else if ( fabs( dot ) < 20.0f )
		      {
		      dot = 0;
		      }
         newvel = flatvel * dot;
         velocity[ 0 ] = newvel[ 0 ];
         velocity[ 1 ] = newvel[ 1 ];
         velocity[ 2 ] += dot * jumpimpulse;

		   avelocity *= 0.05;
         if ( steerinplace )
            {
            if ( dot < 350 )
               dot = 350;
  		      avelocity.y += turn * dot;
            }
         else
            {
  		      avelocity.y += turn * dot;
            }
         }
      else
         {
		   avelocity *= 0.1;
		   }
	   angles += avelocity * FRAMETIME;
	   setAngles( angles );
      }
   drivespeed = velocity * Vector( orientation[ 0 ] );

   if ( drivable && driver )
      {
      Event * event;

      event = new Event( EV_RandomEntitySound );
      if ( currentspeed > 0 )
         event->AddString( "snd_forward" );
      else if ( currentspeed < 0 )
         event->AddString( "snd_backward" );
      else
         event->AddString( "snd_idle" );
      ProcessEvent( event );
      }
	i = Vector( orientation[ 0 ] );
	j = Vector( orientation[ 1 ] );
	k = Vector( orientation[ 2 ] );

	if ( driver )
		{
      Player * player;

      player = ( Player * )( Sentient * )driver;
		player->setOrigin( worldorigin + i * driveroffset[0] + j * driveroffset[1] + k * driveroffset[2] );
      if ( drivable )
         {
         player->velocity = vec_zero;
         player->setAngles( angles );
         player->v_angle = angles;
         player->v_angle[ PITCH ] = player->v_angle[ PITCH ];
         }
		}

	last = this;
	while( last->vlink )
		{
		v = last->vlink;
		v->setOrigin( worldorigin + i * v->offset.x + j * v->offset.y + k * v->offset.z );
		v->avelocity = avelocity;
		v->velocity = velocity;
		v->angles[ ROLL ] = angles[ ROLL ];
		v->angles[ YAW ] = angles[ YAW ];
		v->angles[ PITCH ] = (int)( v->angles[ PITCH ] + (drivespeed/4) ) % 360;

		if ( v->isSubclassOf( FrontWheels ) )
			{
			v->angles += Vector( 0, turnangle, 0 );
			}
		v->setAngles( v->angles );

		last = v;
		}

   CheckWater();
   WorldEffects();

   // save off last origin
   last_origin = worldorigin;

	if ( !driver && !velocity.length() && groundentity && !( watertype & CONTENTS_LAVA ) )
		{
		flags &= ~FL_POSTTHINK;
      if ( drivable )
   	   setMoveType( MOVETYPE_NONE );
		}
   }

void Vehicle::VehicleTouched
	(
	Event *ev
	)

	{
	Entity	*other;
	float		speed;
   Vector   delta;
	Vector	dir;

 	other = ev->GetEntity( 1 );
	if ( other == driver )
		{
		return;
		}

   if ( other == world )
      {
      return;
      }

   if ( drivable && !driver )
      {
      return;
      }

   delta = worldorigin - last_origin;
	speed = delta.length();
	if ( speed > 2 )
		{
      RandomGlobalSound( "vehicle_crash" );
		dir = delta * ( 1 / speed );
		other->Damage( this, lastdriver, speed * 8, worldorigin, dir, vec_zero, speed*15, 0, MOD_VEHICLE, -1, -1, 1.0f );
		}

	}

void Vehicle::VehicleBlocked
	(
	Event *ev
	)

	{
	Entity	*other;
	float		speed;
   float    damage;
   Vector   delta;
   Vector   newvel;
	Vector	dir;

   return;
   if ( !velocity[0] && !velocity[1] )
      return;

	other = ev->GetEntity( 1 );
	if ( other == driver )
		{
		return;
		}
   if ( other->isSubclassOf( VehicleBase ) )
      {
      delta = other->worldorigin - worldorigin;
      delta.normalize();

      newvel = vec_zero - ( velocity) + ( other->velocity * 0.25 );
      if ( newvel * delta < 0 )
         {
         velocity = newvel;
         delta = velocity - other->velocity;
         damage = delta.length()/4;
         }
      else
         {
         return;
         }
      }
   else if ( ( velocity.length() < 350 ) )
      {
      other->velocity += velocity*1.25f;
      other->velocity[ 2 ] += 100; 
      damage = velocity.length()/4;
      }
   else
      {
      damage = other->health + 1000;
      }

	// Gib 'em outright
	speed = fabs( velocity.length() );
	dir = velocity * ( 1 / speed );
 	other->Damage( this, lastdriver, damage, worldorigin, dir, vec_zero, speed, 0, MOD_VEHICLE, -1, -1, 1.0f );
   } 

Sentient *Vehicle::Driver
	(
	void
	)

	{
	return driver;
	}

qboolean Vehicle::IsDrivable
	(
	void
	)

	{
	return drivable;
	}

void Vehicle::SetSpeed
	(
	Event *ev
	)
   {
   speed = ev->GetFloat( 1 );
   }

void Vehicle::SetTurnRate
	(
	Event *ev
	)
   {
   maxturnrate = ev->GetFloat( 1 );
   }

float Vehicle::SetDriverPitch
	(
	float pitch
	)
   {
   return pitch;
   }


CLASS_DECLARATION( Vehicle, DrivableVehicle, "script_drivablevehicle" );

ResponseDef DrivableVehicle::Responses[] =
	{
		{ &EV_Damage,				( Response )&Entity::DamageEvent },
		{ &EV_Killed,				( Response )&DrivableVehicle::Killed },
	   { &EV_Item_Respawn,		( Response )&DrivableVehicle::Respawn },
		{ NULL, NULL }
	};

DrivableVehicle::DrivableVehicle()
	{
   drivable = true;
   flags |= FL_SPARKS|FL_DIE_TESSELATE|FL_DIE_EXPLODE|FL_DARKEN;
   if ( ctf->value )
	   respawntime = G_GetFloatArg( "respawntime", 60 );
	}

void DrivableVehicle::Killed(Event *ev)
   {
   Entity * ent;
   Entity * attacker;
   Vector dir;
   Event * event;
   const char * name;
   int num;
	VehicleBase *last;

   takedamage = DAMAGE_NO;
   setSolidType( SOLID_NOT );
	hideModel();

	attacker		= ev->GetEntity( 1 );

//
// kill the driver
//
   if ( driver )
      {
      Vector dir;
      SentientPtr sent;
      Event * event;

      velocity = vec_zero;
      sent = driver;
      event = new Event( EV_Use );
      event->AddEntity( sent );
      ProcessEvent( event );
      dir = sent->worldorigin - worldorigin;
      dir[ 2 ] += 64;
      dir.normalize();
    	sent->Damage( this, this, sent->health*2, worldorigin, dir, vec_zero, 50, 0, MOD_VEHICLE, -1, -1, 1.0f );
      }

   if (flags & FL_DIE_TESSELATE)
      {
      dir = worldorigin - attacker->worldorigin;
      TesselateModel
         (
         this,
         tess_min_size,
         tess_max_size,
         dir,
         ev->GetInteger( 2 ),
         tess_percentage,
         tess_thickness,
         vec3_origin
         );
		ProcessEvent( EV_BreakingSound );
      }

   if (flags & FL_DIE_EXPLODE)
      {
	   CreateExplosion( centroid, 150*edict->s.scale, edict->s.scale * 2, true, this, this, this );
      }

   if (flags & FL_DIE_GIBS)
      {
      setSolidType( SOLID_NOT );
      hideModel();

      CreateGibs( this, -150, edict->s.scale, 3 );
      }
//
// kill all my wheels
//
   if ( ctf->value )
      {
	   last = this;
	   while( last->vlink )
		   {
		   last->vlink->hideModel();
		   last = last->vlink;
		   }
      }
   else
      {
	   last = this;
	   while( last->vlink )
		   {
		   last->vlink->PostEvent( EV_Remove, 0 );
		   last = last->vlink;
		   }
      vlink = NULL;
      }


//
// 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->ProcessEvent( event );
			}
		while ( 1 );
		}

   if ( ctf->value )
      {
      hideModel();
      // cancel events of type remove
	   CancelEventsOfType( EV_Remove );
      PostEvent( EV_Item_Respawn, respawntime );
      }
   else
      {
      PostEvent( EV_Remove, 0 );
      }
   }

void DrivableVehicle::Respawn
   (
   Event *ev
   )
	{
	VehicleBase *last;

   health = max_health;
   edict->s.lightofs = 0;

	showModel();

	last = this;
	while( last->vlink )
		{
		last->vlink->showModel();
		last = last->vlink;
		}

	// allow it to be touched again
   setSolidType( SOLID_BBOX );
   takedamage = DAMAGE_YES;

	// play respawn sound
  	RandomGlobalSound( "snd_itemspawn" );

   setOrigin( startorigin );
   KillBox( this );
   Postthink();
	};
