/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***                            Confidential - Westwood Studios                              ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Commando                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Combat/vehicle.cpp                           $*
 *                                                                                             *
 *                      $Author:: Byon_g                                                      $*
 *                                                                                             *
 *                     $Modtime:: 3/29/02 4:08p                                               $*
 *                                                                                             *
 *                    $Revision:: 266                                                         $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */


/*
**	Includes
*/
#include "vehicle.h"
#include "animcontrol.h"
#include "debug.h"
#include "combat.h"
#include "pscene.h"
#include "assets.h"
#include "rendobj.h"
#include "soldier.h"
#include "weapons.h"
#include "damage.h"
#include "wwpacket.h"
#include "gameobjmanager.h"
#include <stdio.h>
#include "WWAudio.H"
#include "Sound3D.H"
#include "vehiclephys.h"
#include "motorcycle.h"
#include "trackedvehicle.h"
#include "vtolvehicle.h"
#include "wheelvehicle.h"
#include "wheel.h"
#include "phys3.h"
#include "humanphys.h"
#include "persistfactory.h"
#include "combatchunkid.h"
#include "simpledefinitionfactory.h"
#include "wwhack.h"
#include "explosion.h"
#include "surfaceeffects.h"
#include "bitpackids.h"
#include "wwprofile.h"
#include "playertype.h"
#include "playerdata.h"
#include "translatedb.h"
#include "apppackettypes.h"
#include "stealtheffect.h"
#include "gametype.h"
#include "globalsettings.h"
#include "basecontroller.h"
#include "string_ids.h"
#include "specialbuilds.h"

/*
** Local prototypes
*/
static void	Set_Subobject_Visibility (RenderObjClass *model, int bone_index, bool show);

/*
** Defines
*/
#define	NORMALIZE_RADIANS( v ) 	while ( v > (float)DEG_TO_RAD(  180.0f ) ) v -= (float)DEG_TO_RAD( 360.0f ); \
											while ( v < (float)DEG_TO_RAD( -180.0f ) ) v += (float)DEG_TO_RAD( 360.0f );

//
// Class statics
//
bool	VehicleGameObj::DefaultDriverIsGunner	= false;
bool	VehicleGameObj::CameraLockedToTurret	= false;


/*
** Target Steering Option.
** (gth) 07/11/2001 - making this default to true
** (gth) 08/21/2001 - new default is false, user can turn it on with a console command...
*/
bool	_Use_Target_Steering = false;

bool	VehicleGameObj::Toggle_Target_Steering( void )
{
	return _Use_Target_Steering = !_Use_Target_Steering;
}

void	VehicleGameObj::Set_Target_Steering( bool onoff )
{
	_Use_Target_Steering = onoff;
	return ;
}

bool	VehicleGameObj::Is_Target_Steering( void )
{
	return _Use_Target_Steering;
}

/*
** VehicleGameObjDef
*/
DECLARE_FORCE_LINK( Vehicle )

SimplePersistFactoryClass<VehicleGameObjDef, CHUNKID_GAME_OBJECT_DEF_VEHICLE>	_VehicleGameObjDefPersistFactory;

DECLARE_DEFINITION_FACTORY(VehicleGameObjDef, CLASSID_GAME_OBJECT_DEF_VEHICLE, "Vehicle") _VehicleGameObjDefDefFactory;

VehicleGameObjDef::VehicleGameObjDef( void ) :
	Type( VEHICLE_TYPE_CAR ),
	TurnRadius( 10.0f ),
	OccupantsVisible( true ),
	EngineSoundMaxPitchFactor( 2.0F ),
	SightDownMuzzle( false ),
	Aim2D( true ),
	SquishVelocity( 1.5f ),
	VehicleNameID( 0 ),
	NumSeats( 2 ),
	GDIDamageReportID(0),
	NodDamageReportID(0),
	GDIDestroyReportID(0),
	NodDestroyReportID(0)
{
	// initialize all engine sound defs to zero
	for (int i=0; i<MAX_ENGINE_SOUND_STATES; i++) {
		EngineSound[i] = 0;
	}

	MODEL_DEF_PARAM( VehicleGameObjDef, PhysDefID, "DynamicPhysDef" );

	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_STRING, 	TypeName );
	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_STRING, 	Fire0Anim );
	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_STRING, 	Fire1Anim );
	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_STRING, 	Profile );

	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_FLOAT, 	TurnRadius );
	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_FLOAT, 	SquishVelocity );
	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_BOOL,		Aim2D );

#ifdef	PARAM_EDITING_ON
	EnumParameterClass *param;
	param = new EnumParameterClass( (int*)&Type );
	param->Set_Name ("Type");
	param->Add_Value ( "Car",		VEHICLE_TYPE_CAR );
	param->Add_Value ( "Tank", 	VEHICLE_TYPE_TANK );
	param->Add_Value ( "Bike", 	VEHICLE_TYPE_BIKE );
	param->Add_Value ( "Flying", 	VEHICLE_TYPE_FLYING );
	param->Add_Value ( "Turret", 	VEHICLE_TYPE_TURRET );

	GENERIC_EDITABLE_PARAM(VehicleGameObjDef,param)
#endif

	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_BOOL, 	OccupantsVisible );

	// engine sounds
	FLOAT_EDITABLE_PARAM(VehicleGameObjDef, EngineSoundMaxPitchFactor, 1.0F, 10.0F);
	NAMED_EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_SOUNDDEFINITIONID, EngineSound[ENGINE_SOUND_STATE_STARTING],"Engine Start Sound");
	NAMED_EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_SOUNDDEFINITIONID, EngineSound[ENGINE_SOUND_STATE_RUNNING],"Engine Running Loop");
	NAMED_EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_SOUNDDEFINITIONID, EngineSound[ENGINE_SOUND_STATE_STOPPING],"Engine Stop Sound");
//	NAMED_EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_SOUNDDEFINITIONID, EngineSound[ENGINE_SOUND_STATE_OFF],"Engine Off Sound");

	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_BOOL,		SightDownMuzzle );

	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_STRINGSDB_ID,		VehicleNameID );

	EDITABLE_PARAM( VehicleGameObjDef, ParameterClass::TYPE_INT,		NumSeats );

	EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, GDIDamageReportID);
	EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, NodDamageReportID);
	EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, GDIDestroyReportID);
	EDITABLE_PARAM(VehicleGameObjDef, ParameterClass::TYPE_STRINGSDB_ID, NodDestroyReportID);
}

VehicleGameObjDef::~VehicleGameObjDef( void )
{
	Free_Transition_List();
}

uint32	VehicleGameObjDef::Get_Class_ID (void) const
{
	return CLASSID_GAME_OBJECT_DEF_VEHICLE;
}

PersistClass *	VehicleGameObjDef::Create( void ) const
{
	VehicleGameObj * obj = new VehicleGameObj;
	obj->Init( *this );
	return obj;
}

enum	{
	CHUNKID_DEF_PARENT							=	930991656,
	CHUNKID_DEF_VARIABLES,
	CHUNKID_DEF_TRANSITION,

	MICROCHUNKID_TYPE								=	1,
	MICROCHUNKID_TYPE_NAME,
	MICROCHUNKID_FIRE0ANIM,
	MICROCHUNKID_FIRE1ANIM,
	MICROCHUNKID_PROFILE,
	MICROCHUNKID_WEAPON_TURN_TRANS,

	XXXMICROCHUNKID_SOUND,
	MICROCHUNKID_EMITER_NAME,
	MICROCHUNKID_EMITER_OFFSET,
	MICROCHUNKID_EMITER2_NAME,
	MICROCHUNKID_EMITER2_OFFSET,
	XXX_MICROCHUNKID_MASS,
	XXX_MICROCHUNKID_MAX_ENGINE_TORQUE,
	XXX_MICROCHUNKID_STEERING_ANGLE,
	XXX_MICROCHUNKID_SPRING_CONSTANT,
	XXX_MICROCHUNKID_DAMPING_COEFFIENT,
	XXX_MICROCHUNKID_SPRING_LENGTH,
	MICROCHUNKID_PHYS_ID,
	MICROCHUNKID_TURN_RADIUS,
	MICROCHUNKID_OCCUPANTS_VISIBLE,

	XXX_MICROCHUNKID_ENGINE_SOUND_RPM_SCALE_MIN,
	XXX_MICROCHUNKID_ENGINE_SOUND_RPM_SCALE_MAX,
	MICROCHUNKID_ENGINE_START_SOUND,
	MICROCHUNKID_ENGINE_RUN_SOUND,
	MICROCHUNKID_ENGINE_STOP_SOUND,
	MICROCHUNKID_ENGINE_OFF_SOUND,

	MICROCHUNKID_DEF_SIGHT_DOWN_MUZZLE,
	MICROCHUNKID_DEF_AIM_2D,

	MICROCHUNKID_DEF_SQUISH_VELOCITY,
	MICROCHUNKID_ENGINE_SOUND_MAX_PITCH_FACTOR,
	MICROCHUNKID_DEF_VEHICLE_NAME_ID,
	MICROCHUNKID_DEF_NUM_SEATS,
	MICROCHUNKID_DEF_GDI_DAMAGE_REPORT_ID,
	MICROCHUNKID_DEF_NOD_DAMAGE_REPORT_ID,
	MICROCHUNKID_DEF_GDI_DESTROY_REPORT_ID,
	MICROCHUNKID_DEF_NOD_DESTROY_REPORT_ID,
};

bool	VehicleGameObjDef::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_DEF_PARENT );
		SmartGameObjDef::Save( csave );
	csave.End_Chunk();

	csave.Begin_Chunk( CHUNKID_DEF_VARIABLES );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TYPE, Type );
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_TYPE_NAME, TypeName );
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_FIRE0ANIM, Fire0Anim );
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_FIRE1ANIM, Fire1Anim );
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_PROFILE, Profile );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TURN_RADIUS, TurnRadius );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OCCUPANTS_VISIBLE, OccupantsVisible );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ENGINE_SOUND_MAX_PITCH_FACTOR, EngineSoundMaxPitchFactor );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ENGINE_START_SOUND, EngineSound[ENGINE_SOUND_STATE_STARTING] );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ENGINE_RUN_SOUND, EngineSound[ENGINE_SOUND_STATE_RUNNING] );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ENGINE_STOP_SOUND, EngineSound[ENGINE_SOUND_STATE_STOPPING] );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ENGINE_OFF_SOUND, EngineSound[ENGINE_SOUND_STATE_OFF] );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_SIGHT_DOWN_MUZZLE, SightDownMuzzle );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_AIM_2D, Aim2D );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_SQUISH_VELOCITY, SquishVelocity );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_VEHICLE_NAME_ID,	VehicleNameID );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_NUM_SEATS, NumSeats );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_GDI_DAMAGE_REPORT_ID, GDIDamageReportID );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_NOD_DAMAGE_REPORT_ID, NodDamageReportID );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_GDI_DESTROY_REPORT_ID, GDIDestroyReportID );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DEF_NOD_DESTROY_REPORT_ID, NodDestroyReportID );
	csave.End_Chunk();

	//	Save each of the transition 'definitions' in our list.
	for( int index = 0; index < Transitions.Count (); index ++ ) {
		TransitionDataClass *transition = Transitions[index];
		if( transition != NULL ) {

			//	Save this transition 'defintion' to its own chunk.
			csave.Begin_Chunk( CHUNKID_DEF_TRANSITION );
			transition->Save( csave );
			csave.End_Chunk();
		}
	}

	return true;
}

bool	VehicleGameObjDef::Load( ChunkLoadClass &cload )
{
	Free_Transition_List ();

	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_DEF_PARENT:
				SmartGameObjDef::Load( cload );
				break;

			case CHUNKID_DEF_TRANSITION:
			{
				TransitionDataClass *transition = new TransitionDataClass;
				transition->Load( cload );
				Transitions.Add( transition );
			}
			break;

			case CHUNKID_DEF_VARIABLES:
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TYPE, Type );
						READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_TYPE_NAME, TypeName );
						READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_FIRE0ANIM, Fire0Anim );
						READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_FIRE1ANIM, Fire1Anim );
						READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_PROFILE, Profile );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_PHYS_ID, PhysDefID );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TURN_RADIUS, TurnRadius );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_OCCUPANTS_VISIBLE, OccupantsVisible );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ENGINE_SOUND_MAX_PITCH_FACTOR, EngineSoundMaxPitchFactor);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ENGINE_START_SOUND, EngineSound[ENGINE_SOUND_STATE_STARTING] );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ENGINE_RUN_SOUND, EngineSound[ENGINE_SOUND_STATE_RUNNING] );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ENGINE_STOP_SOUND, EngineSound[ENGINE_SOUND_STATE_STOPPING] );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ENGINE_OFF_SOUND, EngineSound[ENGINE_SOUND_STATE_OFF] );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_SIGHT_DOWN_MUZZLE, SightDownMuzzle );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_AIM_2D, Aim2D );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_SQUISH_VELOCITY, SquishVelocity );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_VEHICLE_NAME_ID,	VehicleNameID );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_NUM_SEATS, NumSeats );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_GDI_DAMAGE_REPORT_ID, GDIDamageReportID );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_NOD_DAMAGE_REPORT_ID, NodDamageReportID );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_GDI_DESTROY_REPORT_ID, GDIDestroyReportID );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_DEF_NOD_DESTROY_REPORT_ID, NodDestroyReportID );
						default:
							Debug_Say(( "Unrecognized VehicleDef Variable chunkID\n" ));
							break;
					}
					cload.Close_Micro_Chunk();
				}
				break;

			default:
				Debug_Say(( "Unrecognized VehicleDef chunkID\n" ));
				break;
		}
		cload.Close_Chunk();
	}

	return true;
}

/*
**
*/
void VehicleGameObjDef::Free_Transition_List( void )
{
	//	Delete each of the transition 'definitions' in our list.
	for( int index = 0; index < Transitions.Count (); index ++ ) {
		TransitionDataClass *transition = Transitions[index];
		if( transition != NULL ) {
			delete transition;
		}
	}
	Transitions.Delete_All();
	return ;
}

const PersistFactoryClass & VehicleGameObjDef::Get_Factory (void) const
{
	return _VehicleGameObjDefPersistFactory;
}


int VehicleGameObjDef::Get_Damage_Report(int team) const
{
	if (PLAYERTYPE_GDI == team) {
		return GDIDamageReportID;
	} else if (PLAYERTYPE_NOD == team) {
		return NodDamageReportID;
	}

	return 0;
}


int VehicleGameObjDef::Get_Destroy_Report(int team) const
{
	if (PLAYERTYPE_GDI == team) {
		return GDIDestroyReportID;
	} else if (PLAYERTYPE_NOD == team) {
		return NodDestroyReportID;
	}

	return 0;
}

/*
** VehicleGameObj
*/
SimplePersistFactoryClass<VehicleGameObj, CHUNKID_GAME_OBJECT_VEHICLE>	_VehicleGameObjPersistFactory;

const PersistFactoryClass & VehicleGameObj::Get_Factory (void) const
{
	return _VehicleGameObjPersistFactory;
}


VehicleGameObj::VehicleGameObj()	:
	TurretBone( 0 ),
	BarrelBone( 0 ),
	TurretTurn( 0 ),
	BarrelTilt( 0 ),
	BarrelOffset( 0 ),
	Sound( NULL ),
	EngineSoundState( ENGINE_SOUND_STATE_OFF ),
	CachedEngineSound( NULL ),
	WheelSurfaceSound(NULL),
	TransitionsEnabled( true ),
	OccupiedSeats( 0 ),
	HasEnterTransitions( false ),
	HasExitTransitions( false ),
	VehicleDelivered(false),
	LockTimer( 0 )
{
	DriverIsGunner = DefaultDriverIsGunner;
	Set_App_Packet_Type(APPPACKETTYPE_VEHICLE);
}

VehicleGameObj::~VehicleGameObj()
{
	if ( Peek_Physical_Object() != NULL && COMBAT_SCENE != NULL) {

		// Make sure the exiters don't hit me
		Peek_Physical_Object()->Set_Collision_Group( UNCOLLIDEABLE_GROUP );

		// Eject all the occupants around the vehicle so they don't intersect
		Vector3 vehicle_pos;
		Peek_Physical_Object()->Get_Position( &vehicle_pos );
		for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
			if ( SeatOccupants[i] != NULL ) {
				SeatOccupants[i]->Exit_Destroyed_Vehicle( i, vehicle_pos );
			}
			SeatOccupants[i] = NULL;
		}
	} else {
		for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
			if ( SeatOccupants[i] != NULL ) {
				SeatOccupants[i]->Exit_Vehicle();
			}
			SeatOccupants[i] = NULL;
		}
	}

	Destroy_Transitions();

	if (Sound) {
		Sound->Remove_From_Scene ();
		Sound->Release_Ref ();
		Sound = NULL;
	}

	if (CachedEngineSound) {
		CachedEngineSound->Remove_From_Scene();
		REF_PTR_RELEASE(CachedEngineSound);
	}

	Release_Turret_Bones();

	Shutdown_Wheel_Effects();
}

/*
**
*/
void VehicleGameObj::Init( void )
{
	DriverIsGunner = DefaultDriverIsGunner;
	// Vehicles are too complex to re-init
//	Init( Get_Definition() );
}

void	VehicleGameObj::Init( const VehicleGameObjDef & definition )
{
	DriverIsGunner = DefaultDriverIsGunner;
	SmartGameObj::Init( definition );

	SeatOccupants.Resize(definition.NumSeats);
	for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
		SeatOccupants[i] = NULL;
	}

	Aquire_Turret_Bones();
	Init_Wheel_Effects();
	Create_And_Destroy_Transitions();
	Update_Damage_Meshes();

	//
	// Refine the app packet type if this is a turret
	//
	if (Get_Definition().Type == VEHICLE_TYPE_TURRET)
	{
		Set_App_Packet_Type(APPPACKETTYPE_TURRET);
	}

	//WWDEBUG_SAY((">> VehicleGameObj::Init, Type = %d\n", Get_Definition().Type));
}

const VehicleGameObjDef & VehicleGameObj::Get_Definition( void ) const
{
	return (const VehicleGameObjDef &)BaseGameObj::Get_Definition();
}


/*
** VehicleGameObj Save and Load
*/
enum	{
	CHUNKID_PARENT							=	923991632,
	CHUNKID_VARIABLES,
	XXXCHUNKID_ANIM_CONTROL,
	XXXCHUNKID_TRANSITION_LIST_OLD,
	CHUNKID_SEAT_LIST,

	MICROCHUNKID_TURRET_BONE			=	1,
	MICROCHUNKID_BARREL_BONE,
	XXXMICROCHUNKID_PHYSICAL_OBJECT,
	MICROCHUNKID_TRANSITION_LIST_ENTRY_OLD,
	MICROCHUNKID_TURRET_TURN,
	MICROCHUNKID_BARREL_TILT,
	MICROCHUNKID_TRANSITIONS_ENABLED,
	MICROCHUNKID_OCCUPIED_SEATS,
	MICROCHUNKID_NUM_SEATS,
};

bool	VehicleGameObj::Save( ChunkSaveClass & csave )
{
	// Destroy the transitions, because we don't save them, and don't
	// want to have the vehicle have a un-matched reference to them.
	// Re-create at the end of save
	Destroy_Transitions();

	csave.Begin_Chunk( CHUNKID_PARENT );
	SmartGameObj::Save( csave );
	csave.End_Chunk();

	csave.Begin_Chunk( CHUNKID_VARIABLES );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TURRET_TURN, TurretTurn );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_BARREL_TILT, BarrelTilt );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TRANSITIONS_ENABLED, TransitionsEnabled );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OCCUPIED_SEATS, OccupiedSeats );
		int num_seats = SeatOccupants.Length();
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_NUM_SEATS, num_seats );
	csave.End_Chunk();

	if ( num_seats != 0 ) {
		csave.Begin_Chunk( CHUNKID_SEAT_LIST );
		csave.Write( &SeatOccupants[0], num_seats * sizeof( SeatOccupants[0] ) );
		csave.End_Chunk();
	}

	Create_And_Destroy_Transitions();

	return true;
}

bool	VehicleGameObj::Load( ChunkLoadClass &cload )
{
	int num_seats = 0;

	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_PARENT:
				SmartGameObj::Load( cload );
				break;

			case CHUNKID_VARIABLES:
			{
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TURRET_TURN, TurretTurn );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_BARREL_TILT, BarrelTilt );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_TRANSITIONS_ENABLED, TransitionsEnabled );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_OCCUPIED_SEATS, OccupiedSeats );
						READ_MICRO_CHUNK( cload, MICROCHUNKID_NUM_SEATS, num_seats );

						default:
							Debug_Say(( "Unrecognized Vehicle Variable chunkID\n" ));
							break;
					}
					cload.Close_Micro_Chunk();
				}

				break;
			}

			case	CHUNKID_SEAT_LIST:
			{
				if ( (num_seats == 0) || (num_seats != Get_Definition().NumSeats)) {
					SeatOccupants.Resize(Get_Definition().NumSeats);
					for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
						SeatOccupants[i] = NULL;
					}
					break;		// May be legacy
				}

				SeatOccupants.Resize( num_seats );
				cload.Read( &SeatOccupants[0], num_seats * sizeof( SeatOccupants[0] ) );
				for ( int i = 0; i < num_seats; i++ ) {
					if ( SeatOccupants[i] != NULL ) {
						REQUEST_POINTER_REMAP( (void **)&SeatOccupants[i] );
					}
				}
				break;
			}

			default:
				Debug_Say(( "Unrecognized Vehicle chunkID\n" ));
				break;

		}
		cload.Close_Chunk();
	}

	SaveLoadSystemClass::Register_Post_Load_Callback(this);
	return true;
}

void	VehicleGameObj::On_Post_Load( void )
{
	DriverIsGunner = DefaultDriverIsGunner;

	Init_Wheel_Effects();
	Aquire_Turret_Bones();
	SmartGameObj::On_Post_Load();
	Create_And_Destroy_Transitions();
	Update_Damage_Meshes();

	// For some reason???  some vehicles come in with an anim control, but no model in the anim control.
	if ( Get_Anim_Control() != NULL && Get_Anim_Control()->Peek_Model() == NULL ) {
		Get_Anim_Control()->Set_Model( Peek_Model() );
	}


	//
	// Refine the app packet type if this is a turret
	//
	if (Get_Definition().Type == VEHICLE_TYPE_TURRET)
	{
		Set_App_Packet_Type(APPPACKETTYPE_TURRET);
	}
}


#if 0
void	VehicleGameObj::Create_Transitions( void )
{
	Destroy_Transitions();

	WWASSERT( TransitionInstances.Count() == 0 );

	const TRANSITION_DATA_LIST & trans_data_list = Get_Definition().Get_Transition_List();

	if ( trans_data_list.Count() != 0 ) {		// new style

		if ( Seats[0] == NULL ) {
			Create_New_Transitions( TransitionDataClass::VEHICLE_ENTER_0 );
		} else {
			Create_New_Transitions( TransitionDataClass::VEHICLE_EXIT_0 );
		}

		if ( Seats[1] == NULL ) {
			Create_New_Transitions( TransitionDataClass::VEHICLE_ENTER_1 );
		} else {
			Create_New_Transitions( TransitionDataClass::VEHICLE_EXIT_1 );
		}
	}
}
#endif

void	VehicleGameObj::Destroy_Transitions( void )
{
	HasEnterTransitions = false;
	HasExitTransitions = false;
	while ( TransitionInstances.Count() ) {
  		TransitionManager::Destroy( TransitionInstances[0] );
  		TransitionInstances.Delete( 0 );
	}

	// They get put in a list, so flush the list.
	TransitionManager::Destroy_Pending();
}

void	VehicleGameObj::Create_And_Destroy_Transitions( void )
{
	// Manage the enter transitions
	bool	should_have_enter_transitions = (OccupiedSeats < Get_Definition().NumSeats) && TransitionsEnabled;
	if ( should_have_enter_transitions != HasEnterTransitions ) {
		if ( should_have_enter_transitions ) {
			Create_New_Transitions( TransitionDataClass::VEHICLE_ENTER );
			HasEnterTransitions = true;
		} else {
			Remove_Transitions( TransitionDataClass::VEHICLE_ENTER );
			HasEnterTransitions = false;
		}
	}

	// Manage the exit transitions
	bool	should_have_exit_transitions = (OccupiedSeats > 0) && TransitionsEnabled;
	if ( should_have_exit_transitions != HasExitTransitions ) {
		if ( should_have_exit_transitions ) {
			Create_New_Transitions( TransitionDataClass::VEHICLE_EXIT );
			HasExitTransitions = true;
		} else {
			Remove_Transitions( TransitionDataClass::VEHICLE_EXIT );
			HasExitTransitions = false;
		}
	}

}


/*
**
*/
void VehicleGameObj::Aquire_Turret_Bones( void )
{
   if ( TurretBone == 0 ) {
		TurretBone = Peek_Model()->Get_Bone_Index( "turret" );
		if ( TurretBone != 0 ) {
			Peek_Model()->Capture_Bone( TurretBone );
			if ( !Peek_Model()->Is_Bone_Captured( TurretBone ) ) {
				Debug_Say(( "VehicleGameObj::Aquire_Bones() : Turret Bone not captured for model %s\n",
					Peek_Model()->Get_Name()));
				TurretBone = 0;
			}
		}
	}

	BarrelOffset = 0;

	if ( BarrelBone == 0 ) {
		BarrelBone = Peek_Model()->Get_Bone_Index( "barrel" );
		if ( BarrelBone != 0 ) {
			Peek_Model()->Capture_Bone( BarrelBone );
			if ( !Peek_Model()->Is_Bone_Captured( BarrelBone ) ) {
				BarrelBone = 0;
				Debug_Say(( "VehicleGameObj::Aquire_Bones() : Barrel Bone not captured for model %s\n",
					Peek_Model()->Get_Name()));
			}
		}

		// find the barrel in turret space
	   if ( TurretBone != 0 ) {
			Matrix3D	turret_base = Peek_Model()->Get_Bone_Transform( TurretBone );
			Vector3	barrel_pos = Peek_Model()->Get_Bone_Transform( BarrelBone ).Get_Translation();
			Vector3	turret_space_barrel;
			Matrix3D::Inverse_Transform_Vector( turret_base, barrel_pos, &turret_space_barrel );
			BarrelOffset = turret_space_barrel.Y;
			if ( WWMath::Fabs( BarrelOffset ) < 0.1f  ) {
				BarrelOffset = 0;
			}
		}
	}

}

void VehicleGameObj::Release_Turret_Bones( void )
{
	if ( Peek_Model() ) {
		if ( TurretBone != 0 ) {
	 		if ( Peek_Model()->Is_Bone_Captured( TurretBone ) ) {
				Peek_Model()->Release_Bone( TurretBone );
				TurretBone = 0;
			}
		}

		if ( BarrelBone != 0 ) {
	 		if ( Peek_Model()->Is_Bone_Captured( BarrelBone ) ) {
				Peek_Model()->Release_Bone( BarrelBone );
				BarrelBone = 0;
			}
		}
	}

	BarrelOffset = 0;
}

void	VehicleGameObj::Export_Creation( BitStreamClass &packet )
{
	SmartGameObj::Export_Creation( packet );

	//
	//	Send the lock status to the client
	//
	int lock_owner_id = 0;
	if (LockOwner != NULL) {
		lock_owner_id = LockOwner.Get_Ptr ()->Get_ID ();
	}
	packet.Add(lock_owner_id);

	if (lock_owner_id != 0) {
		packet.Add(LockTimer,BITPACK_VEHICLE_LOCK_TIMER);
	}

	return ;
}


void	VehicleGameObj::Import_Creation( BitStreamClass &packet )
{
	SmartGameObj::Import_Creation( packet );

	//
	//	Get the lock status from the server
	//
	int lock_owner_id;
	packet.Get(lock_owner_id);
	if (lock_owner_id != 0) {
		LockOwner = GameObjManager::Find_PhysicalGameObj( lock_owner_id );
		packet.Get(LockTimer,BITPACK_VEHICLE_LOCK_TIMER);
	}

	return ;
}


void VehicleGameObj::Export_Rare( BitStreamClass &packet )
{
	SmartGameObj::Export_Rare( packet );

	//
	// Export the seat occupants
	//
	for (int i = 0; i < SeatOccupants.Length(); i++) {
		if (SeatOccupants[i] == NULL) {
			const int NO_OCCUPANTS = -1;
			packet.Add(NO_OCCUPANTS);
		} else {
			packet.Add(SeatOccupants[i]->Get_ID());
		}
	}

	packet.Add(VehicleDelivered);
}

void VehicleGameObj::Import_Rare( BitStreamClass &packet )
{
   WWASSERT(CombatManager::I_Am_Only_Client());
	SmartGameObj::Import_Rare( packet );

	//
	// Update the seat occupants
	//
	for (int i = 0; i < SeatOccupants.Length(); i++) {
		int occupant = packet.Get(occupant);
		if (occupant == -1) {

			//
			//	Remove the occupant from the seat
			//
			if (SeatOccupants[i] != NULL) {
				Remove_Occupant (SeatOccupants[i]);
			}

		} else {
			if ((SeatOccupants[i] == NULL) || (SeatOccupants[i]->Get_ID() != occupant)) {
				SmartGameObj * obj = GameObjManager::Find_SmartGameObj(occupant);

				if (SeatOccupants[i] != NULL) {
					Remove_Occupant (SeatOccupants[i]);
				}

				if (obj != NULL) {

					//
					//	Add the occupant to the seat
					//
					if (SeatOccupants[i] == NULL) {
						Add_Occupant (obj->As_SoldierGameObj(), i);
					}
				}
			}
		}
	}

	bool wasDelivered = VehicleDelivered;

	packet.Get(VehicleDelivered);

	if (!wasDelivered && VehicleDelivered) {
		BaseControllerClass* base = BaseControllerClass::Find_Base(Get_Player_Type());

		if (base) {
			base->On_Vehicle_Delivered(this);
		}
	}
}


//-----------------------------------------------------------------------------
/*
**
*/
void VehicleGameObj::Import_Frequent(BitStreamClass & packet)
{
	/*
	if (Get_Definition().Type == VEHICLE_TYPE_TURRET) {
		WWDEBUG_SAY(("VEHICLE_TYPE_TURRET::Import_Frequent: %s, %d, %d\n",
			Get_Definition().Get_Name(),
			Get_Definition().NumSeats,
			SeatOccupants.Length()));
	}
	*/

   WWASSERT(CombatManager::I_Am_Only_Client());

	int rounds = packet.Get(rounds);
	if (Get_Weapon() != NULL) {
		Get_Weapon()->Set_Total_Rounds(rounds);
	}

	switch(Get_Definition().Type) {

		case VEHICLE_TYPE_BIKE:
		case VEHICLE_TYPE_CAR:
		case VEHICLE_TYPE_TANK:
		case VEHICLE_TYPE_FLYING:
		{
			VehiclePhysClass * p_obj = Peek_Vehicle_Phys();

			if (p_obj != NULL) {


				Vector3 sc_position;
				Quaternion q;
				Vector3 vel;
				Vector3 ang_vel;
				bool is_engine_on;

				packet.Get(is_engine_on);
				packet.Get(sc_position.X, BITPACK_WORLD_POSITION_X);
				packet.Get(sc_position.Y, BITPACK_WORLD_POSITION_Y);
				packet.Get(sc_position.Z, BITPACK_WORLD_POSITION_Z);

				packet.Get(q.X, BITPACK_VEHICLE_QUATERNION);
				packet.Get(q.Y, BITPACK_VEHICLE_QUATERNION);
				packet.Get(q.Z, BITPACK_VEHICLE_QUATERNION);
				packet.Get(q.W, BITPACK_VEHICLE_QUATERNION);
				q.Normalize();


#ifdef MULTIPLAYERDEMO
				//
				// Mix up the packet order to make demo/non-demo code more incompatible.
				//
				packet.Get(vel.Y, BITPACK_VEHICLE_VELOCITY);
				packet.Get(vel.Z, BITPACK_VEHICLE_VELOCITY);
				packet.Get(vel.X, BITPACK_VEHICLE_VELOCITY);
#else
				packet.Get(vel.X, BITPACK_VEHICLE_VELOCITY);
				packet.Get(vel.Y, BITPACK_VEHICLE_VELOCITY);
				packet.Get(vel.Z, BITPACK_VEHICLE_VELOCITY);
#endif
				
				packet.Get(ang_vel.X, BITPACK_VEHICLE_ANGULAR_VELOCITY);
				packet.Get(ang_vel.Y, BITPACK_VEHICLE_ANGULAR_VELOCITY);
				packet.Get(ang_vel.Z, BITPACK_VEHICLE_ANGULAR_VELOCITY);

				if (COMBAT_STAR && (COMBAT_STAR->Get_Vehicle() == this)) {
					p_obj->Network_Latency_State_Update(		sc_position,
																			q,
																			vel,
																			ang_vel);
				} else {
					p_obj->Network_Interpolate_State_Update(	sc_position,
																			q,
																			vel,
																			ang_vel,
																			0.1f );
				}
				p_obj->Enable_Engine(is_engine_on);
			}
			break;
		}

		case VEHICLE_TYPE_TURRET:
			//WWDEBUG_SAY(("turret read %d\n", packet.Get_Bit_Write_Position()));
			break;

		default:
			WWASSERT( 0 );
			break;
	}

	packet.Get( DriverIsGunner );

   SmartGameObj::Import_Frequent(packet);

   WWASSERT(packet.Is_Flushed());
}

//-----------------------------------------------------------------------------
void VehicleGameObj::Export_Frequent(BitStreamClass & packet)
{
	Apply_Control();	// Make sure we have our drivers controls
	/*
	if (Get_Definition().Type == VEHICLE_TYPE_TURRET) {
		WWDEBUG_SAY(("VEHICLE_TYPE_TURRET::Export_Frequent: %s, %d, %d\n",
			Get_Definition().Get_Name(),
			Get_Definition().NumSeats,
			SeatOccupants.Length()));
	}
	*/

	//
	// Vehicles never change their weapon type
	//
	int total_rounds = 0;
	if (Get_Weapon() != NULL) {
		total_rounds = Get_Weapon()->Get_Total_Rounds();
	}
	packet.Add(total_rounds);

	switch(Get_Definition().Type) {

		case VEHICLE_TYPE_BIKE:
		case VEHICLE_TYPE_TANK:
		case VEHICLE_TYPE_CAR:
		case VEHICLE_TYPE_FLYING:
		{
			VehiclePhysClass * p_obj = Peek_Vehicle_Phys();
			if (p_obj != NULL) {

				Vector3 pos;
				Quaternion q;
				Vector3 vel;
				Vector3 ang_vel;
				bool is_engine_on;

				p_obj->Get_Position(&pos);
				p_obj->Get_Orientation(&q);
				p_obj->Get_Velocity(&vel);
				p_obj->Get_Angular_Velocity(&ang_vel);
				is_engine_on = p_obj->Is_Engine_Enabled();

				packet.Add(is_engine_on);
				packet.Add(pos.X, BITPACK_WORLD_POSITION_X);
				packet.Add(pos.Y, BITPACK_WORLD_POSITION_Y);
				packet.Add(pos.Z, BITPACK_WORLD_POSITION_Z);

				packet.Add(q.X, BITPACK_VEHICLE_QUATERNION);
				packet.Add(q.Y, BITPACK_VEHICLE_QUATERNION);
				packet.Add(q.Z, BITPACK_VEHICLE_QUATERNION);
				packet.Add(q.W, BITPACK_VEHICLE_QUATERNION);


#ifdef MULTIPLAYERDEMO
				//
				// Mix up the packet order to make demo/non-demo code more incompatible.
				//
				packet.Add(vel.Y, BITPACK_VEHICLE_VELOCITY);
				packet.Add(vel.Z, BITPACK_VEHICLE_VELOCITY);
				packet.Add(vel.X, BITPACK_VEHICLE_VELOCITY);
#else
				packet.Add(vel.X, BITPACK_VEHICLE_VELOCITY);
				packet.Add(vel.Y, BITPACK_VEHICLE_VELOCITY);
				packet.Add(vel.Z, BITPACK_VEHICLE_VELOCITY);
#endif


				packet.Add(ang_vel.X, BITPACK_VEHICLE_ANGULAR_VELOCITY);
				packet.Add(ang_vel.Y, BITPACK_VEHICLE_ANGULAR_VELOCITY);
				packet.Add(ang_vel.Z, BITPACK_VEHICLE_ANGULAR_VELOCITY);
			}

         break;
		}

		case VEHICLE_TYPE_TURRET:
			//WWDEBUG_SAY(("turret write %d\n", packet.Get_Bit_Write_Position()));
			break;

		default:
			WWASSERT(0);
			break;
	}

	packet.Add( DriverIsGunner );

	SmartGameObj::Export_Frequent(packet);

}

//-----------------------------------------------------------------------------
void VehicleGameObj::Import_State_Cs(BitStreamClass & packet)
{
   SmartGameObj::Import_State_Cs(packet);
}

void VehicleGameObj::Export_State_Cs(BitStreamClass & packet)
{
   SmartGameObj::Export_State_Cs(packet);
}

void VehicleGameObj::Get_Velocity(Vector3 & vel)
{
	VehiclePhysClass * vp = Peek_Vehicle_Phys();
	if (vp != NULL) {
		vp->Get_Velocity(&vel);
	} else {
		vel.Set(0,0,0);
	}
}

void VehicleGameObj::Set_Velocity(Vector3 & vel)
{
	VehiclePhysClass * vehicle = Peek_Vehicle_Phys();
	if (vehicle != NULL) {
		vehicle->Set_Velocity(vel);
	}
}

void	VehicleGameObj::Startup( void )
{
	// Setup all Seats for entry
	Create_And_Destroy_Transitions();
}

void	VehicleGameObj::Update_Turret( float weapon_turn, float weapon_tilt )
{
	if ( TurretBone != 0 ) {

		Matrix3D	facing(1);
		facing.Rotate_Z( weapon_turn );

		if ( BarrelBone == 0 ) {		// if no barrel bone
			facing.Rotate_Y( -weapon_tilt );  // neg rotate y tilts up
		}

		Peek_Model()->Control_Bone( TurretBone, facing );
	}

	if ( BarrelBone != 0 ) {
		Matrix3D	facing(1);
		facing.Rotate_Y( -weapon_tilt );  // neg rotate y tilts up
		Peek_Model()->Control_Bone( BarrelBone, facing );
	}

}

/*
** Vehicle Weapons
*/
bool	VehicleGameObj::Set_Targeting( const Vector3 & target_pos, bool do_tilt )
{
	bool	ready = true;

//Debug_Say(( "Weapon set targeting %f %f %f\n", target_pos.X, target_pos.Y, target_pos.Z ));

	SmartGameObj::Set_Targeting( target_pos );

	// Find the desired turret tilt and turn
	float relative_turn = 0;
	float relative_tilt = 0;

	if ( TurretBone != 0 ) {

		// find the target pos in turret space
		Matrix3D	turret_base = Peek_Model()->Get_Bone_Transform( TurretBone );
		Vector3	turret_space_target;
		Matrix3D::Inverse_Transform_Vector( turret_base, target_pos, &turret_space_target );

		// Set the tilt and turn
		relative_turn = WWMath::Atan2( turret_space_target.Y, turret_space_target.X );

		if ( BarrelOffset ) {
			turret_space_target.Z = 0;
			float barrel_offset_angle = WWMath::Atan2( BarrelOffset, turret_space_target.Length() );
//			Debug_Say(( "barrel angle %f %f\n", RAD_TO_DEG( barrel_offset_angle ), RAD_TO_DEG( relative_turn + barrel_offset_angle ) ));
			relative_turn -= barrel_offset_angle;
		}

	}

	if ( WWMath::Fabs( relative_turn ) < DEG_TO_RAD( 80 ) ) {

		if ( BarrelBone != 0 ) {
			// find the target pos in barrel space
			Matrix3D	barrel_base = Peek_Model()->Get_Bone_Transform( BarrelBone );
			Vector3	barrel_space_target;
			Matrix3D::Inverse_Transform_Vector( barrel_base, target_pos, &barrel_space_target );

			float dist = barrel_space_target.Length();
			if ( dist ) {
			// Only tilt when the turn is within 80 deg
				relative_tilt = WWMath::Fast_Asin( barrel_space_target.Z / dist );
			}
		}
	}


	// Move the tilt and turn towards the desired, following rates and limits
	float max_move;
	max_move = Get_Definition().WeaponTurnRate * TimeManager::Get_Frame_Seconds();
	if ( Get_Definition().WeaponTurnRate < DEG_TO_RAD(1000) ) {
		TurretTurn += WWMath::Clamp( relative_turn, -max_move, max_move );
		if ( WWMath::Fabs( relative_turn ) > WWMath::Fabs( max_move ) ) {
			ready = false;
		}
	} else {
		TurretTurn += relative_turn;
	}
	TurretTurn = WWMath::Clamp( TurretTurn, Get_Definition().WeaponTurnMin, Get_Definition().WeaponTurnMax );
	max_move = Get_Definition().WeaponTiltRate * TimeManager::Get_Frame_Seconds();
	if ( do_tilt ) {
		if ( Get_Definition().WeaponTiltRate < DEG_TO_RAD(1000) ) {
			BarrelTilt += WWMath::Clamp( relative_tilt, -max_move, max_move );
			if ( WWMath::Fabs( relative_tilt ) > WWMath::Fabs( max_move ) ) {
				ready = false;
			}
		} else {
			BarrelTilt += relative_tilt;
		}
	}
	BarrelTilt = WWMath::Clamp( BarrelTilt, Get_Definition().WeaponTiltMin, Get_Definition().WeaponTiltMax );

	// Apply the turn and tilt to the bones
	Update_Turret( TurretTurn, BarrelTilt );

	// if a fast turner and had to turn, do it again,just to make sure (trying to fix obelisk)
	if ( Get_Definition().WeaponTurnRate > DEG_TO_RAD(1000) &&
		( WWMath::Fabs(relative_turn) >= DEG_TO_RAD( 2 ) || WWMath::Fabs(relative_tilt) > DEG_TO_RAD( 2 ) ) ) {
		static int calls = 0;
		if ( calls < 3 ) {
			calls++;
			Set_Targeting( target_pos, do_tilt );		// recurse
			calls--;
		}
		ready = true;
	}

	return ready;
}


int	VehicleGameObj::Get_Weapon_Control_Owner(void)
{
	SoldierGameObj * gunner = Get_Gunner();
	if ( gunner && !Get_Driver_Is_Gunner() ) {
		return gunner->Get_Control_Owner();
	}
	SoldierGameObj * driver = Get_Driver();
	if ( driver ) {
		return driver->Get_Control_Owner();
	}
	return Get_Control_Owner();
}


/*
void VehicleGameObj::Apply_Control( void )
{
	SoldierGameObj * driver = Seats[DRIVER_SEAT];
	SoldierGameObj * gunner = Seats[GUNNER_SEAT];
	if ( gunner == NULL ) {
		gunner = driver;
	}

	bool target_steering = false;
	// If I have a driver, use his control
	if ( driver && !driver->In_Transition() ) {
		//Debug_Say(( "Vehicle copy driver's control\n" ));	//copy his control???

		Control = driver->Get_Control();
		target_steering = true;

	} else if (	!Get_Action()->Is_Acting() ) {
		Control.Clear_Control();	// Bad for network????
	}

	// If i have a gunner, use him for the gun control
	if ( gunner && (gunner != driver) && !gunner->In_Transition() ) {
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY,		gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY ) );
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY,		gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY ) );
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_NEXT,		gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_NEXT ) );
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_PREV,		gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_PREV ) );
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_RELOAD,	gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_RELOAD ) );
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_USE,		gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_USE ) );
	}

	if ( target_steering ) {
		// Lets make turns come from the targeting
		Vector3 target_pos = Get_Targeting_Pos();
		Vector3 obj_space_target;
		Matrix3D::Inverse_Transform_Vector( Get_Transform(), target_pos, &obj_space_target );
		float target_direction = WWMath::Atan2( obj_space_target.Y, obj_space_target.X );
		float turn_amount = target_direction / DEG_TO_RAD( 60 );
		Control.Set_Analog( ControlClass::ANALOG_TURN_LEFT, turn_amount );
	}

	// Clamp turning
	Control.Set_Analog( ControlClass::ANALOG_TURN_LEFT,
		WWMath::Clamp( Control.Get_Analog( ControlClass::ANALOG_TURN_LEFT ), -1.0F, 1.0F ) );

	SmartGameObj::Apply_Control();
}
*/

void VehicleGameObj::Apply_Control( void )
{
	SoldierGameObj * driver = Get_Driver();
	SoldierGameObj * gunner = Get_Gunner();

	if ( gunner == NULL || Get_Driver_Is_Gunner() ) {
		gunner = driver;
	}

//	if ( gunner || driver ) {
		if ( !CombatManager::Is_Gameplay_Permitted() ) {
			Clear_Control();
			Controller.Reset();
			if ( Peek_Vehicle_Phys() != NULL) {
				Peek_Vehicle_Phys()->Set_Velocity( Vector3(0,0,0) );
			}
			return;
		}
//	}

	bool target_steering = false;
	// If I have a driver, use his control

	if ( driver && !driver->In_Transition() && driver->Is_Human_Controlled () == true) {

		/*
		bool use_control = CombatManager::I_Am_Server() ||
			(CombatManager::Get_Client_Interpolation_Model() !=
				CLIENT_INTERPOLATION_PATHFIND) ||
			(CombatManager::I_Am_Client() &&
			 driver->Get_Control_Owner() == CombatManager::Get_My_Id());

		//Debug_Say(( "Vehicle copy driver's control\n" ));	//copy his control???

		if (use_control) {
			Control = driver->Get_Control();
			target_steering = true;
		}
		*/

		//
		// This could be a little dodgy... does client do this?
		//
		Control = driver->Get_Control();
		target_steering = true;
	}

	// If i have a gunner, use him for the gun control
	if ( gunner && (gunner != driver) && !gunner->In_Transition() && !Get_Driver_Is_Gunner() ) {
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY,		gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY ) );
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY,		gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY ) );
		Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_RELOAD,	gunner->Get_Control().Get_Boolean( ControlClass::BOOLEAN_WEAPON_RELOAD ) );
	}

	// Vehicles never change weapons
	Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_NEXT,		false );
	Control.Set_Boolean( ControlClass::BOOLEAN_WEAPON_PREV,		false );


	if ( target_steering && _Use_Target_Steering ) {

		// Target Steering kicks in if the user is pressing the accelerator key.
		if (Control.Get_Analog( ControlClass::ANALOG_MOVE_FORWARD ) >= 0.0f) {

			// Lets make turns come from the targeting
			Vector3 target_pos = Get_Targeting_Pos();
			Vector3 obj_space_target;
			Matrix3D::Inverse_Transform_Vector( Get_Transform(), target_pos, &obj_space_target );
			float target_direction = WWMath::Atan2( obj_space_target.Y, obj_space_target.X );

			// Offset the target direction if the user is turning while driving forward
			const float TARGET_STRAFE_ANGLE = DEG_TO_RADF(50.0f);
			target_direction += TARGET_STRAFE_ANGLE * Control.Get_Analog( ControlClass::ANALOG_TURN_LEFT );

			// Set the final steering angle
			float turn_amount = target_direction / DEG_TO_RAD( 60 ) * Control.Get_Analog( ControlClass::ANALOG_MOVE_FORWARD );
			Control.Set_Analog( ControlClass::ANALOG_TURN_LEFT, turn_amount );

		} else {

			// When over-riding the target steering with keyboard steering,
			// scale the turn rate down if we're going forward.
			if (Control.Get_Analog( ControlClass::ANALOG_MOVE_FORWARD ) > 0.0f) {
				Control.Set_Analog( ControlClass::ANALOG_TURN_LEFT, 0.5f * Control.Get_Analog(ControlClass::ANALOG_TURN_LEFT) );
			}
		}
	}

	// Clamp turning
	Control.Set_Analog( ControlClass::ANALOG_TURN_LEFT,
		WWMath::Clamp( Control.Get_Analog( ControlClass::ANALOG_TURN_LEFT ), -1.0F, 1.0F ) );

	SmartGameObj::Apply_Control();
}

static char * _profile_name = "Vehicle Think";

void	VehicleGameObj::Think( void )
{
{	WWPROFILE( _profile_name );

	Apply_Control();	// ????

	Update_Transitions();
}
	SmartGameObj::Think(); 										// Perform smart object thinking
{	WWPROFILE( _profile_name );

	Update_Sound_Effects();

	Update_Wheel_Effects();

	// Update the lock status
	if (LockTimer > 0.0f) {
		LockTimer -= TimeManager::Get_Frame_Seconds();
	} else {
		LockOwner = NULL;
	}

	// UnStealth if we don't have any occupants, and we aren't in single play
	if (StealthEffect != NULL) {
		if ((Get_Occupant_Count() == 0 ) && ( !IS_MISSION )) {
			StealthEffect->Enable_Stealth(false);
		}
	}
}
}

static char * _post_profile_name = "Vehicle PostThink";

void	VehicleGameObj::Post_Think( void )
{
{	WWPROFILE( _post_profile_name );
	RenderObjClass * model = Peek_Model();
	WWASSERT( model );

	for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
		if ( SeatOccupants[i] != NULL ) {
			char	bone_name[80];
			sprintf( bone_name, "SEAT%d", i );
			int seat_bone_index = model->Get_Bone_Index( bone_name );
			if ( seat_bone_index != -1 ) {
				Matrix3D seat = model->Get_Bone_Transform( seat_bone_index );
				if ( !SeatOccupants[i]->In_Transition() ) {
					SeatOccupants[i]->Set_Transform( seat );
				}
			} else {
				Debug_Say(( "No Seat bone %s\n", bone_name ));
			}
		}
	}
}
	SmartGameObj::Post_Think();

{	WWPROFILE( _post_profile_name );
	if ( Get_Weapon() && !Get_Definition().Fire0Anim.Is_Empty() ) {
		Get_Weapon()->Reset_Anim_Update();
		int state = Get_Weapon()->Get_Anim_State();
		if ( state == WEAPON_ANIM_NOT_FIRING ) {
			Set_Animation( NULL );
		} else if ( state == WEAPON_ANIM_FIRING_0 ) {
			Set_Animation( Get_Definition().Fire0Anim );
		} else if ( state == WEAPON_ANIM_FIRING_1 ) {
			Set_Animation( !Get_Definition().Fire1Anim.Is_Empty() ? Get_Definition().Fire1Anim : Get_Definition().Fire0Anim );
		} else {
			Debug_Say(( "Bad Weapon Anim State\n" ));
		}
	}
}
}


/*
**
*/
int VehicleGameObj::Get_Player_Type(void) const
{
	// If they have a driver, they are his team.
	for (int i = 0; i < SeatOccupants.Length(); i++) {
		if (SeatOccupants[i] != NULL) {
			return SeatOccupants[i]->Get_Player_Type();
		}
	}

#if 0	// Do this at exit time
	// in MP, empty vehicles are neutral
	if ( !IS_MISSION ) {
		return PLAYERTYPE_NEUTRAL;
	}
#endif

	return SmartGameObj::Get_Player_Type();
}

/*
**
*/
void	VehicleGameObj::Init_Wheel_Effects( void )
{
	// Create an array of persistant surface emitters, one for each "real" wheel
	WWASSERT( WheelSurfaceEmitters.Length() == 0 );
	if (	(Peek_Vehicle_Phys() != NULL) &&
			(Peek_Vehicle_Phys()->Get_VehiclePhysDef()->Is_Fake() == false) )
	{
		int real_wheel_count = Peek_Vehicle_Phys()->Get_Real_Wheel_Count();
		WheelSurfaceEmitters.Resize(real_wheel_count);
		for (int i=0; i<real_wheel_count; i++) {
			WheelSurfaceEmitters[i] = SurfaceEffectsManager::Create_Persistant_Emitter();
		}
	}

	// Create a single persistant surface effect sound for tire squealing
	WWASSERT(WheelSurfaceSound == NULL);
	WheelSurfaceSound = SurfaceEffectsManager::Create_Persistant_Sound();
}

void	VehicleGameObj::Shutdown_Wheel_Effects( void )
{
	// Release the persistant surface emitters
	for (int i=0; i<WheelSurfaceEmitters.Length(); i++) {
		SurfaceEffectsManager::Destroy_Persistant_Emitter( WheelSurfaceEmitters[i] );
		WheelSurfaceEmitters[i] = NULL;
	}
	WheelSurfaceEmitters.Clear();

	// Release the persistant surface sound effect
	SurfaceEffectsManager::Destroy_Persistant_Sound(WheelSurfaceSound);
	WheelSurfaceSound = NULL;
}

void	VehicleGameObj::Update_Wheel_Effects( void )
{
	if (	(Peek_Vehicle_Phys() == NULL) ||
			(Peek_Vehicle_Phys()->Get_VehiclePhysDef()->Is_Fake()) )
	{
		return;
	}

	int roll_hitter_type = SurfaceEffectsManager::HITTER_TYPE_TIRE_ROLLING;
	int slide_hitter_type = SurfaceEffectsManager::HITTER_TYPE_TIRE_SLIDING;

	if ( Peek_Vehicle_Phys()->As_TrackedVehicleClass() != NULL) {
		roll_hitter_type = SurfaceEffectsManager::HITTER_TYPE_TRACK_ROLLING;
		slide_hitter_type = SurfaceEffectsManager::HITTER_TYPE_TRACK_SLIDING;
	}

	Vector3 vel;
	Peek_Vehicle_Phys()->Get_Velocity(&vel);
	float vlen2 = vel.Length2();

	// Update the emitters and sound
	const float EMITTER_SLIDE_THRESHOLD = 1.0f;
	const float EMITTER_ROLL_THRESHOLD2 = 2.0f * 2.0f;
	const float EMITTER_CINEMATIC_ROLL_AVEL = DEG_TO_RADF(1.0f);

	bool sound_done = false;

	if (WheelSurfaceEmitters.Length() > 0) {

		if ( Peek_Vehicle_Phys() != NULL) {
			int emitter_index = 0;
			int wheel_count = Peek_Vehicle_Phys()->Get_Wheel_Count();
			for (int i=0; i<wheel_count; i++) {

				SuspensionElementClass * wheel = Peek_Vehicle_Phys()->Peek_Wheel(i);
				if (wheel->Get_Flag(SuspensionElementClass::FAKE) == false) {

					// Look up the surface and hitter types
					int surface_type = wheel->Get_Contact_Surface();
					int hitter_type = SurfaceEffectsManager::HITTER_TYPE_NONE;

					// If stealthed, no emitters.
					if (Is_Stealthed()) {
						surface_type = SURFACE_TYPE_DEFAULT;
					}

					if (wheel->Get_Flag( SuspensionElementClass::INCONTACT ) ) {
						if (wheel->Get_Slip_Factor() > EMITTER_SLIDE_THRESHOLD) {

							hitter_type = slide_hitter_type;

						} else {

							// normally, trigger roll emitters by the velocity of the vehicle
							if (vlen2 > EMITTER_ROLL_THRESHOLD2) {
								hitter_type = roll_hitter_type;
							}

							// when attached to a cinematic, we deduce whether the wheels are rolling
							// by using the rotation delta feature of wheels
							if (Is_Attached_To_An_Object()) {
								float wheel_avel = WWMath::Fabs(wheel->Get_Rotation_Delta() / TimeManager::Get_Frame_Seconds());
								if (wheel_avel > EMITTER_CINEMATIC_ROLL_AVEL) {
									hitter_type = roll_hitter_type;
								}
							}
						}
					}

	   			// Update the emitter and move to the next emitter
					SurfaceEffectsManager::Update_Persistant_Emitter(	WheelSurfaceEmitters[emitter_index],
																						surface_type,
																						hitter_type,
																						wheel->Get_Wheel_Transform());

					emitter_index++;


					// If this is the sound generating wheel, update the sound
					// I'm just using the first engine wheel as the one that generates the tire sound effects.
					if (!sound_done && wheel->Get_Flag(SuspensionElementClass::ENGINE)) {
						SurfaceEffectsManager::Update_Persistant_Sound( WheelSurfaceSound,
																						surface_type,
																						hitter_type,
																						Get_Transform());

						sound_done = true;
					}
				}
			}
		}
	}
}

void VehicleGameObj::Update_Sound_Effects(void)
{
	if (Peek_Vehicle_Phys() == NULL) {
		return;
	}

	switch (EngineSoundState)
	{

	case ENGINE_SOUND_STATE_STARTING:
		{
			// play our ENGINE_STARTING sound, if it is finished or not found, destroy and go to ENGINE_RUNNING
			if ((CachedEngineSound == NULL) || ((CachedEngineSound != NULL) && (CachedEngineSound->Get_State()==AudibleSoundClass::STATE_STOPPED))) {
				Change_Engine_Sound_State(ENGINE_SOUND_STATE_RUNNING);
			}
		}
		break;

	case ENGINE_SOUND_STATE_RUNNING:
		{
			if (CachedEngineSound != NULL) {
				Update_Engine_Sound_Pitch ();
			}
			if (Peek_Vehicle_Phys()->Is_Engine_Enabled() == false) {
				Change_Engine_Sound_State(ENGINE_SOUND_STATE_STOPPING);
			}
		}
		break;

	case ENGINE_SOUND_STATE_STOPPING:
		{
			// play the ENGINE_STOPPING sound, if it is finished or not found, destroy and go to ENGINE_OFF
			if ((CachedEngineSound == NULL) || ((CachedEngineSound != NULL) && (CachedEngineSound->Get_State()==AudibleSoundClass::STATE_STOPPED))) {
				Change_Engine_Sound_State(ENGINE_SOUND_STATE_OFF);
			}
		}
		break;
	case ENGINE_SOUND_STATE_OFF:
		{
			// if the engine is on, go to ENGINE_STARTING
			if (Peek_Vehicle_Phys()->Is_Engine_Enabled() == true) {
				Change_Engine_Sound_State(ENGINE_SOUND_STATE_STARTING);
			}
		}
		break;

	default:
		WWDEBUG_SAY(("Error: Invalid Engine Sound State %d! file: %s line: %d\n",EngineSoundState,__FILE__,__LINE__));
		break;
	}

}

void VehicleGameObj::Change_Engine_Sound_State(int new_state)
{
	if (CachedEngineSound != NULL) {
		CachedEngineSound->Remove_From_Scene();
		CachedEngineSound->Release_Ref();
		CachedEngineSound = NULL;
	}

	EngineSoundState = new_state;

	DefinitionClass * sound_def = DefinitionMgrClass::Find_Definition(Get_Definition().EngineSound[new_state]);
	if (sound_def != NULL) {
		CachedEngineSound = (AudibleSoundClass *)sound_def->Create();
	}

	if (CachedEngineSound != NULL) {
		CachedEngineSound->Add_To_Scene();
		CachedEngineSound->Attach_To_Object(Peek_Model());
	}

	return ;
}

void	VehicleGameObj::Update_Engine_Sound_Pitch(void)
{
	if (CachedEngineSound == NULL) {
		return ;
	}

	float pitch_factor = 1.0F;

	if (Peek_Vehicle_Phys() != NULL) {

		MotorVehicleClass * motophys = Peek_Physical_Object()->As_MotorVehicleClass();
		TrackedVehicleClass * trackphys = Peek_Physical_Object()->As_TrackedVehicleClass();
		VTOLVehicleClass * vtolphys = Peek_Physical_Object()->As_VTOLVehicleClass();

		float max_factor = Get_Definition().EngineSoundMaxPitchFactor;

		if (motophys != NULL) {

			// Wheeled vehicle engine pitch depends on their rpms
			float norm_rpm = motophys->Get_Normalized_Engine_RPM();
			if (norm_rpm < 0) {
				norm_rpm = -norm_rpm;
			}
			norm_rpm = WWMath::Clamp(norm_rpm,0.0f,1.0f);

			pitch_factor = 1.0F + ((max_factor - 1.0F) * norm_rpm);

		} else if (trackphys != NULL) {

			// Tracked vehicle engine pitch depends on their overall speed
			const float MAX_TRACKED_SPEED = 10.0f;
			Vector3 vel;
			trackphys->Get_Velocity(&vel);

			float norm_speed = WWMath::Clamp(vel.Length() / MAX_TRACKED_SPEED,0.0f,1.0f);

			pitch_factor = 1.0F + ((max_factor - 1.0F) * norm_speed);

		} else if (vtolphys != NULL) {

			// VTOL vehicle engine pitch depends on absolute acceleration
			const float MAX_VTOL_SPEED = 20.0f;
			Vector3 vel;
			vtolphys->Get_Velocity(&vel);

			float norm_speed = WWMath::Clamp(vel.Length() / MAX_VTOL_SPEED,0.0f,1.0f);

			pitch_factor = 1.0F + ((max_factor - 1.0F) * norm_speed);
		}
	}

	CachedEngineSound->Set_Pitch_Factor (pitch_factor);
	return ;
}

VehiclePhysClass	*VehicleGameObj::Peek_Vehicle_Phys( void ) const
{
	WWASSERT( Peek_Physical_Object() );
	return Peek_Physical_Object()->As_VehiclePhysClass();		// NOTE: sometimes will return NULL!
}


/*
** Occupants ( drivers, gunners, passengers )
*/
void	VehicleGameObj::Add_Occupant( SoldierGameObj * occupant )
{
	// Add the the lowest empty seat
	for ( int i = 0; i < Get_Definition().NumSeats; i++ ) {
		if ( SeatOccupants[i] == NULL ) {
			Add_Occupant( occupant, i );
			break;;
		}
	}
}

void	VehicleGameObj::Add_Occupant( SoldierGameObj * occupant, int seat_id )
{
	WWASSERT( occupant );

	if ( SeatOccupants[seat_id] != NULL ) {
		Debug_Say(( "Vehicle already has an occupant in that seat\n" ));
		return;
	}

	if ( Find_Seat( occupant ) != -1 ) {
		Debug_Say(( "Soldier already in vehicle\n" ));
	}


	if ( seat_id == 0 ) {
		DriverIsGunner = DefaultDriverIsGunner;
	}

	Debug_Say(( "Soldier %p added to seat %d\n", occupant, seat_id ));

	if ( seat_id > Get_Definition().NumSeats-1 ) {
		Debug_Say(( "Adding to extra seat %d\n", seat_id ));
	}

	SeatOccupants[seat_id] = occupant;
	OccupiedSeats++;
	Set_Object_Dirty_Bit( NetworkObjectClass::BIT_RARE, true );

	const char * anim_name = NULL;
	if ( Get_Definition().Type == VEHICLE_TYPE_BIKE ) {
		anim_name = "S_A_HUMAN.H_A_V20A";
	} else {
		anim_name = "S_A_HUMAN.H_A_V10A";
	}
	occupant->Enter_Vehicle( this, anim_name );

//	Debug_Say(( "Vehicle has a occupant in seat %d\n", seat_id ));

	// possibily delete transitions
	Create_And_Destroy_Transitions();

	Enable_Engine(true);

	if ( Get_Definition().OccupantsVisible == false ) {
		occupant->Peek_Model()->Set_Hidden( true );
	}

	// Notify the observers that someone entered
	const GameObjObserverList & observer_list = Get_Observers();
	for( int index = 0; index < observer_list.Count(); index++ ) {
		observer_list[ index ]->Custom( this, CUSTOM_EVENT_VEHICLE_ENTERED, seat_id, occupant );
	}

	//
	//	Reveal this vehicle to the player if he was the one to enter the vehicle
	//
	if ( occupant == COMBAT_STAR ) {
		EncyclopediaMgrClass::Reveal_Object( this );
	}

	//
	// Reset any action the vehicle was performing
	//
	if (Get_Action() != NULL) {
		Get_Action()->Reset(1);
	}

	//
	// Unlock the vehicle!
	//
	LockTimer = 0.0f;
	LockOwner = NULL;
}

int	VehicleGameObj::Find_Seat( SoldierGameObj * occupant )
{
	for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
		if ( SeatOccupants[i] == occupant ) {
			return i;
		}
	}
	return -1;
}

void	VehicleGameObj::Remove_Occupant( SoldierGameObj * occupant )
{
	WWASSERT( occupant );

	int	seat_num = Find_Seat( occupant );

	if ( seat_num == -1 ) {
		Debug_Say(( "Object is not in this vehicle\n" ));
		return;
	}

	// Do this anyway, just to make sure it happens.
	if ( Get_Definition().OccupantsVisible == false ) {
		if ( SeatOccupants[seat_num] && SeatOccupants[seat_num]->Peek_Model() ) {
			SeatOccupants[seat_num]->Peek_Model()->Set_Hidden( false );
		}
	}

	Debug_Say(( "Soldier %p removed from seat %d\n", occupant, seat_num ));

	SeatOccupants[seat_num]->Exit_Vehicle();
	SeatOccupants[seat_num] = NULL;
	OccupiedSeats--;

	// in MP, empty vehicles are neutral
	if ( !IS_MISSION && OccupiedSeats == 0 ) {
		Set_Player_Type( PLAYERTYPE_NEUTRAL );
	}

	Set_Object_Dirty_Bit( NetworkObjectClass::BIT_RARE, true );

	// possibily re-enable transitions
	Create_And_Destroy_Transitions();

	if ( seat_num == 0 ) {
		Enable_Engine(false);
	}

	// Notify the observers that someone left
	const GameObjObserverList & observer_list = Get_Observers();
	for( int index = 0; index < observer_list.Count(); index++ ) {
		observer_list[ index ]->Custom( this, CUSTOM_EVENT_VEHICLE_EXITED, seat_num, occupant );
	}
}

bool	VehicleGameObj::Contains_Occupant( SoldierGameObj * occupant )
{
	WWASSERT( occupant );

	for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
		if ( SeatOccupants[i] == occupant ) {
			return true;
		}
	}

	return false;
}

int VehicleGameObj::Get_Occupant_Count(void)
{
	int count = 0;
	for ( int i = 0; i < SeatOccupants.Length(); i++ ) {
		if ( SeatOccupants[i] != NULL ) {
			count++;
		}
	}
	return count;
}

bool	VehicleGameObj::Is_Entry_Permitted( SoldierGameObj * p_soldier )
{
	if (!CombatManager::Is_Gameplay_Permitted()) {
		//
		// You can't enter a vehicle if there are no opponents, etc
		//
		return false;
	}

	//
	// Do not permit a soldier to enter a vehicle if there is already
	// another soldier of a different player type in the vehicle.
	// Actually, only allow 2 Nods or 2 GDI's to share a vehicle...
	//

	WWASSERT(p_soldier != NULL);

	bool is_permitted = true;

	int player_type = p_soldier->Get_Player_Type();

	int i;
	for (i = 0; i < SeatOccupants.Length(); i++) {

		if (SeatOccupants[i] != NULL) {
			int seated_pt = SeatOccupants[i]->Get_Player_Type();

			if (seated_pt != player_type ||
				(seated_pt != PLAYERTYPE_NOD && seated_pt != PLAYERTYPE_GDI)) {

				is_permitted = false;
				break;
			}
		}
	}

	// If there are no entry transitions, entry is not possible
	bool entry_exists = false;
	for ( i = 0; i < TransitionInstances.Count(); i++ ) {
//		if ( ( TransitionInstances[i]->Get_Type() == TransitionDataClass::LEGACY_VEHICLE_ENTER_0 ) ||
//			  ( TransitionInstances[i]->Get_Type() == TransitionDataClass::LEGACY_VEHICLE_ENTER_1 ) ) {
		if ( TransitionInstances[i]->Get_Type() == TransitionDataClass::VEHICLE_ENTER ) {
			entry_exists = true;
		}
	}
	if ( !entry_exists ) {
		is_permitted = false;
	}

	// If the vehicle is locked and you are not the lock owner, entry is not permitted
	if (Is_Locked() && (p_soldier != LockOwner.Get_Ptr())) {
		is_permitted = false;
	}

	return is_permitted;
}

void	VehicleGameObj::Remove_Transitions( TransitionDataClass::StyleType transition_type )
{
	for ( int i = 0; i < TransitionInstances.Count(); i++ ) {
		if ( TransitionInstances[i]->Get_Type() == transition_type ) {
			TransitionManager::Destroy( TransitionInstances[i] );
			TransitionInstances.Delete( i );
			i--;
		}
	}
}

void	VehicleGameObj::Create_New_Transitions( TransitionDataClass::StyleType transition_type )
{
	const TRANSITION_DATA_LIST & trans_data_list = Get_Definition().Get_Transition_List();

	for ( int i = 0; i < trans_data_list.Count(); i++ ) {
		if ( trans_data_list[i]->Get_Type() == transition_type ) {
			// make new instance
			TransitionInstanceClass * trans = new TransitionInstanceClass( *trans_data_list[i] );
			// setup
			trans->Set_Vehicle( this );
			// add to our list
			TransitionInstances.Add( trans );
			// add to master list
			TransitionManager::Add( trans );
		}
	}
}

void	VehicleGameObj::Update_Transitions( void )
{
	Matrix3D tm = Get_Transform();

	// Update all transitions
	for ( int i = 0; i < TransitionInstances.Count(); i++ ) {
		TransitionInstances[i]->Set_Parent_Transform( tm );
	}
}


void	VehicleGameObj::Passenger_Entering( void )
{
	// Play the passenger entering animation
	char string[80];
	sprintf( string, "V_%sL1.M_%sCL", Get_Definition().TypeName, Get_Definition().TypeName );
	Set_Animation( string );
	Get_Anim_Control()->Set_Mode( ANIM_MODE_ONCE );
	//Debug_Say(("VehicleGameObj::Passenger_Entering\n"));
}

void	VehicleGameObj::Passenger_Exiting( void )
{
	// Play the passenger leaving animation
	char string[80];
	sprintf( string, "V_%sL1.M_%sOP", Get_Definition().TypeName, Get_Definition().TypeName );
	Set_Animation( string );
	Get_Anim_Control()->Set_Mode( ANIM_MODE_ONCE );
	Control.Clear_Control();
}

//void VehicleGameObj::Get_Extended_Information(StringClass & description)
void VehicleGameObj::Get_Description(StringClass & description)
{
	StringClass line;

   line.Format("ID:    %d\n", Get_ID());
	description += line;

   line.Format("NAME:  %s\n", Get_Definition().Get_Name());
	description += line;

   line.Format("TEAM:  %d\n", Get_Player_Type());
	description += line;

   line.Format("CONTR: %d\n", Get_Control_Owner());
	description += line;

	Vector3 position;
	Get_Position(&position);
	line.Format("POS:   %-5.2f, %-5.2f, %-5.2f\n", position.X, position.Y, position.Z);
	description += line;

	Vector3 target = Get_Targeting_Pos();
	line.Format("TGT:   %-5.2f, %-5.2f, %-5.2f\n", target.X, target.Y, target.Z);
	description += line;

	Vector3 velocity;
	Get_Velocity(velocity);
	line.Format("VEL:   %-5.2f, %-5.2f, %-5.2f\n", velocity.X, velocity.Y, velocity.Z);
	description += line;

	line.Format("SPD:   %-5.2f\n", velocity.Length());
	description += line;

	WeaponClass	* p_weapon = Get_Weapon();
	if (p_weapon != NULL) {
		line.Format("WEAP:  %s\n", p_weapon->Get_Name());
		description += line;

		line.Format("RNDS:  %d\n", p_weapon->Get_Total_Rounds());
		description += line;
   }

	if (Get_Defense_Object() != NULL) {
		line.Format("HLTH:  %-5.2f\n", Get_Defense_Object()->Get_Health());
		description += line;
	}

   if (Get_Driver() != NULL) {
		line.Format("DRVR:  %d\n", Get_Driver()->Get_ID());
		description += line;
   }

   if (Get_Gunner() != NULL) {
		line.Format("GUNR:   %d\n", Get_Gunner()->Get_ID());
		description += line;
   }

	line.Format("HIB:   %d\n", Is_Hibernating());
	description += line;

	ControlClass & control = Get_Control();
	line.Format("CTRL:  %d, %d, %5.2f, %5.2f, %5.2f, %5.2f\n",
		control.Get_One_Time_Boolean_Bits(),
		control.Get_Continuous_Boolean_Bits(),
		control.Get_Analog(ControlClass::ANALOG_MOVE_FORWARD),
		control.Get_Analog(ControlClass::ANALOG_MOVE_LEFT),
		control.Get_Analog(ControlClass::ANALOG_MOVE_UP),
		control.Get_Analog(ControlClass::ANALOG_TURN_LEFT));
	description += line;

	if (Get_Action() != NULL) {

		line.Format("ACTCT: %d\n", Get_Action()->Get_Act_Count());
		description += line;

		Vector3 move_loc = Get_Action()->Get_Parameters().MoveLocation;
		line.Format("MVLOC: (%5.2f, %5.2f, %5.2f)\n", move_loc.X, move_loc.Y, move_loc.Z);
		description += line;
	}

	line.Format ("ATTCH: %d\n", Is_Attached_To_An_Object ());
	description += line;

	line.Format("stlth: %d\n", Is_Stealth_Enabled());
	description += line;

	line.Format("   on: %d\n", Is_Stealthed());
	description += line;

	line.Format("ISC:   %d\n", Get_Import_State_Count());
	description += line;
}

bool	VehicleGameObj::Is_Engine_Enabled (void) const
{
	if (Peek_Vehicle_Phys() != NULL) {
		return Peek_Vehicle_Phys()->Is_Engine_Enabled();
	} else {
		return false;
	}
}

void	VehicleGameObj::Enable_Engine (bool onoff)
{
	if (Peek_Vehicle_Phys() != NULL) {
		Peek_Vehicle_Phys()->Enable_Engine(onoff);
	}
}

void VehicleGameObj::Set_Precision(void)
{
	cEncoderList::Set_Precision(BITPACK_VEHICLE_VELOCITY,
		-90.0f, 90.0f, 0.01f); // 200 mph = approx. 90 m/s
	cEncoderList::Set_Precision(BITPACK_VEHICLE_ANGULAR_VELOCITY,
		-20.0f, 20.0f, 0.01f); // to approx 6 PI
	cEncoderList::Set_Precision(BITPACK_VEHICLE_QUATERNION,
		-1.0f, 1.0f, 0.0005f); // -1 to 1
	cEncoderList::Set_Precision(BITPACK_VEHICLE_LOCK_TIMER,
		0.0f, 16.0f, 0.25f);
}

void	VehicleGameObj::Apply_Damage( const OffenseObjectClass & damager, float scale, int alternate_skin )
{
	float starting_health = Get_Defense_Object()->Get_Health();

	//
	//	Let the base class actually handle the call
	//
	SmartGameObj::Apply_Damage( damager, scale, alternate_skin );

	// Stats
	if ( starting_health > 0 && Get_Defense_Object()->Get_Health() <= 0 ) {
		if ( damager.Get_Owner() && damager.Get_Owner()->As_SoldierGameObj() != NULL ) {
			if ( damager.Get_Owner()->As_SoldierGameObj()->Get_Player_Data() != NULL ) {
				damager.Get_Owner()->As_SoldierGameObj()->Get_Player_Data()->Stats_Add_Vehicle_Destroyed();
			}
		}
	}

	//
	//	Now make sure the model is in a state to match its damage level
	//
	Update_Damage_Meshes();
}

void	VehicleGameObj::Update_Damage_Meshes( void )
{
	//
	//	Calculate what percent health we have after this damage
	//
	DefenseObjectClass *defense_object = Get_Defense_Object();
	if ( defense_object != NULL ) {
		float ending_health = defense_object->Get_Health() / defense_object->Get_Health_Max();

		//
		//	Make sure we have a render object to lookup bones on
		//
		RenderObjClass *model = Peek_Model ();
		if (model != NULL) {
			bool show_damage25 = false;
			bool show_damage50 = false;
			bool show_damage75 = false;

			//
			//	Determine what the visibility state of these bones should be
			//
			if (ending_health < 0.25F) {
				show_damage25 = true;
				show_damage50 = true;
				show_damage75 = true;
			} else if (ending_health < 0.5F) {
				show_damage25 = true;
				show_damage50 = true;
			} else if (ending_health < 0.75F) {
				show_damage25 = true;
			}

			static const char *	DAMAGE25_BONE_NAME		= "DAMAGE25";
			static const char *	DAMAGE50_BONE_NAME		= "DAMAGE50";
			static const char *	DAMAGE75_BONE_NAME		= "DAMAGE75";
			static int				DAMAGE25_BONE_NAME_LEN	= ::strlen( DAMAGE25_BONE_NAME );
			static int				DAMAGE50_BONE_NAME_LEN	= ::strlen( DAMAGE50_BONE_NAME );
			static int				DAMAGE75_BONE_NAME_LEN	= ::strlen( DAMAGE75_BONE_NAME );

			//
			//	Loop over all the bones in the model, showing and hiding any that represent
			// damage levels
			//
			int bone_count = model->Get_Num_Bones();
			for (	int index = 0; index < bone_count; index ++) {
				const char *bone_name = model->Get_Bone_Name( index );

				if ( ::strnicmp( bone_name, DAMAGE25_BONE_NAME, DAMAGE25_BONE_NAME_LEN ) == 0 ) {

					//
					//	Show/hide the subobjects associated with 25% damage
					//
					::Set_Subobject_Visibility( model, index, show_damage25 );
				} else if ( ::strnicmp( bone_name, DAMAGE50_BONE_NAME, DAMAGE50_BONE_NAME_LEN ) == 0 ) {

					//
					//	Show/hide the subobjects associated with 50% damage
					//
					::Set_Subobject_Visibility( model, index, show_damage50 );
				} else if ( ::strnicmp( bone_name, DAMAGE75_BONE_NAME, DAMAGE75_BONE_NAME_LEN ) == 0 ) {

					//
					//	Show/hide the subobjects associated with 75% damage
					//
					::Set_Subobject_Visibility( model, index, show_damage75 );
				}
			}
		}
	}

	return ;
}


void	Set_Subobject_Visibility (RenderObjClass *model, int bone_index, bool show)
{
	WWASSERT( model != NULL );

	//
	//	Loop over all the subobjects that are attached to this bone
	//
	int count = model->Get_Num_Sub_Objects_On_Bone( bone_index );
	for (int index = 0; index < count; index ++) {
		RenderObjClass *sub_obj = model->Get_Sub_Object_On_Bone(index, bone_index);
		if (sub_obj != NULL) {

			//
			//	Show or hide this subobject
			//
			sub_obj->Set_Hidden (!show);
			sub_obj->Release_Ref();
		}
	}

	return ;
}

Matrix3D VehicleGameObj::Get_Look_Transform(void)
{
	if ( Get_Definition().SightDownMuzzle ) {
		return Get_Muzzle();
	}
	return SmartGameObj::Get_Look_Transform();
}

const WCHAR *	VehicleGameObj::Get_Vehicle_Name( void )
{
	if ( Get_Definition().VehicleNameID != 0 ) {
		return TranslateDBClass::Get_String( Get_Definition().VehicleNameID );
	}
	return TRANSLATE(IDS_HUD_VEHICLE_NAME);
}

// Vehicles should explode when expired / flipped
ExpirationReactionType	VehicleGameObj::Object_Expired( PhysClass * observed_obj )
{
	// Only on the server
	if ( CombatManager::I_Am_Server() ) {

		if ( Get_Definition().KilledExplosion != 0 && !Is_Delete_Pending() ) {
			Vector3 pos;
			Get_Position(&pos);
			WWASSERT(pos.Is_Valid());// most likely candidate for explosion damage bug....?
			ExplosionManager::Server_Explode( Get_Definition().KilledExplosion,pos,0 );
		}

		if ( !Is_Delete_Pending() ) {
			// notify the observers of killed
			const GameObjObserverList & observer_list = Get_Observers();
			for( int index = 0; index < observer_list.Count(); index++ ) {
				observer_list[ index ]->Killed( this, NULL );
			}
		}

		return SmartGameObj::Object_Expired( observed_obj );

	} else {
		return EXPIRATION_DENIED;
	}
}

/*
**
*/
SoldierGameObj * VehicleGameObj::Get_Driver(void)
{
	if ( SeatOccupants.Length() > DRIVER_SEAT ) {
		return SeatOccupants[DRIVER_SEAT];
	}
	return NULL;
}

SoldierGameObj * VehicleGameObj::Get_Gunner(void)
{
	if ( SeatOccupants.Length() > GUNNER_SEAT ) {
		return SeatOccupants[GUNNER_SEAT];
	}
	return NULL;
}

SoldierGameObj * VehicleGameObj::Get_Actual_Gunner(void)
{
	SoldierGameObj * gunner = Get_Gunner();
	if ( gunner && !Get_Driver_Is_Gunner() ) {
		return gunner;
	}
	return Get_Driver();
}

float VehicleGameObj::Get_Stealth_Fade_Distance(void) const
{
	if (IS_MISSION) {
		return GlobalSettingsDef::Get_Global_Settings()->Get_Stealth_Distance_Vehicle();
	} else {
		return GlobalSettingsDef::Get_Global_Settings()->Get_MP_Stealth_Distance_Vehicle();
	}
}


void VehicleGameObj::Set_Vehicle_Delivered(void)
{
	VehicleDelivered = true;
	Set_Object_Dirty_Bit( NetworkObjectClass::BIT_RARE, true );
}



float VehicleGameObj::Get_Filter_Distance(void) const
{
	if (Get_Definition().Type == VEHICLE_TYPE_TURRET) {
		WeaponClass *weapon = ((VehicleGameObj*)this)->Get_Weapon();
		float range = Get_Definition().SightRange;
		if (weapon != NULL) {
			range = min(weapon->Get_Range(), range);
		}
		return(range);
	}
	return(SmartGameObj::Get_Filter_Distance());
}



void	VehicleGameObj::Ignore_Occupants( void )
{
	for (int i = 0; i < SeatOccupants.Length(); i++) {
		if (SeatOccupants[i] != NULL) {
			if ( SeatOccupants[i]->Peek_Physical_Object() != NULL ) {
				SeatOccupants[i]->Peek_Physical_Object()->Inc_Ignore_Counter();
			}
		}
	}
}

void	VehicleGameObj::Unignore_Occupants( void )
{
	for (int i = 0; i < SeatOccupants.Length(); i++) {
		if (SeatOccupants[i] != NULL) {
			if ( SeatOccupants[i]->Peek_Physical_Object() != NULL ) {
				SeatOccupants[i]->Peek_Physical_Object()->Dec_Ignore_Counter();
			}
		}
	}
}




				/*
				Vector3 current_position;
				Get_Position(&current_position);
				Vector3 pos_error = current_position - sc_position;

				if (pos_error.Length() > 2.0f && Is_Attached_To_An_Object () == false) {
					Matrix3D tm(q, sc_position);
					p_obj->Set_Transform(tm);
					p_obj->Set_Velocity(vel);
					p_obj->Set_Angular_Velocity(ang_vel);
				} else if (Is_Attached_To_An_Object ()) {
					Debug_Say(( "Skipping vehicle position update - attached to bone.\n" ));
				}
				*/

				/*
				// New temporary scheme : constantly correct to server position by linearly
				// interpolating a fraction of the error
				//
				Matrix3D dest_tm(q, sc_position);
				Matrix3D tm = ::Lerp(Get_Transform (), dest_tm, 0.1F);
				p_obj->Set_Transform(tm);
				p_obj->Set_Velocity(vel);
				p_obj->Set_Angular_Velocity(ang_vel);
				*/

				/*
				packet.Add_Vector3(pos);
				packet.Add_Quaternion(q);
				packet.Add_Vector3(vel);
				packet.Add_Vector3(ang_vel);
				*/

















































/*
void VehicleGameObj::Import_State_Sc(BitStreamClass & packet)
{
   WWASSERT(CombatManager::I_Am_Only_Client());

	if (Get_Weapon() != NULL) {
		int rounds = packet.Get(rounds);
		Get_Weapon()->Set_Total_Rounds(rounds);
	}

	for (int i = 0; i < NUM_SEATS; i++) {
		int occupant = packet.Get(occupant);
		if (occupant == -1) {
			Seats[i] = NULL;
		} else {
			if ((Seats[i] == NULL) || (Seats[i]->Get_ID() != occupant)) {
				SmartGameObj * obj = GameObjManager::Find_SmartGameObj(occupant);
				Seats[i] = NULL;
				if (obj != NULL) {
					Seats[i] = obj->As_SoldierGameObj();
				}
			}
		}
	}

	switch(Get_Definition().Type) {

		case VEHICLE_TYPE_BIKE:
		case VEHICLE_TYPE_CAR:
		case VEHICLE_TYPE_TANK:
		{
			VehiclePhysClass * p_obj = Peek_Vehicle_Phys();
			WWASSERT(p_obj != NULL);

			Vector3 sc_position;
			Quaternion q;
			Vector3 vel;
			Vector3 ang_vel;

			//packet.Get_Vector3(pos);
			//packet.Get_Quaternion(q);
			//packet.Get_Vector3(vel);
			//packet.Get_Vector3(ang_vel);

			packet.Get(sc_position.X, BITPACK_WORLD_POSITION_X);
			packet.Get(sc_position.Y, BITPACK_WORLD_POSITION_Y);
			packet.Get(sc_position.Z, BITPACK_WORLD_POSITION_Z);

			packet.Get(q.X, BITPACK_VEHICLE_QUATERNION);
			packet.Get(q.Y, BITPACK_VEHICLE_QUATERNION);
			packet.Get(q.Z, BITPACK_VEHICLE_QUATERNION);
			packet.Get(q.W, BITPACK_VEHICLE_QUATERNION);

			packet.Get(vel.X, BITPACK_VEHICLE_VELOCITY);
			packet.Get(vel.Y, BITPACK_VEHICLE_VELOCITY);
			packet.Get(vel.Z, BITPACK_VEHICLE_VELOCITY);

			packet.Get(ang_vel.X, BITPACK_VEHICLE_ANGULAR_VELOCITY);
			packet.Get(ang_vel.Y, BITPACK_VEHICLE_ANGULAR_VELOCITY);
			packet.Get(ang_vel.Z, BITPACK_VEHICLE_ANGULAR_VELOCITY);

			switch (CombatManager::Get_Client_Interpolation_Model()) {

				case CLIENT_INTERPOLATION_SERVER_AUTHORITATIVE:
				case CLIENT_INTERPOLATION_SERVER_AUTHORITATIVE_WITH_BLENDING:
				{
					//Matrix3D tm(q, sc_position);
					//p_obj->Set_Transform(tm);
					//p_obj->Set_Velocity(vel);
					//p_obj->Set_Angular_Velocity(ang_vel);

					Vector3 current_position;
					Get_Position(&current_position);
					Vector3 pos_error = current_position - sc_position;
					if (pos_error.Length() > 2.0f) {
						Matrix3D tm(q, sc_position);
						p_obj->Set_Transform(tm);
						p_obj->Set_Velocity(vel);
						p_obj->Set_Angular_Velocity(ang_vel);
					}
					break;
				}

				case CLIENT_INTERPOLATION_PATHFIND: {

					SoldierGameObj * p_driver = Seats[DRIVER_SEAT];
					if (p_driver != NULL && p_driver->Get_Control_Owner() == CombatManager::Get_My_Id()) {

						Matrix3D tm(q, sc_position);
						p_obj->Set_Transform(tm);
						p_obj->Set_Velocity(vel);
						p_obj->Set_Angular_Velocity(ang_vel);

					} else {

						ActionParamsStruct parameters;
						parameters.Priority = 1;
						parameters.MoveLocation = sc_position;
						parameters.MoveArrivedDistance = 0.1f;
						WWASSERT(Get_Action() != NULL);
						Get_Action()->Goto(parameters);
					}
					break;
				}

				default:
					WWASSERT(0);
			}
			break;
		}

		case VEHICLE_TYPE_FLYING:
			break;

		case VEHICLE_TYPE_TURRET:
			break;

		default:
			WWASSERT( 0 );
			break;
	}

   SmartGameObj::Import_State_Sc(packet);

   WWASSERT(packet.Is_Flushed());
}
*/
