//===========================================================================//
// File:	NonCom.cpp										
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 05/06/99  DPB  Inital base class
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive										 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "MW4Headers.hpp"

#include "NonCom.hpp"
#include "ai.hpp"

#include "MWDamageObject.hpp"
#include "adept\CameraComponent.hpp"

//#############################################################################
//###############################    NonCom    ################################
//#############################################################################

NonCom::ClassData* NonCom::DefaultData = NULL;


DWORD MechWarrior4::Executed_NonCom_Count = 0;
const int MAX_NONCOM_FRAME = 8;
const Stuff::Scalar NONCOM_FRAME_WIDTH = 256/16;
const Stuff::Scalar NONCOM_ANIMAL_FRAME_WIDTH = 256/8;
const Stuff::Scalar NONCOM_FRAME_HEIGHT = 256/8;
const Stuff::Scalar RUN_FRAME_RATE = 15.0f;			// frames per second
const Stuff::Scalar WALK_FRAME_RATE = 8.0f;			// frames per second
const Stuff::Scalar ANIMAL_FRAME_RATE = 15.0f;			// frames per second
const Stuff::Scalar RUN_SPEED = 5.99f;				// meters per second to use run frame
const Stuff::Scalar min_index_change_duration = 1.5f;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
	new ClassData(
							 NonComClassID,
							 "MechWarrior4::NonCom",
							 MWObject::DefaultData,
							 0, NULL,
							 (Entity::Factory)Make,
							 (Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
							 ExecutionStateEngine::DefaultData,
							 (Entity::GameModel::Factory)GameModel::ConstructGameModel,
							 (Entity::GameModel::Factory)GameModel::ConstructOBBStream,
							 (Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
							 (Entity::GameModel::ModelWrite)GameModel::WriteToText,
							 (Entity::GameModel::ModelSave)GameModel::SaveGameModel,
							 AnimationStateEngine::Make
							 );
	Register_Object(DefaultData);

	CUSTOM_DIRECT_ATTRIBUTE(
		NonCom,
		UVMatrix,
		UVMatrix,
		AffineMatrix4D,
		AffineMatrix4DClassID
	);		

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NonCom* NonCom::Make(CreateMessage *message,ReplicatorID *base_id)
{
	gos_PushCurrentHeap(Heap);
	NonCom *new_entity = new NonCom(DefaultData, message, base_id, NULL);
	gos_PopCurrentHeap();
	Check_Object(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NonCom::NonCom(ClassData *class_data,CreateMessage *message,ReplicatorID *base_id,ElementRenderer::Element *element):
MWObject(class_data, message, base_id, element)
{
	Check_Pointer(this);
	Check_Object(message);

	CommonCreation(message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::CommonCreation(CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);

	m_UVs[0] = Point3D (0,0,0);
	m_UVs[1] = Point3D (0,0,0);
	m_UVs[2] = Point3D (0,0,0);
	m_UVs[3] = Point3D (0,0,0);
	m_TextureID = 0;
	UVMatrix = AffineMatrix4D::Identity;
	m_AnimFrame = (objectID & 0x07);
	m_FrameTime = 0;
	m_Speed = 0;
	m_IsAnimal = false;
	m_LastPos = Point3D::Identity;
	m_LastFacing = 0;

//	executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::Respawn(Entity::CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);

	MWObject::Respawn(message);
	CreateMessage *NonCom_message = Cast_Pointer(CreateMessage *, message);
	CommonCreation(NonCom_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NonCom::~NonCom()
{
}

void NonCom::TurnOn (void)
{
	lastParameterization = gos_GetElapsedTime ();
	executionState->RequestState(ExecutionStateEngine::AlwaysExecuteState);
}

void NonCom::TurnOff (void)
{
	executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::Reuse(const CreateMessage *message,ReplicatorID *base_id)
{
	Check_Object(this);
	STOP(("Not implemented"));
	MWObject::Reuse(message, base_id);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::PreCollisionExecute(Stuff::Time till)
{
	Check_Object(this);
	Check_Object(animStateEngine);
	PRECOLLISION_LOGIC("NonCom");

	Verify(executionState->GetState() != ExecutionStateEngine::NeverExecuteState);

	Set_Statistic(Executed_NonCom_Count, Executed_NonCom_Count+1);
	DetermineUV (GetTimeSlice (till));

	MWObject::PreCollisionExecute(till);
}

void NonCom::DetermineUV (Stuff::Time slice)
{
	Check_Object (this);

	bool flip,run=false;
	Scalar gridx,gridy;								// index into grid

	int index;

	Point3D distance_to_target;
	Point3D loc (GetLocalToWorld ());
	LinearMatrix4D tempmat (GetLocalToWorld ());

	tempmat.AlignLocalAxisToWorldVector (Vector3D::Up,Y_Axis,X_Axis,Z_Axis);

	CameraComponent *camera = VideoRenderer::Instance->GetSceneCamera();

	// camera can be null on dedicated servers and in prerender.
	if (camera == NULL)
		return;

	Check_Object(camera);

	Stuff::Vector2DOf<Stuff::Scalar> lastPos2D;
	camera->ComputeCursor(&lastPos2D,m_LastPos);

	Stuff::Vector2DOf<Stuff::Scalar> curPos2D;
	camera->ComputeCursor(&curPos2D,loc);

	if (lastPos2D.x < curPos2D.x)
	{
		if (lastPos2D.y < curPos2D.y)
		{
			index = 2;
		}
		else
		{
			index = 3;
		}
	}
	else
	{
		if (lastPos2D.y < curPos2D.y)
		{
			index = 0;
		}
		else
		{
			index = 1;
		}
	}

/*
	ElementRenderer::CameraElement* camelement = camera->GetElement();

	Point3D camera_pos(camelement->GetLocalToWorld());

	if (m_LastPos == Point3D::Identity)
	{
		m_LastPos = camelement->GetLocalToWorld();
	}

	Stuff::UnitVector3D forward_vector;
	camelement->GetLocalToWorld().GetLocalForwardInWorld(&forward_vector);

	Point3D delta;
	delta.Subtract(m_LastPos,loc);

	if (!Small_Enough(delta.GetLengthSquared()))
	{
		delta.Normalize(delta);
	}

	Scalar angle = delta * forward_vector;

	bool left = (angle < 0);

	if (GetLengthSquared(camera_pos,loc) > GetLengthSquared(camera_pos,m_LastPos))
	{
		if (left == true)
		{
			index = 1;
		}
		else
		{
			index = 3;
		}
		// 1 or 3
	}
	else
	{
		if (left == true)
		{
			index = 0;
		}
		else
		{
			index = 2;
		}
		// 0 or 2
	}
*/

/*
	Stuff::UnitVector3D forward_vector;
	camelement->GetLocalToWorld().GetLocalForwardInWorld(&forward_vector);

	if (m_LastPos == Point3D::Identity)
	{
		m_LastPos = camelement->GetLocalToWorld();
	}

	Point3D delta;
	delta.Subtract(m_LastPos,loc);

	if (!Small_Enough(delta.GetLengthSquared()))
	{
		delta.Normalize(delta);
	}

	Scalar angle = delta * forward_vector;

	if ((angle <= -1) ||
		(angle >= 1))
	{
		angle = 0;
	}
	else
	{
		angle = Arccos(angle);
	}

	if (angle < 0)
	{
		angle = Two_Pi + angle;
	}

	m_LastFacing = angle;

	if (angle < Pi_Over_4)
	{
		index = 0;
	}
	else
	{
		if (angle < Pi_Over_2)
		{
			index = 1;
		}
		else
		{
			if (angle < Pi_Over_4 + Pi_Over_2)
			{
				index = 2;
			}
			else
			{
				index = 3;
			}
		}
	}
*/

	m_LastIndex = index;
	if ((m_Speed >= RUN_SPEED) &&
		(m_IsAnimal == false))
	{
		index += 4;
		run = true;
	}
	flip = false;
	Verify (index >=0);
	Verify (index <= 7);
	gridx = gridy = 0;

	if (m_IsAnimal == true)
	{
		switch (index)
		{
			case 0:
				gridx = 0; gridy = 0;
				break;
			case 1:
				gridx = 0; gridy = 1;
				break;
			case 2:
				gridx = 0; gridy = 0;
				flip = true;
				break;
			case 3:
				gridx = 0; gridy = 1;
				flip = true;
				break;
		}
	}
	else
	{
		switch (index)
		{
			case 0:
				gridx = 0; gridy = 0;
				break;
			case 1:
				gridx = 8; gridy = 0;
				break;
			case 2:
				gridx = 0; gridy = 0;
				flip = true;
				break;
			case 3:
				gridx = 8; gridy = 0;
				flip = true;
				break;

			case 4:
				gridx = 0; gridy = 1;
				break;
			case 5:
				gridx = 8; gridy = 1;
				break;
			case 6:
				gridx = 0; gridy = 1;
				flip = true;
				break;
			case 7:
				gridx = 8; gridy = 1;
				flip = true;
				break;
		}
		}

	if (m_Speed == 0)
		m_AnimFrame = (objectID & 0x07);
	else
	{
		m_FrameTime += slice;
		Scalar frame_rate;
		
		if (m_IsAnimal == true)
		{
			frame_rate = ANIMAL_FRAME_RATE;
		}
		else
		{
			frame_rate = run ? RUN_FRAME_RATE : WALK_FRAME_RATE;
		}

		while (m_FrameTime >= (1.0/frame_rate))
		{
			m_FrameTime -= (1.0/frame_rate);
			m_AnimFrame++;
		}
	}

	if ((m_IsAnimal == true) &&
		(m_TextureID == 0))
	{
		flip = (flip == true) ? false : true;
	}

	Verify (m_TextureID >=0);
	Verify (m_TextureID <= 3);
	gridy += (m_TextureID * 2);
	m_AnimFrame = m_AnimFrame & 0x07;

	UVMatrix = AffineMatrix4D::Identity;
	if (flip)
		UVMatrix(0,0) = -1.0f;
	else
		UVMatrix(0,0) = 1.0f;
	UVMatrix(1,1) = 1.0f;

	UVMatrix(3,0) = (FrameWidth() / 256.0f) * (m_AnimFrame + gridx);
	if (flip)
		UVMatrix(3,0) += (FrameWidth() / 256.0f);
	UVMatrix(3,1) = (FrameHeight() / 256.0f) * gridy;
//	UVMatrix(3,0) = m_UVs[2].x / 256.0f;
//	UVMatrix(3,1) = m_UVs[2].y / 256.0f;

#if 0
	for (i=0;i<4;i++)				// normalize values
	{
		m_UVs[i].x /= 256.0f;
		m_UVs[i].y /= 256.0f;
	}
#endif

	m_LastPos = loc;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::ReactToDestruction(int damage_mode, int damage_type)
{
	Check_Object(this);

	if (!IsDestroyed())
	{
		if (m_AI)
		{
			Check_Object(m_AI);
			m_AI->Die();
		}

		SetDestroyedFlag(damage_mode);

		if (damage_type != SplashDamageType)
			DealSplashDamage();

		const GameModel *model = GetGameModel();
		Check_Object(model);

		CreateEffect(model->destroyedEffectResource, (Point3D)GetLocalToWorld());
		if(!m_deathEntity.GetCurrent())
		{
			Entity *death_entity = CreateStaticHermitEntity(model->deathEntityResource);
			if(death_entity) 
			{
				m_deathEntity.Remove();
				m_deathEntity.Add(death_entity);
			}
		}	

		RemoveFromExecution();
	}

	Entity::ReactToDestruction(damage_mode, damage_type);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void NonCom::ReactToHit(const Entity__TakeDamageMessage *message,Adept::DamageObject *part_hit)
{
	Check_Object(this);
	Check_Object(message);

	MWObject::ReactToHit(message,part_hit);

}

void NonCom::SetAsAnimal()
{
	m_IsAnimal = true;
}

Scalar NonCom::FrameWidth() const
{
	if (m_IsAnimal == true)
	{
		return (NONCOM_ANIMAL_FRAME_WIDTH);
	}

	return (NONCOM_FRAME_WIDTH);
}

Scalar NonCom::FrameHeight() const
{
	return (NONCOM_FRAME_HEIGHT);
}
