///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_ServerProjectile_Mine.cpp
// Author : Chris Wallace
// Date   : 25-Feb-2009
//
// Copyright (C) 2009 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// This file's header...
#include "Entity/PhysicalObject/Projectile/AVP_ServerProjectile_Mine.h"

// Asura independent header-files...
#include "Blueprint/Asura_Blueprint.h"
#include "Blueprint/Asura_Blueprint_System.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Timers.h"
#include "Network/Asura_Network.h"
#include "SpecialFX/pfx/Asura_PFX_Entity_Effect.h"
#include "SpecialFX/pfx/Asura_PFX_System.h"

// Game independent header-files...
#include "Actor/Vertebrates/Predator/AVP_Predator_Description.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/AVP_Messages.h"
#include "Entity/Damage/AVP_EntityDamage_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_Actor_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Predator/AVP_ClientActor_Predator.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Predator/AVP_ServerActor_Predator.h"
#include "Entity/PhysicalObject/Projectile/AVP_ClientProjectile_Mine.h"
#include "Entity/PhysicalObject/Projectile/AVP_Projectile_Messages.h"
#include "Entity/PhysicalObject/Projectile/AVP_GenericProjectileData.h"
#include "Inventory/AVP_Inventory_System.h"
#include "Inventory/AVP_Inventory_Group.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/Items/Weapons/AVP_InventoryWeapon_Mine.h"
#include "Physics/Asura_Physics.h"
#include "SpecialFX/Asura_Decal_System.h"


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

static const u_int	g_uChunkVersion	= 0;

Asura_Hashed_List<AVP_ServerProjectile_Mine>	AVP_ServerProjectile_Mine::s_xList;

AVP_ServerProjectile_Mine::AVP_ServerProjectile_Mine (const Asura_Guid uGuid,
													  const Asura_Message* pxMsg)
: Asura_Entity_Server( uGuid )
, PARENT( uGuid, pxMsg )
, m_uMineState(MINE_IN_FLIGHT)
, m_fStateTimer(0.0f)
, m_fMineArmingTimer(2.0f)
, m_fMineDetonateTimer(3.0f)
, m_fTriggerRange(4.0f)
, m_fDetonationOffset(0.0f)
{
	// Update the data...
	Classification	= AVP_ENTITYCLASS_MINE;

	// Set the stats...
	SetHealth(100.0f);
	SetDestructible(true);

	// Don't forget to store us in the list...
	s_xList.AddToList(this);
}

AVP_ServerProjectile_Mine::AVP_ServerProjectile_Mine (const Asura_Guid uGuid)
													  : Asura_Entity_Server( uGuid )
													  , PARENT( uGuid )
													  , m_uMineState(MINE_IN_FLIGHT)
													  , m_fStateTimer(0.0f)
													  , m_fMineArmingTimer(2.0f)
													  , m_fMineDetonateTimer(3.0f)
													  , m_fTriggerRange(4.0f)
													  , m_fDetonationOffset(0.0f)
{
	// Update the data...
	Classification	= AVP_ENTITYCLASS_MINE;

	// Set the stats...
	SetHealth(100.0f);
	SetDestructible(true);

	// Don't forget to store us in the list...
	s_xList.AddToList(this);
}


AVP_ServerProjectile_Mine::~AVP_ServerProjectile_Mine ()
{
	// Don't forget to remove us from the lists...

	AVP_ServerActor_Predator* pxOwnerServerActor = AVP_ServerActor_Predator::FindEntity(m_uOwnerGuid);
	if(pxOwnerServerActor)
	{
		Asura_LinkList<Asura_Guid>* pxList = pxOwnerServerActor->GetMineGuidList();
		pxList->Remove(GetGuid());
	}

	s_xList.RemoveFromList(this);
}


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

	Asura_Value xValue;
	if(xBlueprint.GetParameterValueByHashID( 0x1769B685, xValue )) // Asura_GetHashID( "TriggerRange" )
	{
		m_fTriggerRange = xValue.GetFloatValue();
	}

	if(xBlueprint.GetParameterValueByHashID( 0x54B7DF25, xValue )) // Asura_GetHashID( "DetonateTimer" )
	{
		m_fMineDetonateTimer = xValue.GetFloatValue();
	}

	if(xBlueprint.GetParameterValueByHashID( 0x611F6AFF, xValue )) // Asura_GetHashID( "ArmingTimer" )
	{
		m_fMineArmingTimer = xValue.GetFloatValue();
	}
}


AVP_ServerProjectile_Mine*	AVP_ServerProjectile_Mine::Create( const Asura_Message* pxMsg )
{
	AVP_ServerProjectile_Mine*	pxMine = new AVP_ServerProjectile_Mine( Asura_Entity_System::GetCreatedByGameGuid(), pxMsg);
    pxMine->InitFromBlueprint();
	const AVP_GenericProjectileData* pxProjectileData = AVP_GenericProjectileData::GetDataByHash( pxMine->GetBlueprintHashID() );
	const AVP_Message_CreateGenericProjectile* pxData = static_cast< const AVP_Message_CreateGenericProjectile* >( pxMsg != NULL ? pxMsg->GetData() : NULL );
	if( pxData != NULL )
	{
		const float				fMuzzleVel = pxProjectileData->GetMuzzleVelocity();
		Asura_Vector_3			xVelocity( 0.0f, 0.0f, fMuzzleVel );
		const Asura_Vector_3&	xPosition = pxData->GetPosition();
		Asura_Matrix_3x3		xOrientation;
		pxData->GetOrientation().ToMatrix( xOrientation );
		xVelocity = xVelocity * xOrientation;
		float fMag = xVelocity.Normalise();
		fMag += xVelocity * pxData->GetActorVelocity();

		Asura_Maths::OrientationFromNormal(xOrientation, -xVelocity);

		pxMine->Launch( xPosition, xOrientation, xVelocity * fMag, pxMine->m_uOwnerGuid, pxData->GetDamageScale() ); // Launch last as this creates the client entity

		AVP_ServerActor_Predator* pxOwnerServerActor = AVP_ServerActor_Predator::FindEntity(pxMine->m_uOwnerGuid);
		if(pxOwnerServerActor)
		{
			Asura_LinkList<Asura_Guid>* pxList = pxOwnerServerActor->GetMineGuidList();
			if(pxList)
			{
				if(pxList->Size() >= 5/*m_fNumActiveMines*/)
				{
					Asura_ListElt<Asura_Guid>* pxElt = pxList->Front();
					if(pxElt)
					{
						Asura_Guid uGuid = pxElt->Data();
						AVP_ServerProjectile_Mine*	pxDeadMine = AVP_ServerProjectile_Mine::FindEntity(uGuid);
						if(pxDeadMine)
						{
							pxDeadMine->SetDisarmed(true);
						}
						pxList->PopFront();
					}
				}
				pxList->Push_Back(pxMine->GetGuid());
			}
		}
	}

    return pxMine;
}


void AVP_ServerProjectile_Mine::Launch (const Asura_Vector_3 &xPosition,
										const Asura_Matrix_3x3 &xOrientation,
										const Asura_Vector_3 &xVelocity,
										const Asura_Guid uGuidToIgnore,
										float fDamageMultiplier)
{
	PARENT::Launch(xPosition, xOrientation, xVelocity, uGuidToIgnore, fDamageMultiplier);
}


void AVP_ServerProjectile_Mine::MakeClientUpdateMessage (void)
{
	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_CLIENTUPDATE, sizeof(AVP_Message_MineClientUpdate), GetClassification());
	
	if(pxMessage != NULL)
	{
		// Fill in the message...
		AVP_Message_MineClientUpdate* pxData = static_cast<AVP_Message_MineClientUpdate*>(pxMessage->GetData());
		pxData->FillMine(this);

		// Send the message...
		Send(pxMessage, ASURA_GUID_MY_CLIENT_ENTITIES);
	}
}

void AVP_ServerProjectile_Mine::CreateFromChunkStream( Asura_Guid uGuid, Asura_Chunk_Stream& xStream)
{
	AVP_ServerProjectile_Mine* pxMine = new AVP_ServerProjectile_Mine(uGuid);

	if (pxMine)
	{
		pxMine->ReadFromChunkStream(xStream);
	}
}

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

	// Read the version...
	xStream >> uVersion;
	Asura_Assert((uVersion <= g_uChunkVersion), "Unsupported chunk version");

	xStream >> m_uMineState;
	xStream >> m_fStateTimer;

	// Read the parent data...
	PARENT::ReadFromChunkStream(xStream);
}


void AVP_ServerProjectile_Mine::WriteToChunkStream (Asura_Chunk_Stream& xStream) const
{
	// Write the version...
	xStream << g_uChunkVersion;

	xStream << m_uMineState;
	xStream << m_fStateTimer;

	// Write the parent data...
	PARENT::WriteToChunkStream(xStream);
}

void AVP_ServerProjectile_Mine::Update ( void )
{
	// Call the base-function...
	PARENT::Update();

	if( !m_pxDynamicObject )
	{
		Asura_Assert( false, "Predator Mine doesn't have Dynamic Object. Something is terrible wrong. Update failed." );
		SetThisAndClientsAsDestroyed();	// Destroy disc immediately and silently.
		return;
	}

	if(IsDisarmed())
	{
		SetThisAndClientsAsDestroyed();
	}

	switch(m_uMineState)
	{
		case MINE_IN_FLIGHT:
		{
			// Re-orient to target surface

			AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(GetOwnerGuid());
			if(pxActor)
			{
				Asura_Euler_3 xMineRot;
				xMineRot.FromMatrix(GetOrientation());

				Asura_Vector_3 xDir = GetVelocity();
				xDir.Normalise();
				Asura_LOS_InputData xInput(GetPosition(), xDir, ASURA_GUID_UNREGISTERED, 2.0f);
				const Asura_LOS_OutputData* pxOutput = Asura_LOS::TestAgainstEnvironment(&xInput);
				
				Asura_Matrix_3x3 xDestOri;
				if(pxOutput && pxOutput->m_fDistanceToIntersection < 2.0f)
				{
					Asura_Maths::OrientationFromNormal(xDestOri, pxOutput->m_xNormal);

					Asura_Euler_3 xDestRot;
					xDestRot.FromMatrix(xDestOri);

					float fRotSpeed = Asura_Timers::GetGameTimeSlice()*20.0f;

					if(xMineRot.y > xDestRot.y)
					{
						xMineRot.y -= fRotSpeed;
					}
					else if(xMineRot.y < xDestRot.y)
					{
						xMineRot.y += fRotSpeed;
					}

					if(xMineRot.x > xDestRot.x)
					{
						xMineRot.x -= fRotSpeed;
					}
					else if(xMineRot.x < xDestRot.x)
					{
						xMineRot.x += fRotSpeed;
					}

					if(xMineRot.z > xDestRot.z)
					{
						xMineRot.x -= fRotSpeed;
					}
					else if(xMineRot.z < xDestRot.z)
					{
						xMineRot.z += fRotSpeed;
					}

					Asura_Matrix_3x3 xMat;
					xMineRot.ToMatrix(xMat);
					SetOrientation(xMat);
				}
 				else
 				{
					
					Asura_Vector_3 xDir(GetVelocity());
					if(!xDir.ApproximatelyEquals(Asura_Maths::ZeroVector3))
					{
						xDir.Normalise();

						Asura_Matrix_3x3 xMat;
						Asura_Maths::OrientationFromNormal(xMat, -xDir);
						SetOrientation(xMat);
					}
				}
			}
		}
		break;

		case MINE_ARMING:
		{
			// Just a timer
			m_fStateTimer += Asura_Timers::GetGameTimeSlice();
			if(m_fStateTimer > m_fMineArmingTimer)
			{
				m_fStateTimer = 0.0f;
				m_uMineState = MINE_ARMED;
			}
		}
		break;

		case MINE_ARMED:
		{
			// Detect actors
			for( Asura_Hashed_List_ConstIt<AVP_ServerActor_Vertebrate> xIt(AVP_ServerActor_Vertebrate::GetList()); !xIt.Done(); xIt.Next() )
			{
				const AVP_ServerActor_Vertebrate* pxVert = xIt.GetCurrent();
				const Asura_Vector_3 xPosition = pxVert->GetPosition();

				if (!pxVert->IsDead())
				{
					float fDist = (xPosition - GetPosition()).Magnitude();
					if(fDist < m_fTriggerRange)
					{
						m_fStateTimer = 0.0f;
						m_uMineState = MINE_TRIGGERED;
					}
				}
			}
		}
		break;

		case MINE_TRIGGERED:
		{
			// Another timer
			m_fStateTimer += Asura_Timers::GetGameTimeSlice();
			if(m_fStateTimer > (m_fMineDetonateTimer+m_fDetonationOffset))
			{
				m_fStateTimer = 0.0f;
				Explode();// Boom
			}
		}
		break;

		default: break;
	}

	// Update Timers.
	float fTimeStep = Asura_Timers::GetGameTimeSlice();
	
	// If mine owner is gone, destroy this entity.
	AVP_ServerActor_Predator* pxServerPred = AVP_ServerActor_Predator::FindEntity(m_uOwnerGuid);
	if(!pxServerPred || pxServerPred->IsDead())
	{
		// Slowly destroy mine. When health of mine hits zero, destroy it in next Update call.
		float fNewHealth = GetHealth() - fTimeStep * 50.0f;
		SetHealth(fNewHealth);
		return;
	}
}


void AVP_ServerProjectile_Mine::OnEmbed( const Asura_Vector_3& xImpactPoint, const Asura_Vector_3& xImpactNormal )
{
	if(m_uMineState == MINE_IN_FLIGHT)
	{
		Asura_Matrix_3x3 xOri;
		if ( xImpactNormal.ApproximatelyEquals( Asura_Maths::ZeroVector3 ) )
		{
			xOri = GetOrientation();
		}
		else
		{
			Asura_Maths::OrientationFromNormal( xOri, xImpactNormal );
		}
		TeleportTo( xImpactPoint, xOri, true );
		AttachToWall();
		
		PARENT::OnEmbed( xImpactPoint, xImpactNormal );
	}
}

void AVP_ServerProjectile_Mine::AttachToWall(void)
{
	// for now just turn off physics... we need to re orient at this point too
	m_uMineState = MINE_ARMING;
}

bool AVP_ServerProjectile_Mine::HandleMessage( const Asura_Message* pxMessage )
{
	if(pxMessage)
	{
		if(pxMessage->GetMsgID() == AVP_MSGID_DESTROY_MINE)
		{
			m_fStateTimer = 0.0f;
			m_fDetonationOffset = Asura_Random::GetFloat(-0.5f, 0.5f); // vary timer when forcing detonation so dont all blow up at same time
			m_uMineState = MINE_TRIGGERED;
			return true;
		}
	}
	return PARENT::HandleMessage(pxMessage);
}

/// End of file. ///

