//---------------------------------------------------------------------------
// File			: AVP_Behaviour_Sentrygun.cpp
// Created		: 
// Created By	: Gareth Thomas
// Copyright (C) 2007 Rebellion. All Rights Reserved.
//---------------------------------------------------------------------------

// INCLUDES ///////////////////////////////////////////////////////////

// core
#include "Core/Asura.h"
// header
#include "Behaviour/BehaviourTypes/Sentrygun/AVP_Behaviour_Sentrygun.h"
// asura
#include "Network/Asura_Message.h"
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#include "Rendering/Asura_Font_System.h"
#endif
// axon
// AVP
#include "Actor/Weapons/Sentrygun/AVP_Sentrygun_AIAnimStateMachine.h"
#include "Entity/AVP_Messages.h"
#include "Entity/PhysicalObject/Actor/Weapons/Sentrygun/AVP_Sentrygun_Messages.h"
#include "Entity/PhysicalObject/Actor/Weapons/Sentrygun/AVP_ServerWeapon_Sentrygun.h"
#include "Inventory/AVP_Inventory_System.h"
#include "Inventory/Items/AVP_InventoryItem_Weapon.h"
#include "Inventory/Items/Weapons/AVP_InventoryWeapon_RangedWeapon.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Human/AVP_ServerActor_Human.h"

// AVP axon
#include "Behaviour/BehaviourTypes/AVP_Behaviour_Types.h"
#include "Core/BrainTypes/AVP_Brain_Actor_Civilian.h"
#include "Core/BrainTypes/AVP_Brain_Actor_Marine.h"
#include "Core/BrainTypes/AVP_Brain_Actor_Sentrygun.h"
#include "Knowledge/AVP_Knowledge_System_Actor.h"
#include "Knowledge/AVP_Knowledge_System_Sentrygun.h"
#include "Tactical/AVP_Tactical_System.h"

static const u_int s_uFirstVersion = 0x00001001;	// Need this because of stupid chunking code below
static const u_int s_uCurrentVersion = 0x00001003;

//---------------------------------------------------------------------------------
//*********************************************************************************
//---------------------------------------------------------------------------------
// Class		: AVP_Behaviour_Sentrygun
// Description	: 
//---------------------------------------------------------------------------------
//*********************************************************************************
//---------------------------------------------------------------------------------

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: Constructor
//---------------------------------------------------------------------------
AVP_Behaviour_Sentrygun::AVP_Behaviour_Sentrygun( AVP_Brain_Actor_Sentrygun* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_SENTRYGUN )
, m_eCurrentState( UNSET )
, m_eFiringState( FIRING_STATE_SHOOT )
, m_pxSentrygunBrain( pxBrain )
, m_fFiringTimer( 0.0f )
, m_fFireTime( 1.0f ) 
, m_fWaitTime( 0.5f )
, m_xSweepAngle( 0.0f )
, m_bSweepDirection( false )
, m_fSweepTimer( 0.0f )
, m_fFoundTargetPause( 0.75f )
, m_fLostTargetPause( 1.5f )
, m_fPauseTimer( 0.0f )
, m_uLastTargetGuid( ASURA_GUID_UNREGISTERED )
, m_iDryFireCount( 0 )
{

}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: Deactivate
//---------------------------------------------------------------------------
bool AVP_Behaviour_Sentrygun::Activate()
{
	bool bReturn = PARENT::Activate();

	AVP_ServerWeapon_Sentrygun* pxSentryGun = GetSentrygunBrain()->GetSentrygunActor();

	if ( pxSentryGun && pxSentryGun->GetBlueprintHashID() != ASURA_HASH_ID_UNSET )
	{
		if ( pxSentryGun->GetStartsEnabled() )
		{
			GetSentrygunBrain()->GetSentrygunActor()->SetLifeState( AVP_ServerWeapon_Sentrygun::ALIVE );
			SwitchToState( SWEEPING );
		}
		else
		{
			GetSentrygunBrain()->GetSentrygunActor()->SetLifeState( AVP_ServerWeapon_Sentrygun::DORMANT );
			SwitchToState( DISABLED );
		}
	}

	float fTanHorHalfSweep = pxSentryGun->GetTanFireArcHalfAngle();
	m_xSweepAngle = Asura_Maths::ArcTangent (fTanHorHalfSweep, 1.0f);

	return bReturn;
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: Deactivate
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::Deactivate()
{
	// CAH commented out as if it is a checkpoint restart we need to preserve the state the gun is in. 
	//SwitchToState( DISABLED );

	DestroyAllChildren();
	PARENT::Deactivate();
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: PostLoadInitalise
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::PostLoadInitialise()
{
	AVP_ServerWeapon_Sentrygun* pxSentryGun			= GetSentrygunBrain()->GetSentrygunActor();
	float						fTanHorHalfSweep		= pxSentryGun->GetTanFireArcHalfAngle();
	
	m_xSweepAngle = Asura_Maths::ArcTangent (fTanHorHalfSweep, 1.0f);

	u_int uLifeState = pxSentryGun->GetLifeState();

	if( uLifeState == AVP_ServerWeapon_Sentrygun::DEAD )
	{
		SwitchToState( DESTROYED );
	}
	else if( uLifeState == AVP_ServerWeapon_Sentrygun::DORMANT )
	{
		SwitchToState( DISABLED );
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: HandleMessage
//---------------------------------------------------------------------------
bool AVP_Behaviour_Sentrygun::HandleMessage( const Asura_Message* pxMessage )
{
	switch( pxMessage->GetMsgID() )
	{
		//-----------------------------------------------------------

		case AVP_MSGID_SENTRYGUN_ENABLE:
		{
			// only switch if the state is disabled, if its destroyed then we cant re-enable. 
			if( m_eCurrentState == DISABLED )
			{
				SwitchToState( SWEEPING );
			}

			return true;
		}

		//-----------------------------------------------------------

		case AVP_MSGID_SENTRYGUN_DISABLE:
		{
			if( m_eCurrentState != DISABLED )
			{
				SwitchToState( DISABLED );
			}

			return true;
		}

		//-----------------------------------------------------------

		case AVP_MSGID_SENTRYGUN_DESTROY:
		{
			if( m_eCurrentState != DESTROYED )
			{
				SwitchToState( DESTROYED );
			}

			return true;
		}

		//-----------------------------------------------------------
	
		default:
		{
			return PARENT::HandleMessage( pxMessage );
		}

		//-----------------------------------------------------------
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: PerFrameUpdate
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::PerFrameUpdate( const float fTimeslice )
{
	AVP_ClientWeapon_Sentrygun* pxClient = AVP_ClientWeapon_Sentrygun::FindEntity( GetSentrygunBrain()->GetOwnerGuid() );

	if ( !pxClient || pxClient->GetBlueprintHashID() == ASURA_HASH_ID_UNSET )
	{
		// Client doen't exist yet just return until it does.
		return;
	}

	if( m_fPauseTimer > 0.0f )
	{
		// if the pause timer has not expired then keep it counting down. 
		m_fPauseTimer -= fTimeslice;

		if( ( m_eCurrentState == SWEEPING ) && ( m_fPauseTimer <= 0 ) )
		{
			// if we are in sweeping state and the timer has just run out this frame play a sound to indicate the start of sweeping. 
			GetSentrygunBrain()->GetSentrygunActor()->SendSoundMessage( AVP_ClientWeapon_Sentrygun::SOUND_RESUMING_SWEEP );
		}
	}

	switch ( m_eCurrentState )
	{
		//---------------------------------------------------------------------------		
		case UNSET:
		{
		}break;
		//---------------------------------------------------------------------------
		case DISABLED:
		{
		}break;
		//---------------------------------------------------------------------------
		case DESTROYED:
		{
		}break;
		//---------------------------------------------------------------------------
		case SWEEPING:
		{
			if ( GetSentrygunBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().GetGuid() != ASURA_GUID_UNREGISTERED )
			{
				SwitchToState( ATTACK );
				
				if( m_fPauseTimer <= 0.0f )
				{
					// if the pause timer is empty then fill it with the found target pause
					m_fPauseTimer = m_fFoundTargetPause;
				}
				else
				{
					// if it is still counting down in this state then it has just recently lost a target and is not yet sweeping, so no pause before attack
					m_fPauseTimer = 0.0f;
				}
			}
			else if( m_fPauseTimer <= 0.0f )
			{
				// if there are no other targets in the FOV do a sweep only if the lost target pause is finished. 
				DoSweep( fTimeslice );
			}

			StopShooting();

		}break;
		//---------------------------------------------------------------------------
		case ATTACK:
		{
			if ( GetSentrygunBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().GetGuid() == ASURA_GUID_UNREGISTERED )
			{
				GetSentrygunBrain()->GetSentrygunActor()->SendSoundMessage( AVP_ClientWeapon_Sentrygun::SOUND_LOST_TARGET );

				SwitchToState( SWEEPING );

				// get a reference to the last entity we were targeting
				const AVP_ServerEntity_Actor* pxTargetActor = AVP_ServerEntity_Actor::FindEntity( m_uLastTargetGuid );
				if( pxTargetActor && ( pxTargetActor->IsDead() ) )
				{
					// if we found the entity and it is dead then dont set a pause, go straight to sweeping
					m_fPauseTimer = 0.0f;
				}
				else
				{
					// else it is still alive, pause whiule we wait for it to re-enter our FOV. 
					m_fPauseTimer = m_fLostTargetPause;
				}
			}
			else
			{
				// as we know the id is not ASURA_GUID_UNREGISTERED, record it for reference when we lose it. 
				m_uLastTargetGuid = GetSentrygunBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().GetGuid();

				TurnTowardsTarget();

				if( IsAbleToFire() && ( m_fPauseTimer <= 0.0f ) )
				{
					FiringUpdate( fTimeslice );
				}
				else
				{
					StopShooting();
				}

				SendAttackInfo();
			}

		}break;
		//---------------------------------------------------------------------------
		default:
		{
		}break;
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: SendAttackInfo
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::SendAttackInfo( void )
{
	AVP_ServerWeapon_Sentrygun*	pxSentryGun	= GetSentrygunBrain()->GetSentrygunActor();
	AVP_ServerEntity_Actor*		pxThreat	= AVP_ServerEntity_Actor::FindEntity( GetSentrygunBrain()->GetPrimaryThreatGuid() );

	if( ( pxSentryGun != NULL ) && ( pxThreat != NULL ) )
	{
		for (Asura_Hashed_List_It<AVP_ServerActor_Human> it(AVP_ServerActor_Human::GetList()); !it.Done(); it.Next())
		{
			AVP_ServerActor_Human* pxHuman = it.GetCurrent();

			if ( pxHuman && !pxHuman->IsPlayer() && !pxHuman->IsDead())
			{
				if ( AVP_Tactical_System::GetAllegiance( *pxSentryGun, *pxHuman ) == AVP_ALLEGIANCE_FRIEND )
				{
					float fDistancefromGunSq		= ( pxSentryGun->GetPosition() - pxHuman->GetPosition() ).MagnitudeSquared();
					float fDistanceFromThreatSq		= ( pxThreat->GetPosition() - pxHuman->GetPosition() ).MagnitudeSquared();

					if ( fDistancefromGunSq < 625.0f || fDistanceFromThreatSq < 100.0f )
					{
						if ( pxHuman->GetClassification() == AVP_ENTITYCLASS_MARINE || pxHuman->GetClassification() == AVP_ENTITYCLASS_ANDROID )
						{
							AVP_Brain_Actor_Marine* pxMarineBrain = ( AVP_Brain_Actor_Marine* )pxHuman->GetActorBrain();
							pxMarineBrain->GetKnowledgeSystem()->AddKnowledgeIfNeeded( GetSentrygunBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge() );
						}
						else if ( pxHuman->GetClassification() == AVP_ENTITYCLASS_CIVILIAN )
						{
							AVP_Brain_Actor_Civilian* pxCivilianBrain = ( AVP_Brain_Actor_Civilian* )pxHuman->GetActorBrain();
							pxCivilianBrain->GetKnowledgeSystem()->AddKnowledgeIfNeeded( GetSentrygunBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge() );
						}
					}
				}
			}
		}
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: FiringUpdate
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::FiringUpdate( const float fTimeslice )
{
	if( ! GetSentrygunBrain()->GetSentrygunActor()->GetWeaponIsDryFiring() )
	{
		// CAH commented out burst fire and just added continuous shooting request. (designer decision)
		StartShooting();
	}
	else
	{
		m_fFiringTimer += fTimeslice;

		if ( m_eFiringState == FIRING_STATE_SHOOT )
		{
			if ( m_fFiringTimer > m_fFireTime )
			{
				m_fFiringTimer = 0.0f;
				m_eFiringState = FIRING_STATE_WAIT;
			}
			else
			{
				StartShooting();
			}
		}
		else if ( m_eFiringState == FIRING_STATE_WAIT )
		{
			if ( m_fFiringTimer > m_fWaitTime )
			{
				m_fFiringTimer = 0.0f;
				m_eFiringState = FIRING_STATE_SHOOT;
			}
			else
			{
				StopShooting();
			}
		}
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: DoSweep
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::DoSweep( const float fTimeslice )
{
	m_fSweepTimer += fTimeslice;

	//GetSentrygunBrain()->SetMaxAimDirAngularVelocity( m_xSweepAngle * 1.5f );
	GetSentrygunBrain()->SetMaxAimDirAngularVelocity( GetSentrygunBrain()->GetSentrygunActor()->GetSpeed() );

	AVP_ServerWeapon_Sentrygun* pxSentryGun			= GetSentrygunBrain()->GetSentrygunActor();
	Asura_Angle					xSweepEndAngle		= ( m_bSweepDirection == true ) ? m_xSweepAngle : -m_xSweepAngle;
	const Asura_Matrix_3x3&		xOri				= pxSentryGun->GetOrientation();
	const Asura_Vector_3		xFwd				( xOri.e13, xOri.e23, xOri.e33 );
	const Asura_Vector_3		xUp					( xOri.e12, xOri.e22, xOri.e32 );
	Asura_Vector_3				xDirection			( Asura_Maths::ZeroVector3 );
	Asura_Quat					xQuat;
	
	xQuat.FromMatrix(xOri);
	xQuat.FromRotation(xSweepEndAngle, xUp);
	xDirection = xQuat.RotateVector(xFwd);

	xDirection.Normalise();

	Asura_Vector_3 xAimDirection = GetSentrygunBrain()->GetAimDirection();

	if ( xDirection.ApproximatelyEquals( xAimDirection, 0.001f ) && m_fSweepTimer > 0.1f )
	{
		m_bSweepDirection = !m_bSweepDirection;

		GetSentrygunBrain()->GetSentrygunActor()->SendSoundMessage( AVP_ClientWeapon_Sentrygun::SOUND_RESUMING_SWEEP );
		GetSentrygunBrain()->GetSentrygunActor()->SendSoundMessage( AVP_ClientWeapon_Sentrygun::SOUND_INVESTIGATING );

		m_fSweepTimer = 0.0f;
	}

	GetSentrygunBrain()->SetAimDirection( xDirection );
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: StopShooting
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::StopShooting( void )
{
	AVP_Inventory_System*		pxInventorySystem	= GetSentrygunBrain()->GetActor()->GetInventorySystem();
	AVP_InventoryItem_Weapon*	pxCurrentWeapon		= static_cast<AVP_InventoryItem_Weapon*>(pxInventorySystem ? pxInventorySystem->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON) : NULL);

	if( pxCurrentWeapon != NULL  && pxCurrentWeapon->IsUsing( true ) )
	{
		// fire weapon
		pxCurrentWeapon->RequestEndUsage( AVP_InventoryWeapon_RangedWeapon::UT_FULL_AUTO_PRIMARY );
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: StartShooting
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::StartShooting( void )
{
	AVP_Inventory_System*		pxInventorySystem	= GetSentrygunBrain()->GetActor()->GetInventorySystem();
	AVP_InventoryItem_Weapon*	pxCurrentWeapon		= static_cast<AVP_InventoryItem_Weapon*>(pxInventorySystem ? pxInventorySystem->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON) : NULL);

	if ( pxCurrentWeapon && pxCurrentWeapon->IsIdling( true ) )
	{
		if( ! GetSentrygunBrain()->GetSentrygunActor()->GetWeaponIsDryFiring() )
		{
			// fire weapon if we are not dryfiring at the moment. 
			pxCurrentWeapon->RequestBeginUsage( AVP_InventoryWeapon_RangedWeapon::UT_FULL_AUTO_PRIMARY );
		}
		else
		{
			// else play the dry fire sound every other bullet (otherwise its too fast to sound good)
			// CAH, yes I know you could check to see if the sound has stopped playing before playing another, but its such a short sound it still needs this pause. 
			if( m_iDryFireCount > 0 )
			{
				GetSentrygunBrain()->GetSentrygunActor()->SendSoundMessage( AVP_ClientWeapon_Sentrygun::SOUND_DRY_FIRE );
				m_iDryFireCount = 0;
			}
			else
			{
				m_iDryFireCount ++;
			}
		}
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: SwitchToState
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::SwitchToState( UpdateState eState )
{
	switch ( m_eCurrentState )
	{
		//---------------------------------------------------------------------------
		case UNSET:
		case DISABLED:
		{
			if( ( eState == SWEEPING ) || ( eState == ATTACK ) )
			{
				if( GetSentrygunBrain()->GetSentrygunActor()->GetSentryGunAIAnimStateMachine() )
				{
					// if we are moving to sweep or attack then get rid of any animations over the top of the aimgroup in animations.
					GetSentrygunBrain()->GetSentrygunActor()->GetSentryGunAIAnimStateMachine()->RequestEndGamescene();

					// then request the idle animation state, so the aim animations are loaded and controlled in code. 
					GetSentrygunBrain()->GetSentrygunActor()->GetSentryGunAIAnimStateMachine()->RequestIdle();
				}
			}
			break;
		}
		//---------------------------------------------------------------------------
		case DESTROYED:
		{
			// if we are trying to change state from the state of Destoyed just return to not allow it. 
			return;
		}
		//---------------------------------------------------------------------------
		case SWEEPING:
		{
			break;
		}
		//---------------------------------------------------------------------------
		case ATTACK:
		{
			StopShooting();
			break;
		}
		//---------------------------------------------------------------------------
		default:
		{
			break;
		}
	}

	m_eCurrentState = eState;

	switch ( m_eCurrentState )
	{
		//---------------------------------------------------------------------------
		case UNSET:
		{
			break;
		}
		//---------------------------------------------------------------------------
		case DISABLED:
		{
			GetSentrygunBrain()->GetSentrygunActor()->SetLifeState(  AVP_ServerWeapon_Sentrygun::DORMANT );

			if( GetSentrygunBrain()->GetSentrygunActor()->GetSentryGunAIAnimStateMachine() )
			{
				GetSentrygunBrain()->GetSentrygunActor()->GetSentryGunAIAnimStateMachine()->RequestDisabled();
			}
			break;
		}
		//---------------------------------------------------------------------------
		case DESTROYED:
		{
			GetSentrygunBrain()->GetSentrygunActor()->SetLifeState(  AVP_ServerWeapon_Sentrygun::DEAD );

			if( GetSentrygunBrain()->GetSentrygunActor()->GetSentryGunAIAnimStateMachine() )
			{
				GetSentrygunBrain()->GetSentrygunActor()->GetSentryGunAIAnimStateMachine()->RequestDestroyed();
			}
			break;
		}
		//---------------------------------------------------------------------------
		case SWEEPING:
		{
			GetSentrygunBrain()->GetSentrygunActor()->SetLifeState(  AVP_ServerWeapon_Sentrygun::ALIVE );

			StopShooting();
			m_fSweepTimer = 0.0f;
			break;
		}
		//---------------------------------------------------------------------------
		case ATTACK:
		{
			GetSentrygunBrain()->GetSentrygunActor()->SendSoundMessage( AVP_ClientWeapon_Sentrygun::SOUND_LOCKED_ON );

			GetSentrygunBrain()->SetMaxAimDirAngularVelocity( Asura_Maths::Deg180 * 4.0f );

			// reset the firing timer and set state to shoot, so fire will be requested in the fireing update. 
			m_fFiringTimer = 0.0f;
			m_eFiringState = FIRING_STATE_SHOOT;
			
			break;
		}
		//---------------------------------------------------------------------------
		default:
		{
			break;
		}
	}
}

DEBUGVAR float dbg_fCloseDotProduct = Asura_Maths::Cosine( Asura_Maths::DegToRad( 10.0f ) ); // 10 degrees
static const Asura_Vector_3 g_xSentrygunTargetingOffset( 0.0f, -0.6f, 0.0f );

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: TurnTowardsTarget
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::TurnTowardsTarget( void )
{
	Asura_Guid uTargetGuid = GetSentrygunBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().GetGuid();

	if ( uTargetGuid != ASURA_GUID_UNREGISTERED )
	{
		AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity( uTargetGuid );

		if ( pxActor )
		{
			Asura_Vector_3 xChestPos( Asura_Maths::ZeroVector3 );
			pxActor->GetChestPosition( xChestPos );

			// Ideally we'd turn eye to eye, but this'll do for now.
			Asura_Vector_3 xDirection = xChestPos - ( GetSentrygunBrain()->GetActor()->GetPosition() + g_xSentrygunTargetingOffset );

			if( ! static_cast<AVP_ServerWeapon_Sentrygun*>( GetSentrygunBrain()->GetActor() )->IsOutsideFireArc( xDirection ) )
			{
				xDirection.Normalise();
				const Asura_Matrix_3x3& xOri = GetSentrygunBrain()->GetActor()->GetOrientation();
				const Asura_Vector_3 xFwd( xOri.e13, xOri.e23, xOri.e33 );
				float fDot = xFwd * xDirection;

				if ( fDot > 0.0f ) // if target is in front of us
				{
					GetSentrygunBrain()->SetAimDirection( xDirection );
					GetSentrygunBrain()->SetLookAtPosition( xChestPos );
				}
			}
			else
			{
				GetSentrygunBrain()->SetAimDirection( GetSentrygunBrain()->GetAimDirection() );
				GetSentrygunBrain()->SetLookAtPosition( GetSentrygunBrain()->GetLookAtPosition() );	 
			}
		}
	}
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: IsAbleToFire
//---------------------------------------------------------------------------
bool AVP_Behaviour_Sentrygun::IsAbleToFire()
{
	// Get our aim direction
	const Asura_Vector_3& xAimDir = GetSentrygunBrain()->GetAimDirection();
	
	// Get the Pulse rifles muzzle marker point
	Asura_Matrix_3x3 xMuzOri;

	if ( !GetSentrygunBrain()->GetActor()->GetWorldSpaceMarkerPoint( 0x0001A7D2, NULL, &xMuzOri ) ) // Asura_GetHashID("muz")
	{
		Asura_Assert( false, "Couldn't get muzzle MP" );
		return false;
	}

	const Asura_Vector_3	xMuzFwd	= Asura_Vector_3( xMuzOri.e13, xMuzOri.e23, xMuzOri.e33 );
	return ( xMuzFwd * xAimDir > dbg_fCloseDotProduct );
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: ReadFromChunkStream
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	u_int uVersion;
	AVP_ServerWeapon_Sentrygun::LifeState eLifeState = AVP_ServerWeapon_Sentrygun::ALIVE;

	// This is because the first version of this behaviour, which had no data, had no ReadFrom/WriteToChunkStream, horrible, but whadyagonnado?
	if ( xStream.PeekUInt() < s_uFirstVersion )
	{
		PARENT::ReadFromChunkStream( xStream );
		return;
	}

	xStream >> uVersion;

	if( uVersion > s_uCurrentVersion )
	{
		Asura_Assertf( uVersion <= s_uCurrentVersion, ( "Invalid chunk version, %d, in AVP_Behaviour_Sentrygun", uVersion ) );
		return;
	}
	
	PARENT::ReadFromChunkStream( xStream );

	switch( uVersion )
	{
		case( s_uCurrentVersion ):
		{
			// in this version we are now getting the life state from the Sentry Gun server entity
			eLifeState = GetSentrygunBrain()->GetSentrygunActor()->GetLifeState();

			if ( HasActivateBeenCalled() )
			{
				if( eLifeState == AVP_ServerWeapon_Sentrygun::ALIVE )
				{
					SwitchToState( SWEEPING );
				}
				else if( eLifeState == AVP_ServerWeapon_Sentrygun::DEAD )
				{
					SwitchToState( DESTROYED );
				}
				else
				{
					SwitchToState( DISABLED );
				}
			}
			break;
		}
		case( 0x00001002 ):
		{
			bool bEnabled;
			xStream >> bEnabled;

			if ( HasActivateBeenCalled() )
			{
				if ( bEnabled )
				{
					GetSentrygunBrain()->GetSentrygunActor()->SetLifeState( AVP_ServerWeapon_Sentrygun::ALIVE );
					SwitchToState( SWEEPING );
				}
				else
				{
					GetSentrygunBrain()->GetSentrygunActor()->SetLifeState( AVP_ServerWeapon_Sentrygun::DORMANT );
					SwitchToState( DISABLED );
				}
			}

			break;
		}
		case( s_uFirstVersion ):
		{
			PARENT::ReadFromChunkStream( xStream );

			u_int uState;
			xStream >> uState;
			Asura_Assertf( uState < MAX_STATES, ( "Invalid state, %d, found in chunk data for AVP_Behaviour_Sentrygun", uState ) );
			if ( uState < MAX_STATES )
			{
				SwitchToState( static_cast< UpdateState >( uState ) );
			}
			return;
		}
	}

	AVP_ServerWeapon_Sentrygun* pxSentryGun = GetSentrygunBrain()->GetSentrygunActor();
	float fTanHorHalfSweep = pxSentryGun->GetTanFireArcHalfAngle();
	m_xSweepAngle = Asura_Maths::ArcTangent (fTanHorHalfSweep, 1.0f);
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: WriteToChunkStream
//---------------------------------------------------------------------------
void AVP_Behaviour_Sentrygun::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << s_uCurrentVersion;

	PARENT::WriteToChunkStream( xStream );
}

//---------------------------------------------------------------------------
// AVP_Behaviour_Sentrygun :: RenderDebugInfo
//---------------------------------------------------------------------------
#ifndef ASURA_NO_DEBUG_OUTPUT
static const char* s_aszStateStrings[AVP_Behaviour_Sentrygun::MAX_STATES] =
{
	"SENTRY GUN - UNSET",
	"SENTRY GUN - DISABLED",
	"SENTRY GUN - SWEEPING",
	"SENTRY GUN - ATTACK",
	"SENTRY GUN - DESTROYED",
};

void AVP_Behaviour_Sentrygun::RenderDebugInfo()
{
	PARENT::RenderDebugInfo();

	Asura_Font_System::Print
	(
		GetProjectBrain()->GetDebugOutputPos().x,
		GetProjectBrain()->GetDebugOutputPos().y,
		0.5f,
		0xffffffff,
		ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING,
		"%s",
		s_aszStateStrings[ m_eCurrentState ]
	);

	GetProjectBrain()->GetDebugOutputPos().y += 10.0f;

	const Asura_Guid					uActorGuid			= GetSentrygunBrain()->GetOwnerGuid();
	const AVP_ClientWeapon_Sentrygun*	pxClient			= AVP_ClientWeapon_Sentrygun::FindEntity(uActorGuid);
	const AVP_Inventory_System*			pxClientSystem		= (pxClient == NULL) ? NULL : pxClient->GetInventorySystem();
	const AVP_InventoryItem_Weapon*		pxClientWeapon		= static_cast<const AVP_InventoryItem_Weapon*>(pxClientSystem ? pxClientSystem->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON) : NULL);

	const AVP_Inventory_System*			pxServerSystem		= GetSentrygunBrain()->GetActor()->GetInventorySystem();
	const AVP_InventoryItem_Weapon*		pxServerWeapon		= static_cast<const AVP_InventoryItem_Weapon*>(pxServerSystem ? pxServerSystem->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON) : NULL);

	if(pxClientWeapon != NULL)
	{
		Asura_Char	szClientTemp[256]	= "CLNT: ";
		pxClientWeapon->CreateDebugString(250, &szClientTemp[6]);

		Asura_Font_System::Print( GetProjectBrain()->GetDebugOutputPos().x, GetProjectBrain()->GetDebugOutputPos().y, 0.5f, 0xffff8080,
			ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, szClientTemp );
		GetProjectBrain()->GetDebugOutputPos().y += 10.0f;
	}

	if(pxServerWeapon != NULL)
	{
		Asura_Char	szServerTemp[256]	= "SRVR: ";
		pxServerWeapon->CreateDebugString(250, &szServerTemp[6]);

		Asura_Font_System::Print( GetProjectBrain()->GetDebugOutputPos().x, GetProjectBrain()->GetDebugOutputPos().y, 0.5f, 0xffff8080,
			ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, szServerTemp );
		GetProjectBrain()->GetDebugOutputPos().y += 10.0f;
	}

	Asura_Vector_3 xPosition(Asura_Maths::ZeroVector3);
	Asura_Matrix_3x3 xOri;
	Asura_Frustum xFrustum;
	
	// draw an arrow out of the muz marker point.
	GetSentrygunBrain()->GetSentrygunActor()->GetWorldSpaceMarkerPoint( 0x0001A7D2 /*muz*/, &xPosition, &xOri );
	Asura_DebugLineRender::AddArrow( xPosition, Asura_Vector_3( xOri.e13, xOri.e23, xOri.e33 ) + xPosition );

	// draw the FOV from the muz marker point
	AddNonSymetricalFrustumToRenderList(	0x0001A7D2 /*muz*/, 
											GetSentrygunBrain()->GetSentrygunActor()->GetVisionRange(), 
											GetSentrygunBrain()->GetSentrygunActor()->GetTanHorizontalFOVHalfAngle(), 
											0xffff0000 /*red*/ );

	// draw the Fire Arch from the eyes marker point (always forward)
	AddNonSymetricalFrustumToRenderList(	0x002FBC62 /*eyes*/, 
											GetSentrygunBrain()->GetSentrygunActor()->GetVisionRange(), 
											GetSentrygunBrain()->GetSentrygunActor()->GetTanFireArcHalfAngle(), 
											0xff0033ff /*blueish*/ );

	// draw the Lethal Range from the eyes marker point (always forward)
	AddNonSymetricalFrustumToRenderList(	0x002FBC62 /*eyes*/, 
											GetSentrygunBrain()->GetSentrygunActor()->GetLethalRange(), 
											GetSentrygunBrain()->GetSentrygunActor()->GetTanFireArcHalfAngle(), 
											0xff5500ff /*purple*/ );
}

void AVP_Behaviour_Sentrygun::AddNonSymetricalFrustumToRenderList( u_int uMarkerPoint, float fVisionRange, float fTanHorHalfFOV, u_int uColour )
{
	Asura_Vector_3 xPos;
	Asura_Matrix_3x3 xOri;

	Asura_Frustum xFrustum;
	Asura_Vector_3 pxTopVerts[ Asura_Frustum::MAX_VERTICES ];
	Asura_Vector_3 pxBottomVerts[ Asura_Frustum::MAX_VERTICES ];
	Asura_Vector_3 pxRenderVerts[ Asura_Frustum::MAX_VERTICES ];

	// get the position and orientation of the chosen marker point
	GetSentrygunBrain()->GetSentrygunActor()->GetWorldSpaceMarkerPoint( uMarkerPoint, &xPos, &xOri );

	// get the vert for a frustrum with the top half Vert FOV angle
	xFrustum.SetForFOV( 0.0f, fVisionRange, fTanHorHalfFOV, GetSentrygunBrain()->GetSentrygunActor()->GetTanVerticalFOVTopAngle() );
	xFrustum.GenerateVertices( pxTopVerts, xPos, xOri );

	// get the vert for a frustrum with the bottom half Vert FOV angle
	xFrustum.SetForFOV( 0.0f, fVisionRange, fTanHorHalfFOV, GetSentrygunBrain()->GetSentrygunActor()->GetTanVerticalFOVBottomAngle() );
	xFrustum.GenerateVertices( pxBottomVerts, xPos, xOri );

	// now set the verts into the render verts array. 
	pxRenderVerts[0] = pxTopVerts[0];
	pxRenderVerts[1] = pxTopVerts[1];
	pxRenderVerts[2] = pxTopVerts[2];
	pxRenderVerts[3] = pxTopVerts[3];

	pxRenderVerts[4] = pxBottomVerts[4];
	pxRenderVerts[5] = pxBottomVerts[5];
	pxRenderVerts[6] = pxTopVerts[6];
	pxRenderVerts[7] = pxTopVerts[7];

	Asura_DebugLineRender::AddLine( pxRenderVerts[0], pxRenderVerts[4], uColour );
	Asura_DebugLineRender::AddLine( pxRenderVerts[0], pxRenderVerts[5], uColour );
	Asura_DebugLineRender::AddLine( pxRenderVerts[0], pxRenderVerts[6], uColour );
	Asura_DebugLineRender::AddLine( pxRenderVerts[0], pxRenderVerts[7], uColour );

	Asura_DebugLineRender::AddLine( pxRenderVerts[4], pxRenderVerts[5], uColour );
	Asura_DebugLineRender::AddLine( pxRenderVerts[5], pxRenderVerts[7], uColour );
	Asura_DebugLineRender::AddLine( pxRenderVerts[7], pxRenderVerts[6], uColour );
	Asura_DebugLineRender::AddLine( pxRenderVerts[6], pxRenderVerts[4], uColour );
}

#endif
/// End Of File 'AVP_Behaviour_Sentrygun.cpp'
