
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_ServerActor_AlienQueen.cpp
// Author : Andy McCabe
// Date   : 09-Nov-2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header...
#include "Entity/PhysicalObject/Actor/Vertebrates/AlienQueen/AVP_ServerActor_AlienQueen.h"

// Asura independent header-files...
#include "Camera/Asura_Camera.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Timers.h"
#include "Physics/Asura_Physics.h"

// Asura platform header-files...

// Game independent header-files...
#include "Actor/AVP_Actor_InputHandler.h"
#include "Actor/AVP_Actor_PhysicsObjectHandler.h"
#include "Actor/Vertebrates/AlienQueen/AVP_AlienQueen_AIAnimStateMachine.h"
#include "Actor/Vertebrates/AlienQueen/AVP_AlienQueen_Description.h"
#include "Actor/Vertebrates/AlienQueen/AVP_AlienQueen_MeleeCounter_System.h"
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/AVP_Messages.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AlienQueen/AVP_ClientActor_AlienQueen.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AlienQueen/AVP_AlienQueen_Messages.h"
#include "Mesh/AVP_PolygonFlags.h"
#include "Moves/AVP_Movement_System.h"
#include "Entity/Damage/AVP_EntityDamage_Apply.h"
#include "Entity/Damage/AVP_EntityDamage_Messages.h"
#include "Entity/PhysicalObject/Projectile/AVP_GenericProjectileData.h"
#include "Entity/PhysicalObject/Projectile/AVP_Projectile_Messages.h"

// axon
#include "Core/BrainTypes/AVP_Brain_Actor_AlienQueen.h"

////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////

static Asura_Hash_ID uAVP_BLUEPRINT_PARAM_QUEEN_DAMAGETILLSTUNNED = 0xD61505FD; //Asura_GetHashID("AIDamageBeforeStunned");

//////////////////////////////////////////////////////////////////////////////
//
// AVP_ServerActor_AlienQueen
//
//////////////////////////////////////////////////////////////////////////////

Asura_Hashed_List<AVP_ServerActor_AlienQueen>	AVP_ServerActor_AlienQueen::s_xList;

AVP_ServerActor_AlienQueen::AVP_ServerActor_AlienQueen(const Asura_Guid uGUID)
: Asura_Entity_Server(uGUID)
, PARENT(AVP_PolygonFlag_IgnoreForAlienCollision, uGUID)
, m_fDamageTillStunned( 200.0f )
, m_fDamageAccumulated( 0.0f )
{
	Classification	= AVP_ENTITYCLASS_ALIENQUEEN;

	m_pxMeleeSystem = new AVP_AlienQueen_MeleeCounter_System(*this);

	s_xList.AddToList(this);
}

AVP_ServerActor_AlienQueen::~AVP_ServerActor_AlienQueen()
{
	s_xList.RemoveFromList(this);
}


void AVP_ServerActor_AlienQueen::InitialiseFromBlueprint( const Asura_Blueprint& xBlueprint )
{
	PARENT::InitialiseFromBlueprint( xBlueprint );

	Asura_Value	xValue;

	if( xBlueprint.GetParameterValueByHashID( uAVP_BLUEPRINT_PARAM_QUEEN_DAMAGETILLSTUNNED, xValue ) )
	{
		m_fDamageTillStunned = xValue.GetFloatValue();
	}
}

void AVP_ServerActor_AlienQueen::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	u_int uVersion;

	xStream >> uVersion;

	if ( uVersion > 0 )
	{
		PARENT::ReadFromChunkStream(xStream);
	}

	if ( uVersion == 2 )
	{
		xStream >> m_fDamageTillStunned;
		xStream >> m_fDamageAccumulated;
	}
	else if ( uVersion == 1 )
	{
	}
	else if ( uVersion == 0 )
	{
		// The vertibrate didn't have chunk data orignally, so direct subclass chunk versions have been bumped to work around it.
		AVP_ServerEntity_Actor::ReadFromChunkStream(xStream);
	}
	else
	{
		Asura_Assertf( false, ( "AVP_ServerActor_AlienQueen::ReadFromChunkStream - unsupported chunk version (%u)", uVersion ) );
	}
}

void AVP_ServerActor_AlienQueen::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << uCURRENT_CHUNK_VERSION;

	PARENT::WriteToChunkStream( xStream );

	xStream << m_fDamageTillStunned;
	xStream << m_fDamageAccumulated;
}

Axon_Brain* AVP_ServerActor_AlienQueen::CreateBrain()
{
	return new AVP_Brain_Actor_AlienQueen( this );
}

void AVP_ServerActor_AlienQueen::PostLoadInitialise()
{
	if ( !IsPlayer() && !IsADeadBody() && !IsDead() )
	{
		if ( m_pxAIAnimStateMachine == NULL )
		{
			m_pxAIAnimStateMachine = new AVP_AlienQueen_AIAnimStateMachine( *this );
		}
		m_pxAIAnimStateMachine->Initialise();
	}

	PARENT::PostLoadInitialise();
}

void AVP_ServerActor_AlienQueen::MakeAI()
{
	PARENT::MakeAI();
	if ( m_pxAIAnimStateMachine == NULL )
	{
		m_pxAIAnimStateMachine = new AVP_AlienQueen_AIAnimStateMachine( *this );
	}
}


void AVP_ServerActor_AlienQueen::OnApplyDamage(const AVP_EntityDamage_ApplyToEntity& xDamage)
{
	PARENT::OnApplyDamage(xDamage);
}

bool AVP_ServerActor_AlienQueen::HandleMessage(const Asura_Message* pxMessage)
{
	// What sort of message is it?
	switch (pxMessage->GetMsgID())
	{
		/////////////////////
		case AVP_MSGID_LAUNCH_ACID_SPIT_PROJECTILE:
		/////////////////////
		{
			const AVP_Msg_AlienQueen_LaunchAcidSpit_Projectile* pxData = static_cast<const AVP_Msg_AlienQueen_LaunchAcidSpit_Projectile*>( pxMessage->GetData() );
			if( pxData )
			{
				SendCreateAcidSpitProjectileMessage( pxData->GetTargetGuid() );
			}
			return true; 
		}
		/////////////////////
		case AVP_MSGID_ALIENQUEEN_SET_BEHAVIOUR:
		case AVP_MSGID_LOOK_AT_ENTITY:
		case AVP_MSGID_STOP_LOOKING_AT_ENTITY:
		case AVP_MSGID_ALIENQUEEN_ADD_TO_TARGET_LIST:
		case AVP_MSGID_ALIENQUEEN_REMOVE_FROM_TARGET_LIST:
		case AVP_MSGID_ALIENQUEEN_FORCE_ROAR:
		/////////////////////
		{
			if( GetBrain() && !IsADeadBody() ) 
			{
				GetBrain()->DoHandleMessage( pxMessage );
			}
			return true;
		}
		/////////////////////
		default:
		/////////////////////
		{
			return(PARENT::HandleMessage(pxMessage));
		}
	}
}

const Asura_Blueprint* AVP_ServerActor_AlienQueen::GetCurrentFlinchBlueprint() const
{
	// The alien doesn't have weapon variations, so the flinches come from the main actor blueprint.
	return GetBlueprint();
}

Asura_Hash_ID AVP_ServerActor_AlienQueen::GetMovesBlueprintType() const
{
	return uAVP_BLUEPRINT_TYPE_MOVESALIENQUEEN;
}

void AVP_ServerActor_AlienQueen::TakeDamageExtended(const AVP_EntityDamage_RequestVictim& xDamage, const bool bForceFatal, const bool bSendMessageToClient )
{
	AVP_EntityDamage_RequestVictim xLocalDamage = xDamage;
	bool bForceFatalLocal = bForceFatal;

	if(xDamage.GetTypeHash() == uAVP_BLUEPRINT_NAME_DAMAGE_BULLET ||
		xDamage.GetTypeHash() == uAVP_BLUEPRINT_NAME_DAMAGE_DISC ||
		xDamage.GetTypeHash() == uAVP_BLUEPRINT_NAME_DAMAGE_BLADE )
	{
		// set the damage done to 0 before sending to PARENT and make sure that nothing can kill the queen 
		xLocalDamage.SetAmount(0.0f);
		xLocalDamage.SetDoNotFlinch(false);
		bForceFatalLocal = false;

		// register the damage taken for the stunned counter. we dont want to damage queen, just register how much damage has been taken. 
		m_fDamageAccumulated += 1.0f;
	}
	else if(xDamage.GetTypeHash() == uAVP_BLUEPRINT_NAME_DAMAGE_PLASMA ||
		xDamage.GetTypeHash() == uAVP_BLUEPRINT_NAME_DAMAGE_EXPLOSIVE)
	{
		// set the damage to 0 before forwarding to PARENT, but do make her flinch from the impact. 
		xLocalDamage.SetAmount(0.0f);
		xLocalDamage.SetDoNotFlinch(false);
		bForceFatalLocal = false;

		// register the damage taken for the stunned counter. we dont want to damage queen, just register how much damage has been taken. 
		m_fDamageAccumulated += 20.0f;
	}
	else
	{
		// any other type of damage just make sure it doesnt effect her. 
		xLocalDamage.SetAmount(0.0f);
		bForceFatalLocal = false;
	}

	PARENT::TakeDamageExtended(xLocalDamage,bForceFatalLocal,bSendMessageToClient);
}

void AVP_ServerActor_AlienQueen::SendCreateAcidSpitProjectileMessage(const Asura_Guid uTargetGuid)
{
	Asura_Message* pxMessage = Asura_Network::CreateMessage( AVP_MSGID_CREATE_PROJECTILE_ACIDSPIT, sizeof( AVP_Message_CreateGenericProjectile ) );
	Asura_Assert( pxMessage != NULL, "Unable to create message" );
	if(pxMessage != NULL)
	{
		AVP_Message_CreateGenericProjectile*	pxData		= static_cast< AVP_Message_CreateGenericProjectile* >( pxMessage->GetData() );
		
		const AVP_GenericProjectileData* pxProjectileData = AVP_GenericProjectileData::GetDataByHash( 0x4588122E );	// Asura_GetHashID( "AcidSpitProjectile" )
		//const AVP_GenericProjectileData* pxProjectileData = AVP_GenericProjectileData::GetDataByHash( 0x18FF28D8 );	// Asura_GetHashID( "FlamethrowerProjectileNPC" )

		Asura_Vector_3 xSpitPosition;
		Asura_Matrix_3x3 xSpitOrientation;

		if ( !GetWorldSpaceMarkerPoint( 0x0035F848, &xSpitPosition, &xSpitOrientation ) )	// Asura_GetHashID( "Spit" )
		{
			Asura_Assert( false, "Unable to find marker point spit on Alien Queen" );
			return;
		}

		const AVP_ServerEntity_Actor* pxTargetActor = AVP_ServerEntity_Actor::FindEntity( uTargetGuid );

		if ( !pxTargetActor ) return;

		Asura_Vector_3 xTargetPosition;
		pxTargetActor->GetChestPosition( xTargetPosition );

		Asura_Vector_3 xSpitToTargetDir = xTargetPosition - xSpitPosition;
		const float fDistToTarget = xSpitToTargetDir.Normalise();
		
		Asura_Matrix_3x3 xAdjustedShotOri;
		Asura_Maths::OrientationFromDirection( xAdjustedShotOri, xSpitToTargetDir );

		if ( pxProjectileData->GetDoBallisticAdjustment() )
		{
			const float fMuzzleVel = pxProjectileData->GetMuzzleVelocity();
			float fBallisticAngle = 0.5f * Asura_Maths::ArcSine( Asura_Maths::ClampToRange( ( pxProjectileData->GetGravityScale() * Asura_Physics_System::GetGravityScalar() * fDistToTarget ) / ( fMuzzleVel * fMuzzleVel ), 0.0f, 1.0f ) ); // zero drag, zero wind, zero elevation approximation seems to work ok in most cases, well done Galileo
			fBallisticAngle = Asura_Maths::ClampToRange( fBallisticAngle, 0.0f, Asura_Maths::DegToRad( 15.0f ) );
			float fSin;
			float fCos;
			Asura_Maths::SinCos( fBallisticAngle, fSin, fCos );
			Asura_Matrix_3x3 xBallisticAdjstment( 1.0f, 0.0f, 0.0f, 0.0f, fCos, -fSin, 0.0f, fSin, fCos );

			xAdjustedShotOri = xAdjustedShotOri * xBallisticAdjstment;
		}

		// Fill-in the message...
		pxData->SetProjectileBlueprintHash( pxProjectileData->GetProjectileHash() );
		pxData->SetPosition( xSpitPosition );
		pxData->SetOrientation( Asura_Quat(xAdjustedShotOri) );
		pxData->SetActorVelocity( GetVelocity() );

		pxData->SetDamageScale( 1.0f );
		pxData->SetLaunch( true );

		pxData->SetTargetGuid(ASURA_GUID_UNREGISTERED);

		pxMessage->SetToGuid( GetGuid() );
		pxMessage->SetFromGuid( GetGuid() );

		Asura_Network::Send( pxMessage );
	
	} // if ( pxMessage != NULL )
}

bool AVP_ServerActor_AlienQueen::HasQueenHadEnoughDamageToBeStunned()
{
	if( m_fDamageAccumulated >= m_fDamageTillStunned )
	{
		m_fDamageAccumulated = 0.0f;
		return true;
	}

	return false;
}

//////////////////////////////////////////////////////////////////////////////
//
// AVP_AlienQueenBehaviourData
//
//////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_TOOLS
const char* AVP_AlienQueenBehaviourData::s_apszBehaviourStrings[ NUM_BEHAVIOURS ] =
{
	"WANDER",			// Wander around the maze
	"STUNNED",			// Stay at position for certain time and play stunned anim.
	"ROARING"			// Roar on spot or at current target.
};
#endif

void AVP_AlienQueenBehaviourData::SetParamsFromBehaviour()
{
}

void AVP_AlienQueenBehaviourData::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	int iVersion;
	xStream >> iVersion;

	switch( iVersion )
	{
		////////////////////
		case 1:
		default:
		////////////////////
		{
			xStream >> m_iBehaviour;
		}
	}
}

void AVP_AlienQueenBehaviourData::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << uCURRENT_CHUNK_VERSION;

	xStream << m_iBehaviour;
}

int AVP_AlienQueenBehaviourData::GetAVPBehaviourType() const
{
	return m_iBehaviour;
}
