/*!
	@project Rune
	@file RuFusion_Actor.cpp

	Copyright (c) 2004-2006 All rights reserved

	@author John Tang
	@date 2006/07/28
*/

#include "../Fusion/RuFusion_Actor.h"
#include "../Helper/RuHelper_Entity.h"
#include "../Helper/RuHelper_Math.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuFusion_Actor, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuFusion_Actor, "CRuFusion_Actor", "IRuObject")
ruCLASSGUID_DEFINE(CRuFusion_Actor, 0xE5AD9C6E, 0x57F347d0, 0x93F12EE0, 0xDB36D2C8)

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

CRuFusion_Actor::CRuFusion_Actor()
:	m_targetEntity(NULL),
	m_targetMime(NULL),
	m_sojourner(NULL),
	m_actorState(ruFUSION_ACTORSTATE_NORMAL),
	m_foregroundState(ruFUSION_ACTORSTATE_NORMAL),
	m_transientState(ruFUSION_ACTORSTATE_NORMAL),
	m_runThreshold(20.0f),
	m_gravitySpeed(0.0f),
	m_targetBodyHeadingOffset(0.0f),
	m_targetTorsoHeadingOffset(0.0f),
	m_targetYawOffset(0.0f),
	m_currentBodyHeadingOffset(0.0f),
	m_currentTorsoHeadingOffset(0.0f),
	m_currentYawOffset(0.0f)
{
	// Build transform to rotate from impulse/velocity space (where +Z is forward and +X is right) into entity reference space
	m_refFrameBasis.SetToBasis(CRuVector3(-1.0f, 0.0f, 0.0f), CRuVector3(0.0f, 1.0f, 0.0f), CRuVector3(0.0f, 0.0f, -1.0f));

	m_latentPeriod = 0;

	m_swim_Surface_EntrySegment.Origin().Set(0.0f, 17.0f, 0.0f);
	m_swim_Surface_EntrySegment.Direction().Set(0.0f, -2.0f, 0.0f);

	m_swim_Surface_ExitSegment.Origin().Set(0.0f, 15.0f, 0.0f);
	m_swim_Surface_ExitSegment.Direction().Set(0.0f, -5.0f, 0.0f);

	m_swim_Dive_EntrySegment.Origin().Set(0.0f, 20.0f, 0.0f);
	m_swim_Dive_EntrySegment.Direction().Set(0.0f, -3.0f, 0.0f);

	m_swim_Dive_ExitSegment.Origin().Set(0.0f, 15.0f, 0.0f);
	m_swim_Dive_ExitSegment.Direction().Set(0.0f, -5.0f, 0.0f);
}

CRuFusion_Actor::~CRuFusion_Actor()
{
	ruSAFE_RELEASE(m_targetEntity);
	ruSAFE_RELEASE(m_targetMime);
	ruSAFE_RELEASE(m_sojourner);
}

CRuEntity *CRuFusion_Actor::GetTargetEntity()
{
	return m_targetEntity;
}

CRuFusion_Mime *CRuFusion_Actor::GetTargetMime()
{
	return m_targetMime;
}

BOOL CRuFusion_Actor::SetTargetMime(CRuFusion_Mime *targetMime)
{
	if(targetMime)
	{
		// Release previous target
		ruSAFE_RELEASE(m_targetEntity);
		ruSAFE_RELEASE(m_targetMime);

		// Store reference to new target
		m_targetEntity = targetMime->GetTarget();
		m_targetMime = targetMime;

		// Increment reference on new target
		m_targetEntity->AddRef();
		m_targetMime->AddRef();

		return TRUE;
	}

	return FALSE;
}

CRuFusion_Sojourner *CRuFusion_Actor::GetSojourner()
{
	return m_sojourner;
}

BOOL CRuFusion_Actor::SetCollisionObject(IRuObject *collisionObject)
{
	ruSAFE_RELEASE(m_sojourner);
	m_sojourner = ruNEW CRuFusion_Sojourner(collisionObject);

	return TRUE;
}

void CRuFusion_Actor::SetForwardVector(const CRuVector3 &forwardVector)
{
	switch(m_actorState)
	{
		case ruFUSION_ACTORSTATE_NORMAL:
		case ruFUSION_ACTORSTATE_JUMP:
		case ruFUSION_ACTORSTATE_FALLING:
		case ruFUSION_ACTORSTATE_SWIM_SURFACE:
		case ruFUSION_ACTORSTATE_SWIM_DIVE:
			m_targetForwardVector = forwardVector;
			break;

		default:
			break;
	}
}

void CRuFusion_Actor::SetRunThreshold(REAL runThreshold)
{
	m_runThreshold = runThreshold;
}

void CRuFusion_Actor::Move(const CRuVector3 &impulse)
{
	m_inputVelocity.m_x = impulse.m_x;
	m_inputVelocity.m_z = impulse.m_z;
}

BOOL CRuFusion_Actor::Jump(REAL risingImpulse)
{
	switch(m_actorState)
	{
		case ruFUSION_ACTORSTATE_NORMAL:
			{
				m_actorState = ruFUSION_ACTORSTATE_JUMP;

				// Store current input velocity as latent velocity, and apply the rising impulse to gravity
				m_latentVelocity = m_inputVelocity;
				m_gravitySpeed += risingImpulse;

				// Transform latent velocity into entity local frame
				m_refFrameBasis.TransformVector(m_latentVelocity);
				m_targetEntity->GetRotation().TransformVector(m_latentVelocity);
			}

			return TRUE;
	}

	return FALSE;
}

BOOL CRuFusion_Actor::ForcedJump(REAL risingImpulse, const CRuVector3 &latentVelocity)
{
	m_actorState = ruFUSION_ACTORSTATE_JUMP;

	// Store current input velocity as latent velocity, and apply the rising impulse to gravity
	m_latentVelocity = latentVelocity;
	m_gravitySpeed += risingImpulse;

	return TRUE;
}

const CRuVector3 &CRuFusion_Actor::GetLatentVelocity() const
{
	return m_latentVelocity;
}

CRuFusion_ActorState CRuFusion_Actor::GetBackgroundState()
{
	return m_actorState;
}

BOOL CRuFusion_Actor::SetBackgroundState(CRuFusion_ActorState actorState)
{
	m_actorState = actorState;

	return TRUE;
}

CRuFusion_ActorState CRuFusion_Actor::GetForegroundState()
{
	return m_foregroundState;
}

BOOL CRuFusion_Actor::SetForegroundState(CRuFusion_ActorState actorState)
{
	m_foregroundState = actorState;

	return TRUE;
}

CRuFusion_ActorState CRuFusion_Actor::GetTransientState()
{
	return m_transientState;
}

BOOL CRuFusion_Actor::SetTransientState(CRuFusion_ActorState actorState)
{
	m_transientState = actorState;

	return TRUE;
}

void CRuFusion_Actor::Update(REAL elapsedTime)
{
	REAL m_frictionSurfaceThreshold = 0.9f;
	REAL m_frictionConstant = 1.0f;
	REAL m_gravityAcceleration = -98.0f;

	CRuVector3 entityReferenceForward(0.0f, 0.0f, -1.0f);

	// Accumulate update time
	m_latentPeriod += elapsedTime;

	// If the update period is too short, return
	if(m_latentPeriod < 0.01000f)
		return;

	m_gravitySpeed += -98.0f * m_latentPeriod;

	// Orient entity toward forward vector (change this to a different routine)
	RuEntity_Orient(m_targetEntity, entityReferenceForward, m_targetForwardVector, 1.0f);

	CRuVector3 actorOffset(0.0f, 9.0f, 0.0f);
	CRuVector3 actorEllipsoid(4.0f, 9.0f, 4.0f);
	CRuVector3 origin = m_targetEntity->GetTranslation() + actorOffset;
	CRuVector3 destination0 = origin;
	CRuVector3 destination1 = origin;

	REAL speed = 0.0f;
	CRuVector3 instantaneousVelocityGradient;
	CRuVector3 instantaneousGravityGradient;

	switch(m_actorState)
	{
		case ruFUSION_ACTORSTATE_NORMAL:
			{
				// Transform input velocity into entity local frame
				CRuVector3 localVelocity;
				m_refFrameBasis.TransformVector(m_inputVelocity, localVelocity);
				m_targetEntity->GetRotation().TransformVector(localVelocity, localVelocity);

				// Calculate instantaneous velocity
				speed = localVelocity.Magnitude();
				instantaneousVelocityGradient = localVelocity * m_latentPeriod;
				instantaneousGravityGradient.Set(0.0f, m_gravitySpeed * m_latentPeriod, 0.0f);
			}

			break;

		case ruFUSION_ACTORSTATE_JUMP:
		case ruFUSION_ACTORSTATE_FALLING:
			{
				// Calculate instantaneous velocity
				speed = m_latentVelocity.Magnitude();
				instantaneousVelocityGradient = m_latentVelocity * m_latentPeriod;
				instantaneousGravityGradient.Set(0.0f, m_gravitySpeed * m_latentPeriod, 0.0f);
			}

			break;

		case ruFUSION_ACTORSTATE_SWIM_SURFACE:
			{
				// Transform input velocity into entity local frame
				CRuVector3 localVelocity;
				m_refFrameBasis.TransformVector(m_inputVelocity, localVelocity);

				if(fabs(m_targetForwardVector.m_y) > 0.5f)
				{
					m_targetEntity->GetRotation().TransformVector(localVelocity, localVelocity);

					CRuVector3 blarf(entityReferenceForward);
					m_targetEntity->GetRotation().TransformVector(blarf);
					CRuQuaternion quat;
					quat.RotateIntoVector(m_targetForwardVector, blarf);

					quat.TransformVector(localVelocity, localVelocity);
				}
				else
				{
					m_targetEntity->GetRotation().TransformVector(localVelocity, localVelocity);
				}

				// Calculate instantaneous velocity
				speed = localVelocity.Magnitude();
				instantaneousVelocityGradient = localVelocity * m_latentPeriod;
				instantaneousGravityGradient.Set(0.0f, 0.0f, 0.0f);
			}

			break;

		case ruFUSION_ACTORSTATE_SWIM_DIVE:
			{
				// Transform input velocity into entity local frame
				CRuVector3 localVelocity;
				m_refFrameBasis.TransformVector(m_inputVelocity, localVelocity);
				m_targetEntity->GetRotation().TransformVector(localVelocity, localVelocity);

				CRuVector3 blarf(entityReferenceForward);
				m_targetEntity->GetRotation().TransformVector(blarf);
				CRuQuaternion quat;
				quat.RotateIntoVector(m_targetForwardVector, blarf);

				quat.TransformVector(localVelocity, localVelocity);

				// Calculate instantaneous velocity
				speed = localVelocity.Magnitude();
				instantaneousVelocityGradient = localVelocity * m_latentPeriod;
				instantaneousGravityGradient.Set(0.0f, 0.0f, 0.0f);
			}

			break;

		default:
			{
				speed = 0.0f;
				instantaneousVelocityGradient.Set(0.0f, 0.0f, 0.0f);
				instantaneousGravityGradient.Set(0.0f, m_gravitySpeed * m_latentPeriod, 0.0f);
			}

			break;
	}

	// Set expected motion scale for the mime
	m_targetMime->SetExpectedMotionScale(speed);

	// Calculate expected destination based on the instantaneous velocity
	CRuVector3 expectedDestination = origin + instantaneousVelocityGradient;

	// Apply transformed input velocity gradient
	m_sojourner->SetFrictionParameters(CRuVector3(0.0f, 1.0f, 0.0f), 0.0f, 0.0f);
	m_sojourner->Slide(origin, instantaneousVelocityGradient, actorEllipsoid, destination0);

	// Apply gravity gradient
	m_sojourner->SetFrictionParameters(CRuVector3(0.0f, 1.0f, 0.0f), m_frictionSurfaceThreshold, m_frictionConstant);
	m_sojourner->Slide(destination0, instantaneousGravityGradient, actorEllipsoid, destination1);

	// Calculate the distance traveled due to gravity
	REAL gravityInducedTravel = destination0.m_y - destination1.m_y;

	// Ensure the travel distance is positive along the gravity gradient
	if(gravityInducedTravel > ruEPSILON)
	{
		// Increase gravity speed by the length of time we spent falling
		REAL gravityInducedTravelTime = fabs(gravityInducedTravel / m_gravityAcceleration);
		m_gravitySpeed += gravityInducedTravelTime * m_gravityAcceleration;
	}
	else if(gravityInducedTravel < -ruEPSILON)
	{
		// We are actually rising
		m_gravitySpeed += m_gravityAcceleration * m_latentPeriod;
	}
	else
	{
		if(m_gravitySpeed == 0.0f)
		{
			// Increase gravity speed if we are currently vertically stationary
			m_gravitySpeed += m_gravityAcceleration * m_latentPeriod;
		}
		else
		{
			// Decay gravity speed by a fixed quantity
			m_gravitySpeed = m_gravitySpeed * 0.2f;

			// Zero out gravity speed if we are reasonably close to zero
			if(m_gravitySpeed < ruEPSILON)
				m_gravitySpeed = 0.0f;
		}
	}

	// Calculate difference between actual destination and expected destination
	REAL destDiff = (expectedDestination - destination0).Magnitude();

	// Restore destinations to entity coordinate frame
	destination0 = destination0 - actorOffset;
	destination1 = destination1 - actorOffset;

	// Move target entity to final destination
	m_targetEntity->Translate(destination1, ruTRANSFORMOP_SET);

	if(m_inputVelocity.m_x > 0.0f)
	{
		if(m_inputVelocity.m_z > 0.0f)
		{
			m_targetBodyHeadingOffset = ruPI / 4.0f;
		}
		else if (m_inputVelocity.m_z < 0.0f)
		{
			m_targetBodyHeadingOffset = -ruPI / 4.0f;
		}
		else
		{
			m_targetBodyHeadingOffset = ruPI / 2.0f;
		}
	}
	else if(m_inputVelocity.m_x < 0.0f)
	{
		if(m_inputVelocity.m_z > 0.0f)
		{
			m_targetBodyHeadingOffset = -ruPI / 4.0f;
		}
		else if (m_inputVelocity.m_z < 0.0f)
		{
			m_targetBodyHeadingOffset = ruPI / 4.0f;
		}
		else
		{
			m_targetBodyHeadingOffset = -ruPI / 2.0f;
		}
	}
	else
	{
		m_targetBodyHeadingOffset = 0.0f;
	}

	m_targetTorsoHeadingOffset = -m_targetBodyHeadingOffset;
	m_targetYawOffset = 0.0f;

	switch(m_actorState)
	{
		case ruFUSION_ACTORSTATE_NORMAL:
		case ruFUSION_ACTORSTATE_JUMP:
		case ruFUSION_ACTORSTATE_FALLING:
			{
				CRuSegment3 entrySegment(m_swim_Surface_EntrySegment);
				entrySegment.Origin() += destination1;

				if(m_sojourner->IntersectsWaterSurface(entrySegment))
				{
					m_actorState = ruFUSION_ACTORSTATE_SWIM_SURFACE;
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SWIM_SURFACE:
			{
				CRuSegment3 surfaceExitSegment(m_swim_Surface_ExitSegment);
				surfaceExitSegment.Origin() += destination1;

				if(m_sojourner->IntersectsWaterSurface(surfaceExitSegment))
				{
					m_actorState = ruFUSION_ACTORSTATE_NORMAL;
				}

				CRuSegment3 diveEntrySegment(m_swim_Dive_EntrySegment);
				diveEntrySegment.Origin() += destination1;

				if(m_sojourner->IntersectsWaterSurface(diveEntrySegment))
				{
					m_actorState = ruFUSION_ACTORSTATE_SWIM_DIVE;
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SWIM_DIVE:
			{
				CRuSegment3 diveExitSegment(m_swim_Dive_ExitSegment);
				diveExitSegment.Origin() += destination1;

				if(m_sojourner->IntersectsWaterSurface(diveExitSegment))
				{
					m_actorState = ruFUSION_ACTORSTATE_SWIM_SURFACE;
				}
			}

			break;

		default:
			break;
	}

	// State transitions and motion
	switch(m_actorState)
	{
		case ruFUSION_ACTORSTATE_NORMAL:
			{
				REAL inputSpeed = m_inputVelocity.Magnitude();

				if(m_inputVelocity.m_z < -ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_RUN_BACKWARD, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);
				}
				else if(m_inputVelocity.m_z > ruEPSILON || fabs(m_inputVelocity.m_x) > ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(inputSpeed > m_runThreshold ? ruFUSION_MIME_RUN_FORWARD : ruFUSION_MIME_WALK_FORWARD, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);
				}
				else
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_IDLE_STAND);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_JUMP:
			{
				m_targetMime->SetBackgroundAction(ruFUSION_MIME_JUMP_UP);

				// If we have begun our descent, switch state to falling
				if(m_gravitySpeed < 0.0f)
				{
					m_actorState = ruFUSION_ACTORSTATE_FALLING;
				}
			}

			break;

		case ruFUSION_ACTORSTATE_FALLING:
			{
				m_targetMime->SetBackgroundAction(ruFUSION_MIME_JUMP_LOOP);

				if(destDiff > ruEPSILON)
				{
					m_latentVelocity.Set(0.0f, 0.0f, 0.0f);
				}

				// Blafrgh?
				if(fabs(gravityInducedTravel) < ruEPSILON)
				{
					m_actorState = ruFUSION_ACTORSTATE_NORMAL;
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_JUMP_DOWN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SWIM_SURFACE:
			{
				REAL inputSpeed = m_inputVelocity.Magnitude();

				if(m_inputVelocity.m_z > ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_FORWARD, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
				else if(m_inputVelocity.m_z < -ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_BACKWARD, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
				else if(m_inputVelocity.m_x > ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_RIGHT, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetBodyHeadingOffset = 0.0f;
					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
				else if(m_inputVelocity.m_x < -ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_LEFT, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetBodyHeadingOffset = 0.0f;
					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
				else
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_IDLE);

					m_targetBodyHeadingOffset = 0.0f;
					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SWIM_DIVE:
			{
				REAL inputSpeed = m_inputVelocity.Magnitude();

				REAL yawOffset = 0.0f;
				CRuVector3 orthoVec(m_targetForwardVector.m_x, 0.0f, m_targetForwardVector.m_z);
				if(orthoVec.SquaredMagnitude() > ruEPSILON)
				{
					orthoVec.Normalize();

					if(m_targetForwardVector.m_y >= 0.0f)
					{
						yawOffset = acos(DotProduct(m_targetForwardVector, orthoVec));
					}
					else
					{
						yawOffset = -acos(DotProduct(m_targetForwardVector, orthoVec));
					}

				}

				if(m_inputVelocity.m_z > ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_FORWARD, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = yawOffset;
				}
				else if(m_inputVelocity.m_z < -ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_BACKWARD, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
				else if(m_inputVelocity.m_x > ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_RIGHT, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetBodyHeadingOffset = 0.0f;
					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
				else if(m_inputVelocity.m_x < -ruEPSILON)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_LEFT, ruFUSION_MIME_MOTIONTYPE_NORMAL_OVERRIDE);

					m_targetBodyHeadingOffset = 0.0f;
					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
				else
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SWIM_IDLE);

					m_targetBodyHeadingOffset = 0.0f;
					m_targetTorsoHeadingOffset = 0.0f;
					m_targetYawOffset = 0.0f;
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SIT_BEGIN:
			{
				if(m_targetMime->GetBackgroundAction() == ruFUSION_MIME_SIT_LOOP)
				{
					m_actorState = ruFUSION_ACTORSTATE_SIT_LOOP;
				}
				else if(m_targetMime->GetBackgroundAction() != ruFUSION_MIME_SIT_DOWN)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SIT_DOWN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SIT_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SIT_LOOP:
			{
			}

			break;

		case ruFUSION_ACTORSTATE_SIT_END:
			{
				if(m_targetMime->GetBackgroundAction() == ruFUSION_MIME_IDLE_STAND)
				{
					m_actorState = ruFUSION_ACTORSTATE_NORMAL;
				}
				else if(m_targetMime->GetBackgroundAction() != ruFUSION_MIME_SIT_UP)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SIT_UP, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_IDLE_STAND);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SLEEP_BEGIN:
			{
				if(m_targetMime->GetBackgroundAction() == ruFUSION_MIME_SLEEP_LOOP)
				{
					m_actorState = ruFUSION_ACTORSTATE_SLEEP_LOOP;
				}
				else if(m_targetMime->GetBackgroundAction() != ruFUSION_MIME_SLEEP_DOWN)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SLEEP_DOWN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SLEEP_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_SLEEP_LOOP:
			{
			}

			break;

		case ruFUSION_ACTORSTATE_SLEEP_END:
			{
				if(m_targetMime->GetBackgroundAction() == ruFUSION_MIME_IDLE_STAND)
				{
					m_actorState = ruFUSION_ACTORSTATE_NORMAL;
				}
				else if(m_targetMime->GetBackgroundAction() != ruFUSION_MIME_SLEEP_UP)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_SLEEP_UP, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_IDLE_STAND);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_DYING:
			{
				if(m_targetMime->GetBackgroundAction() == ruFUSION_MIME_DEATH_LOOP)
				{
					m_actorState = ruFUSION_ACTORSTATE_DEAD;
				}
				else if(m_targetMime->GetBackgroundAction() != ruFUSION_MIME_DEATH_DOWN)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_DEATH_DOWN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_DEATH_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_DEAD:
			{
			}

			break;

		case ruFUSION_ACTORSTATE_REVIVING:
			{
				if(m_targetMime->GetBackgroundAction() == ruFUSION_MIME_IDLE_STAND)
				{
					m_actorState = ruFUSION_ACTORSTATE_NORMAL;
				}
				else if(m_targetMime->GetBackgroundAction() == ruFUSION_MIME_DEATH_LOOP)
				{
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_DEATH_UP, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetBackgroundAction(ruFUSION_MIME_IDLE_STAND);
				}
			}

			break;
	}

	CRuFusion_ActorState &state = (m_transientState != ruFUSION_ACTORSTATE_NORMAL) ? m_transientState : m_foregroundState;

	switch(state)
	{
		case ruFUSION_ACTORSTATE_NORMAL:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_NONE);
			}

			break;

		case ruFUSION_ACTORSTATE_COMBAT_1H:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_IDLE_COMBAT_1H);
			}

			break;

		case ruFUSION_ACTORSTATE_COMBAT_2H:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_IDLE_COMBAT_2H);
			}

			break;

		case ruFUSION_ACTORSTATE_COMBAT_BOW:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_IDLE_COMBAT_BOW);
			}

			break;

		case ruFUSION_ACTORSTATE_COMBAT_GUN:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_IDLE_COMBAT_GUN);
			}

			break;

		case ruFUSION_ACTORSTATE_COMBAT_POLEARM:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_IDLE_COMBAT_POLEARM);
			}

			break;

		case ruFUSION_ACTORSTATE_COMBAT_UNARMED:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_IDLE_COMBAT_UNARMED);
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_1H:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_1H);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_2H:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_2H);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_BOW_BEGIN:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_ATTACK_BOW_LOOP)
				{
					state = ruFUSION_ACTORSTATE_ATTACK_BOW_LOOP;
				}
				else if(m_targetMime->GetForegroundAction() != ruFUSION_MIME_ATTACK_BOW_BEGIN)
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_BOW_BEGIN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_BOW_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_BOW_LOOP:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_BOW_LOOP);
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_BOW_END:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_BOW_END);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_GUN_BEGIN:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_ATTACK_GUN_LOOP)
				{
					state = ruFUSION_ACTORSTATE_ATTACK_GUN_LOOP;
				}
				else if(m_targetMime->GetForegroundAction() != ruFUSION_MIME_ATTACK_GUN_BEGIN)
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_GUN_BEGIN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_GUN_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_GUN_LOOP:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_GUN_LOOP);
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_GUN_END:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_GUN_END);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_OH:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_OH);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_OH_PIERCE:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_OH_PIERCE);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_POLEARM:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_POLEARM);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_ATTACK_UNARMED:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_ATTACK_UNARMED);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_BUFF_BEGIN:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_BUFF_LOOP)
				{
					state = ruFUSION_ACTORSTATE_BUFF_LOOP;
				}
				else if(m_targetMime->GetForegroundAction() != ruFUSION_MIME_BUFF_BEGIN)
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_BUFF_BEGIN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetForegroundAction(ruFUSION_MIME_BUFF_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_BUFF_LOOP:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_BUFF_LOOP);
			}

			break;

		case ruFUSION_ACTORSTATE_BUFF_END:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_BUFF_END);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_BUFF_INSTANT:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_BUFF_INSTANT, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;

		case ruFUSION_ACTORSTATE_CAST_BEGIN:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_CAST_LOOP)
				{
					state = ruFUSION_ACTORSTATE_CAST_LOOP;
				}
				else if(m_targetMime->GetForegroundAction() != ruFUSION_MIME_CAST_BEGIN)
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_CAST_BEGIN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetForegroundAction(ruFUSION_MIME_CAST_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_CAST_LOOP:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_CAST_LOOP);
			}

			break;

		case ruFUSION_ACTORSTATE_CAST_END:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_CAST_END);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_CAST_INSTANT:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_CAST_INSTANT, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;

		case ruFUSION_ACTORSTATE_CHANNEL_BEGIN:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_CHANNEL_LOOP)
				{
					state = ruFUSION_ACTORSTATE_CHANNEL_LOOP;
				}
				else if(m_targetMime->GetForegroundAction() != ruFUSION_MIME_CHANNEL_BEGIN)
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_CHANNEL_BEGIN, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);
					m_targetMime->SetForegroundAction(ruFUSION_MIME_CHANNEL_LOOP);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_CHANNEL_LOOP:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_CHANNEL_LOOP);
			}

			break;

		case ruFUSION_ACTORSTATE_CHANNEL_END:
			{
				if(m_targetMime->GetForegroundAction() == ruFUSION_MIME_NONE)
				{
					state = ruFUSION_ACTORSTATE_NORMAL;
				}
				else
				{
					m_targetMime->SetForegroundAction(ruFUSION_MIME_CHANNEL_END);
				}
			}

			break;

		case ruFUSION_ACTORSTATE_CHANNEL_INSTANT:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_CHANNEL_INSTANT, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;

		case ruFUSION_ACTORSTATE_EMOTE_ANGRY:
		case ruFUSION_ACTORSTATE_EMOTE_APPLAUSE:
		case ruFUSION_ACTORSTATE_EMOTE_APPROVAL:
		case ruFUSION_ACTORSTATE_EMOTE_BEG:
		case ruFUSION_ACTORSTATE_EMOTE_CRY:
		case ruFUSION_ACTORSTATE_EMOTE_HEAD_NOD:
		case ruFUSION_ACTORSTATE_EMOTE_HEAD_SHAKE:
		case ruFUSION_ACTORSTATE_EMOTE_LAUGH:
		case ruFUSION_ACTORSTATE_EMOTE_POINT:
		case ruFUSION_ACTORSTATE_EMOTE_PROVOKE:
		case ruFUSION_ACTORSTATE_EMOTE_SALUTE:
		case ruFUSION_ACTORSTATE_EMOTE_SPEAK:
		case ruFUSION_ACTORSTATE_EMOTE_SURRENDER:
		case ruFUSION_ACTORSTATE_EMOTE_THREATEN:
		case ruFUSION_ACTORSTATE_EMOTE_VICTORY:
		case ruFUSION_ACTORSTATE_EMOTE_WAVE:
			{
				CRuFusion_Mime_Action mimeAction = (CRuFusion_Mime_Action) (ruFUSION_MIME_EMOTE_ANGRY + (state - ruFUSION_ACTORSTATE_EMOTE_ANGRY));
				m_targetMime->SetForegroundAction(mimeAction, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;

		case ruFUSION_ACTORSTATE_HOLSTER:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MINE_HOLSTER, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;

		case ruFUSION_ACTORSTATE_UNHOLSTER:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_UNHOLSTER, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;

		case ruFUSION_ACTORSTATE_HOLSTER_2H:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MINE_HOLSTER_2H, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;

		case ruFUSION_ACTORSTATE_UNHOLSTER_2H:
			{
				m_targetMime->SetForegroundAction(ruFUSION_MIME_UNHOLSTER_2H, ruFUSION_MIME_MOTIONTYPE_EXCLUSIVE);

				state = ruFUSION_ACTORSTATE_NORMAL;
			}

			break;
	}

	// Interpolate avatar heading and rotation
	m_currentBodyHeadingOffset = RuMathHelper_StepInterpolate(m_currentBodyHeadingOffset, -m_targetBodyHeadingOffset, 10.00f, 5.00f, elapsedTime);
	m_currentTorsoHeadingOffset = RuMathHelper_StepInterpolate(m_currentTorsoHeadingOffset, -m_targetTorsoHeadingOffset, 10.00f, 5.00f, elapsedTime);
	m_currentYawOffset = RuMathHelper_StepInterpolate(m_currentYawOffset, -m_targetYawOffset, 10.00f, 5.00f, elapsedTime);

	// Set offsets
	m_targetMime->SetBodyHeadingOffset(m_currentBodyHeadingOffset);
	m_targetMime->SetTorsoHeadingOffset(m_currentTorsoHeadingOffset);
	m_targetMime->SetBodyYawOffset(m_currentYawOffset);

	// Reset latent period
	m_latentPeriod = 0.0f;
}

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

#pragma managed
