/*
**	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/action.cpp                            $*
 *                                                                                             *
 *                      $Author:: Byon_g                                                      $*
 *                                                                                             *
 *                     $Modtime:: 3/19/02 11:31a                                              $*
 *                                                                                             *
 *                    $Revision:: 266                                                         $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "action.h"
#include "input.h"
#include "persistfactory.h"
#include "combatchunkid.h"
#include "path.h"
#include "pathsolve.h"
#include "pathmgr.h"
#include "vehicledriver.h"
#include "pilot.h"
#include "soldier.h"
#include "vehicle.h"
#include "weapons.h"
#include "movephys.h"
#include "pathfind.h"
#include "pathfindportal.h"
#include "debug.h"
#include "animcontrol.h"
#include "conversationmgr.h"
#include "activeconversation.h"
#include "orator.h"
#include "soldierobserver.h"
#include "physcon.h"
#include "phys3.h"
#include "humanphys.h"
#include "pscene.h"
#include "staticphys.h"
#include "elevator.h"
#include "doors.h"
#include "pathaction.h"
#include "crandom.h"
#include "playertype.h"
#include "wwprofile.h"

#include "combat.h"
#include "waypath.h"
#include "waypoint.h"
#include "gameobjmanager.h"

#include "colmathaabox.h"
#include "dinput.h"

int _ActionActCalls = 0;
int _ActionCodeChanges = 0;

/*
**
*/
static	bool	Display_Findpaths = false;
void	Toggle_Display_Findpaths( void ) { Display_Findpaths = !Display_Findpaths; }

/*
** ActionCodeClass
*/
class	ActionCodeClass : public PersistClass {

public:
	typedef enum {
		ACTION_DONE,
		ACTION_IN_PROGRESS,
	} ActResult;

	ActionCodeClass( void ) : Action( NULL )	  		{}
	virtual ~ActionCodeClass( void )				  		{}

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001350,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			PersistClass::Save( csave );
		csave.End_Chunk();
		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;
			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	void	Init( ActionClass * action )
	{
		WWASSERT( action );
		WWASSERT( Action == NULL );
		Action = action;

		//
		//	Check to see if we need to change the soldier's AI state
		//
		if ( Action->Get_Parameters().AIState != NO_AI_STATE_CHANGE ) {
			SmartGameObj *	obj = Action->Get_Action_Obj();
			if ( obj != NULL ) {

				//
				//	Check to ensure this is really a soldier
				//
				SoldierGameObj * soldier = obj->As_SoldierGameObj();
				if ( soldier != NULL ) {
					soldier->Set_AI_State( Action->Get_Parameters().AIState );
				}
			}
		}
	}

	virtual	void	Modify_Parameters( const SafeActionParamsStruct & parameters, bool modify_move, bool modify_attack ) {}

	virtual	void	Set_Action( ActionClass * action )	{ WWASSERT( action ); WWASSERT( Action == NULL ); Action = action; }
	virtual	void	Shutdown( void )						{ Action = NULL; }
	virtual	ActResult	Act( void )			  			{ return ACTION_IN_PROGRESS; }
	virtual	bool	Is_Animating( void )					{ return false; }
	virtual	bool	Is_Busy( void )						{ return false; }

	virtual	void	Begin_Hibernation( void )	{}
	virtual	void	End_Hibernation( void )		{}

protected:
	ActionClass					*	Action;

};

/*
**
*/
SimpleDynVecClass<ActionCodeClass*>	ActionDeleteList;

void	Register_Action_Code_Deletion( ActionCodeClass * action )
{
	ActionDeleteList.Add( action );
}

void	Delete_Action_Code( void )
{
	for ( int i = 0; i < ActionDeleteList.Count(); i++ ) {
		delete ActionDeleteList[i];
	}
	// Clear vector but don't allow shrink.
	ActionDeleteList.Delete_All(false);
}


/*
** Follow Input Action Code Class
*/
class	FollowInputActionCodeClass;
SimplePersistFactoryClass<FollowInputActionCodeClass, CHUNKID_ACTION_CODE_FOLLOW_INPUT>	_FollowInputActionCodeClassFactory;

class	FollowInputActionCodeClass : public ActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _FollowInputActionCodeClassFactory; }

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001349,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			ActionCodeClass::Save( csave );
		csave.End_Chunk();
		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;
			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	ActResult	Act( void )
	{
		float amount;
		SmartGameObj *	obj = Action->Get_Action_Obj();

		// If we're in a vehicle, use the input function VEHICLE_TURN_LEFT...
		if ((obj->As_SoldierGameObj() != NULL) && (obj->As_SoldierGameObj()->Get_Vehicle() != NULL)) {
			amount = Input::Get_Amount( INPUT_FUNCTION_VEHICLE_TURN_LEFT ) - Input::Get_Amount( INPUT_FUNCTION_VEHICLE_TURN_RIGHT );
		} else {
			amount = Input::Get_Amount( INPUT_FUNCTION_TURN_LEFT ) - Input::Get_Amount( INPUT_FUNCTION_TURN_RIGHT );
		}
		obj->Set_Analog_Control( ControlClass::ANALOG_TURN_LEFT, amount );

		float forward_amount = Input::Get_Amount( INPUT_FUNCTION_MOVE_FORWARD ) - Input::Get_Amount( INPUT_FUNCTION_MOVE_BACKWARD );
		float left_amount = Input::Get_Amount( INPUT_FUNCTION_MOVE_LEFT ) - Input::Get_Amount( INPUT_FUNCTION_MOVE_RIGHT );
		float up_amount = Input::Get_Amount( INPUT_FUNCTION_MOVE_UP ) - Input::Get_Amount( INPUT_FUNCTION_MOVE_DOWN );

		if ( obj->As_SoldierGameObj() && obj->As_SoldierGameObj()->Get_Vehicle() ) {
			VehicleGameObj * vehicle = obj->As_SoldierGameObj()->Get_Vehicle();
			// Convert turn to straffe
			if ( vehicle->Is_Aircraft() && (Input::Is_Button_Down (DIK_LCONTROL) || Input::Is_Button_Down (DIK_LMENU) ) ) {
				left_amount = amount;
				obj->Set_Analog_Control( ControlClass::ANALOG_TURN_LEFT, 0 );
			}
		}

		Vector3 move( forward_amount, left_amount, up_amount );
		float length = move.Length();
		if ( length > 1 ) {
			forward_amount /= length;
			left_amount /= length;
		}

#if 0
		if ( Input::Get_State( INPUT_FUNCTION_WALK_MODE ) ) {
			forward_amount /= 4;
			left_amount /= 4;
		}
#else
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_WALK,					Input::Get_State( INPUT_FUNCTION_WALK_MODE ) );
#endif
		obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_FORWARD, forward_amount );
		obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_LEFT, left_amount );
		obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_UP, up_amount );

		obj->Set_Boolean_Control( ControlClass::BOOLEAN_JUMP,					Input::Get_State( INPUT_FUNCTION_JUMP ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_VEHICLE_TOGGLE_GUNNER, Input::Get_State( INPUT_FUNCTION_VEHICLE_TOGGLE_GUNNER ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_NEXT,		Input::Get_State( INPUT_FUNCTION_NEXT_WEAPON ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_PREV,		Input::Get_State( INPUT_FUNCTION_PREV_WEAPON ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY,	Input::Get_State( INPUT_FUNCTION_FIRE_WEAPON_PRIMARY ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY,	Input::Get_State( INPUT_FUNCTION_FIRE_WEAPON_SECONDARY ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_USE,			Input::Get_State( INPUT_FUNCTION_USE_WEAPON ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_RELOAD,		Input::Get_State( INPUT_FUNCTION_RELOAD_WEAPON ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_FORWARD,		Input::Get_State( INPUT_FUNCTION_DIVE_FORWARD ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_BACKWARD,		Input::Get_State( INPUT_FUNCTION_DIVE_BACKWARD ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_LEFT,			Input::Get_State( INPUT_FUNCTION_DIVE_LEFT ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_RIGHT,			Input::Get_State( INPUT_FUNCTION_DIVE_RIGHT ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH,				Input::Get_State( INPUT_FUNCTION_CROUCH ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_ACTION,				Input::Get_State( INPUT_FUNCTION_ACTION ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_DROP_FLAG,			Input::Get_State( INPUT_FUNCTION_DROP_FLAG ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_NO_WEAPON,	Input::Get_State( INPUT_FUNCTION_SELECT_NO_WEAPON ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_0,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_0 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_1,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_1 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_2,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_2 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_3,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_3 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_4,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_4 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_5,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_5 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_6,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_6 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_7,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_7 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_8,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_8 ) );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_9,	Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_9 ) );
		return ACTION_IN_PROGRESS;
	}
};


/*
** Stand Action Code Class
*/
class	StandActionCodeClass;
SimplePersistFactoryClass<StandActionCodeClass, CHUNKID_ACTION_CODE_STAND>	_StandActionCodeClassFactory;

class	StandActionCodeClass : public ActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _StandActionCodeClassFactory; }

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001348,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			ActionCodeClass::Save( csave );
		csave.End_Chunk();
		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;
			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	ActResult	Act( void )
	{
		return ACTION_IN_PROGRESS;
	}
};


/*
** Play Animation Action Code Class
*/
class	PlayAnimationActionCodeClass;
SimplePersistFactoryClass<PlayAnimationActionCodeClass, CHUNKID_ACTION_CODE_PLAY_ANIMATION>	_PlayAnimationActionCodeClassFactory;

class	PlayAnimationActionCodeClass : public ActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _PlayAnimationActionCodeClassFactory; }

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001347,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			ActionCodeClass::Save( csave );
		csave.End_Chunk();
		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;
			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	bool	Is_Animating( void )					{ return true; }

	virtual	void	Init( ActionClass * action )
	{
		ActionCodeClass::Init( action );

		SmartGameObj *	obj = Action->Get_Action_Obj();

		WWASSERT( obj->Get_Anim_Control() != NULL );
		obj->Set_Animation( action->Get_Parameters().SafeAnimationName, action->Get_Parameters().AnimationLooping );
	}

	virtual	void	Shutdown( void )
	{
		SmartGameObj *	obj = Action->Get_Action_Obj();
		WWASSERT( obj->Get_Anim_Control() != NULL );
//		WWASSERT( obj->Get_Anim_Control()->Get_Animation_Name()[0] != 0 );
		obj->Set_Animation( NULL, false );
		ActionCodeClass::Shutdown();
	}

	virtual	ActResult	Act( void )
	{
		SmartGameObj *	obj = Action->Get_Action_Obj();
		WWASSERT( obj->Get_Anim_Control() != NULL );

		if ( obj->Get_Anim_Control()->Get_Animation_Name()[0] == 0 ) {
			Debug_Say(( "Not playing an anim when we should be playing %s\n", Action->Get_Parameters().SafeAnimationName ));
			Action->Done( ACTION_COMPLETE_NORMAL );
			return ACTION_DONE;
		}

//		WWASSERT( obj->Get_Anim_Control()->Get_Animation_Name()[0] != 0 );

		if ( obj->Get_Anim_Control()->Is_Complete() ) {
			Action->Done( ACTION_COMPLETE_NORMAL );
			return ACTION_DONE;
		}

		return ACTION_IN_PROGRESS;
	}
};


/*
** Base Action Code Class
**
** This action will move and/or shoot context Object to the context Locations
** From the outside, you can set the moveto location, and you can set the
** shootAt location.  The last one you set sets the moving flag, which
** is ignored if object can move and shoot independently.
** Need to know:
		Can object move and fire independantly( if turreted || moves laterally )
		Is Object Turreted ( gun can face a different direction than object, just tanks )
		Can object move laterally (tanks and cars cannot, humans can)
		Can object turn without moving ( cars cannot )
*/
class	BaseActionCodeClass : public ActionCodeClass {
public:

	BaseActionCodeClass( void )
	{
		IsMoving = false;
		IsAttacking = false;
	}

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001345,
		CHUNKID_VARIABLES,
		CHUNKID_VEHICLE_DRIVER,
		CHUNKID_PILOT,

		MICROCHUNKID_IS_ATTACKING					=	1,
		MICROCHUNKID_IS_MOVING						=	2,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			ActionCodeClass::Save( csave );
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_VARIABLES );
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_ATTACKING,				IsAttacking				);
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_MOVING,					IsMoving					);
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_VEHICLE_DRIVER );
			VehicleDriver.Save( csave );
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_PILOT );
			Pilot.Save( csave );
		csave.End_Chunk();

		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				case CHUNKID_VARIABLES:
					while (cload.Open_Micro_Chunk()) {
						switch(cload.Cur_Micro_Chunk_ID()) {
							READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_ATTACKING,				IsAttacking				);
							READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_MOVING,					IsMoving					);

							default:
								Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
								break;
						}
						cload.Close_Micro_Chunk();
					}
					break;

				case CHUNKID_VEHICLE_DRIVER:
					VehicleDriver.Load( cload );
					break;

				case CHUNKID_PILOT:
					Pilot.Load( cload );
					break;

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;

			}
			cload.Close_Chunk();
		}
		return true;
	}


		// Return a pointer to the object that will be doing the movement
	SmartGameObj *Peek_Transport_Object( SmartGameObj *game_obj )
	{
		SmartGameObj *transport = game_obj;

		if (	game_obj->As_SoldierGameObj() != NULL  &&
				game_obj->As_SoldierGameObj()->Get_Profile_Vehicle() != NULL ) {
			transport = game_obj->As_SoldierGameObj()->Get_Profile_Vehicle();
		}

		return transport;
	}

	void Clamped_Units( Vector3& value, float rate )
	{
		// convert to "units", and clamp
		rate *= TimeManager::Get_Frame_Seconds();
		if ( rate > 0.0001f ) {
			value /= rate;
		}
		if ( (rate <= 0.0001f) || value.Length() > 1.0 ) {
			value.Normalize();
		}
	}

	// Move Human object to given relative position
	void	Human_Move_To_Relative( Vector3 & orig_rel_pos )
	{
//Debug_Say(( "Mov To Rel %f %f %f\n", rel_pos.X, rel_pos.Y, rel_pos.Z ));
		SmartGameObj *	obj = Action->Get_Action_Obj();
		float speed = Action->Get_Parameters().MoveSpeed;

		Vector3 move = orig_rel_pos;

		//
		// Zero out the relative distance in the z axis (if necessary)
		//
		SoldierGameObj * soldier = obj->As_SoldierGameObj();
		if ( soldier == NULL || soldier->Is_On_Ladder() == false) {
			move.Z = 0;
		}

		Clamped_Units( move, obj->Get_Max_Speed() * speed );

		if ( move.Length2() > 1 ) {
			move.Normalize();
		}

   	obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_FORWARD, move[0] * speed );
		obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_LEFT, move[1] * speed );

		// Special case for soldier moving
		if ( soldier && soldier->Is_On_Ladder() ) {
			obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_FORWARD, (move[2] >= 0) ? 1 : -1 );		// Could we make this move up?
		}
	}

	// Move object to given relative position, return if complete
	bool	Move_To_Absolute( const Vector3 & abs_pos, bool ignore_arrived_dist = false )
	{
		SmartGameObj *	obj = Action->Get_Action_Obj();

		//	Should the vehicle AI code take over?
		SmartGameObj *transport_obj = Peek_Transport_Object( obj );
		VehicleGameObj *vehicle = transport_obj->As_VehicleGameObj ();
		if ( vehicle != NULL ) {

			bool retval = false;
			if (vehicle->Is_Aircraft ()) {
				Pilot.Set_Speed_Factor( Action->Get_Parameters().MoveSpeed );

				if ( Action->Get_Parameters().AttackObject != NULL ) {
					Vector3 target_pos;
					Action->Get_Parameters().AttackObject->Get_Position( &target_pos );
					Pilot.Set_Target( &target_pos );
				}

				Pilot.Set_Speed_Factor( Action->Get_Parameters().MoveSpeed );
				Pilot.Set_Arrived_Dist( Action->Get_Parameters().MoveArrivedDistance );

				retval = Pilot.Think();
			} else {
				VehicleDriver.Set_Next_Position( abs_pos );
				VehicleDriver.Set_Speed_Factor( Action->Get_Parameters().MoveSpeed );
				VehicleDriver.Set_Arrived_Dist( Action->Get_Parameters().MoveArrivedDistance );
				VehicleDriver.Force_Backup( Action->Get_Parameters().MoveBackup );
				retval = VehicleDriver.Drive();
			}

			IsMoving = true;
			return retval;
		}

		// If we want to be facing a particular position, set targeting to it.
		if ( Action->Get_Parameters().ForceFacing ) {
			obj->Set_Targeting( Action->Get_Parameters().FaceLocation, false ); // Don't tilt
		}

		Vector3	curr_pos;
		obj->Get_Position( &curr_pos );
		curr_pos -= abs_pos;

		// I'm having soldiers get stuck because they are directly below
		// the goal point.  Lets try ignoring Z
		curr_pos.Z = 0;

		float range = curr_pos.Length();
		SoldierGameObj *soldier	= obj->As_SoldierGameObj();

		bool done_facing = true;	// Assume we are done facing

		//
		// If we are not attacking, and not force facing, and are a soldier and
		// are not on a ladder and not very close
		//
		if (	!IsAttacking && !Action->Get_Parameters().ForceFacing &&
				!Action->Get_Parameters().IgnoreFacing && soldier &&
				!soldier->Is_On_Ladder() && (range > 0.1f ) )
		{
			// Then try to face the point we are moving to
			Vector3 face_pt = abs_pos + Vector3( 0,0, obj->Get_Bullseye_Offset_Z() );
			done_facing = obj->Set_Targeting( face_pt, false );
		}

		//
		//	Determine how close we want to get to the end-point before
		// we stop moving
		//
		float arrived_dist = Action->Get_Parameters().MoveArrivedDistance;
		if ( ignore_arrived_dist ) {
			arrived_dist = 0.05F;
		}

		//
		// If we are close enough
		//
		if ( (Get_Distance_From_Goal() + range) <=  arrived_dist) {
			IsMoving = false;
			return done_facing;	// complete;
		}

		IsMoving = true;

		// Find the relitive location
		Vector3 rel_pos;
		Matrix3D::Inverse_Transform_Vector( obj->Get_Transform(), abs_pos, &rel_pos );

		// we done facing, or facing close enough, move
		float goal_bearing = WWMath::Atan2( rel_pos.Y, rel_pos.X );
		if ( done_facing || WWMath::Fabs(goal_bearing) < DEG_TO_RAD( 40 ) ) {
			Human_Move_To_Relative( rel_pos );
		}

		return false;
	}

	virtual float	Get_Distance_From_Goal (void)
	{
		SmartGameObj *	obj = Action->Get_Action_Obj();

		//
		//	Get the object's current position
		//
		Vector3	curr_pos;
		obj->Get_Position( &curr_pos );

		//
		//	Find the difference between the current position and
		// the destination
		//
		curr_pos	-= Action->Get_Parameters().MoveLocation;
		curr_pos.Z = 0;

		//
		//	Return the length
		//
		return curr_pos.Length ();
	}

protected:
	bool						IsAttacking;
	bool						IsMoving;
	VehicleDriverClass	VehicleDriver;
	PilotClass				Pilot;
};


/*
** Goto Action Code Class
*/
class	GotoActionCodeClass;
SimplePersistFactoryClass<GotoActionCodeClass, CHUNKID_ACTION_CODE_GOTO>	_GotoActionCodeClassFactory;

class	GotoActionCodeClass : public BaseActionCodeClass {
public:

	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _GotoActionCodeClassFactory; }

	GotoActionCodeClass( void ) :
		UseMoveObject( false ),
		FirstCall( true ),
		PathSolver( NULL ),
		Path( NULL )
	{
	}

	~GotoActionCodeClass( void )
	{
		return ;
	}

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001308,
		CHUNKID_VARIABLES,
		CHUNKID_PATH,
		CHUNKID_PATH_SOLVER,
		CHUNKID_PATH_ACTION,

		MICROCHUNKID_FIRST_CALL						=	1,
		MICROCHUNKID_USE_MOVE_OBJECT				=	2,
		MICROCHUNKID_TARGET_PREV_POS				=	3,
		MICROCHUNKID_PATH_SOLVE_PTR				=	4,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			BaseActionCodeClass::Save( csave );
		csave.End_Chunk();

		if ( Path != NULL ) {
			csave.Begin_Chunk( CHUNKID_PATH );
				Path->Save( csave );
			csave.End_Chunk();
		}

		csave.Begin_Chunk( CHUNKID_VARIABLES );
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FIRST_CALL,			FirstCall		);
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_USE_MOVE_OBJECT,	UseMoveObject	);
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TARGET_PREV_POS,	TargetPrevPos	);
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_PATH_SOLVE_PTR,	PathSolver		);
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_PATH_ACTION );
			PathAction.Save( csave );
		csave.End_Chunk();

		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				case CHUNKID_VARIABLES:
					while (cload.Open_Micro_Chunk()) {
						switch(cload.Cur_Micro_Chunk_ID()) {
							READ_MICRO_CHUNK( cload, MICROCHUNKID_FIRST_CALL,			FirstCall		);
							READ_MICRO_CHUNK( cload, MICROCHUNKID_USE_MOVE_OBJECT,	UseMoveObject	);
							READ_MICRO_CHUNK( cload, MICROCHUNKID_TARGET_PREV_POS,	TargetPrevPos	);

							case MICROCHUNKID_PATH_SOLVE_PTR:
								LOAD_MICRO_CHUNK( cload, PathSolver );
								if (PathSolver != NULL) {
									REQUEST_REF_COUNTED_POINTER_REMAP( (RefCountClass **)&PathSolver );
								}
								break;

							default:
								Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
								break;
						}
						cload.Close_Micro_Chunk();
					}
					break;

				case CHUNKID_PATH:
				{
					Path = new PathClass;
					Path->Load( cload );
				}
				break;

				case CHUNKID_PATH_ACTION:
					PathAction.Load( cload );
					break;

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;

			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual void	Begin_Hibernation( void )
	{
		BaseActionCodeClass::Begin_Hibernation();
		FirstCall = true;

		//
		//	Return the path we got from the manager
		//
		if ( PathSolver != NULL ) {
			PathMgrClass::Return_Path_Object( PathSolver );
			PathSolver = NULL;
		}

		return ;
	}


	virtual	void	Init( ActionClass * action )
	{
		BaseActionCodeClass::Init( action );
		FirstCall = true;

		SoldierGameObj *soldier_obj = Action->Get_Action_Obj()->As_SoldierGameObj();
		if ( soldier_obj && Action->Get_Parameters().LookDuration != 0 ) {
			soldier_obj->Look_At( Action->Get_Parameters().LookLocation, Action->Get_Parameters().LookDuration );
		}

		UseMoveObject = (Action->Get_Parameters().MoveObjectRef.Get_Ptr() != NULL);
	}

	virtual void	Shutdown( void )
	{
		//
		//	Return the path we got from the manager
		//
		if (PathSolver != NULL) {
			PathMgrClass::Return_Path_Object (PathSolver);
			PathSolver = NULL;
		}

		Release_Path();
		BaseActionCodeClass::Shutdown();
		return ;
	}

	virtual	void	Init( WaypathClass *waypath )
	{
		//
		//	Return the path we got from the manager
		//
		if (PathSolver != NULL) {
			PathMgrClass::Return_Path_Object (PathSolver);
			PathSolver = NULL;
		}

		Release_Path();

		Prepare_Path ();
		Path->Set_Traversal_Type( PathClass::LINEAR );
		Path->Initialize( waypath, -1, -1 );
		Initialize_Vehicle_AI();

		return ;
	}

	virtual	bool	Is_Busy( void )
	{
		return ( PathAction.Get_State () != PathActionClass::STATE_FINISHED );
	}


	virtual	ActResult	Arrived()
	{
		if ( Action->Get_Parameters().MoveFollow ) {
			return ACTION_IN_PROGRESS;				// Do nothing special when we arrive
		}

		if ( Action->Get_Action_Obj() && Action->Get_Parameters().ShutdownEngineOnArrival ) {
			SmartGameObj *transport_obj = Peek_Transport_Object( Action->Get_Action_Obj() );
			if ( transport_obj && transport_obj->As_VehicleGameObj() ) {
				transport_obj->As_VehicleGameObj()->Enable_Engine (false);
			}
		}

		Action->Done( ACTION_COMPLETE_NORMAL );
		return ACTION_DONE;			// We are done
	}

	virtual	ActResult	Beeline( void )
	{
		//
		//	Make sure we have a valid path object to follow
		//
		if ( Path == NULL ) {
			Prepare_Path();
			Vector3 curr_pos;
			Action->Get_Action_Obj()->Get_Position( &curr_pos );
			Path->Initialize( curr_pos, Action->Get_Parameters().MoveLocation );
			Initialize_Vehicle_AI();
		}

		ActResult result = ACTION_IN_PROGRESS;
		if ( Path != NULL ) {
			result = Traverse_Path ();
		}

		// Tell the unit to beeline w/o using pathfind
		/*ActResult result = ACTION_IN_PROGRESS;
		if ( Move_To_Absolute( Action->Get_Parameters().MoveLocation ) ) {
			result = Arrived();
		}*/

		return result;
	}

	void	Prepare_Path( void )
	{
		//
		//	Allocate a path object (if necessary)
		//
		if ( Path == NULL ) {
			Path = new PathClass;
		}

		//
		//	Setup the path traverser
		//
		PathObjectClass path_obj;
		Configure_Path_Object( path_obj );

		//
		//	Initialize the path
		//
		Path->Set_Path_Object( path_obj );
		return ;
	}

	void	Initialize_Vehicle_AI( void )
	{
		//
		//	Get the transport object
		//
		SmartGameObj *transport_obj = Peek_Transport_Object( Action->Get_Action_Obj() );

		//
		//	Initialize the different driving/flying AI's
		//
		VehicleDriver.Initialize( transport_obj, Path );
		VehicleDriver.Force_Backup( Action->Get_Parameters().MoveBackup );
		Pilot.Initialize( transport_obj );
		Pilot.Set_Mode( PilotClass::MODE_FLY_TO_POINT );
		Pilot.Set_Current_Path ( Path );
		Pilot.Set_Arrived_Dist( Action->Get_Parameters().MoveArrivedDistance );
		Pilot.Set_Is_Exact_Z_Important( bool(Action->Get_Parameters().WaypathID != 0) );

		//
		//	Check to make sure we aren't trying to drive a "simple" vehicle around
		//
		if (	transport_obj != NULL && transport_obj->As_VehicleGameObj () != NULL &&
				transport_obj->Peek_Physical_Object () != NULL &&
				transport_obj->Peek_Physical_Object ()->As_MoveablePhysClass () == NULL )
		{
			//
			//	Warn the user that they are trying to drive a non-driveable object
			//
			Debug_Say (("Warning!  Attempting to drive a non-moveable vehicle.  ID = %d.\n", transport_obj->Get_ID ()));
		}

		return ;
	}

	void	Initialize_Waypath( void )
	{
		Prepare_Path ();

		//
		//	Lookup the waypath object
		//
		WaypathClass *waypath = PathfindClass::Get_Instance()->Find_Waypath( Action->Get_Parameters().WaypathID );
		if ( waypath == NULL ) {
			Debug_Say (("Warning!  Waypath %d does not exist.\n", Action->Get_Parameters().WaypathID));
		}
		WWASSERT( waypath != NULL );

		//
		//	Initialize the path
		//
		Path->Set_Traversal_Type( Action->Get_Parameters().WaypathSplined ? PathClass::SPLINE : PathClass::LINEAR );
		Path->Initialize( waypath, Action->Get_Parameters().WaypointStartID, Action->Get_Parameters().WaypointEndID );
		return ;
	}

	void	Configure_Path_Object( PathObjectClass &path_obj )
	{
		//
		//	Determine which game object will actually be doing the moving along the path
		//
		SmartGameObj *transport_obj = Peek_Transport_Object( Action->Get_Action_Obj() );
		WWASSERT( transport_obj != NULL );

		//
		//	Determine the turn radius
		//
		float turn_radius					= 0;
		VehicleGameObj *vehicle_obj	= transport_obj->As_VehicleGameObj();

		if ( vehicle_obj != NULL ) {
			turn_radius = vehicle_obj->Get_Turn_Radius();
		}

		//
		//	Dig out the physics object that will be moving along the path
		//
		MoveablePhysClass *phys_obj = transport_obj->Peek_Physical_Object()->As_MoveablePhysClass();
		if (phys_obj != NULL) {

			//
			//	Configure the path-traverser object
			//
			path_obj.Initialize( *phys_obj );
			path_obj.Set_Max_Speed( transport_obj->Get_Max_Speed () );
			path_obj.Set_Turn_Radius( turn_radius );
			path_obj.Set_Flag( PathObjectClass::IS_VEHICLE, bool(vehicle_obj != NULL) );
		}

		//
		//	Pass the key information onto the path object
		//
		SoldierGameObj *soldier = Action->Get_Action_Obj()->As_SoldierGameObj();
		if ( soldier != NULL ) {
			path_obj.Set_Key_Ring( soldier->Get_Key_Ring () );
		}

		return ;
	}

	void	Initialize_Path_Solver( void )
	{
		//
		//	Get a new path object from the manager (if necessary)
		//
		if (PathSolver == NULL) {
			PathSolver = PathMgrClass::Request_Path_Object();
		}

		//
		//	Get the destination position
		//
		Vector3 dest_pos	= Action->Get_Parameters().MoveLocation;
		TargetPrevPos		= Action->Get_Parameters().MoveLocation;

		//
		//	Setup the path traverser
		//
		PathObjectClass path_obj;
		Configure_Path_Object( path_obj );

		//
		//	Initialize the path itself
		//
		Vector3 cur_pos;
		Peek_Transport_Object (Action->Get_Action_Obj())->Get_Position( &cur_pos );

		PathSolver->Set_Path_Object( path_obj );
		PathSolver->Reset( cur_pos, dest_pos, 2.0F );
		return ;
	}

	void	Initialize_Path( void )
	{
		Release_Path();

		//
		//	Determine if we should use a waypath instead of trying to solve
		// the path dynamically
		//
		if ( Action->Get_Parameters().WaypathID != 0 ) {
			Initialize_Waypath();
			Initialize_Vehicle_AI();
		} else {
			Initialize_Path_Solver();
		}

		return ;
	}

	void	Release_Path( void )
	{
		Pilot.Reset();
		VehicleDriver.Reset();
		REF_PTR_RELEASE( Path );
		// Reset the PathAction class too - otherwise it keeps the pointer we game it to Path. ST - 9/6/2001 10:36AM
		PathAction.Reset();
		return ;
	}

bool
Clip_Point (Vector3 *point, const AABoxClass &box)
{
	Vector3 temp_point = *point;

	//
	//	Clip the temporary point
	//
	temp_point.X = max (temp_point.X, box.Center.X - box.Extent.X);
	temp_point.Y = max (temp_point.Y, box.Center.Y - box.Extent.Y);
	temp_point.Z = max (temp_point.Z, box.Center.Z - box.Extent.Z);
	temp_point.X = min (temp_point.X, box.Center.X + box.Extent.X);
	temp_point.Y = min (temp_point.Y, box.Center.Y + box.Extent.Y);
	temp_point.Z = min (temp_point.Z, box.Center.Z + box.Extent.Z);

	//
	//	Did the clip change the point?
	//
	bool retval = (point->X != temp_point.X);
	retval		|= (point->Y != temp_point.Y);
	retval		|= (point->Z != temp_point.Z);

	//
	//	Pass the new point back to the caller
	//
	(*point) = temp_point;

	return retval;
}

	virtual	ActResult	Traverse_Path ( void )
	{
		ActResult act_result = ACTION_IN_PROGRESS;

		//
		// Let the pathfind system know which directions we can (and are) moving
		//
		Path->Set_Movement_Directions( PathClass::MOVE_X | PathClass::MOVE_Y );

		//
		//	Is the soldier only moving in Z?
		//
		SmartGameObj *obj			= Action->Get_Action_Obj();
		SoldierGameObj *soldier	= obj->As_SoldierGameObj();
		if( soldier != NULL ) {
			if ( soldier->Is_On_Ladder() ) {
				Path->Set_Movement_Directions( PathClass::MOVE_Z );
			}
		}

		//
		//	Get the object's current position
		//
		Vector3 curr_pos;
		obj->Get_Position( &curr_pos );

		//
		//	Determine where the object should go next
		//
		Vector3 next_pos;
		if( Path->Evaluate_Next_Point( curr_pos, next_pos ) ) {

			/*return act_result;
			}*/

			//
			//	Allow debug display of the path
			//
			Path->Display_Path( Display_Findpaths );

			//
			//	What should we be doing?
			//
			switch( Path->Get_State() )
			{
				case PathClass::STATE_PATH_COMPLETE:
				case PathClass::STATE_TRAVERSING_PATH:

					//
					//	Don't allow any movement if something is in our way
					//					
					if ( soldier == NULL || soldier->Is_Soldier_Blocked( curr_pos ) == false ) {
						if ( Move_To_Absolute( next_pos ) ) {
							act_result = Arrived();
						}
					}
					break;

				case PathClass::STATE_ACTION_REQUIRED:
					act_result = Perform_Action();
					break;

				/*case PathClass::STATE_PATH_COMPLETE:
					act_result = Arrived();
					break;					*/
			}

		} else {

			//
			//	Error, free the path object
			//
			Release_Path();
			act_result = ACTION_DONE;
			Action->Done( ACTION_COMPLETE_MOVE_NO_PROGRESS_MADE );
		}

		return act_result;
	}

	virtual	ActResult	Perform_Action ( void )
	{
		SmartGameObj *obj = Action->Get_Action_Obj();

		//
		//	Determine what action we need to perform
		//
		PathClass::ACTION_ID action_id = Path->Get_Action_Type();
		switch( action_id )
		{
			case PathClass::ACTION_LADDER:
			{
				//
				//	Initialize the action object that will be responsible for
				// controlling this action
				//
				PathAction.Initialize( PathActionClass::TYPE_LADDER, Path, obj, NULL );
				PathAction.Set_Ladder_Index( Path->Get_Action_Mechanism() );
			}
			break;

			case PathClass::ACTION_MECHANISM:
			{
				//
				//	Lookup the physics object we will be interacting with
				//
				uint32 mechanism_id	= Path->Get_Action_Mechanism();
				StaticPhysClass *mechanism = PhysicsSceneClass::Get_Instance ()->Find_Static_Object( mechanism_id );
				if (mechanism != NULL) {

					PathActionClass::TYPE type = PathActionClass::TYPE_UNKNOWN;

					//
					//	Determine what type of mechanism this is
					//
					if( mechanism->As_ElevatorPhysClass () != NULL ) {
						type = PathActionClass::TYPE_ELEVATOR;
					} else if( mechanism->As_DoorPhysClass () != NULL ) {
						type = PathActionClass::TYPE_DOOR;
					}

					//
					//	Initialize the action object that will be responsible for
					// controlling this action
					//
					PathAction.Initialize( type, Path, obj, mechanism );
				}
			}
			break;

			case PathClass::ACTION_LEAP:
			case PathClass::ACTION_JUMP:
			{
				//
				//	Get the point where we will be jumping to
				//
				Vector3 dest_pos;
				Path->Get_Action_Destination( dest_pos );

				//
				//	Instruct the physics object to jump to this point
				//
				HumanPhysClass *human_phys_obj = obj->Peek_Physical_Object()->As_HumanPhysClass ();
				if( human_phys_obj != NULL ) {
					human_phys_obj->Jump_To_Point( dest_pos );
				}

				//
				//	Initialize the action object that will be responsible for
				// controlling this action
				//
				PathAction.Initialize( PathActionClass::TYPE_JUMPING, Path, obj, NULL );
			}
			break;
		}

		return ACTION_IN_PROGRESS;
	}

	virtual	ActResult	Process_Action ( void )
	{
		//
		//	Continue to let the action take place
		//
		PathAction.Process ();

		//
		//	Does this action require us to move somewhere?
		//
		if( PathAction.Get_State () == PathActionClass::STATE_MOVING ) {
			Move_To_Absolute( PathAction.Get_Destination (), true );
		}

		return ACTION_IN_PROGRESS;
	}

	virtual	ActResult	Solve_Path ( void )
	{
		ActResult act_result = ACTION_IN_PROGRESS;

		//
		//	Evaluate the path
		//
		PathSolveClass::STATE_DESC result = PathSolver->Get_State();

		//
		//	Has the path finished solving?
		//
		bool is_finished = true;
		switch( result ) {

			case PathSolveClass::THINKING:
				is_finished	= false;
				break;

			case PathSolveClass::SOLVED_PATH:

				//
				//	Allocate and initialize the path object
				//
				Prepare_Path();
				Initialize_Vehicle_AI();

				//
				//	Configure the path object with information we learned
				// during the path solve
				//
				Path->Set_Traversal_Type( PathClass::SPLINE );
				Path->Initialize( *PathSolver );
				break;

			case PathSolveClass::ERROR_INVALID_START_POS:
			case PathSolveClass::ERROR_INVALID_DEST_POS:
			case PathSolveClass::ERROR_NO_PATH:
				break;
		}

		//
		//	If we are finished solving the path, then release our
		// hold on the path solver.
		//
		if ( is_finished ) {
			PathMgrClass::Return_Path_Object( PathSolver );
			PathSolver = NULL;
		}

		return act_result;
	}


	virtual	ActResult	Pathfind( void )
	{
		ActResult act_result	= ACTION_IN_PROGRESS;

		if( PathAction.Get_State () != PathActionClass::STATE_FINISHED ) {

			//
			//	Keep performing the action until we've completed it
			//
			act_result = Process_Action();
		} else {

			if( PathSolver != NULL ) {

				//
				//	Try to find a path to the destination
				//
				act_result = Solve_Path();
			}

			if( PathSolver == NULL) {
				if( Path != NULL ) {

					//
					//	Move along the path we found
					//
					act_result = Traverse_Path();
				} else if ( PathSolver == NULL ){

					//
					//	Unable to solve path...
					//
					//	If this level doesn't have any pathfind data, then allow the unit
					// to beeline -- otherwise, kill the action...
					//
					if (PathfindClass::Get_Instance ()->Does_Pathfind_Data_Exist ()) {
						
						//
						//	Kill the action
						//
						Action->Done( ACTION_COMPLETE_PATH_BAD_DEST );
						act_result = ACTION_DONE;

					} else {
						act_result = Beeline ();
					}
				}
			}
		}

		return act_result;
	}

	virtual float	Get_Distance_From_Goal (void)
	{
		float dist_to_goal = 0;

		//
		//	Get the distance remaining from the path object (if possible)
		//
		if ( Path != NULL ) {
			dist_to_goal = Path->Get_Remaining_Path_Length();
		} else {
			dist_to_goal = BaseActionCodeClass::Get_Distance_From_Goal();
		}

		//
		//	Return the length
		//
		return dist_to_goal;
	}

	void	Update_Move_Location( void )
	{
		ScriptableGameObj *target	= Action->Get_Parameters().MoveObjectRef.Get_Ptr();

		if ( target == NULL ) {		// If the target is gone
//			Debug_Say(( "No Movement Target\n" ));
//			Move_Target_Gone();
//			Action->Reset( Action->Get_Parameters().Priority );		// Stop current Command	(this may get this deleted!)
			// Do we need to notify observers?
//			return ACTION_DONE;
			return;
		}

//		Action->Get_Parameters().MoveLocation = target->Get_Transform() * Action->Get_Parameters().MoveObjectOffset;
		target->Get_Position( &(Action->Get_Parameters().MoveLocation) );

		if ( FirstCall == false ) {
			SmartGameObj *	obj = Action->Get_Action_Obj();
			SoldierGameObj * soldier = NULL;
			if ( target->As_PhysicalGameObj() != NULL ) {
				soldier = target->As_PhysicalGameObj()->As_SoldierGameObj();
			}

			//	Get the current positions of the action object and the target object
			Vector3 curr_pos;
			Vector3 target_pos;
			target->Get_Position( &target_pos );
			obj->Get_Position( &curr_pos );

			//	Determine how far the target has moved since we last
			// did a pathfind.
			float target_move_dist	= (target_pos - TargetPrevPos).Length ();
			float to_target_dist		= (curr_pos - target_pos).Length ();
			if (	to_target_dist > 0 &&
					(soldier == NULL || (soldier->Is_Airborne () == false && PathAction.Get_State () == PathActionClass::STATE_FINISHED)))
			{
				//	Get the ratio of the distance the target has moved relative
				// to the distance we are from the target.
				float ratio = target_move_dist / to_target_dist;
				if ((target_move_dist > 2.0F && ratio > 0.1F)) {
					Initialize_Path();
				}
			}
		}
	}

	virtual	ActResult	Act()
	{
		WWPROFILE( "Goto Act" );
		SoldierGameObj * soldier = Action->Get_Action_Obj()->As_SoldierGameObj();
		ScriptableGameObj * look_obj = Action->Get_Parameters().LookObjectRef.Get_Ptr();
		if ( soldier != NULL && look_obj != NULL ) {
			Vector3 pos;
			look_obj->Get_Position( &pos );
			pos.Z += 1;	// Offset bullseye
			soldier->Update_Look_At( pos );
		}

		if ( UseMoveObject ) {
			Update_Move_Location();
		}

		if ( FirstCall ) {
			Initialize_Path();
			FirstCall = false;
		}

#if 0
		//	Make the soldier crouch (if necessary)
		if ( soldier && (soldier->Is_Crouched() != Action->Get_Parameters().MoveCrouched) ) {
			soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH );
		}
#else
		//	Make the soldier crouch (if necessary)
		if ( soldier ) {
			soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH, Action->Get_Parameters().MoveCrouched );
		}
#endif

		ActResult result = ACTION_IN_PROGRESS;

		//	Determine whether we should use pathfinding or
		// simply beeline to the destination
		if ( Action->Get_Parameters().MovePathfind ) {
			result = Pathfind();
		} else {
			result = Beeline();
		}

		return result;
	}

private:

	PathClass *			Path;
	PathSolveClass	*	PathSolver;
	PathActionClass	PathAction;

	bool					FirstCall;
	bool					UseMoveObject;
	Vector3				TargetPrevPos;
};


/*
** EnterExit Action Code Class
*/
class	EnterExitActionCodeClass;
SimplePersistFactoryClass<EnterExitActionCodeClass, CHUNKID_ACTION_CODE_ENTER_EXIT>	_EnterExitActionCodeClassFactory;

class	EnterExitActionCodeClass : public BaseActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _EnterExitActionCodeClassFactory; }

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001312,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			BaseActionCodeClass::Save( csave );
		csave.End_Chunk();
		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;
			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	ActResult	Act( void )
	{
		SoldierGameObj	* obj = Action->Get_Action_Obj()->As_SoldierGameObj();
		WWASSERT( obj );
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_ACTION );
		Action->Done( ACTION_COMPLETE_NORMAL );
		return ACTION_DONE;
	}
};


/*
** Dive Action Code Class
*/
class	DiveActionCodeClass;
SimplePersistFactoryClass<DiveActionCodeClass, CHUNKID_ACTION_CODE_DIVE>	_DiveActionCodeClassFactory;

class	DiveActionCodeClass : public BaseActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _DiveActionCodeClassFactory; }

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001310,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			BaseActionCodeClass::Save( csave );
		csave.End_Chunk();
		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;
			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	ActResult	Act( void )
	{
		SoldierGameObj	* obj = Action->Get_Action_Obj()->As_SoldierGameObj();
		WWASSERT( obj );

		Vector3 rel_pos;
		Matrix3D::Inverse_Transform_Vector( obj->Get_Transform(), Action->Get_Parameters().MoveLocation, &rel_pos );

		if ( WWMath::Fabs( rel_pos.X ) > WWMath::Fabs( rel_pos.Y ) ) {
			obj->Set_Boolean_Control( rel_pos.X > 0 ? ControlClass::BOOLEAN_DIVE_FORWARD : ControlClass::BOOLEAN_DIVE_BACKWARD );
		} else {
			obj->Set_Boolean_Control( rel_pos.Y > 0 ? ControlClass::BOOLEAN_DIVE_LEFT : ControlClass::BOOLEAN_DIVE_RIGHT );
		}

#if 0
		if ( !obj->Is_Crouched() ) {
			obj->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH );
		}
#else
		obj->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH );
#endif

		Action->Done( ACTION_COMPLETE_NORMAL );
		return ACTION_DONE;
	}
};


/*
** Attack - Location
*/
class	AttackActionCodeClass;
SimplePersistFactoryClass<AttackActionCodeClass, CHUNKID_ACTION_CODE_ATTACK>	_AttackActionCodeClassFactory;

class	AttackActionCodeClass : public GotoActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _AttackActionCodeClassFactory; }

	AttackActionCodeClass( void ) :
		UseAttackObject( false ),
		WanderPos(0,0,0),
		WanderTimer(0),
		HasArrived( false ),
		HoldPrimaryTriggerTimer( 0 )
	{
		ErrorScale = FreeRandom.Get_Float( 0.75f, 1.25f );
	}

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001308,
		CHUNKID_VARIABLES,

		MICROCHUNKID_USE_ATTACK_OBJECT			=	1,
		MICROCHUNKID_IS_BLOCKED						=	2,
		MICROCHUNKID_CHECK_BLOCKED_TIMER			=	3,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			GotoActionCodeClass::Save( csave );
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_VARIABLES );
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_USE_ATTACK_OBJECT,		UseAttackObject		);
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_BLOCKED,					IsBlocked				);
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CHECK_BLOCKED_TIMER,		CheckBlockedTimer		);
		csave.End_Chunk();

		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				case CHUNKID_VARIABLES:
					while (cload.Open_Micro_Chunk()) {
						switch(cload.Cur_Micro_Chunk_ID()) {
							READ_MICRO_CHUNK( cload, MICROCHUNKID_USE_ATTACK_OBJECT,		UseAttackObject		);
							READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_BLOCKED,				IsBlocked				);
							READ_MICRO_CHUNK( cload, MICROCHUNKID_CHECK_BLOCKED_TIMER,	CheckBlockedTimer		);
							default:
								Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
								break;
						}
						cload.Close_Micro_Chunk();
					}
					break;

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;

			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	void	Init( ActionClass * action )
	{
		GotoActionCodeClass::Init( action );
		UseAttackObject = (Action->Get_Parameters().AttackObjectRef.Get_Ptr() != NULL);
		IsBlocked = action->Get_Parameters().AttackCheckBlocked;
		CheckBlockedTimer = 0;
		WanderPos = Vector3(0,0,0);
	}

	virtual	ActResult	Arrived()
	{
		if ( !HasArrived ) {
//			Debug_Say(( "Attack Arrived %d\n", HasArrived ));
			HasArrived = true;
			SoldierGameObj	* obj = Action->Get_Action_Obj()->As_SoldierGameObj();
			if ( obj != NULL ) {
				int observer_id = Action->Get_Parameters ().ObserverID;
				if ( observer_id != 0 ) {
					const GameObjObserverList & observer_list = obj->Get_Observers();
					for( int index = 0; index < observer_list.Count(); index++ ) {
						if ( observer_list[ index ]->Get_ID() == observer_id ) {
							observer_list[ index ]->Custom( obj, CUSTOM_EVENT_ATTACK_ARRIVED, Action->Get_Parameters().ActionID, NULL );
						}
					}
				}
			}
		}

		return ACTION_IN_PROGRESS;				// Do nothing special when we arrive
	}

	// Attack the given absolute position, return nothing
	void	Attack_Absolute( Vector3 & abs_pos )
	{
		WWPROFILE( "Attack Absolute" );
		IsAttacking = true;

		float range = Action->Get_Parameters().AttackRange;

		SmartGameObj *	obj = Action->Get_Action_Obj();

		Vector3 my_pos;
		obj->Get_Position( &my_pos );

		// move the abs_pos around a little for AttackError
		Vector3 attack_pos = abs_pos;
		Matrix3D error;
		error.Obj_Look_At( my_pos, abs_pos, 0 );
		float dist = (my_pos - abs_pos).Length();
		float move = TimeManager::Get_Total_Seconds() * ErrorScale;

		float error_angle = WWMath::Fabs(Action->Get_Parameters().AttackError) + 
									WWMath::Fabs(obj->Get_Weapon_Error());

#if 0		
		// Modify error angle based on difficulty
		switch ( CombatManager::Get_Difficulty_Level() ) {
			case 0:	
				error_angle += 20;
				break;
			case 1:	
				// no change
				break;
			case 2:	
				error_angle -= 20;
				break;
		};

		if ( error_angle < 0 ) {
			error_angle = 0;
		}

#endif

		// If AttackErrorOverride, only use AttackError
		if ( Action->Get_Parameters().AttackErrorOverride ) {
			error_angle = WWMath::Fabs(Action->Get_Parameters().AttackError);
		}

		error.Rotate_Z( DEG_TO_RADF( WWMath::Sin( move * 1.7f ) * error_angle ) );
		error.Rotate_Y( DEG_TO_RADF( WWMath::Cos( move ) * error_angle ) * 0.5f );
		attack_pos = error * Vector3( dist,0,0 );

		if ( obj->Set_Targeting( attack_pos ) == false && !Action->Get_Parameters().AttackForceFire ) {
			// we are not facing it yet, don't fire
			return;
		}

		WeaponClass * weapon = obj->Get_Weapon();
		WWASSERT( weapon );

		if ( Action->Get_Parameters().AttackCheckBlocked ) {
			WWPROFILE( "Check Blocked" );
			CheckBlockedTimer -= TimeManager::Get_Frame_Seconds();
			if ( CheckBlockedTimer <= 0 ) {
				CheckBlockedTimer = FreeRandom.Get_Float( 2, 3 );		// Re check every 2 - 3 sec

				// cast a ray from muzzle to target
				PhysicalGameObj * hit_obj = weapon->Cast_Weapon( abs_pos );
				if ( hit_obj != NULL ) {
					if ( UseAttackObject ) {
						PhysicalGameObj * target = (PhysicalGameObj *)Action->Get_Parameters().AttackObjectRef.Get_Ptr();
						IsBlocked = ( hit_obj != target );
						if ( IsBlocked && target->As_SoldierGameObj() && target->As_SoldierGameObj()->Get_Vehicle() == hit_obj ) {
							IsBlocked = false;
						}
					} else {
						IsBlocked = !hit_obj->Is_Enemy( obj );
					}
				} else {
					IsBlocked = true;
				}
			}
		}

		if ( IsBlocked && !Action->Get_Parameters().AttackForceFire ) {
			return;
		}

		// Update Weapons Dynamic Error
		if ( weapon ) {
			if ( Action->Get_Parameters().AttackCheckBlocked != false ) {
				range = MIN( range, weapon->Get_Range() );	// dont fire if not in range
			}
		}

		my_pos -= abs_pos;
		my_pos.Z = 0;
		if ( my_pos.Length() < range || Action->Get_Parameters().AttackForceFire ) {
			if ( Action->Get_Parameters().AttackPrimaryFire ) {
				obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY );

				// Keep the trigger down (helps obelisk)
				float rate = 0;
				if ( obj->Get_Weapon() ) {
					rate = obj->Get_Weapon()->Get_Primary_Fire_Rate();
				}
				if ( rate != 0 ) {
					HoldPrimaryTriggerTimer = (1 / rate) * 0.75f;
				}
			} else {
				obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY );
			}
		}
	}

	virtual	void	Modify_Parameters( const SafeActionParamsStruct & parameters, bool modify_move, bool modify_attack )
	{
		//
		//	Copy the parameters that are safe to change in the middle of an action
		//
		if ( modify_attack ) {
			Action->Get_Parameters().AttackLocation		= parameters.AttackLocation;
			Action->Get_Parameters().AttackObjectRef		= parameters.AttackObjectRef;
			Action->Get_Parameters().AttackCrouched		= parameters.AttackCrouched;
			Action->Get_Parameters().AttackRange			= parameters.AttackRange;
			Action->Get_Parameters().AttackCheckBlocked	= parameters.AttackCheckBlocked;
			Action->Get_Parameters().AttackError			= parameters.AttackError;
			Action->Get_Parameters().AttackErrorOverride	= parameters.AttackErrorOverride;
			Action->Get_Parameters().AttackPrimaryFire	= parameters.AttackPrimaryFire;
			Action->Get_Parameters().AttackActive			= parameters.AttackActive;
			Action->Get_Parameters().AttackWanderAllowed	= parameters.AttackWanderAllowed;
			Action->Get_Parameters().AttackFaceTarget		= parameters.AttackFaceTarget;
			Action->Get_Parameters().AttackForceFire		= parameters.AttackForceFire;
		}

		if ( modify_move ) {
			Action->Get_Parameters().MoveObject				= parameters.MoveObject;
			Action->Get_Parameters().MoveObjectOffset		= parameters.MoveObjectOffset;
			Action->Get_Parameters().MoveSpeed				= parameters.MoveSpeed;
			Action->Get_Parameters().MoveArrivedDistance	= parameters.MoveArrivedDistance;
			Action->Get_Parameters().MoveBackup				= parameters.MoveBackup;
			Action->Get_Parameters().MoveFollow				= parameters.MoveFollow;
			Action->Get_Parameters().MoveCrouched			= parameters.MoveCrouched;
			Action->Get_Parameters().ShutdownEngineOnArrival = parameters.ShutdownEngineOnArrival;
			Action->Get_Parameters().MovePathfind			= parameters.MovePathfind;
		}


		//
		//	Reset some of the internal flags
		//
		UseAttackObject	= (parameters.AttackObjectRef.Get_Ptr() != NULL);
		IsBlocked			= false;
		CheckBlockedTimer	= 0;
		return ;
	}

	virtual	ActResult	Act( void )
	{
		WWPROFILE( "Attack Act" );
		//
		//	Do the movement (if necessary)
		//
		if ( Action->Get_Parameters().MoveArrivedDistance < DONT_MOVE_ARRIVED_DIST ) {
			GotoActionCodeClass::Act();
		}

		ActResult result = ACTION_IN_PROGRESS;

		//
		//	Only "do" the attack if the attack state is currently active
		//
		if ( Action != NULL && Action->Get_Parameters().AttackActive ) {
			Vector3 set_target;
			result = Process_Attack(&set_target);

			if ( Action != NULL && Action->Get_Parameters().AttackFaceTarget) {
				Pilot.Set_Target (&set_target);
			}

		} else {
			Pilot.Set_Target (NULL);
		}
		
		return result;
	}

	ActResult Process_Attack( Vector3 * set_target )
	{
		SmartGameObj *	obj = Action->Get_Action_Obj();

		if ( HoldPrimaryTriggerTimer > 0 ) {
			HoldPrimaryTriggerTimer -= TimeManager::Get_Frame_Seconds();
			obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY );
		}

		Vector3	target_pos = Action->Get_Parameters().AttackLocation;

		WeaponClass * weapon = obj->Get_Weapon();
		if ( weapon == NULL ) {
			Action->Done( ACTION_COMPLETE_NORMAL );		// Stop current Command	(this may get this deleted!)
			return ACTION_DONE;
		}

		if ( UseAttackObject ) {
			WWPROFILE( "Attack Object" );

			// See if our target still exists
			PhysicalGameObj * target = (PhysicalGameObj *)Action->Get_Parameters().AttackObjectRef.Get_Ptr();
			if ( target ) {
				if ( target->Get_Defense_Object()->Get_Health() <= 0 ) {
//					Debug_Say(( "Attack target dead\n" ));
					target = NULL;
				}
			}
			if ( target == NULL ) {		// If the target is gone or dead

				static int count = 0;
				if (count++ < 20) {
//					Debug_Say(( "No Attack Target\n" ));
				}

				Action->Done( ACTION_COMPLETE_NORMAL );		// Stop current Command	(this may get this deleted!)
				return ACTION_DONE;
			}

			// Goto our target's location
			target_pos = target->Get_Bullseye_Position();

			if ( target->As_SoldierGameObj() != NULL && target->As_SoldierGameObj()->Peek_Human_Phys() != NULL ) {
				Vector3 vel;
				target->As_SoldierGameObj()->Peek_Human_Phys()->Get_Velocity( &vel );
				vel *= TimeManager::Get_Frame_Seconds();
				target_pos += vel;
			}
		}

		// If not moving, use attack crouch
		if ( !IsMoving ) {
			SoldierGameObj	* soldier = Action->Get_Action_Obj()->As_SoldierGameObj();
			if ( soldier != NULL ) {
#if 0
				if ( soldier->Is_Crouched() != Action->Get_Parameters().AttackCrouched ) {
					soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH );
				}
#else
				soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH, Action->Get_Parameters().AttackCrouched );
#endif
			}
		}


		Attack_Absolute( target_pos );

#if 0
		// Elie Wander!
		// Always wander if attacking!
		WanderTimer -= TimeManager::Get_Frame_Seconds();
		SoldierGameObj	* soldier = Action->Get_Action_Obj()->As_SoldierGameObj();

		if ( !IsMoving && WanderTimer <= 0 && soldier != NULL && Action->Get_Parameters().AttackWanderAllowed ) {
			WWPROFILE( "Wander" );

			Vector3 rel_pos;
			Matrix3D::Inverse_Transform_Vector( obj->Get_Transform(), WanderPos, &rel_pos );
//			Debug_Say(( "Wander Pos %f %f %f\n", WanderPos.X, WanderPos.Y, WanderPos.Z ));
//			Debug_Say(( "Rel Pos %f %f %f\n", rel_pos.X, rel_pos.Y, rel_pos.Z ));

			// If he is near the wander position, or very far from it, pick a new wander pos
			float dist = rel_pos.Length();
			if ( dist < 0.5 || dist > 8 ) {
				WanderTimer = FreeRandom.Get_Float( 0.4f, 1.2f );	// random pause
				// If near the move loc, wander away, else wander back
				Vector3	dif = WanderPos - Action->Get_Parameters().MoveLocation;
				WanderPos = Action->Get_Parameters().MoveLocation;
				if ( dif.Length() < 2 ) {
#if 1
					PathfindClass *pathfind = PathfindClass::Get_Instance();		//	Lookup a safe random position to walk to
					pathfind->Find_Random_Spot( Action->Get_Parameters().MoveLocation, 5, &WanderPos );
#else
					float angle = FreeRandom.Get_Float( 0, DEG_TO_RADF( 360.0f ) );
					Vector3 move( ::sinf(angle), ::cosf(angle), 0 );
//					Debug_Say(( " Move to %f %f %f\n", move.X, move.Y, move.Z ));
					WanderPos += move * FreeRandom.Get_Float( 3, 5 );
#endif

					soldier->Look_At( WanderPos + Vector3( 0,0,1 ), FreeRandom.Get_Float( 0.2f, 0.6f ) );

				}
			}

			Human_Move_To_Relative( rel_pos );
		}
#endif
		*set_target = target_pos;
		return ACTION_IN_PROGRESS;
	}

	bool	UseAttackObject;
	bool	IsBlocked;
	float	CheckBlockedTimer;
	bool	HasArrived;
	float	ErrorScale;

	float	HoldPrimaryTriggerTimer;

	Vector3	WanderPos;
	float		WanderTimer;
};


/*
** Face - Location
*/
class	FaceLocationActionCodeClass;
SimplePersistFactoryClass<FaceLocationActionCodeClass, CHUNKID_ACTION_CODE_FACE_LOCATION>	_FaceLocationActionCodeClassFactory;

class	FaceLocationActionCodeClass : public BaseActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _FaceLocationActionCodeClassFactory; }

	FaceLocationActionCodeClass( void )	:
		EndTime( 0 )
	{
	}

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001305,
		CHUNKID_VARIABLES,

		MICROCHUNKID_END_TIME						=	1,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			BaseActionCodeClass::Save( csave );
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_VARIABLES );
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_END_TIME,					EndTime					);
		csave.End_Chunk();

		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				case CHUNKID_VARIABLES:
					while (cload.Open_Micro_Chunk()) {
						switch(cload.Cur_Micro_Chunk_ID()) {
							READ_MICRO_CHUNK( cload, MICROCHUNKID_END_TIME,					EndTime					);
							default:
								Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
								break;
						}
						cload.Close_Micro_Chunk();
					}
					break;

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;

			}
			cload.Close_Chunk();
		}
		return true;
	}

	virtual	void	Init( ActionClass * action )
	{
		BaseActionCodeClass::Init( action );

		SoldierGameObj *soldier_obj = Action->Get_Action_Obj()->As_SoldierGameObj();
		if ( soldier_obj && Action->Get_Parameters().LookDuration != 0 ) {
			soldier_obj->Look_At( Action->Get_Parameters().LookLocation, Action->Get_Parameters().LookDuration );
		}

		EndTime = TimeManager::Get_Seconds() +
			MAX(action->Get_Parameters().FaceDuration, Action->Get_Parameters().LookDuration);
	}

	virtual	ActResult	Act( void )
	{
		BaseActionCodeClass::Act();

		// Look at the moving object
		SoldierGameObj * soldier = Action->Get_Action_Obj()->As_SoldierGameObj();
		ScriptableGameObj * look_obj = Action->Get_Parameters().LookObjectRef.Get_Ptr();
		if ( soldier != NULL && look_obj != NULL ) {
			Vector3 pos;
			look_obj->Get_Position( &pos );
			pos.Z += 1;	// Offset bullseye
			soldier->Update_Look_At( pos );
		}

		if ( Action->Get_Parameters().FaceDuration != 0 ) {
			//
			//	Make the object target (face for humans) the given location
			//
			SmartGameObj *	obj		= Action->Get_Action_Obj();
			Vector3 face_location	= Action->Get_Parameters().FaceLocation;

			obj->Set_Targeting( face_location, false );
		}

		//
		//	Have we timed out yet?
		//
		ActResult result = ACTION_IN_PROGRESS;
		if ( EndTime < TimeManager::Get_Seconds() ) {

			//
			//	Stop this action.  Note:  The following call may cause the
			// object to be deleted...
			//
			Action->Done( ACTION_COMPLETE_NORMAL );
			result = ACTION_DONE;
		}

		return result;
	}

	float	EndTime;
};



/*
** Have a conversation
*/
class	ConversationActionCodeClass;
SimplePersistFactoryClass<ConversationActionCodeClass, CHUNKID_ACTION_CODE_CONVERSATION>	_ConversationActionCodeClassFactory;

class	ConversationActionCodeClass : public GotoActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _ConversationActionCodeClassFactory; }

	enum
	{
		STATE_WALKING_TO_START_POS		= 0,
		STATE_HAVING_CONVERSATION,
		STATE_FINISHED
	};

	ConversationActionCodeClass( void )	:
		Conversation( NULL ),
		State( STATE_WALKING_TO_START_POS ),
		OriginalPos( 0, 0, 0 )
	{
	}

	~ConversationActionCodeClass( void )
	{
		return ;
	}

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001258,
		CHUNKID_VARIABLES,

		MICROCHUNKID_STATE							=	1,
		MICROCHUNKID_ORIGINAL_POS					=	2,
		MICROCHUNKID_CONVERSATION_PTR				=	3,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			GotoActionCodeClass::Save( csave );
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_VARIABLES );
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE,						State				);
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ORIGINAL_POS,				OriginalPos		);
			
			//
			//	Only save the conversation ptr if it exists in the manager
			//
			if (Conversation != NULL) {
				ConversationClass *conv = ConversationMgrClass::Find_Conversation (Conversation->Get_ID ());
				if (conv != NULL) {

					//
					//	Save the conversation pointer
					//
					WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CONVERSATION_PTR, Conversation );
					REF_PTR_RELEASE( conv );
				}
			}

		csave.End_Chunk();

		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				case CHUNKID_VARIABLES:
					while (cload.Open_Micro_Chunk()) {
						switch(cload.Cur_Micro_Chunk_ID()) {
							READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE,						State				);
							READ_MICRO_CHUNK( cload, MICROCHUNKID_ORIGINAL_POS,			OriginalPos		);

							case MICROCHUNKID_CONVERSATION_PTR:
								LOAD_MICRO_CHUNK( cload, Conversation );

								//
								//	Fixup the pointer
								//
								if (Conversation != NULL) {
									REQUEST_REF_COUNTED_POINTER_REMAP( (RefCountClass **)&Conversation );
								}
								break;

							default:
								Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
								break;
						}
						cload.Close_Micro_Chunk();
					}
					break;

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;

			}
			cload.Close_Chunk();
		}

		return true;
	}

	virtual void	Begin_Hibernation( void )
	{
		//
		//	Stop this action.  Note:  The following call may cause the
		// object to be deleted...
		//
		if ( Action != NULL ) {
			Action->Done( ACTION_COMPLETE_NORMAL );
		}

		return ;
	}

	virtual	void	Init( ActionClass * action )
	{
		State = STATE_WALKING_TO_START_POS;
		REF_PTR_RELEASE( Conversation );
		OriginalPos.Set( 0, 0, 0 );

		SmartGameObj *obj = action->Get_Action_Obj();
		if ( obj != NULL ) {
			SoldierGameObj *soldier = obj->As_SoldierGameObj ();
			if( soldier != NULL ) {


				//
				//	Should we join an already active conversation or should
				// we start our own conversation?
				//
				if ( action->Get_Parameters().ActiveConversationID != 0 ) {
					Conversation = ConversationMgrClass::Find_Active_Conversation( action->Get_Parameters().ActiveConversationID );
				} else {

					//
					//	Start a new conversation
					//
					Conversation = ConversationMgrClass::Start_Conversation( soldier, action->Get_Parameters().SafeConversationName );
				}

				//
				//	By default our new position is our current position
				//
				soldier->Get_Position( &OriginalPos );

				if ( Conversation != NULL ) {
					OratorClass *orator_info = Conversation->Get_Orator_Information( soldier );

					//
					//	Determine if the soldier needs to move to a new location
					//
					if (orator_info->Get_Flag (OratorClass::FLAG_DONT_MOVE) == false) {

						//
						//	Get the soldier's expected position
						//
						Vector3 new_pos = OriginalPos;
						Conversation->Get_Orator_Location( soldier, &new_pos );

						//
						//	Set our movement location
						//
						action->Get_Parameters().MoveLocation			= new_pos;
						action->Get_Parameters().MoveSpeed				= 0.15F;
						action->Get_Parameters().MoveArrivedDistance	= 0.25F;

					} else {
						State = STATE_HAVING_CONVERSATION;
					}
				}
			}
		}

		GotoActionCodeClass::Init( action );
		return ;
	}

	virtual	void	Shutdown( void )
	{
		//
		//	Release our hold on the conversation (but don't terminate it)
		//
		if ( Conversation != NULL ) {
			//Conversation->Stop_Conversation ();
			REF_PTR_RELEASE( Conversation );
		}

		GotoActionCodeClass::Shutdown();
		return ;
	}

	virtual	ActResult	Act( void )
	{
		SmartGameObj *obj = Action->Get_Action_Obj();
		if ( obj != NULL ) {
			SoldierGameObj *soldier = obj->As_SoldierGameObj ();
			if( soldier != NULL ) {

				if( State == STATE_WALKING_TO_START_POS ) {
					GotoActionCodeClass::Act();
				} else if ( State == STATE_HAVING_CONVERSATION ) {

					//
					//	Check to see if the conversation has finished yet
					//
					if ( Conversation == NULL || Conversation->Is_Finished () ) {
						State ++;

						//
						//	Tell this soldier to return to his original position
						//
						ActionParamsStruct parameters;
						parameters.Priority					= Action->Get_Parameters().Priority;
						parameters.ObserverID				= Action->Get_Parameters().ObserverID;
						parameters.MoveLocation				= OriginalPos;
						parameters.MoveSpeed					= 0.15F;
						parameters.MoveArrivedDistance	= 1.0F;
						soldier->Get_Action()->Goto (parameters);
					}
				}
			}
		}

		//
		//	Have we timed out yet?
		//
		ActResult result = ACTION_IN_PROGRESS;
		if ( Conversation == NULL || Conversation->Is_Finished () ) {

			//
			//	Stop this action.  Note:  The following call may cause the
			// object to be deleted...
			//
			if ( Action != NULL ) {
				Action->Done( ACTION_COMPLETE_NORMAL );
			}
			result = ACTION_DONE;
		}

		return result;
	}

	virtual	ActResult	Arrived (void)
	{
		if ( Conversation != NULL && State == STATE_WALKING_TO_START_POS ) {

			//
			//	Let the conversation object know this soldier has arrived
			//
			SmartGameObj *obj = Action->Get_Action_Obj();
			if ( obj != NULL ) {
				SoldierGameObj *soldier = obj->As_SoldierGameObj();
				if( soldier != NULL ) {

					OratorClass *orator_info = Conversation->Get_Orator_Information( soldier );
					if ( orator_info != NULL ) {
						orator_info->Set_Flag( OratorClass::FLAG_TEMP_DONT_FACE, false );
					}

					Conversation->Set_Orator_Arrived( soldier, true );
				}
			}


			State ++;
		}

		return ACTION_IN_PROGRESS;
	}

private:
	ActiveConversationClass *	Conversation;
	int								State;
	Vector3							OriginalPos;
};






/*
** Dock vehicle (backup) into a bay
*/
class	DockActionCodeClass;
SimplePersistFactoryClass<DockActionCodeClass, CHUNKID_ACTION_CODE_DOCK>	_DockActionCodeClassFactory;

class	DockActionCodeClass : public GotoActionCodeClass {
public:
	virtual const PersistFactoryClass &	Get_Factory( void ) const	{ return _DockActionCodeClassFactory; }

	enum
	{
		STATE_DRIVING_TO_ENTRANCE		= 0,
		STATE_DRIVING_TO_SIDE,
		STATE_BACKING_INTO_BAY
	};

	DockActionCodeClass( void )	:
		State( STATE_DRIVING_TO_ENTRANCE )
	{
	}

	~DockActionCodeClass( void )
	{
		return ;
	}

	/*
	** Save / Load
	*/
	enum	{
		CHUNKID_PARENT									=	1031001258,
		CHUNKID_VARIABLES,

		MICROCHUNKID_STATE							=	1,
	};

	virtual	bool	Save( ChunkSaveClass & csave )
	{
		csave.Begin_Chunk( CHUNKID_PARENT );
			GotoActionCodeClass::Save( csave );
		csave.End_Chunk();

		csave.Begin_Chunk( CHUNKID_VARIABLES );
			WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE,						State				);
		csave.End_Chunk();

		return true;
	}

	virtual	bool	Load( ChunkLoadClass & cload )
	{
		while (cload.Open_Chunk()) {
			switch(cload.Cur_Chunk_ID()) {

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

				case CHUNKID_VARIABLES:
					while (cload.Open_Micro_Chunk()) {
						switch(cload.Cur_Micro_Chunk_ID()) {
							READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE,						State				);

							default:
								Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
								break;
						}
						cload.Close_Micro_Chunk();
					}
					break;

				default:
					Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
					break;

			}
			cload.Close_Chunk();
		}

		return true;
	}


	virtual	void	Init( ActionClass * action )
	{
		State = STATE_DRIVING_TO_ENTRANCE;

		//
		//	Skip the "driving to side" state if we're a tracked vehicle which
		// can turn in place.
		//
		SmartGameObj *obj			= action->Get_Action_Obj();
		VehicleGameObj *vehicle	= obj->As_VehicleGameObj();
		if ( vehicle != NULL && vehicle->Get_Turn_Radius() == 0 ) {
			State = STATE_DRIVING_TO_SIDE;
		}

		//
		//	Setup the action structure so the vehicle will drive
		// to the entrance of the docking bay
		//
		action->Get_Parameters().MoveLocation	= action->Get_Parameters().DockEntrance;
		action->Get_Parameters().MovePathfind	= true;
		action->Get_Parameters().MoveBackup		= false;
		OriginalMoveSpeed = action->Get_Parameters().MoveSpeed;

		//
		//	Ensure that the arrived distance is valid
		//
		if (action->Get_Parameters().MoveArrivedDistance == DONT_MOVE_ARRIVED_DIST) {
			action->Get_Parameters().MoveArrivedDistance = 1.0F;
		}

		GotoActionCodeClass::Init( action );
		return ;
	}

	virtual	ActResult	Arrived (void)
	{
		SafeActionParamsStruct &params	= Action->Get_Parameters();
		ActResult result						= ACTION_IN_PROGRESS;

		//
		//	Determine which action we just finished
		//
		if ( State == STATE_DRIVING_TO_ENTRANCE ) {

			//
			//	Terminate the action, we have arrived successfully
			//
			SmartGameObj *obj			= Action->Get_Action_Obj();
			VehicleGameObj *vehicle	= obj->As_VehicleGameObj();

			//
			//	Get the vector which points the direction the vehicle is
			// currently facing
			//
			const Matrix3D &tm	= obj->Get_Transform();
			Vector3 facing_vector;
			tm.Get_X_Vector( &facing_vector );

			//
			//	Build a vector which points the way into the docking bay
			//
			Vector3 entrance_vector = params.DockEntrance - params.DockLocation;
			entrance_vector.Normalize();

			//
			//	Lookup the vehicle's turn radius
			//
			float turn_radius = 2.0F;
			if ( vehicle != NULL ) {
				turn_radius = vehicle->Get_Turn_Radius();
			}			

			if (turn_radius == 0) {
								
				//
				//	Instruct the object to drive to the side of the docking bay
				//
				ActionClass *action = Action;
				action->Get_Parameters().MoveLocation	= params.DockEntrance;
				action->Get_Parameters().MovePathfind	= false;
				action->Get_Parameters().MoveBackup		= true;
				action->Get_Parameters().MoveSpeed		= OriginalMoveSpeed * 0.25F;
				GotoActionCodeClass::Init( action );

			} else {
				
				WaypathClass *test_waypath = new WaypathClass;
				Vector3 side_pos = params.DockLocation + (params.DockEntrance - params.DockLocation) * 0.8F;

				//
				// Determine which side we should drive to in order to be oriented
				// correctly for backing up
				//
				float dot_product = Vector3::Dot_Product (facing_vector, entrance_vector);
				if (dot_product > 0) {

					Vector3 vector1 = entrance_vector;
					Vector3 vector2 = Vector3 (0, 0, 1.0F);
					Vector3 side_vector = Vector3::Cross_Product (vector1, vector2);

					test_waypath->Add_Point (side_pos + (side_vector * (turn_radius * 0.5F)));

					side_pos += (side_vector * (turn_radius * 1.4F));
					test_waypath->Add_Point (side_pos);

				} else {
					Vector3 vector1 = entrance_vector;
					Vector3 vector2 = Vector3 (0, 0, 1.0F);
					Vector3 side_vector = Vector3::Cross_Product (vector1, vector2);

					test_waypath->Add_Point (side_pos - (side_vector * (turn_radius * 0.5F)));

					side_pos -= (side_vector * (turn_radius * 1.4F));
					test_waypath->Add_Point (side_pos);
				}

				//
				//	Instruct the object to drive to the side of the docking bay
				//
				ActionClass *action = Action;
				action->Get_Parameters().MoveLocation	= side_pos;
				action->Get_Parameters().MovePathfind	= true;
				action->Get_Parameters().MoveBackup		= false;
				action->Get_Parameters().MoveSpeed		= OriginalMoveSpeed * 0.25F;
				GotoActionCodeClass::Init( test_waypath );

				//
				// Free our temporary waypath
				//
				REF_PTR_RELEASE( test_waypath );
			}

			//
			//	Move onto the next state
			//
			State ++;
		} else if ( State == STATE_DRIVING_TO_SIDE ) {

			WaypathClass *test_waypath = new WaypathClass;

			SmartGameObj *obj			= Action->Get_Action_Obj();
			VehicleGameObj *vehicle	= obj->As_VehicleGameObj();
			float turn_radius = 2.0F;
			if ( vehicle != NULL ) {
				turn_radius = vehicle->Get_Turn_Radius();
			}

			Vector3 entrance_vector = params.DockEntrance - params.DockLocation;
			entrance_vector.Normalize();
			Vector3 vector1 = entrance_vector;
			Vector3 vector2 = Vector3 (0, 0, 1.0F);
			Vector3 side_vector = Vector3::Cross_Product (vector1, vector2);

			test_waypath->Add_Point (params.DockLocation + (params.DockEntrance - params.DockLocation) * 0.7F - (side_vector * turn_radius * 0.6F));
			test_waypath->Add_Point (params.DockLocation + (params.DockEntrance - params.DockLocation) * 0.3F);
			test_waypath->Add_Point (params.DockLocation);

			//
			//	Instruct the object to back into the docking bay
			//
			ActionClass *action = Action;
			action->Get_Parameters().MoveLocation	= params.DockLocation;
			action->Get_Parameters().MovePathfind	= true;
			action->Get_Parameters().MoveBackup		= true;
			action->Get_Parameters().MoveSpeed		= OriginalMoveSpeed * 0.1F;
			GotoActionCodeClass::Init( test_waypath );

			//
			//	Move onto the next state
			//
			State ++;

			//
			// Free our temporary waypath
			//
			REF_PTR_RELEASE( test_waypath );

			//
			//	Let the observer know that the vehicle will be
			// backing into the bay
			//
			int observer_id = Action->Get_Parameters ().ObserverID;
			if ( observer_id != 0 ) {
				const GameObjObserverList & observer_list = obj->Get_Observers();
				for( int index = 0; index < observer_list.Count(); index++ ) {
					if ( observer_list[ index ]->Get_ID() == observer_id ) {
						observer_list[ index ]->Custom( obj, CUSTOM_EVENT_DOCK_BACKING_IN, 0, NULL );
					}
				}
			}

		} else if ( State == STATE_BACKING_INTO_BAY ) {

			//
			//	Terminate the action, we have arrived successfully
			//
			State ++;
			Action->Done( ACTION_COMPLETE_NORMAL );
			result = ACTION_DONE;
		}

		return result;
	}

private:
	int	State;
	float	OriginalMoveSpeed;
};









/*
**
*/
SafeActionParamsStruct & SafeActionParamsStruct::operator = (const ActionParamsStruct & src)
{
	Priority					= src.Priority;
	ActionID					= src.ActionID;
	ObserverID				= src.ObserverID;
	LookLocation			= src.LookLocation;
	LookObjectRef			= src.LookObject;
	LookDuration			= src.LookDuration;
	MoveLocation			= src.MoveLocation;
	MoveObjectRef			= src.MoveObject;
	MoveObjectOffset		= src.MoveObjectOffset;
	MoveSpeed				= src.MoveSpeed;
	MoveArrivedDistance	= src.MoveArrivedDistance;
	MoveBackup				= src.MoveBackup;
	MoveFollow				= src.MoveFollow;
	MoveCrouched			= src.MoveCrouched;
	ShutdownEngineOnArrival = src.ShutdownEngineOnArrival;
	MovePathfind			= src.MovePathfind;
	AttackRange				= src.AttackRange;
	AttackError				= src.AttackError;
	AttackErrorOverride	= src.AttackErrorOverride;
	AttackPrimaryFire		= src.AttackPrimaryFire;
	AttackCrouched			= src.AttackCrouched;
	AttackObjectRef		= src.AttackObject;
	AttackLocation			= src.AttackLocation;
	AttackCheckBlocked	= src.AttackCheckBlocked;
	AttackActive			= src.AttackActive;
	AttackWanderAllowed	= src.AttackWanderAllowed;
	AttackFaceTarget		= src.AttackFaceTarget;
	AttackForceFire		= src.AttackForceFire;
	WaypathID				= src.WaypathID;
	WaypointStartID		= src.WaypointStartID;
	WaypointEndID			= src.WaypointEndID;
	WaypathSplined			= src.WaypathSplined;
	AnimationLooping		= src.AnimationLooping;
	ForceFacing				= src.ForceFacing;
	IgnoreFacing			= src.IgnoreFacing;
	FaceLocation			= src.FaceLocation;
	FaceDuration			= src.FaceDuration;
	SafeConversationName	= src.ConversationName;
	ActiveConversationID	= src.ActiveConversationID;
	SafeAnimationName		= src.AnimationName;
	AIState					= src.AIState;
	DockLocation			= src.DockLocation;
	DockEntrance			= src.DockEntrance;

	return *this;
}

enum	{
	CHUNKID_VARIABLES								=	712001855,
	CHUNKID_MOVE_OBJECT,
	CHUNKID_ATTACK_OBJECT,
	CHUNKID_LOOK_OBJECT,

	MICROCHUNKID_PRIORITY						=	1,
	MICROCHUNKID_ACTION_ID,
	MICROCHUNKID_OBSERVER_ID,
	MICROCHUNKID_MOVE_LOCATION,
	MICROCHUNKID_MOVE_OBJECT_OFFSET,
	MICROCHUNKID_MOVE_SPEED,
	MICROCHUNKID_MOVE_ARRIVED_DISTANCE,
	MICROCHUNKID_MOVE_BACKUP,
	MICROCHUNKID_MOVE_FOLLOW,
	MICROCHUNKID_MOVE_CROUCHED,
	MICROCHUNKID_MOVE_PATHFIND,
	MICROCHUNKID_ATTACK_RANGE,
	MICROCHUNKID_ATTACK_ERROR,
	MICROCHUNKID_ATTACK_PRIMARY_FIRE,
	MICROCHUNKID_ATTACK_CROUCHED,
	MICROCHUNKID_ATTACK_LOCATION,
	MICROCHUNKID_WAYPATH_ID,
	MICROCHUNKID_WAYPOINT_START_ID,
	MICROCHUNKID_WAYPOINT_END_ID,
	MICROCHUNKID_WAYPATH_SPLINED,
	MICROCHUNKID_ANIMATION_NAME,
	MICROCHUNKID_ANIMATION_LOOPING,
	MICROCHUNKID_FACE_LOCATION,
	MICROCHUNKID_FACE_DURATION,
	MICROCHUNKID_CONVERSATION_NAME,
	MICROCHUNKID_ACTIVE_CONVERSATION_ID,
	MICROCHUNKID_LOOK_LOCATION,
	MICROCHUNKID_LOOK_DURATION,
	MICROCHUNKID_DISABLE_HIBERNATION,
	MICROCHUNKID_ATTACK_CHECK_BLOCKED,
	MICROCHUNKID_ATTACK_ACTIVE,
	MICROCHUNKID_ATTACK_WANDER_ALLOWED,
	MICROCHUNKID_FORCE_FACING,
	MICROCHUNKID_ATTACK_ERROR_OVERRIDE,
	MICROCHUNKID_ATTACK_FACE_TARGET,
	MICROCHUNKID_IGNORE_FACING,
	MICROCHUNKID_ATTACK_FORCE_FIRE,
	MICROCHUNKID_SHUTDOWN_ENGINE_ON_ARRIVAL,
};

bool	SafeActionParamsStruct::Save( ChunkSaveClass & csave )
{
	csave.Begin_Chunk( CHUNKID_VARIABLES );
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_PRIORITY,					Priority					);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ACTION_ID,					ActionID					);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OBSERVER_ID,				ObserverID				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_LOOK_LOCATION,				LookLocation			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_LOOK_DURATION,				LookDuration			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_LOCATION,				MoveLocation			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_OBJECT_OFFSET,		MoveObjectOffset		);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_SPEED,					MoveSpeed				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_ARRIVED_DISTANCE,	MoveArrivedDistance	);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_BACKUP,				MoveBackup				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_FOLLOW,				MoveFollow				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_CROUCHED,				MoveCrouched			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_SHUTDOWN_ENGINE_ON_ARRIVAL, ShutdownEngineOnArrival	);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_PATHFIND,				MovePathfind			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_RANGE,				AttackRange				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_ERROR,				AttackError				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_ERROR_OVERRIDE,	AttackErrorOverride	);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_PRIMARY_FIRE,		AttackPrimaryFire		);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_CROUCHED,			AttackCrouched			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_LOCATION,			AttackLocation			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_ACTIVE,				AttackActive			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_CHECK_BLOCKED,	AttackCheckBlocked	);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_WANDER_ALLOWED,	AttackWanderAllowed	);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_FACE_TARGET,		AttackFaceTarget		);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_FORCE_FIRE,		AttackForceFire		);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPATH_ID,					WaypathID				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPOINT_START_ID,		WaypointStartID		);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPOINT_END_ID,			WaypointEndID			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPATH_SPLINED,			WaypathSplined			);
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_ANIMATION_NAME,SafeAnimationName		);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ANIMATION_LOOPING,		AnimationLooping		);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FORCE_FACING,				ForceFacing				);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IGNORE_FACING,				IgnoreFacing			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FACE_LOCATION,				FaceLocation			);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FACE_DURATION,				FaceDuration			);
		WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_CONVERSATION_NAME,	SafeConversationName	);
		WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ACTIVE_CONVERSATION_ID,	ActiveConversationID	);
	csave.End_Chunk();

	if ( MoveObjectRef != NULL ) {
		csave.Begin_Chunk( CHUNKID_MOVE_OBJECT );
		MoveObjectRef.Save( csave );
		csave.End_Chunk();
	}

	if ( AttackObjectRef != NULL ) {
		csave.Begin_Chunk( CHUNKID_ATTACK_OBJECT );
		AttackObjectRef.Save( csave );
		csave.End_Chunk();
	}

	if ( LookObjectRef != NULL ) {
		csave.Begin_Chunk( CHUNKID_LOOK_OBJECT );
		LookObjectRef.Save( csave );
		csave.End_Chunk();
	}

	return true;
}

bool	SafeActionParamsStruct::Load( ChunkLoadClass &cload )
{
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_VARIABLES:
				while (cload.Open_Micro_Chunk()) {
					switch(cload.Cur_Micro_Chunk_ID()) {
						READ_MICRO_CHUNK( cload, MICROCHUNKID_PRIORITY,					Priority					);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ACTION_ID,				ActionID					);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_OBSERVER_ID,				ObserverID				);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_LOOK_LOCATION,			LookLocation			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_LOOK_DURATION,			LookDuration			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_LOCATION,			MoveLocation			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_OBJECT_OFFSET,	MoveObjectOffset		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_SPEED,				MoveSpeed				);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_ARRIVED_DISTANCE,MoveArrivedDistance	);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_BACKUP,				MoveBackup				);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_FOLLOW,				MoveFollow				);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_CROUCHED,			MoveCrouched			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_SHUTDOWN_ENGINE_ON_ARRIVAL, ShutdownEngineOnArrival	);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_PATHFIND,			MovePathfind			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_RANGE,			AttackRange				);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_ERROR,			AttackError				);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_ERROR_OVERRIDE,AttackErrorOverride	);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_PRIMARY_FIRE,	AttackPrimaryFire		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_CROUCHED,		AttackCrouched			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_LOCATION,		AttackLocation			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_ACTIVE,			AttackActive			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_CHECK_BLOCKED,	AttackCheckBlocked	);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_WANDER_ALLOWED,	AttackWanderAllowed	);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_FACE_TARGET,	AttackFaceTarget		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_FORCE_FIRE,		AttackForceFire		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPATH_ID,				WaypathID				);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPOINT_START_ID,		WaypointStartID		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPOINT_END_ID,		WaypointEndID			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPATH_SPLINED,		WaypathSplined			);
						READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_ANIMATION_NAME,SafeAnimationName	);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ANIMATION_LOOPING,		AnimationLooping		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_FORCE_FACING,				ForceFacing			);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_IGNORE_FACING,				IgnoreFacing		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_FACE_LOCATION,				FaceLocation		);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_FACE_DURATION,				FaceDuration		);
						READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_CONVERSATION_NAME,	SafeConversationName	);
						READ_MICRO_CHUNK( cload, MICROCHUNKID_ACTIVE_CONVERSATION_ID,	ActiveConversationID	);

						default:
							Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
							break;
					}
					cload.Close_Micro_Chunk();
				}
				break;

			case CHUNKID_MOVE_OBJECT:
				MoveObjectRef.Load( cload );
				break;

			case CHUNKID_ATTACK_OBJECT:
				AttackObjectRef.Load( cload );
				break;

			case CHUNKID_LOOK_OBJECT:
				LookObjectRef.Load( cload );
				break;

			default:
				Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
				break;

		}
		cload.Close_Chunk();
	}

	return true;
}

/*
**
*/
ActionClass::ActionClass( SmartGameObj *obj ) :
	ActionCode( NULL ),
	ActCount( 0 ),
	IsPaused( false )
{
	WWASSERT( obj != NULL );
	ActionObj = obj;
}

ActionClass::~ActionClass( void )
{
	Set_Action_Code( NULL );
	Delete_Action_Code();
}

void	ActionClass::Set_Action_Code( ActionCodeClass * code )
{
	if ( ActionCode != NULL ) {
		ActionCode->Shutdown();
		Register_Action_Code_Deletion( ActionCode );
	}
	ActionCode = code;
	if ( ActionCode ) {
		_ActionCodeChanges++;
		ActionCode->Init( this );
	}
}

void	ActionClass::Done( int reason )
{
	Parameters.Priority = 0;		// reset
	if ( ActionCode != NULL ) {
		Set_Action_Code( NULL );
		Notify_Completed( Parameters.ObserverID, Parameters.ActionID, reason );	// Notify the observer
	}
}

bool	ActionClass::Is_Acting( void )
{
	return ( ActionCode != NULL );
}

bool	ActionClass::Is_Animating( void )
{
	if ( ActionCode != NULL ) {
		return ActionCode->Is_Animating();
	}
	return false;
}

bool	ActionClass::Modify( const ActionParamsStruct & parameters, bool modify_move, bool modify_attack )
{
	bool retval = false;

	if ( ActionCode != NULL ) {
		SafeActionParamsStruct safe_params;
		safe_params = parameters;
		ActionCode->Modify_Parameters( safe_params, modify_move, modify_attack );
		retval = true;
	}

	return retval;
}


bool	ActionClass::Reset( float priority )
{
	if ( priority >= Parameters.Priority ) {
		Done( ACTION_COMPLETE_LOW_PRIORITY );
		return true;
	}
	return false;
}

void	ActionClass::Notify_Completed( int observer_id, int action_id, int reason )
{
	if ( observer_id != 0 ) {
		// Find the observer and notify
		SmartGameObj *	obj = Get_Action_Obj();
		const GameObjObserverList & observer_list = obj->Get_Observers();
		for( int index = 0; index < observer_list.Count(); index++ ) {
			if ( observer_list[ index ]->Get_ID() == observer_id ) {
				observer_list[ index ]->Action_Complete( obj, action_id, (ActionCompleteReason)reason );
			}
		}
	}
}

bool	ActionClass::Request_Action( ActionCodeClass * action, const ActionParamsStruct & parameters )
{
	if ( parameters.Priority >= Parameters.Priority ) {
		bool old_action_completed = false;
		int old_observer_id = 0;
		int old_action_id = 0;

		if ( ActionCode != NULL ) {
			old_action_completed = true;
			old_observer_id = Parameters.ObserverID;
			old_action_id = Parameters.ActionID;
		}
		Parameters = parameters;

		Set_Action_Code( action );
		if ( old_action_completed ) {
			Notify_Completed( old_observer_id, old_action_id, ACTION_COMPLETE_LOW_PRIORITY );
		}
		return true;
	} else {
		Notify_Completed( parameters.ObserverID, parameters.ActionID, ACTION_COMPLETE_LOW_PRIORITY );
		if ( action != NULL ) {
			delete action;
		}
		return false;
	}
}

bool	ActionClass::Follow_Input( const ActionParamsStruct & parameters )
{
	return Request_Action( new FollowInputActionCodeClass(), parameters );
}

bool	ActionClass::Stand( const ActionParamsStruct & parameters )
{
	return Request_Action( new StandActionCodeClass(), parameters );
}

bool	ActionClass::Play_Animation( const ActionParamsStruct & parameters )
{
#if 0		// This is difficult, because huamns play default anims
	// Try to confirm we are not playing a non-action anim
	if ( !Is_Animating() && ActionObj->Get_Anim_Control()->Get_Animation_Name()[0] != 0 ) {
		Debug_Say(( "Can't Action_Play_Animation when playing a non-action anim\n" ));
		WWASSERT( 0 );
	}
#endif

	return Request_Action( new PlayAnimationActionCodeClass(), parameters );
}

bool	ActionClass::Goto( const ActionParamsStruct & parameters )
{
	return Request_Action( new GotoActionCodeClass(), parameters );
}

bool	ActionClass::Attack( const ActionParamsStruct & parameters )
{
	return Request_Action( new AttackActionCodeClass(), parameters );
}

bool	ActionClass::Face_Location( const ActionParamsStruct & parameters )
{
	return Request_Action( new FaceLocationActionCodeClass(), parameters );
}

bool	ActionClass::Have_Conversation( const ActionParamsStruct & parameters )
{
	return Request_Action( new ConversationActionCodeClass(), parameters );
}

bool	ActionClass::Dock_Vehicle( const ActionParamsStruct & parameters )
{
	return Request_Action( new DockActionCodeClass(), parameters );
}

bool	ActionClass::Enter_Exit( const ActionParamsStruct & parameters )
{
	return Request_Action( new EnterExitActionCodeClass(), parameters );
}

bool	ActionClass::Dive( const ActionParamsStruct & parameters )
{
	return Request_Action( new DiveActionCodeClass(), parameters );
}

void	ActionClass::Act( void )
{
	if ( IsPaused == false ) {
		ActCount++;

		ActionObj->Clear_Control();

		if ( ActionCode != NULL ) {
			WWPROFILE( "Action Code" );
			_ActionActCalls++;
			ActionCode->Act();
		}

		Delete_Action_Code();
	}
}

bool	ActionClass::Is_Active( void )
{
	return ( ActionCode != NULL );
}

bool	ActionClass::Is_Busy( void )
{
	bool retval = false;
	if ( ActionCode != NULL ) {
		retval = ActionCode->Is_Busy();
	}

	return retval;
}


/*
** ActionClass Save and Load
*/
enum	{
	CHUNKID_ACTION_CODE						=	712001900,
	CHUNKID_PARAMETERS,
};

bool	ActionClass::Save( ChunkSaveClass & csave )
{
	if ( ActionCode != NULL ) {
		csave.Begin_Chunk( CHUNKID_ACTION_CODE );
			csave.Begin_Chunk( ActionCode->Get_Factory().Chunk_ID() );
				ActionCode->Get_Factory().Save( csave, ActionCode );
			csave.End_Chunk();
		csave.End_Chunk();
	}

	csave.Begin_Chunk( CHUNKID_PARAMETERS );
		Parameters.Save( csave );
	csave.End_Chunk();

// Don't need to save ActionObj because it is set on ActionClass Constructor
// Don't need to save ActCount because it is diagnostic

	return true;
}

bool	ActionClass::Load( ChunkLoadClass &cload )
{
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_ACTION_CODE:
				while (cload.Open_Chunk()) {
					PersistFactoryClass * factory = SaveLoadSystemClass::Find_Persist_Factory( cload.Cur_Chunk_ID() );
					if ( factory ) {
						ActionCode = (ActionCodeClass *)factory->Load( cload );
						WWASSERT( ActionCode != NULL );
						ActionCode->Set_Action( this );
					}
					cload.Close_Chunk();
				}
				break;

			case CHUNKID_PARAMETERS:
				Parameters.Load( cload );
				break;

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

		}
		cload.Close_Chunk();
	}

	return true;
}


void	ActionClass::Begin_Hibernation( void )
{
	//
	//	Let the action code know we are entering hibernation
	//
	if ( ActionCode != NULL ) {
		ActionCode->Begin_Hibernation();
	}

	return ;
}

void	ActionClass::End_Hibernation( void )
{
	//
	//	Let the action code know we are coming out of hibernation
	//
	if ( ActionCode != NULL ) {
		ActionCode->End_Hibernation();
	}

	return ;
}
