/*!
	@project Rune
	@file RuFusion_Mime.cpp

	Copyright (c) 2004-2005 All rights reserved

	@author John Tang
	@date 2005/07/22
*/

#include "../Fusion/RuFusion_Mime.h"
#include "../Helper/RuHelper_Entity.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuFusion_Mime_Template, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuFusion_Mime_Template, "CRuFusion_Mime_Template", "IRuObject")
ruCLASSGUID_DEFINE(CRuFusion_Mime_Template, 0x2DBA8869, 0xE7874df9, 0x987EE60C, 0xFC1E05EC)

ruRTTI_DEFINE_SUBCLASS(CRuFusion_Mime, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuFusion_Mime, "CRuFusion_Mime", "IRuObject")
ruCLASSGUID_DEFINE(CRuFusion_Mime, 0xF7962690, 0x962F4eb9, 0x985BA20D, 0x8680734D)

// ************************************************************************************************************************************************************

CRuFusion_Mime_Template::CRuFusion_Mime_ActionDescriptor::CRuFusion_Mime_ActionDescriptor()
:	m_actionPriority(0),
	m_actionType(ruFUSION_MIME_ACTIONTYPE_NORMAL)
{
}

CRuFusion_Mime_Template::CRuFusion_Mime_ActionDescriptor::~CRuFusion_Mime_ActionDescriptor()
{
	for(INT32 i = 0; i < m_motionNames.Count(); ++i)
	{
		delete[] m_motionNames[i];
	}

	m_motionNames.Clear();
}

void CRuFusion_Mime_Template::CRuFusion_Mime_ActionDescriptor::SetActionType(CRuFusion_Mime_ActionType actionType)
{
	m_actionType = actionType;
}

INT32 CRuFusion_Mime_Template::CRuFusion_Mime_ActionDescriptor::AddMotion(const char *motionName, REAL motionProbability)
{
	char *newMotionName = ruNEW char [strlen(motionName) + 1];
	memcpy(newMotionName, motionName, sizeof(char) * (strlen(motionName) + 1));

	m_motionNames.Add(newMotionName);
	m_motionProbabilities.Add(motionProbability);

	return m_motionNames.Count() - 1;
}

INT32 CRuFusion_Mime_Template::CRuFusion_Mime_ActionDescriptor::GetNumMotions() const
{
	return m_motionNames.Count();
}

const char *CRuFusion_Mime_Template::CRuFusion_Mime_ActionDescriptor::GetMotion(INT32 motionIndex) const
{
	return m_motionNames[motionIndex];
}

REAL CRuFusion_Mime_Template::CRuFusion_Mime_ActionDescriptor::GetMotionProbability(INT32 motionIndex) const
{
	return m_motionProbabilities[motionIndex];
}

// ************************************************************************************************************************************************************

CRuFusion_Mime_Template::CRuFusion_Mime_Template()
{
	m_actionDescriptors = ruNEW CRuFusion_Mime_ActionDescriptor [ruFUSION_MIME_ACTION_COUNT];

	// temporary
	m_actionDescriptors[ruFUSION_MIME_IDLE_STAND].m_actionPriority = 0;
	m_actionDescriptors[ruFUSION_MIME_IDLE_STAND].AddMotion("stand_idle01", 10.0f);
	m_actionDescriptors[ruFUSION_MIME_IDLE_STAND].AddMotion("idle02", 1.0f);
	m_actionDescriptors[ruFUSION_MIME_IDLE_STAND].AddMotion("idle03", 1.0f);
	m_actionDescriptors[ruFUSION_MIME_IDLE_STAND].AddMotion("idle04", 1.0f);

	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_1H].m_actionPriority = 0;
	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_1H].AddMotion("1h_idle");

	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_2H].m_actionPriority = 0;
	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_2H].AddMotion("2h_idle");

	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_BOW].m_actionPriority = 0;
	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_BOW].AddMotion("bow_idle");

	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_GUN].m_actionPriority = 0;
	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_GUN].AddMotion("gun_idle");

	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_POLEARM].m_actionPriority = 0;
	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_POLEARM].AddMotion("polearm_idle");

	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_UNARMED].m_actionPriority = 0;
	m_actionDescriptors[ruFUSION_MIME_IDLE_COMBAT_UNARMED].AddMotion("unarmed_idle");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_1H].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_1H].AddMotion("1h_attack01");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_1H].AddMotion("1h_attack02");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_1H].AddMotion("1h_attack03");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_2H].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_2H].AddMotion("2h_attack01");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_2H].AddMotion("2h_attack02");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_2H].AddMotion("2h_attack03");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_BOW_BEGIN].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_BOW_BEGIN].AddMotion("bow_ready");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_BOW_LOOP].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_BOW_LOOP].AddMotion("bow_channel");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_BOW_END].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_BOW_END].AddMotion("bow_release");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_GUN_BEGIN].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_GUN_BEGIN].AddMotion("gun_ready");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_GUN_LOOP].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_GUN_LOOP].AddMotion("gun_channel");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_GUN_END].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_GUN_END].AddMotion("gun_release");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_POLEARM].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_POLEARM].AddMotion("polearm_attack01");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_POLEARM].AddMotion("polearm_attack02");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_POLEARM].AddMotion("polearm_attack03");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_UNARMED].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_UNARMED].AddMotion("unarmed_attack01");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_UNARMED].AddMotion("unarmed_attack02");
	m_actionDescriptors[ruFUSION_MIME_ATTACK_UNARMED].AddMotion("unarmed_attack03");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_OH].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_OH].AddMotion("oh_attack01");

	m_actionDescriptors[ruFUSION_MIME_ATTACK_OH_PIERCE].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_ATTACK_OH_PIERCE].AddMotion("oh_pierce01");

	m_actionDescriptors[ruFUSION_MIME_BUFF_BEGIN].m_actionPriority = 10;

	m_actionDescriptors[ruFUSION_MIME_BUFF_LOOP].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_BUFF_LOOP].AddMotion("casting_delay");

	m_actionDescriptors[ruFUSION_MIME_BUFF_END].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_BUFF_END].AddMotion("casting01");

	m_actionDescriptors[ruFUSION_MIME_BUFF_INSTANT].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_BUFF_INSTANT].AddMotion("casting01");

	m_actionDescriptors[ruFUSION_MIME_CAST_BEGIN].m_actionPriority = 10;

	m_actionDescriptors[ruFUSION_MIME_CAST_LOOP].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_CAST_LOOP].AddMotion("casting_delay");

	m_actionDescriptors[ruFUSION_MIME_CAST_END].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_CAST_END].AddMotion("casting02");

	m_actionDescriptors[ruFUSION_MIME_CAST_INSTANT].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_CAST_INSTANT].AddMotion("casting02");

	m_actionDescriptors[ruFUSION_MIME_CHANNEL_BEGIN].m_actionPriority = 10;

	m_actionDescriptors[ruFUSION_MIME_CHANNEL_LOOP].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_CHANNEL_LOOP].AddMotion("continuous_cast");

	m_actionDescriptors[ruFUSION_MIME_CHANNEL_END].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_CHANNEL_END].AddMotion("casting01");

	m_actionDescriptors[ruFUSION_MIME_CHANNEL_INSTANT].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_CHANNEL_INSTANT].AddMotion("casting01");

	m_actionDescriptors[ruFUSION_MIME_RUN_FORWARD].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_RUN_FORWARD].AddMotion("run_forward");

	m_actionDescriptors[ruFUSION_MIME_RUN_BACKWARD].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_RUN_BACKWARD].AddMotion("run_back");

	m_actionDescriptors[ruFUSION_MIME_WALK_FORWARD].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_WALK_FORWARD].AddMotion("walk_forward");

	m_actionDescriptors[ruFUSION_MIME_SWIM_FORWARD].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_SWIM_FORWARD].AddMotion("swim_forward");

	m_actionDescriptors[ruFUSION_MIME_SWIM_BACKWARD].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_SWIM_BACKWARD].AddMotion("swim_back");

	m_actionDescriptors[ruFUSION_MIME_SWIM_LEFT].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_SWIM_LEFT].AddMotion("swim_left");

	m_actionDescriptors[ruFUSION_MIME_SWIM_RIGHT].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_SWIM_RIGHT].AddMotion("swim_right");

	m_actionDescriptors[ruFUSION_MIME_SWIM_IDLE].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_SWIM_IDLE].AddMotion("swim_idle");

	m_actionDescriptors[ruFUSION_MINE_HOLSTER].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MINE_HOLSTER].AddMotion("1h_weapon_holster");

	m_actionDescriptors[ruFUSION_MIME_UNHOLSTER].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_UNHOLSTER].AddMotion("1h_weapon_unholster");

	m_actionDescriptors[ruFUSION_MINE_HOLSTER_2H].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MINE_HOLSTER_2H].AddMotion("2h_weapon_holster");

	m_actionDescriptors[ruFUSION_MIME_UNHOLSTER_2H].m_actionPriority = 20;
	m_actionDescriptors[ruFUSION_MIME_UNHOLSTER_2H].AddMotion("2h_weapon_unholster");

	/************************************************************************/
	/* EMOTES                                                               */
	/************************************************************************/

	m_actionDescriptors[ruFUSION_MIME_EMOTE_ANGRY].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_ANGRY].AddMotion("emote_angry");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_APPLAUSE].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_APPLAUSE].AddMotion("emote_applause");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_APPROVAL].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_APPROVAL].AddMotion("emote_approval");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_BEG].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_BEG].AddMotion("emote_beg");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_CRY].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_CRY].AddMotion("emote_cry");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_HEAD_NOD].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_HEAD_NOD].AddMotion("emote_head_nod");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_HEAD_SHAKE].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_HEAD_SHAKE].AddMotion("emote_head_shake");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_LAUGH].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_LAUGH].AddMotion("emote_laugh");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_POINT].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_POINT].AddMotion("emote_point");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_PROVOKE].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_PROVOKE].AddMotion("emote_provoke");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_SALUTE].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_SALUTE].AddMotion("emote_salute");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_SPEAK].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_SPEAK].AddMotion("emote_speak01");
	m_actionDescriptors[ruFUSION_MIME_EMOTE_SPEAK].AddMotion("emote_speak02");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_SURRENDER].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_SURRENDER].AddMotion("emote_surrender");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_THREATEN].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_THREATEN].AddMotion("emote_threaten");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_VICTORY].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_VICTORY].AddMotion("emote_victory");

	m_actionDescriptors[ruFUSION_MIME_EMOTE_WAVE].m_actionPriority = 10;
	m_actionDescriptors[ruFUSION_MIME_EMOTE_WAVE].AddMotion("emote_wave");

	/************************************************************************/
	/* PERSISTENT STATES                                                    */
	/************************************************************************/

	m_actionDescriptors[ruFUSION_MIME_JUMP_DOWN].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_JUMP_DOWN].AddMotion("jump_down");

	m_actionDescriptors[ruFUSION_MIME_JUMP_LOOP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_JUMP_LOOP].AddMotion("jump_loop");

	m_actionDescriptors[ruFUSION_MIME_JUMP_UP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_JUMP_UP].AddMotion("jump_up");

	m_actionDescriptors[ruFUSION_MIME_SLEEP_DOWN].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_SLEEP_DOWN].AddMotion("emote_sleep_down");

	m_actionDescriptors[ruFUSION_MIME_SLEEP_LOOP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_SLEEP_LOOP].AddMotion("emote_sleep_loop");

	m_actionDescriptors[ruFUSION_MIME_SLEEP_UP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_SLEEP_UP].AddMotion("emote_sleep_up");

	m_actionDescriptors[ruFUSION_MIME_SIT_DOWN].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_SIT_DOWN].AddMotion("sit");

	m_actionDescriptors[ruFUSION_MIME_SIT_LOOP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_SIT_LOOP].AddMotion("sit_idle");

	m_actionDescriptors[ruFUSION_MIME_SIT_UP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_SIT_UP].AddMotion("stand_up");

	m_actionDescriptors[ruFUSION_MIME_DEATH_DOWN].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_DEATH_DOWN].AddMotion("death");

	m_actionDescriptors[ruFUSION_MIME_DEATH_LOOP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_DEATH_LOOP].AddMotion("dead");

	m_actionDescriptors[ruFUSION_MIME_DEATH_UP].m_actionPriority = 30;
	m_actionDescriptors[ruFUSION_MIME_DEATH_UP].AddMotion("revive");

	/************************************************************************/
	/* BONE SETUP                                                           */
	/************************************************************************/

	m_bodyBones.Add(CRuFusion_Mime_Bone_R(1, 1.0f));

	m_torsoBones.Add(CRuFusion_Mime_Bone_R(2002, 0.3f));
	m_torsoBones.Add(CRuFusion_Mime_Bone_R(2003, 0.3f));
	m_torsoBones.Add(CRuFusion_Mime_Bone_R(3001, 0.4f));
}

CRuFusion_Mime_Template::~CRuFusion_Mime_Template()
{
	delete[] m_actionDescriptors;
}

BOOL CRuFusion_Mime_Template::LoadSettings(IRuStream *settingsStream)
{
	// Allocate buffer large enough to contain the entire settings stream
	DWORD bufferSize = settingsStream->GetStreamSize() - settingsStream->GetPosition();
	char *settingsBuffer = ruNEW char [bufferSize];

	// Read entire settings stream into memory
	settingsStream->Read(settingsBuffer, bufferSize);

	// Tokenize based on carriage-return and line-feed
	CRuStringTokenizer strTokenizer;
	strTokenizer.Tokenize(settingsBuffer, "\n\r");

	// Process each token
	for(INT32 i = 0; i < strTokenizer.GetNumTokens(); ++i)
	{
		// Get token and process
		CRuString token = strTokenizer.GetToken(i);
		if(token.GetLength() > 0)
		{
			// Tokenize based on space and tab
			CRuStringTokenizer lineTokenizer;
			lineTokenizer.Tokenize(token, " \t");

			// Read action string

			// Read action priority

			// Read motion string until no tokens remain
		}
	}

	// Release resources
	delete[] settingsBuffer;

	return TRUE;
}

BOOL CRuFusion_Mime_Template::SaveSettings(IRuStream *settingsStream)
{
	return TRUE;
}

INT32 CRuFusion_Mime_Template::GetActionPriority(CRuFusion_Mime_Action action)
{
	return m_actionDescriptors[action].m_actionPriority;
}

CRuFusion_Mime_ActionType CRuFusion_Mime_Template::GetActionType(CRuFusion_Mime_Action action)
{
	return m_actionDescriptors[action].m_actionType;
}

const char *CRuFusion_Mime_Template::GetMotion(CRuFusion_Mime_Action action)
{
	if(m_actionDescriptors[action].GetNumMotions() > 0)
	{
		// Generate the motion index to use
		INT32 motionIndex = m_randGen.GenerateUINT32() % m_actionDescriptors[action].GetNumMotions();

		// Return the motion name
		return m_actionDescriptors[action].GetMotion(motionIndex);
	}

	return "";
}

INT32 CRuFusion_Mime_Template::GetNumBodyBones() const
{
	return m_bodyBones.Count();
}

INT32 CRuFusion_Mime_Template::GetBodyBoneID(INT32 index) const
{
	return m_bodyBones[index].m_boneID;
}

REAL CRuFusion_Mime_Template::GetBodyBoneWeight(INT32 index) const
{
	return m_bodyBones[index].m_weight;
}

INT32 CRuFusion_Mime_Template::GetNumTorsoBones() const
{
	return m_torsoBones.Count();
}

INT32 CRuFusion_Mime_Template::GetTorsoBoneID(INT32 index) const
{
	return m_torsoBones[index].m_boneID;
}

REAL CRuFusion_Mime_Template::GetTorsoBoneWeight(INT32 index) const
{
	return m_torsoBones[index].m_weight;
}

// ************************************************************************************************************************************************************

CRuFusion_Mime::CRuFusion_Mime()
:	m_mimeTemplate(NULL),
	m_defaultChannel(ruACTMOTIONCHANNEL_DEFAULT),
	m_auxiliaryChannel(ruACTMOTIONCHANNEL_BODY_FULL),
	m_backgroundChannel(ruACTMOTIONCHANNEL_BODY_LOWER),
	m_foregroundChannel(ruACTMOTIONCHANNEL_BODY_UPPER),
	m_userChannel(ruACTMOTIONCHANNEL_BODY_AUXILIARY),
	m_blendDelay(0.2f),
	m_targetEntity(NULL),
	m_targetEntitySupportsChannels(TRUE),
	m_backgroundAction(ruFUSION_MIME_NONE),
	m_scheduledBackgroundAction(ruFUSION_MIME_NONE),
	m_backgroundMotionType(ruFUSION_MIME_MOTIONTYPE_NORMAL),
	m_scheduledBackgroundMotionType(ruFUSION_MIME_MOTIONTYPE_NORMAL),
	m_foregroundAction(ruFUSION_MIME_NONE),
	m_scheduledForegroundAction(ruFUSION_MIME_NONE),
	m_foregroundMotionType(ruFUSION_MIME_MOTIONTYPE_NORMAL),
	m_scheduledForegroundMotionType(ruFUSION_MIME_MOTIONTYPE_NORMAL),
	m_body_YawOffset(0.0f),
	m_body_HeadingOffset(0.0f),
	m_body_BoneController(NULL),
	m_torso_HeadingOffset(0.0f),
	m_torso_BoneController(NULL),
	m_backgroundMotion(""),
	m_foregroundMotion(""),
	m_defaultForwardSpeed(61.0f),
	m_forwardSpeed(33.0f),
	m_defaultBackwardSpeed(61.0f),
	m_backwardSpeed(33.0f)
{
}

CRuFusion_Mime::~CRuFusion_Mime()
{
	ruSAFE_RELEASE(m_mimeTemplate);
	ruSAFE_RELEASE(m_targetEntity);
	ruSAFE_RELEASE(m_body_BoneController);
	ruSAFE_RELEASE(m_torso_BoneController);
}

CRuEntity *CRuFusion_Mime::GetTarget() const
{
	return m_targetEntity;
}

BOOL CRuFusion_Mime::SetTarget(CRuEntity *targetEntity)
{
	if(targetEntity)
	{
		ruSAFE_RELEASE(m_targetEntity);
		ruSAFE_RELEASE(m_body_BoneController);
		ruSAFE_RELEASE(m_torso_BoneController);

		m_targetEntity = targetEntity;
		m_targetEntity->AddRef();

		CRuACTEntity *actEntity = RuEntity_FindFirstACTEntity(m_targetEntity);

		if(actEntity)
		{
			// Create bone controller
			m_body_BoneController = ruNEW CRuController_Bone();

			// Target
			m_body_BoneController->SetTarget(RuEntity_FindFirstFrameHierarchy(m_targetEntity));
			m_body_BoneController->SetBasis(CRuVector3(1.0f, 0.0f, 0.0f), CRuVector3(0.0f, 1.0f, 0.0f), CRuVector3(0.0f, 0.0f, 1.0f));

			// Create bone controller
			m_torso_BoneController = ruNEW CRuController_Bone();

			// Target
			m_torso_BoneController->SetTarget(RuEntity_FindFirstFrameHierarchy(m_targetEntity));

			// Attach bone controllers to the ACT
			actEntity->AddEntity(m_body_BoneController);
			actEntity->AddEntity(m_torso_BoneController);

			// Create animation compensators
			actEntity->CreateAnimationCompensator(2, 4, 2002, 0.4f);
			actEntity->CreateAnimationCompensator(2, 4, 2003, 0.3f);

			if(RuACT_SupportsMotionChannel(actEntity, m_backgroundChannel) == FALSE)
			{
				m_targetEntitySupportsChannels = FALSE;
			}
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuFusion_Mime::SetMimeTemplate(CRuFusion_Mime_Template *mimeTemplate)
{
	if(mimeTemplate)
	{
		ruSAFE_RELEASE(m_mimeTemplate);

		m_mimeTemplate = mimeTemplate;
		m_mimeTemplate->AddRef();

		return TRUE;
	}

	return FALSE;
}

CRuFusion_Mime_Action CRuFusion_Mime::GetBackgroundAction()
{
	return m_backgroundAction;
}

BOOL CRuFusion_Mime::SetBackgroundAction(CRuFusion_Mime_Action action, CRuFusion_Mime_MotionType motionType)
{
	if(m_backgroundAction != action)
	{
		if(IsImmediateAction(m_backgroundAction, m_backgroundMotionType, m_scheduledForegroundAction, m_scheduledBackgroundMotionType))
		{
			m_backgroundAction = action;
			m_backgroundMotionType = motionType;
			m_backgroundMotion = m_mimeTemplate->GetMotion(m_backgroundAction);
		}
		else
		{
			m_scheduledBackgroundAction = action;
			m_scheduledBackgroundMotionType = motionType;
		}
	}

	return TRUE;
}

CRuFusion_Mime_Action CRuFusion_Mime::GetForegroundAction()
{
	return m_foregroundAction;
}

BOOL CRuFusion_Mime::SetForegroundAction(CRuFusion_Mime_Action action, CRuFusion_Mime_MotionType motionType)
{
	if(m_foregroundAction != action)
	{
		if(IsImmediateAction(m_foregroundAction, m_foregroundMotionType, m_scheduledForegroundAction, m_scheduledForegroundMotionType))
		{
			m_foregroundAction = action;
			m_foregroundMotionType = motionType;
			m_foregroundMotion = m_mimeTemplate->GetMotion(m_foregroundAction);
		}
		else
		{
			m_scheduledForegroundAction = action;
			m_scheduledForegroundMotionType = motionType;
		}
	}

	return TRUE;
}

BOOL CRuFusion_Mime::SetBodyYawOffset(REAL yawOffset)
{
	m_body_YawOffset = yawOffset;

	return TRUE;
}

BOOL CRuFusion_Mime::SetBodyHeadingOffset(REAL headingOffset)
{
	m_body_HeadingOffset = headingOffset;

	return TRUE;
}

BOOL CRuFusion_Mime::SetTorsoHeadingOffset(REAL headingOffset)
{
	m_torso_HeadingOffset = headingOffset;

	return TRUE;
}

BOOL CRuFusion_Mime::SetExpectedMotionScale(REAL expectedMotionScale)
{
	m_expectedMotionScale = expectedMotionScale;

	return TRUE;
}

BOOL CRuFusion_Mime::SetDefaultForwardSpeed(REAL defaultForwardSpeed)
{
	m_defaultForwardSpeed = defaultForwardSpeed;

	return TRUE;
}

BOOL CRuFusion_Mime::SetForwardSpeed(REAL forwardSpeed)
{
	m_forwardSpeed = forwardSpeed;

	return TRUE;
}

BOOL CRuFusion_Mime::SetDefaultBackwardSpeed(REAL defaultBackwardSpeed)
{
	m_defaultBackwardSpeed = defaultBackwardSpeed;

	return TRUE;
}

BOOL CRuFusion_Mime::SetBackwardSpeed(REAL backwardSpeed)
{
	m_backwardSpeed = backwardSpeed;

	return TRUE;
}

void CRuFusion_Mime::Update(REAL elapsedTime)
{
	if(m_targetEntity == NULL)
		return;

	Update_WithChannel(elapsedTime);

/*
	if(m_targetEntitySupportsChannels)
	{
		Update_WithChannel(elapsedTime);
	}
	else
	{
		Update_WithoutChannel(elapsedTime);
	}
*/
	// Update body twist
	if(m_body_BoneController)
	{
		CRuQuaternion xRot, yRot;
		xRot.FromAngleAxis(CRuVector3(1.0f, 0.0f, 0.0f), m_body_YawOffset);
		yRot.FromAngleAxis(CRuVector3(0.0f, 1.0f, 0.0f), m_body_HeadingOffset);

		CRuVector3 originHeading = CRuVector3(0.0f, 0.0f, 1.0f);
		CRuVector3 targetHeading = originHeading * yRot.GetMatrix() * xRot.GetMatrix();

		for(INT32 i = 0; i < m_mimeTemplate->GetNumBodyBones(); ++i)
		{
			m_body_BoneController->SetTransformTarget(m_mimeTemplate->GetBodyBoneID(i), CRuVector3(1.0f, 1.0f, 1.0f), originHeading, targetHeading, m_mimeTemplate->GetBodyBoneWeight(i));
		}
	}

	// Update torso twist
	if(m_torso_BoneController)
	{
		CRuQuaternion yRot;
		yRot.FromAngleAxis(CRuVector3(0.0f, 1.0f, 0.0f), m_torso_HeadingOffset);

		CRuVector3 originHeading = CRuVector3(0.0f, 0.0f, 1.0f);
		CRuVector3 targetHeading = originHeading * yRot.GetMatrix();

		for(INT32 i = 0; i < m_mimeTemplate->GetNumTorsoBones(); ++i)
		{
			m_torso_BoneController->SetTransformTarget(m_mimeTemplate->GetTorsoBoneID(i), CRuVector3(1.0f, 1.0f, 1.0f), originHeading, targetHeading, m_mimeTemplate->GetTorsoBoneWeight(i));
		}
	}
}

void CRuFusion_Mime::Update_WithChannel(REAL elapsedTime)
{
	if(m_backgroundAction != ruFUSION_MIME_NONE || m_foregroundAction != ruFUSION_MIME_NONE)
	{
		REAL backgroundMotionScale = 0.0f;
		REAL foregroundMotionScale = 0.0f;

		INT32 backgroundPriority = m_mimeTemplate->GetActionPriority(m_backgroundAction);
		INT32 foregroundPriority = m_mimeTemplate->GetActionPriority(m_foregroundAction);

		// Determine motion scale to use for background action
		switch(m_backgroundAction)
		{
			case ruFUSION_MIME_RUN_FORWARD:
			case ruFUSION_MIME_SWIM_FORWARD:
				backgroundMotionScale = m_forwardSpeed / m_defaultForwardSpeed;
				break;

			case ruFUSION_MIME_RUN_BACKWARD:
			case ruFUSION_MIME_SWIM_BACKWARD:
				backgroundMotionScale = m_backwardSpeed / m_defaultBackwardSpeed;
				break;

			default:
				backgroundMotionScale = 0.0f;
				break;
		}

		// Determine motion scale to use for foreground action
		switch(m_foregroundAction)
		{
			case ruFUSION_MIME_RUN_FORWARD:
			case ruFUSION_MIME_SWIM_FORWARD:
				foregroundMotionScale = m_forwardSpeed / m_defaultForwardSpeed;
				break;

			case ruFUSION_MIME_RUN_BACKWARD:
			case ruFUSION_MIME_SWIM_BACKWARD:
				foregroundMotionScale = m_backwardSpeed / m_defaultBackwardSpeed;
				break;

			default:
				foregroundMotionScale = 0.0f;
				break;
		}

		if(m_foregroundAction == m_backgroundAction || m_foregroundAction == ruFUSION_MIME_NONE)
		{
			RuACT_PlayMotionEx(m_targetEntity, m_backgroundChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
			RuACT_PlayMotionEx(m_targetEntity, m_foregroundChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);

			if(!m_targetEntitySupportsChannels)
			{
				RuACT_PlayMotionEx(m_targetEntity, m_defaultChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
			}
		}
		else
		{
			// Check foreground priority versus background priority
			if(foregroundPriority >= backgroundPriority)
			{
				// Foreground has higher priority, so have it override the background channel
				RuACT_PlayMotionEx(m_targetEntity, m_backgroundChannel, m_foregroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
				RuACT_PlayMotionEx(m_targetEntity, m_foregroundChannel, m_foregroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);

				if(!m_targetEntitySupportsChannels)
				{
					RuACT_PlayMotionEx(m_targetEntity, m_defaultChannel, m_foregroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
				}
			}
			else
			{
				INT32 priorityDiff = backgroundPriority - foregroundPriority;

				if(priorityDiff > 10)
				{
					// Background has higher priority, priority difference exceed threshold, so foreground motion does not play at all
					RuACT_PlayMotionEx(m_targetEntity, m_backgroundChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
					RuACT_PlayMotionEx(m_targetEntity, m_foregroundChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);

					if(!m_targetEntitySupportsChannels)
					{
						RuACT_PlayMotionEx(m_targetEntity, m_defaultChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
					}
				}
				else
				{
					// Background has higher priority, foreground motion plays in foreground channel only
					RuACT_PlayMotionEx(m_targetEntity, m_backgroundChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
					RuACT_PlayMotionEx(m_targetEntity, m_foregroundChannel, m_foregroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);

					if(!m_targetEntitySupportsChannels)
					{
						RuACT_PlayMotionEx(m_targetEntity, m_defaultChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, m_expectedMotionScale);
					}
				}
			}
		}

		// Check background channel to see whether the motion has stopped and a new motion needs to be picked.
		if(RuACT_IsMotionComplete(m_targetEntity, m_backgroundChannel) == TRUE || strlen(m_backgroundMotion) == 0)
		{
			// If there is a schedule background action, apply it now
			if(m_scheduledBackgroundAction != ruFUSION_MIME_NONE)
			{
				m_backgroundAction = m_scheduledBackgroundAction;
				m_backgroundMotionType = m_scheduledBackgroundMotionType;
				m_backgroundMotion = m_mimeTemplate->GetMotion(m_backgroundAction);

				// Clear background action schedule
				m_scheduledBackgroundAction = ruFUSION_MIME_NONE;
				m_scheduledBackgroundMotionType = ruFUSION_MIME_MOTIONTYPE_NORMAL;
			}
			else
			{
				m_backgroundMotion = m_mimeTemplate->GetMotion(m_backgroundAction);
			}
		}

		// If the foreground channel has finished, reset its status
		if(RuACT_IsMotionComplete(m_targetEntity, m_foregroundChannel) == TRUE || strlen(m_foregroundMotion) == 0)
		{
			// If there is a schedule foreground action, apply it now
			if(m_scheduledForegroundAction != ruFUSION_MIME_NONE)
			{
				m_foregroundAction = m_scheduledForegroundAction;
				m_foregroundMotionType = m_scheduledForegroundMotionType;
				m_foregroundMotion = m_mimeTemplate->GetMotion(m_foregroundAction);

				// Clear Foreground action schedule
				m_scheduledForegroundAction = ruFUSION_MIME_NONE;
				m_scheduledForegroundMotionType = ruFUSION_MIME_MOTIONTYPE_NORMAL;
			}
			else
			{
				m_foregroundAction = ruFUSION_MIME_NONE;
				m_foregroundMotion = "";
			}
		}
	}
}

void CRuFusion_Mime::Update_WithoutChannel(REAL elapsedTime)
{
	if(m_backgroundAction != ruFUSION_MIME_NONE || m_foregroundAction != ruFUSION_MIME_NONE)
	{
		REAL backgroundMotionScale = 0.0f;
		REAL foregroundMotionScale = 0.0f;

		INT32 backgroundPriority = m_mimeTemplate->GetActionPriority(m_backgroundAction);
		INT32 foregroundPriority = m_mimeTemplate->GetActionPriority(m_foregroundAction);

		// Determine motion scale to use for background action
		switch(m_backgroundAction)
		{
			case ruFUSION_MIME_RUN_FORWARD:
			case ruFUSION_MIME_SWIM_FORWARD:
				backgroundMotionScale = m_forwardSpeed / m_defaultForwardSpeed;
				break;

			case ruFUSION_MIME_RUN_BACKWARD:
			case ruFUSION_MIME_SWIM_BACKWARD:
				backgroundMotionScale = m_backwardSpeed / m_defaultBackwardSpeed;
				break;

			default:
				backgroundMotionScale = 0.0f;
				break;
		}

		// Determine motion scale to use for foreground action
		switch(m_foregroundAction)
		{
			case ruFUSION_MIME_RUN_FORWARD:
			case ruFUSION_MIME_SWIM_FORWARD:
				foregroundMotionScale = m_forwardSpeed / m_defaultForwardSpeed;
				break;

			case ruFUSION_MIME_RUN_BACKWARD:
			case ruFUSION_MIME_SWIM_BACKWARD:
				foregroundMotionScale = m_backwardSpeed / m_defaultBackwardSpeed;
				break;

			default:
				foregroundMotionScale = 0.0f;
				break;
		}

		if(m_foregroundAction == m_backgroundAction || m_foregroundAction == ruFUSION_MIME_NONE)
		{
			RuACT_PlayMotionEx(m_targetEntity, m_defaultChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, foregroundMotionScale);
			m_backgroundActive = TRUE;
		}
		else
		{
			// Check foreground priority versus background priority
			if(foregroundPriority >= backgroundPriority)
			{
				// Foreground has higher priority, so have it override the background channel
				RuACT_PlayMotionEx(m_targetEntity, m_defaultChannel, m_foregroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, foregroundMotionScale);
				m_backgroundActive = FALSE;
			}
			else
			{
				RuACT_PlayMotionEx(m_targetEntity, m_defaultChannel, m_backgroundMotion, m_blendDelay, FALSE, 1.0f, 0.0f, 0.0f, backgroundMotionScale);
				m_backgroundActive = TRUE;
			}
		}

		if(m_backgroundActive)
		{
			// Check background channel to see whether the motion has stopped and a new motion needs to be picked.
			if(RuACT_IsMotionComplete(m_targetEntity, m_defaultChannel) == TRUE || strlen(m_backgroundMotion) == 0)
			{
				// If there is a schedule background action, apply it now
				if(m_scheduledBackgroundAction != ruFUSION_MIME_NONE)
				{
					m_backgroundAction = m_scheduledBackgroundAction;
					m_backgroundMotionType = m_scheduledBackgroundMotionType;
					m_backgroundMotion = m_mimeTemplate->GetMotion(m_backgroundAction);

					// Clear background action schedule
					m_scheduledBackgroundAction = ruFUSION_MIME_NONE;
					m_scheduledBackgroundMotionType = ruFUSION_MIME_MOTIONTYPE_NORMAL;
				}
				else
				{
					m_backgroundMotion = m_mimeTemplate->GetMotion(m_backgroundAction);
				}
			}
		}
		else
		{
			// If the foreground channel has finished, reset its status
			if(RuACT_IsMotionComplete(m_targetEntity, m_defaultChannel) == TRUE || strlen(m_foregroundMotion) == 0)
			{
				// If there is a schedule foreground action, apply it now
				if(m_scheduledForegroundAction != ruFUSION_MIME_NONE)
				{
					m_foregroundAction = m_scheduledForegroundAction;
					m_foregroundMotionType = m_scheduledForegroundMotionType;
					m_foregroundMotion = m_mimeTemplate->GetMotion(m_foregroundAction);

					// Clear Foreground action schedule
					m_scheduledForegroundAction = ruFUSION_MIME_NONE;
					m_scheduledForegroundMotionType = ruFUSION_MIME_MOTIONTYPE_NORMAL;
				}
				else
				{
					m_foregroundAction = ruFUSION_MIME_NONE;
					m_foregroundMotion = "";
				}
			}
		}
	}
}

BOOL CRuFusion_Mime::IsImmediateAction(CRuFusion_Mime_Action currentAction, CRuFusion_Mime_MotionType currentMotionType, CRuFusion_Mime_Action newAction, CRuFusion_Mime_MotionType newMotionType)
{
	if(currentAction == ruFUSION_MIME_NONE)
	{
		return TRUE;
	}

	switch(currentMotionType)
	{
		case ruFUSION_MIME_MOTIONTYPE_NORMAL:
		case ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE:
			return TRUE;

		case ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE:
		case ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE_OVERRIDE:
			{
				switch(newMotionType)
				{
					case ruFUSION_MIME_MOTIONTYPE_NORMAL:
					case ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE:
						return FALSE;

					case ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE:
					case ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE_OVERRIDE:
						return TRUE;
				}
			}

			break;
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

#pragma managed
