//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ai_network.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_node.h"
#include "ai_task.h"
#include "ai_senses.h"
#include "ai_navigator.h"
#include "ai_route.h"
#include "entitylist.h"
#include "soundenvelope.h"
#include "gamerules.h"
#include "ndebugoverlay.h"
#include "soundflags.h"
#include "trains.h"
#include "globalstate.h"
#include "vehicle_base.h"
#include "npc_vehicledriver.h"
#include "vehicle_crane.h"
#include "saverestore_utlvector.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern ConVar g_debug_vehicledriver;

//=========================================================
// Custom schedules
//=========================================================
enum
{
	SCHED_CRANE_RANGE_ATTACK1 = LAST_VEHICLEDRIVER_SCHED,
	SCHED_CRANE_FIND_LARGE_OBJECT,
	SCHED_CRANE_PICKUP_OBJECT,
	SCHED_CRANE_FORCED_GO,
	SCHED_CRANE_CHASE_ENEMY,
	SCHED_CRANE_FORCED_DROP,
};

//=========================================================
// Custom tasks
//=========================================================
enum 
{
	TASK_CRANE_GET_POSITION_OVER_ENEMY = LAST_VEHICLEDRIVER_TASK,
	TASK_CRANE_GET_POSITION_OVER_LASTPOSITION,
	TASK_CRANE_GET_POSITION_OVER_OBJECT,
	TASK_CRANE_TURN_MAGNET_OFF,
	TASK_CRANE_FIND_OBJECT_TO_PICKUP,
	TASK_CRANE_DROP_MAGNET,
	TASK_END_FORCED_DROP,
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CNPC_CraneDriver : public CNPC_VehicleDriver
{
	DECLARE_CLASS( CNPC_CraneDriver, CNPC_VehicleDriver );
public:
	DECLARE_DATADESC();
	DEFINE_CUSTOM_AI;

	void	Spawn( void );
	void	Activate( void );
	
	// AI
	int		RangeAttack1Conditions( float flDot, float flDist );
	int		TranslateSchedule( int scheduleType );
	int		SelectSchedule( void );
	void	StartTask( const Task_t *pTask );
	void	RunTask( const Task_t *pTask );
	void	SetDesiredPosition( const Vector &vecPosition );

	// Driving
	void	DriveVehicle( void );
	bool	OverrideMove( float flInterval );

	// Inputs
	void	InputForcePickup( inputdata_t &inputdata );
	void	InputForceDrop( inputdata_t &inputdata );

protected:
	CHandle<CPropCrane>	m_hCrane;

	EHANDLE					m_hPickupTarget;
	float					m_flDistanceToTarget;
	CUtlVector< EHANDLE >	m_PreviouslyPickedUpObjects;
	bool					m_bForcedPickup;
	bool					m_bForcedDropoff;
	float					m_flDropWait;
	float					m_flReleasePause;
	float					m_flReleaseAt;

	// Outputs
	COutputEvent			m_OnPickedUpObject;
	COutputEvent			m_OnDroppedObject;
	COutputEvent			m_OnPausingBeforeDrop;
};

BEGIN_DATADESC( CNPC_CraneDriver )
	// Inputs
	DEFINE_INPUTFUNC( FIELD_STRING, "ForcePickup", InputForcePickup ),
	DEFINE_INPUTFUNC( FIELD_STRING, "ForceDrop", InputForceDrop ),

	//DEFINE_FIELD( m_hCrane, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hPickupTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_flDistanceToTarget, FIELD_FLOAT ),
	DEFINE_UTLVECTOR( m_PreviouslyPickedUpObjects, FIELD_EHANDLE ),
	DEFINE_FIELD( m_bForcedPickup, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bForcedDropoff, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flDropWait, FIELD_FLOAT ),
	DEFINE_KEYFIELD( m_flReleasePause, FIELD_FLOAT, "releasepause" ),
	DEFINE_FIELD( m_flReleaseAt, FIELD_FLOAT ),

	// Outputs
	DEFINE_OUTPUT( m_OnPickedUpObject, "OnPickedUpObject" ),
	DEFINE_OUTPUT( m_OnDroppedObject, "OnDroppedObject" ),
	DEFINE_OUTPUT( m_OnPausingBeforeDrop, "OnPausingBeforeDrop" ),

END_DATADESC()

LINK_ENTITY_TO_CLASS( npc_cranedriver, CNPC_CraneDriver );

//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CNPC_CraneDriver::Spawn( void )
{
	BaseClass::Spawn();

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 );

	m_flDistTooFar = 2048.0;
	SetDistLook( 2048 );

	m_PreviouslyPickedUpObjects.Purge();
	m_hPickupTarget = NULL;
	m_bForcedPickup = false;
	m_bForcedDropoff = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CraneDriver::Activate( void )
{
	BaseClass::Activate();

	m_hCrane = dynamic_cast<CPropCrane*>((CBaseEntity*)m_hVehicleEntity);
	if ( !m_hCrane )
	{
		Warning( "npc_cranedriver %s couldn't find his crane named %s.\n", STRING(GetEntityName()), STRING(m_iszVehicleName) );
		UTIL_Remove( this );
		return;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
int CNPC_CraneDriver::RangeAttack1Conditions( float flDot, float flDist )
{
	if ( !HasCondition( COND_SEE_ENEMY ) )
		return COND_NONE;

	// Do our distance check in 2D
	Vector2D vecOrigin2D( m_hCrane->GetAbsOrigin().x, m_hCrane->GetAbsOrigin().y );
	Vector2D vecEnemy2D( GetEnemy()->GetAbsOrigin().x, GetEnemy()->GetAbsOrigin().y );
	flDist = (vecOrigin2D - vecEnemy2D).Length();

	// Maximum & Minimum size of the crane's reach
	if ( flDist > MAX_CRANE_FLAT_REACH )
		return COND_TOO_FAR_TO_ATTACK;

	// Crane can't reach any closer than this
	if ( flDist < MIN_CRANE_FLAT_REACH )
		return COND_TOO_CLOSE_TO_ATTACK;

	return COND_CAN_RANGE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CNPC_CraneDriver::SelectSchedule( void )
{
	if ( HasSpawnFlags(SF_VEHICLEDRIVER_INACTIVE) )
		return BaseClass::SelectSchedule();

	// If we've got an object to pickup, so go get it
	if ( m_hPickupTarget )
	{
		// Only clear the pickup target if we managed to pick something up
		if ( m_hCrane->GetTotalMassOnCrane() > 0 )
		{
			if ( m_bForcedPickup )
			{
				m_OnPickedUpObject.FireOutput( m_hPickupTarget, this );
			}

			// Remember what we dropped so we go try something else if we can.
			m_PreviouslyPickedUpObjects.AddToTail( m_hPickupTarget );
			m_hPickupTarget = NULL;
		}
		else
		{
			if ( m_NPCState == NPC_STATE_IDLE )
			{
				SetIdealState( NPC_STATE_ALERT );
			}
			return SCHED_CRANE_PICKUP_OBJECT;
		}
	}

	// If we're currently being forced to pickup something, do only that
	if ( m_bForcedPickup )
	{
		if ( m_hPickupTarget )
			return SCHED_CRANE_PICKUP_OBJECT;

		// We've picked up our target, we're waiting to be told where to put it
		return SCHED_IDLE_STAND;
	}

	// If we've been told to drop something off, do that
	if ( m_bForcedDropoff )
		return SCHED_CRANE_FORCED_DROP;

	switch ( m_NPCState )
	{
	case NPC_STATE_IDLE:
		break;

	case NPC_STATE_ALERT:
		break;

	case NPC_STATE_COMBAT:
		if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
		{
			// Do we have anything on the crane? If not, look for something
			if ( m_hCrane->GetTotalMassOnCrane() == 0 )
				return SCHED_CRANE_FIND_LARGE_OBJECT;

			// We've got something on the crane, so try and drop it on the enemy
			return SCHED_CRANE_RANGE_ATTACK1;
		}

		// We can't attack him, so if we don't have anything on the crane, grab something
		if ( m_hCrane->GetTotalMassOnCrane() == 0 )
			return SCHED_CRANE_FIND_LARGE_OBJECT;
	}

	return BaseClass::SelectSchedule();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CNPC_CraneDriver::TranslateSchedule( int scheduleType ) 
{
	switch ( scheduleType )
	{
	case SCHED_COMBAT_FACE:
			// Vehicles can't rotate, so don't try and face
			return TranslateSchedule( SCHED_CHASE_ENEMY );

	case SCHED_ALERT_FACE:
			// Vehicles can't rotate, so don't try and face
			return SCHED_ALERT_STAND;

	case SCHED_FORCED_GO:
		return SCHED_CRANE_FORCED_GO;

	case SCHED_CHASE_ENEMY:
		return SCHED_CRANE_CHASE_ENEMY;
	}

	return BaseClass::TranslateSchedule(scheduleType);
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CNPC_CraneDriver::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_WAIT_FOR_MOVEMENT:
		break;

	case TASK_CRANE_GET_POSITION_OVER_ENEMY:
		{
			if ( !GetEnemy() )
			{
				TaskFail(FAIL_NO_ROUTE);
				return;
			}

			SetDesiredPosition( GetEnemy()->GetAbsOrigin() );
			TaskComplete();
		}
		break;

	case TASK_CRANE_GET_POSITION_OVER_OBJECT:
		{
			if ( !m_hPickupTarget )
			{
				TaskFail("No object to pickup!");
				return;
			}

			SetDesiredPosition( m_hPickupTarget->GetAbsOrigin() );
			TaskComplete();
		}
		break;

	case TASK_CRANE_GET_POSITION_OVER_LASTPOSITION:
		{
			SetDesiredPosition( m_vecLastPosition );
			TaskComplete();
		}
		break;

	case TASK_CRANE_TURN_MAGNET_OFF:
		{
			// If we picked up something, and we were being forced to pick something up, fire our output
			if ( m_hCrane->GetTotalMassOnCrane() > 0 && m_bForcedDropoff )
			{
				// Are we supposed to pause first?
				if ( m_flReleasePause )
				{
					m_flReleaseAt = gpGlobals->curtime + m_flReleasePause;
					m_OnPausingBeforeDrop.FireOutput( this, this );
					return;
				}

				m_OnDroppedObject.FireOutput( this, this );
			}

			m_hCrane->TurnMagnetOff();
			TaskComplete();
		}
		break;

	case TASK_END_FORCED_DROP:
		{
			m_bForcedDropoff = false;
			TaskComplete();
		}
		break;

	case TASK_CRANE_FIND_OBJECT_TO_PICKUP:
		{
			Vector2D vecOrigin2D( m_hCrane->GetAbsOrigin().x, m_hCrane->GetAbsOrigin().y );

			// Find a large physics object within our reach to pickup
			float flLargestMass = 0;
			CBaseEntity *pLargestEntity = NULL;
			
			CBaseEntity *pList[1024];
			Vector delta( m_flDistTooFar, m_flDistTooFar, m_flDistTooFar*2 );
			int count = UTIL_EntitiesInBox( pList, 1024, m_hCrane->GetAbsOrigin() - delta, m_hCrane->GetAbsOrigin() + delta, 0 );
			for ( int i = 0; i < count; i++ )
			{
				if ( !pList[i] ) 
					continue;
				// Ignore the crane & the magnet
				if ( pList[i] == m_hCrane || pList[i] == m_hCrane->GetMagnet() )
					continue;
				if ( m_PreviouslyPickedUpObjects.Find( pList[i] ) != m_PreviouslyPickedUpObjects.InvalidIndex() )
					continue;

				// Get the VPhysics object
				IPhysicsObject *pPhysics = pList[i]->VPhysicsGetObject();
				if ( pPhysics && pList[i]->GetMoveType() == MOVETYPE_VPHYSICS )
				{
					float flMass = pPhysics->GetMass();
					if ( flMass > flLargestMass && (flMass < MAXIMUM_CRANE_PICKUP_MASS) && (flMass > MINIMUM_CRANE_PICKUP_MASS) )
					{
						// Biggest one we've found so far

						// Now make sure it's within our reach
						// Do our distance check in 2D
						Vector2D vecOrigin2D( m_hCrane->GetAbsOrigin().x, m_hCrane->GetAbsOrigin().y );
						Vector2D vecEnemy2D( pList[i]->GetAbsOrigin().x, pList[i]->GetAbsOrigin().y );
						float flDist = (vecOrigin2D - vecEnemy2D).Length();
						// Maximum & Minimum size of the crane's reach
						if ( flDist > MAX_CRANE_FLAT_REACH )
							continue;
						if ( flDist < MIN_CRANE_FLAT_REACH )
							continue;

						flLargestMass = flMass;
						pLargestEntity = pList[i];
					}
				}
			}

			// If we didn't find anything new, clear our list of targets
			if ( !pLargestEntity )
			{
				m_PreviouslyPickedUpObjects.Purge();
			}

			if ( !pLargestEntity )
			{
				TaskFail("Couldn't find anything to pick up!");
				return;
			}

			m_hPickupTarget = pLargestEntity;
			TaskComplete();
		}
		break;

	case TASK_CRANE_DROP_MAGNET:
		{
			// Drop the magnet, but only end the task once the magnet's back up
			m_pVehicleInterface->NPC_SecondaryFire();

			// Don't check to see if drop's finished until this time is up.
			// This is necessary because the crane won't start dropping this
			// frame, and our cranedriver will think it's finished immediately.
			m_flDropWait = gpGlobals->curtime + 0.5;
		}
		break;

	default:
		BaseClass::StartTask( pTask );
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_CraneDriver::RunTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_WAIT_FOR_MOVEMENT:
		{
			// Is the magnet over the target, and are we not moving too fast?
			AngularImpulse angVel;
			Vector vecVelocity;
			CBaseEntity *pMagnet = m_hCrane->GetMagnet();
			IPhysicsObject *pVehiclePhysics = pMagnet->VPhysicsGetObject();
			pVehiclePhysics->GetVelocity( &vecVelocity, &angVel );
			float flVelocity = 100;
			float flDistance = 90;

			// More accurate on forced drops
			if ( m_bForcedPickup || m_bForcedDropoff )
			{
				flVelocity = 10;
				flDistance = 30;
			}

			if ( m_flDistanceToTarget < flDistance && m_hCrane->GetTurnRate() < 0.1 && vecVelocity.Length() < flVelocity )
			{
				TaskComplete();
			}
		}
		break;

	case TASK_CRANE_DROP_MAGNET:
		{
			// Wait for the magnet to get back up
			if ( m_flDropWait < gpGlobals->curtime && !m_hCrane->IsDropping() )
			{
				TaskComplete();
			}
		}
		break;

	case TASK_CRANE_TURN_MAGNET_OFF:
		{
			// We're waiting for the pause length before dropping whatever's on our magnet
			if ( gpGlobals->curtime > m_flReleaseAt )
			{
				if ( m_bForcedDropoff )
				{
					m_OnDroppedObject.FireOutput( this, this );
				}

				m_hCrane->TurnMagnetOff();
				TaskComplete();
			}
		}
		break;

	default:
		BaseClass::RunTask( pTask );
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_CraneDriver::OverrideMove( float flInterval )
{
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_CraneDriver::SetDesiredPosition( const Vector &vecPosition )
{
	m_vecDesiredPosition = vecPosition;
	m_flDistanceToTarget = 999;
}

//-----------------------------------------------------------------------------
// Purpose: This takes the current place the NPC's trying to get to, figures out
//			what keys to press to get the vehicle to go there, and then sends
//			them to the vehicle.
//-----------------------------------------------------------------------------
void CNPC_CraneDriver::DriveVehicle( void )
{
	// No targets?
	if ( !GetEnemy() && m_vecDesiredPosition == vec3_origin )
		return;

	Vector vecTarget = m_vecDesiredPosition;
	// Track our targets
	if ( m_hPickupTarget )
	{
		vecTarget = m_hPickupTarget->GetAbsOrigin();
	}
	else if ( !m_bForcedPickup && !m_bForcedDropoff && GetEnemy() )
	{
		vecTarget = GetEnemy()->GetAbsOrigin();
	}

	// Move the crane over the target
	// Use the crane type as a targeting point
	Vector vecCraneTip = m_hCrane->GetCraneTipPosition();
	Vector2D vecCraneTip2D( vecCraneTip.x, vecCraneTip.y );
	Vector2D vecTarget2D( vecTarget.x, vecTarget.y );
	Vector2D vecOrigin2D( m_hCrane->GetAbsOrigin().x, m_hCrane->GetAbsOrigin().y );

	if ( g_debug_vehicledriver.GetInt() )
	{
		NDebugOverlay::Box( vecTarget, -Vector(50,50,50), Vector(50,50,50), 0,255,0, true, 0.1 );
		NDebugOverlay::Box( vecCraneTip, -Vector(2,2,5000), Vector(2,2,5), 0,255,0, true, 0.1 );
		NDebugOverlay::Box( vecTarget, -Vector(2,2,5), Vector(2,2,5000), 0,255,0, true, 0.1 );
	}
	// Store off the distance to our target
	m_flDistanceToTarget = (vecTarget2D - vecCraneTip2D).Length();

	// First determine whether we need to extend / retract the arm
	float flDistToTarget = (vecOrigin2D - vecTarget2D).LengthSqr();
	float flDistToCurrent = (vecOrigin2D - vecCraneTip2D).LengthSqr();
	float flDelta = fabs(flDistToTarget - flDistToCurrent);
	// Slow down as we get closer, but do it based upon our current extension rate
	float flMinDelta = 50 + (50 * fabs(m_hCrane->GetExtensionRate() / CRANE_EXTENSION_RATE_MAX));
	flMinDelta *= flMinDelta;
	if ( flDelta > flMinDelta )
	{
		if ( flDistToCurrent > flDistToTarget )
		{
			// Retract
			m_pVehicleInterface->NPC_ThrottleReverse();
		}
		else if ( flDistToCurrent < flDistToTarget )
		{
			// Extend
			m_pVehicleInterface->NPC_ThrottleForward();
		}
	}
	else
	{
		m_pVehicleInterface->NPC_ThrottleCenter();
	}

	// Then figure out if we need to rotate. Do it all in 2D space.
	Vector vecRight, vecForward;
	m_hCrane->GetVectors( &vecForward, &vecRight, NULL );
	vecRight.z = 0;
	vecForward.z = 0;
	VectorNormalize( vecRight );
	VectorNormalize( vecForward );
	Vector vecToTarget = ( vecTarget - m_hCrane->GetAbsOrigin() );
	vecToTarget.z = 0;
	VectorNormalize( vecToTarget );
	float flDotRight = DotProduct( vecRight, vecToTarget );
	float flDotForward = DotProduct( vecForward, vecToTarget );

	// Start slowing if we're going to hit the point soon
	float flTurnInDeg = RAD2DEG( acos(flDotForward) );
	float flSpeed = m_hCrane->GetMaxTurnRate() * (flTurnInDeg / 15.0);
	flSpeed = min( m_hCrane->GetMaxTurnRate(), flSpeed );
	if ( fabs(flSpeed) < 0.05 )
	{
		// We're approaching the target, so stop turning
		m_pVehicleInterface->NPC_TurnCenter();
	}
	else
	{
		if ( flDotRight < 0 )
		{
			// Turn right
			m_pVehicleInterface->NPC_TurnRight( flSpeed );
		}
		else if ( flDotRight > 0 )
		{
			// Turn left
			m_pVehicleInterface->NPC_TurnLeft( flSpeed );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Force the driver to pickup a specific entity
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CraneDriver::InputForcePickup( inputdata_t &inputdata )
{
	string_t iszPickupName = inputdata.value.StringID();
	if ( iszPickupName != NULL_STRING )
	{
		// Turn the magnet off now to drop anything we might have already on the magnet
		m_hCrane->TurnMagnetOff();
		m_hPickupTarget = gEntList.FindEntityByName( NULL, iszPickupName, NULL );
		m_bForcedPickup = true;
		m_bForcedDropoff = false;
		SetCondition( COND_PROVOKED );
		CLEARBITS( m_spawnflags, SF_VEHICLEDRIVER_INACTIVE );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Force the driver to drop his held entity at a specific point
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_CraneDriver::InputForceDrop( inputdata_t &inputdata )
{
	string_t iszDropName = inputdata.value.StringID();
	if ( iszDropName != NULL_STRING )
	{
		CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, iszDropName, NULL );
		if ( !pEntity )
		{
			Warning("Crane couldn't find entity named %s\n", STRING(iszDropName) );
			return;
		}
		m_bForcedPickup = false;
		m_bForcedDropoff = true;
		SetDesiredPosition( pEntity->GetAbsOrigin() );
		SetCondition( COND_PROVOKED );
		CLEARBITS( m_spawnflags, SF_VEHICLEDRIVER_INACTIVE );
	}
}

//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------

AI_BEGIN_CUSTOM_NPC( npc_cranedriver, CNPC_CraneDriver )

	//Tasks
	DECLARE_TASK( TASK_CRANE_GET_POSITION_OVER_ENEMY )
	DECLARE_TASK( TASK_CRANE_GET_POSITION_OVER_LASTPOSITION )
	DECLARE_TASK( TASK_CRANE_GET_POSITION_OVER_OBJECT )
	DECLARE_TASK( TASK_CRANE_TURN_MAGNET_OFF )
	DECLARE_TASK( TASK_END_FORCED_DROP )
	DECLARE_TASK( TASK_CRANE_FIND_OBJECT_TO_PICKUP )
	DECLARE_TASK( TASK_CRANE_DROP_MAGNET )

	//Schedules
	//==================================================
	// SCHED_ANTLION_CHASE_ENEMY_BURROW
	//==================================================

	DEFINE_SCHEDULE
	(
	SCHED_CRANE_RANGE_ATTACK1,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_CHASE_ENEMY"
		"		TASK_CRANE_GET_POSITION_OVER_ENEMY	0"
		"		TASK_WAIT_FOR_MOVEMENT				0"
		"		TASK_CRANE_TURN_MAGNET_OFF			0"
		"	"
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_OCCLUDED"
		"		COND_ENEMY_TOO_FAR"
		"		COND_PROVOKED"
	)

	DEFINE_SCHEDULE
	(
	SCHED_CRANE_FIND_LARGE_OBJECT,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_CHASE_ENEMY"
		"		TASK_CRANE_FIND_OBJECT_TO_PICKUP	0"
		"	"
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_OCCLUDED"
		"		COND_ENEMY_TOO_FAR"
	)

	DEFINE_SCHEDULE
	(
	SCHED_CRANE_PICKUP_OBJECT,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE					SCHEDULE:SCHED_CHASE_ENEMY"
		"		TASK_CRANE_GET_POSITION_OVER_OBJECT		0"
		"		TASK_WAIT_FOR_MOVEMENT					0"
		"		TASK_CRANE_DROP_MAGNET					0"
		"	"
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_OCCLUDED"
		"		COND_ENEMY_TOO_FAR"
		"		COND_PROVOKED"
	)

	DEFINE_SCHEDULE
	(
		SCHED_CRANE_FORCED_GO,

		"	Tasks"
		"		TASK_CRANE_GET_POSITION_OVER_LASTPOSITION	0"
		"		TASK_WAIT_FOR_MOVEMENT						0"
		"		TASK_CRANE_TURN_MAGNET_OFF					0"
		"		TASK_WAIT									2"
		"	"
		"	Interrupts"
	)

	DEFINE_SCHEDULE
	(
		SCHED_CRANE_CHASE_ENEMY,

		"	Tasks"
		"		TASK_CRANE_GET_POSITION_OVER_ENEMY			0"
		"		TASK_WAIT_FOR_MOVEMENT						0"
		"		TASK_WAIT									5"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_UNREACHABLE"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_TOO_CLOSE_TO_ATTACK"
		"		COND_TASK_FAILED"
		"		COND_LOST_ENEMY"
		"		COND_PROVOKED"
	)

	DEFINE_SCHEDULE
	(
		SCHED_CRANE_FORCED_DROP,

		"	Tasks"
		"		TASK_WAIT_FOR_MOVEMENT						0"
		"		TASK_CRANE_TURN_MAGNET_OFF					0"
		"		TASK_END_FORCED_DROP						0"
		"		TASK_WAIT									2"
		"	"
		"	Interrupts"
	)
	
AI_END_CUSTOM_NPC()
