//===========================================================================//
// File:	Effect.cpp																		  //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//  2/8/99   DPB  Created File
//---------------------------------------------------------------------------//
// Copyright (C) 1995-97, Virtual World Entertainment, Inc.                  //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "AdeptHeaders.hpp"

#include "Effect.hpp"
#include "EntityClassData.hpp"
#include "Tool.hpp"
#include "RendererManager.hpp"
#include "Site.hpp"

#include <gosFX\gosFX.hpp>
#include "EntityManager.hpp"

//#############################################################################
//##################    Effect::ExecutionStateEngine    #######################
//#############################################################################

const StateEngine::StateEntry
	Effect::ExecutionStateEngine::StateEntries[]=
{
	STATE_ENTRY(Effect__ExecutionStateEngine, Stopped),
	STATE_ENTRY(Effect__ExecutionStateEngine, Stopping),
	STATE_ENTRY(Effect__ExecutionStateEngine, Running),
	STATE_ENTRY(Effect__ExecutionStateEngine, Killing)
};

Effect::ExecutionStateEngine::ClassData*
	Effect::ExecutionStateEngine::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::ExecutionStateEngine::InitializeClass()
{
	Check_Object(StateEngine::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			Effect__ExecutionStateEngineClassID,
			"Adept::Effect::ExecutionStateEngine",
			BaseClass::DefaultData,
			ELEMENTS(StateEntries), StateEntries,
			(Entity::ExecutionStateEngine::Factory)Make,
			(Entity::ExecutionStateEngine::FactoryRequest::Factory)
				&FactoryRequest::ConstructFactoryRequest
		);
	Register_Object(DefaultData);
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Effect::ExecutionStateEngine*
	Effect::ExecutionStateEngine::Make(
		Effect *effect,
		FactoryRequest *request
	)
{
	Check_Object(effect);
	Check_Object(request);
	Effect::ExecutionStateEngine* engine =
		new Effect::ExecutionStateEngine(DefaultData, effect, request);
	return engine;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	Effect::ExecutionStateEngine::RequestState(
		int new_state,
		void* data
	)
{
	Check_Object(this);
	Check_Object(owningEntity);

	//
	//----------------------------------------------
	// Now, switch the state and tickle the watchers
	//----------------------------------------------
	//

	switch (StateEngine::RequestState(new_state, data))
	{
	
	case StoppingState:
		{
			//
			//--------------------------------------------------------------------
			//We need to tell both our webs that we are going through a Stop state
			//--------------------------------------------------------------------
			//
			for (unsigned i=1; i<RendererManager::RendererTypeCount; ++i)
			{
				RendererComponentWeb *web = owningEntity->GetComponentWeb(i);
				if (web)
				{
					Check_Object(web);
					web->SendCommand(Renderer::StopEffectCommandID);
				}
			}
			break;
		}	
	case RunningState:
		{
			//
			//--------------------------------------------------------------------
			//We need to tell both our webs that we are going through a Stop state
			//--------------------------------------------------------------------
			//
			for (unsigned i=1; i<RendererManager::RendererTypeCount; ++i)
			{
				RendererComponentWeb *web = owningEntity->GetComponentWeb(i);
				if (web)
				{
					Check_Object(web);
					web->SendCommand(Renderer::StartEffectCommandID);
				}
			}
			break;
		}	
	}

	return currentState;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::ExecutionStateEngine::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

//#############################################################################
//#############################    Effect    ##################################
//#############################################################################

Effect::ClassData*
	Effect::DefaultData = NULL;

static DWORD
	Effect_Entities_Count;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			EffectClassID,
			"Adept::Effect",
			BaseClass::DefaultData,
			0, NULL,
			(Entity::Factory)Make,
			(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
			ExecutionStateEngine::DefaultData,
			(Entity::GameModel::Factory)GameModel::ConstructGameModel,
			NULL,
			(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
			(Entity::GameModel::ModelWrite)GameModel::WriteToText,
			(Entity::GameModel::ModelSave)GameModel::SaveGameModel
		);
	Register_Object(DefaultData);

	
	CUSTOM_DIRECT_ATTRIBUTE(
		Effect,
		VideoStatus,
		videoStatus,
		bool,
		BoolClassID
	);

	CUSTOM_DIRECT_ATTRIBUTE(
		Effect,
		AudioStatus,
		audioStatus,
		int,
		IntClassID
	);
	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		Effect__GameModel,
		StartsRunning,
		startsRunning,
		bool,
		BoolClassID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		Effect__GameModel,
		Translation,
		translation,
		Point3D
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		Effect__GameModel,
		Rotation,
		rotation,
		UnitQuaternion
	);

	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		Effect__GameModel,
		KillOnAttachedEntityDeath,
		killOnAttachedEntityDeath,
		bool,
		BoolClassID
	);

	Effect_Entities_Count = 0;
#if !defined(NO_STATS)
	AddStatistic("Effect entities Executed", "entities", gos_DWORD, &Effect_Entities_Count, Stat_AutoReset);
#endif
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Effect*
	Effect::Make(
		const CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(EntityManager::GetInstance());
	Effect *new_entity;
	Entity *entity = EntityManager::GetInstance()->RequestFromArmory(message->dataListID);
	if (entity)
	{
		new_entity = Cast_Object(Effect*, entity);
		new_entity->Reuse(message, base_id);
	}
	else
	{
		gos_PushCurrentHeap(s_Heap);
		new_entity = new Effect(DefaultData, message, base_id, NULL);
		gos_PopCurrentHeap();
	}
	Check_Object(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Effect::Effect(
	ClassData *class_data,
	const CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Mover(class_data, message, base_id, element),
	followEntity(this),
	followSite(this)
{
	Check_Pointer(this);

	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	audioStatus = 0;
	videoStatus = false;
	if (model->startsRunning)
		executionState->RequestState(ExecutionStateEngine::RunningState);
	else
		executionState->RequestState(ExecutionStateEngine::StoppedState);

	effectOffset = LinearMatrix4D::Identity;

	switch(model->rotationType)
	{
	case Effect__GameModel::AgainstMotionRotation:
		{
			YawPitchRoll effect_rotation_offset(gosFX::Effect_Against_Motion);
			effectOffset.BuildRotation(effect_rotation_offset);
			break;
		}
	case Effect__GameModel::IntoMotionRotation:
		{
			YawPitchRoll effect_rotation_offset(gosFX::Effect_Into_Motion);
			effectOffset.BuildRotation(effect_rotation_offset);
			break;
		}
	case Effect__GameModel::UseRotation:
		{
			effectOffset = model->rotation;
			break;
		}
	}
			
	effectOffset.BuildTranslation(model->translation);

	#if defined (_ARMOR)
		iAmDead = false;
	#endif

//	SPEW(("daberger", "CREATING EFFECT %x at time %f", this, gos_GetElapsedTime()));

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Effect::~Effect()
{
	DESTRUCTOR("Effect");

//	SPEW(("daberger", "DELETING EFFECT %x at time %f", this, gos_GetElapsedTime()));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::Reuse(
		const CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Pointer(this);
	Check_Object(message);
	BaseClass::Reuse(message, base_id);

	Check_Pointer(this);

	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	audioStatus = 0;
	videoStatus = false;
	if (model->startsRunning)
		executionState->RequestState(ExecutionStateEngine::RunningState);
	else
		executionState->RequestState(ExecutionStateEngine::StoppedState);

	effectOffset = LinearMatrix4D::Identity;

	switch(model->rotationType)
	{
	case Effect__GameModel::AgainstMotionRotation:
		{
			YawPitchRoll effect_rotation_offset(gosFX::Effect_Against_Motion);
			effectOffset.BuildRotation(effect_rotation_offset);
			break;
		}
	case Effect__GameModel::IntoMotionRotation:
		{
			YawPitchRoll effect_rotation_offset(gosFX::Effect_Into_Motion);
			effectOffset.BuildRotation(effect_rotation_offset);
			break;
		}
	case Effect__GameModel::UseRotation:
		{
			effectOffset = model->rotation;
			break;
		}
	}
			
	effectOffset.BuildTranslation(model->translation);

	#if defined (_ARMOR)
		iAmDead = false;
	#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	Effect::GetExecutionSlot()
{
	Check_Object(this);
	return EffectExecutionSlot;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::PreCollisionExecute(Time till)
{
	Check_Object(this);
	PRECOLLISION_LOGIC("Effect");
	Set_Statistic(Effect_Entities_Count, Effect_Entities_Count+1);
	UsePostCollision();
	BaseClass::PreCollisionExecute(till);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#if defined(LAB_ONLY)
	void Effect::SyncMatrices(bool update_matrix)
	{
		SYNC_LOGIC("Effect");
		BaseClass::SyncMatrices(update_matrix);
	}
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::PostCollisionExecute(Time till)
{
	Check_Object(this);

	POSTCOLLISION_LOGIC("Effect");
	if (IsFollowing())
	{
		if(followEntity.GetCurrent())
			PlaceOnEntity();
		else if(followSite.GetCurrent())
			PlaceOnSite();
		else if (executionState->GetState() != ExecutionStateEngine::StoppingState)
			executionState->RequestState(ExecutionStateEngine::StoppingState);
	}

	BaseClass::PostCollisionExecute(till);

	#if defined (_ARMOR)
		Verify(!iAmDead);
	#endif

	Check_Object(executionState);
	switch(executionState->GetState())
	{
	case ExecutionStateEngine::StoppingState:
		{
			if (!(videoStatus || audioStatus))
			{
				#if defined (_ARMOR)
					iAmDead = true;
				#endif
				executionState->RequestState(ExecutionStateEngine::StoppedState);
				SentenceToDeathRow();
			}
			break;
		}
	case ExecutionStateEngine::RunningState:
		{
			//
			//--------------------------------------------------------------------
			//If the video and audio components are looping this will never happen
			//It is just for the one time play effects
			//--------------------------------------------------------------------
			//
			if (!(videoStatus || audioStatus))
			{
				executionState->RequestState(ExecutionStateEngine::StoppingState);
			}
			break;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

void
	Effect::BecomeInteresting(bool render_me)
{
	LOAD_LOGIC("Become Interesting::Effect");
	if (Application::GetInstance()->serverFlag && Application::GetInstance()->m_localMissionParameters->m_runDedicated)
	{
		videoStatus = false;
		audioStatus = 0;
		SetInterestMask(SimulationInterestLevel);
	}
	else
	{
		const GameModel *model = GetGameModel();
		Check_Object(model);
		if (model->startsRunning)
			executionState->RequestState(ExecutionStateEngine::RunningState);
		else
			executionState->RequestState(ExecutionStateEngine::StoppedState);
		BaseClass::BecomeInteresting(render_me);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::SetFollowEntity(Entity *entity)
{
	Check_Object(this);
	Check_Object(entity);

	SetFollowing();
	followEntity.Remove();
	followEntity.Add(entity);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::SetFollowEntity(Site *site)
{
	Check_Object(this);
	Check_Object(site);

	SetFollowing();
	followSite.Remove();
	followSite.Add(site);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::PlaceOnEntity(LinearMatrix4D offsetOnEntity)
{
	Check_Object(this);

	Entity *entity;
	entity = followEntity.GetCurrent();
	Check_Object(entity);

	if ((entity->IsDestroyed()) && (GetGameModel()->killOnAttachedEntityDeath))
	{
		Check_Object(executionState);
		SentenceToDeathRow();
	}
	else
	{
		//
		// Rather than add additional data to the object I'm going tack onto the
		// current effect offset.
		//

		LinearMatrix4D effect_local_to_parent;
		effect_local_to_parent.Multiply(offsetOnEntity, effectOffset);
		effectOffset = effect_local_to_parent;

		LinearMatrix4D new_position;
		new_position.Multiply(effectOffset, entity->GetLocalToWorld());
		UnitVector3D direction_vector;
		new_position.GetLocalForwardInWorld(&direction_vector);
		SetNewLocalToParent(new_position);
		SyncMatrices(true);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::PlaceOnEntity()
{
	Check_Object(this);

	Entity *entity;
	entity = followEntity.GetCurrent();
	Check_Object(entity);

	if ((entity->IsDestroyed()) && (GetGameModel()->killOnAttachedEntityDeath))
	{
		Check_Object(executionState);
		SentenceToDeathRow();
	}
	else
	{
		LinearMatrix4D new_position;
		new_position.Multiply(effectOffset, entity->GetLocalToWorld());
		UnitVector3D direction_vector;
		new_position.GetLocalForwardInWorld(&direction_vector);
		SetNewLocalToParent(new_position);
		SyncMatrices(true);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::PlaceOnSite()
{
	Check_Object(this);

	Site *site;
	site = followSite.GetCurrent();
	Check_Object(site);

	LinearMatrix4D new_position;
	new_position.Multiply(effectOffset, site->GetLocalToWorld());
	UnitVector3D direction_vector;
	new_position.GetLocalForwardInWorld(&direction_vector);
	SetNewLocalToParent(new_position);
	SyncMatrices(true);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Effect::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

