// ----------------------------------------------------------------------- //
//
// MODULE  : LOFMeleeAI.cpp
//
// PURPOSE : LOFMeleeAI Melee - Implementation
//
// CREATED : 27/1/99
//
// ----------------------------------------------------------------------- //
#include "LOFMeleeAI.h"

#define REARLEG_PERCENTAGE		20
#define FRONTFEET_PERCENTAGE	50

#define AI_UPDATE_DELTA			0.01f

#define ANIM_ATTACK1			"WALK_ATTACK"
#define ANIM_ATTACK2			"FRONT_FEET_ATTACK"
#define ANIM_ATTACK3			"REAR_LEG_ATTACK"

BEGIN_CLASS( MeleeAI )
	ADD_LONGINTPROP( State, BaseAI::AGGRESSIVE )
	ADD_LONGINTPROP( WeaponId, GUN_MCACLAW_ID )
END_CLASS_DEFAULT( MeleeAI, BaseAI, NULL, NULL )


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::MeleeAI()
//
//	PURPOSE:	Constructor
//
// ----------------------------------------------------------------------- //

MeleeAI::MeleeAI() : BaseAI()
{
	m_bIsMecha				= DTRUE;
	m_nWeaponId				= GUN_MCACLAW_ID;

	m_hMeleeAttack1Ani		= INVALID_ANI;
	m_hMeleeAttack2Ani		= INVALID_ANI;
	m_hMeleeAttack3Ani		= INVALID_ANI;

	m_pIdleSound			= "Sounds\\Enemies\\MCA\\MeleeAI\\Idle.wav";

	m_bCreateHandHeldWeapon	= DFALSE;
	m_bChangeAnimation		= DFALSE;
	m_bIsMeleeFiring		= DFALSE;
	m_bAllowMovement		= DTRUE;
	m_cc					= ALIEN;
	m_bSpawnWeapon			= DFALSE;
	
	m_nLastAni				= NOATTACK;
	m_fMeleeRange			= MELEE_ENGAGE_DISTANCE;
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::~MeleeAI()
//
//	PURPOSE:	Destructor
//
// ----------------------------------------------------------------------- //

MeleeAI::~MeleeAI()
{	
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return;

	if (m_hIdleSound)
	{
		pServerDE->KillSound(m_hIdleSound);
		m_hIdleSound = DNULL;
	}

}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::SetAnimationIndexes()
//
//	PURPOSE:	Initialize model animation indexes
//
// ----------------------------------------------------------------------- //
	
void MeleeAI::SetAnimationIndexes()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !m_hObject) return;

	BaseAI::SetAnimationIndexes();

	m_hMeleeAttack1Ani		= pServerDE->GetAnimIndex(m_hObject, ANIM_ATTACK1);
	m_hMeleeAttack2Ani		= pServerDE->GetAnimIndex(m_hObject, ANIM_ATTACK2);
	m_hMeleeAttack3Ani		= pServerDE->GetAnimIndex(m_hObject, ANIM_ATTACK3);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::UpdateWeapon()
//
//	PURPOSE:	Update the our weapon
//
// ----------------------------------------------------------------------- //

void MeleeAI::UpdateWeapon()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !m_hObject) return;

	m_bChangeAnimation = DFALSE;

	// See if we are firing...

	if (IsFiring())
	{
		// If we just started firing, figure out what weapon/animation to use...

		if (!(m_dwLastAction & AI_AFLG_FIRE))
		{
			m_bChangeAnimation = DTRUE;
		}
	}

	BaseAI::UpdateWeapon();
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::UpdateAnimation()
//
//	PURPOSE:	Update the current animation
//
// ----------------------------------------------------------------------- //

void MeleeAI::UpdateAnimation()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !m_hObject) return;

	// See if we should change our animation...

/*	if (m_bChangeAnimation)
	{
		return;
		m_bChangeAnimation = DFALSE;
	}
	else*/
	if (!IsFiring() && !m_bChangeAnimation)
	{
		if (!m_bIsMeleeFiring)
		{
			BaseAI::UpdateAnimation();
		}
	}
/*	if (m_bChangeAnimation)
	{
		return;
		m_bChangeAnimation = DFALSE;
	}
	else if (!IsFiring())
	{
		if (!m_bIsMeleeFiring)
		{
			BaseAI::UpdateAnimation();
		}
	}
*/
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::EngineMessageFn
//
//	PURPOSE:	Handle engine messages
//
// ----------------------------------------------------------------------- //

DDWORD MeleeAI::EngineMessageFn(DDWORD messageID, void *pData, DFLOAT fData)
{
	switch(messageID)
	{
		case MID_INITIALUPDATE:
		{
			if (fData != INITIALUPDATE_SAVEGAME)
			{
				InitialUpdate();
			}

			CacheFiles();
			break;
		}

		case MID_SAVEOBJECT:
		{
			Save((HMESSAGEWRITE)pData, (DDWORD)fData);
		}
		break;

		case MID_LOADOBJECT:
		{
			Load((HMESSAGEREAD)pData, (DDWORD)fData);
		}
		break;
		
		default : break;
	}

	return BaseAI::EngineMessageFn(messageID, pData, fData);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::Save
//
//	PURPOSE:	Save the object
//
// ----------------------------------------------------------------------- //

void MeleeAI::Save(HMESSAGEWRITE hWrite, DDWORD dwSaveFlags)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !hWrite) return;

	pServerDE->WriteToMessageDWord (hWrite, (DDWORD)m_nLastAni);
	pServerDE->WriteToMessageDWord (hWrite, m_hMeleeAttack1Ani);
	pServerDE->WriteToMessageDWord (hWrite, m_hMeleeAttack2Ani);
	pServerDE->WriteToMessageDWord (hWrite, m_hMeleeAttack3Ani);
	pServerDE->WriteToMessageByte  (hWrite, m_bChangeAnimation);
	pServerDE->WriteToMessageByte  (hWrite, m_bIsMeleeFiring);
	pServerDE->WriteToMessageFloat (hWrite, m_fMeleeRange);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::Load
//
//	PURPOSE:	Load the object
//
// ----------------------------------------------------------------------- //

void MeleeAI::Load(HMESSAGEREAD hRead, DDWORD dwLoadFlags)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !hRead) return;

	m_nLastAni=(Melee_Attacks)pServerDE->ReadFromMessageDWord (hRead);
	m_hMeleeAttack1Ani		= pServerDE->ReadFromMessageDWord (hRead);
	m_hMeleeAttack2Ani		= pServerDE->ReadFromMessageDWord (hRead);
	m_hMeleeAttack3Ani		= pServerDE->ReadFromMessageDWord (hRead);
	m_bChangeAnimation		= pServerDE->ReadFromMessageByte  (hRead);
	m_bIsMeleeFiring		= pServerDE->ReadFromMessageByte  (hRead);
	m_fMeleeRange			= pServerDE->ReadFromMessageFloat (hRead);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::InitialUpdate()
//
//	PURPOSE:	Handle initial update
//
// ----------------------------------------------------------------------- //

void MeleeAI::InitialUpdate()
{
			
	DFLOAT fRadius = 2000.0f;
	fRadius *= (m_eModelSize == MS_SMALL ? 0.2f : (m_eModelSize == MS_LARGE ? 5.0f : 1.0f));

	// Play idle sound as long as we're alive...

	m_hIdleSound = PlaySoundFromObject(m_hObject, m_pIdleSound, fRadius, 
									   m_nBasePriority, 
									   DTRUE, DTRUE);

}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::CacheFiles
//
//	PURPOSE:	Cache resources used by this the object
//
// ----------------------------------------------------------------------- //

void MeleeAI::CacheFiles()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return;

	if (m_pIdleSound)
	{
		pServerDE->CacheFile(FT_SOUND, m_pIdleSound);
	}
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::UpdateMovement
//
//	PURPOSE:	Update AI movement
//
// ----------------------------------------------------------------------- //

void MeleeAI::UpdateMovement()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE) return;

	UpdateOnGround();

	// The engine might apply an acceleration to us, make sure 
	// it doesn't last...(keep gravity however...)

	DVector vAccel;
	pServerDE->GetAcceleration(m_hObject, &vAccel);
	vAccel.x = vAccel.z = 0.0f;
	if (vAccel.y > 0.0f) vAccel.y = 0.0f;

	pServerDE->SetAcceleration(m_hObject, &vAccel);


	// Retrieve object vectors for current frame..

	DRotation rRot;
	pServerDE->GetObjectRotation(m_hObject, &rRot);
	pServerDE->GetRotationVectors(&rRot, &m_vUp, &m_vRight, &m_vForward);


	// If we are playing a scripted animation, don't update the movement...

	if ((m_eState == SCRIPT) && (m_curScriptCmd.command == AI_SCMD_PLAYANIMATION))
	{
		return;
	}


	// If we're moving (and not scripted), see if we are stuck...

	if ((m_eState != SCRIPT) && (m_dwControlFlags & BC_CFLG_MOVING))
	{
		// If we're stuck on something, see if turing will help us continue...

		if (m_bStuckOnSomething)
		{
			m_bStuckOnSomething = AvoidObstacle();
			if (!m_bStuckOnSomething) 
			{
				UpdateControlFlags();
			}
		}


		// If we are okay to move, check for ledges...

		if (!m_bStuckOnSomething)
		{
			m_bStuckOnSomething = !CanMoveDir(m_vForward);
		}
		

		// Make us stand still...

		if (m_bStuckOnSomething)
		{
			SetActionFlag(AI_AFLG_STAND);
			UpdateControlFlags();
		}
	}

	if (m_hTarget)
	{
		if (m_eState != AGGRESSIVE)
		{
			BaseAI::SetAggressive();
		}
	} 

	// Do real work...

	NewUpdateMovement();
	

	// Update current animation...

	UpdateAnimation();
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::NewUpdateMovement
//
//	PURPOSE:	Update AI movement using MoveObject
//
// ----------------------------------------------------------------------- //

void MeleeAI::NewUpdateMovement()
{
	CServerDE* pServerDE = GetServerDE();

	if (!pServerDE || m_damage.IsDead() || !m_bAllowMovement) return;

	m_bStuckOnSomething = DFALSE;

	// Make sure we're trying to move...

	if ( !(m_dwControlFlags & BC_CFLG_MOVING) ) return;

	DFLOAT fTimeDelta = pServerDE->GetFrameTime();

	DVector vNewPos;
	VEC_COPY(vNewPos, m_vPos);

	
	// See if we are stuck on something.  Can only happen if we were
	// moving on the last frame...

	if (m_dwLastFrameCtlFlgs & BC_CFLG_MOVING)
	{
		m_fLastDistTraveled = VEC_DIST(m_vPos, m_vLastPos);

		if (m_fLastDistTraveled < m_fPredTravelDist * 0.95f)
		{
			m_bStuckOnSomething = DTRUE;
		}
	}


	DFLOAT fMoveVel = m_fWalkVel;
	DFLOAT fVal		= (m_dwControlFlags & BC_CFLG_REVERSE) ? -1.0f : 1.0f;

	// Check for running...

	if ((m_dwControlFlags & BC_CFLG_RUN) && m_bAllowRun)
	{
		fMoveVel = m_fRunVel;
	}

	fMoveVel *= (fTimeDelta * fVal);
	
	
	// Move us forward/backward...

	if ((m_dwControlFlags & BC_CFLG_FORWARD) || 
		(m_dwControlFlags & BC_CFLG_REVERSE))
	{
		DVector vF;
		VEC_COPY(vF, m_vForward);

		// Limit movement to x and z...

		vF.y = 0.0;
		VEC_MULSCALAR(vF, vF, fMoveVel);
		VEC_ADD(vNewPos, vNewPos, vF);
	}
	
	
	// See if we should strafe...

	DVector vDims, vR;
	pServerDE->GetObjectDims(m_hObject, &vDims);

	if (m_dwControlFlags & BC_CFLG_STRAFE_RIGHT)
	{
		// Make sure new position isn't off a cliff (or into a wall)...

		if (CanMoveDir(m_vRight))
		{
			VEC_MULSCALAR(vR, m_vRight, fMoveVel);
			VEC_ADD(vNewPos, vNewPos, vR);
		}
	}
	else if (m_dwControlFlags & BC_CFLG_STRAFE_LEFT)
	{
		DVector vLeft;
		VEC_COPY(vLeft, m_vRight);
		VEC_MULSCALAR(vLeft, vLeft, -1.0f); // point left

		// Make sure new position isn't off a cliff (or into a wall)...

		if (CanMoveDir(vLeft))
		{
			VEC_MULSCALAR(vR, m_vRight, fMoveVel);
			VEC_SUB(vNewPos, vNewPos, vR);
		}
	}


	// Save our last position...

	VEC_COPY(m_vLastPos, m_vPos);
	m_fPredTravelDist = VEC_DIST(m_vLastPos, vNewPos);
	pServerDE->MoveObject(m_hObject, &vNewPos);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::ApproachTarget
//
//	PURPOSE:	Walk / Run at the target based on distance
//
// ----------------------------------------------------------------------- //

void MeleeAI::ApproachTarget()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !m_hObject || !m_hTarget) return;

	CWeapon* pWeapon = m_weapons.GetCurWeapon();
	if (!pWeapon) return;

	// Distance to target...

	DFLOAT fDist = DistanceToObject(m_hTarget);
	
	// If we can't see our target, see if we can find him...

	if (!IsObjectVisibleToAI(m_hTarget))
	{
		if (IsPlayer(m_hTarget))
		{
			if (m_bLostPlayer)
			{
				SetNewTarget(DNULL);
				m_bSpottedPlayer = DFALSE;
			}
			else
			{
				SearchForPlayer(m_hTarget);
			}
			return;
		}
	}

	if (fDist > m_fMeleeRange)
	{
		ClearActionFlag(AI_AFLG_FIRE);
		m_bChangeAnimation = DFALSE;
		if (fDist > MELEE_RUN_DISTANCE)
		{
			RunForward();
		}
		else
		{
			WalkForward();
		}
	}
	else
	{
		if (!m_bChangeAnimation) 
		{
			m_bChangeAnimation = DTRUE;
		}
		SetActionFlag(AI_AFLG_STAND);
	}
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::UpdateAggressive()
//
//	PURPOSE:	Implement the attacking actions
//
// ----------------------------------------------------------------------- //

void MeleeAI::UpdateAggressive()
{
	// If we don't have a target try to aquire one...

	if (!ChooseTarget()) return;
	
	// Try to shoot target.....

	ShootTarget();

	
	SetActionFlag(AI_AFLG_AIMING);
	SetActionFlag(AI_AFLG_EVASIVE);

	// Always try to get a better shot...

	ApproachTarget();
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::HandleModelString()
//
//	PURPOSE:	Handle model strings for extra functionality
//
// ----------------------------------------------------------------------- //

void MeleeAI::HandleModelString(ArgList* pArgList)
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !pArgList || !pArgList->argv || pArgList->argc == 0) return;

	char* pKey = pArgList->argv[0];
	if (!pKey) return;

	if (stricmp(pKey, MELEE_START_FIRE) == 0)
	{
		HMODELANIM hAni = pServerDE->GetModelAnimation(m_hObject);
		if (hAni == m_hMeleeAttack1Ani)
		{
			m_nLastAni = MATTACK3;
		}
		else if (hAni == m_hMeleeAttack2Ani)
		{
			m_nLastAni = MATTACK1;
		}
		else
		{
			m_nLastAni = MATTACK2;
		}

		// this model string starts the melee firing sequence
		// once started the animation will not be interrupted
		m_bChangeAnimation	= DFALSE;
		m_bIsMeleeFiring	= DTRUE;
		pServerDE->SetNextUpdate(m_hObject, AI_UPDATE_DELTA);
	}
	else if (stricmp(pKey, MELEE_KEY_FIRE_WEAPON) == 0)
	{
		// this is taken directly from BaseAI HandleModelString :)
		CWeapon* pWeapon = m_weapons.GetCurWeapon();
		if (!pWeapon) return;

		DVector vBogus;
		VEC_INIT(vBogus);

		// Make sure we can see our target...

		if (GetTargetPos(vBogus))
		{
			pWeapon->Fire();
		}

		pServerDE->SetNextUpdate(m_hObject, AI_UPDATE_DELTA);
	}
	else if (stricmp(pKey, MELEE_END_FIRE) == 0)
	{
		// this model string ends the melee firing sequence
		// once ended any animation can be played
		m_bChangeAnimation	= DTRUE;
		m_bIsMeleeFiring	= DFALSE;
		pServerDE->SetNextUpdate(m_hObject, AI_UPDATE_DELTA);
	}
	else
	{
		BaseAI::HandleModelString(pArgList);
	}
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::SetStandAnimation()
//
//	PURPOSE:	Set animation to standing (firing or idle)
//
// ----------------------------------------------------------------------- //

void MeleeAI::SetStandAnimation()
{
	CServerDE* pServerDE = GetServerDE();
	if (!pServerDE || !m_hObject) return;

	HMODELANIM hAni = DNULL;


	// See if we are currently firing...

	DDWORD bAttacking = (m_dwControlFlags & BC_CFLG_FIRING);

	if (bAttacking)
	{
		if (m_bOneHandedWeapon)
		{
			if (m_bIsMeleeFiring || !TargetInMeleeRange(m_hTarget)) return;

			if (m_nLastAni == NOATTACK || m_nLastAni == MATTACK2)
			{
				hAni = m_hMeleeAttack1Ani;
			}
			else if (m_nLastAni == MATTACK3)
			{
				hAni = m_hMeleeAttack3Ani;
			}
			else
			{
				hAni = m_hMeleeAttack2Ani;
			}
		}
		else
		{
			hAni = m_hStandRifleAttackAni;
		}

		SetAnimation(hAni, DFALSE);
		return;
	}	
	

	// Make sure idle animation is done if one is currently playing...

	hAni = pServerDE->GetModelAnimation(m_hObject);

	if (m_bOneHandedWeapon)
	{
		if (hAni == m_hKnifeIdle1Ani || hAni == m_hKnifeIdle2Ani)
		{ 
			if (!(pServerDE->GetModelPlaybackState(m_hObject) & MS_PLAYDONE))
			{
				return;
			}
		}
	
		hAni = (GetRandom(0, 1) == 0 ? m_hKnifeIdle1Ani : m_hKnifeIdle2Ani);
	}
	else
	{
		if (hAni == m_hRifleIdleAni[0] || hAni == m_hRifleIdleAni[1] ||
			hAni == m_hRifleIdleAni[2] || hAni == m_hRifleIdleAni[3] ||
			hAni == m_hRifleIdleAni[4])
		{ 
			if (!(pServerDE->GetModelPlaybackState(m_hObject) & MS_PLAYDONE))
			{
				return;
			}
		}
	 
		hAni = m_hRifleIdleAni[GetRandom(0, 4)];

		if (hAni == INVALID_ANI)
		{
			hAni = m_hRifleIdleAni[GetRandom(0,1)];  // Should always be valid
		}
	}

	SetAnimation(hAni, DFALSE);
}


// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::SetWalkAnimation()
//
//	PURPOSE:	Set animation to walking
//
// ----------------------------------------------------------------------- //

void MeleeAI::SetWalkAnimation()
{
	HMODELANIM hAni = INVALID_ANI;
	
	DDWORD bStrafeLeft  = (m_dwControlFlags & BC_CFLG_STRAFE_LEFT);
	DDWORD bStrafeRight = (m_dwControlFlags & BC_CFLG_STRAFE_RIGHT);
	DDWORD bAttacking   = (m_dwControlFlags & BC_CFLG_FIRING);
	DDWORD bBackward	= (m_dwControlFlags & BC_CFLG_REVERSE);

	DBOOL  bLoop		= DTRUE;
	if (m_bOneHandedWeapon)
	{
		if (bAttacking || TargetInMeleeRange(m_hTarget))
		{
			if (m_bIsMeleeFiring) return;

			bLoop = DFALSE; // we never want to loop our melee attacks

			if (m_nLastAni == NOATTACK || m_nLastAni == MATTACK2 || (bStrafeLeft || bStrafeRight))
			{
				hAni = m_hMeleeAttack1Ani;
			}
			else if (m_nLastAni == MATTACK1)
			{
				hAni = m_hMeleeAttack3Ani;
			}
			else
			{
				hAni = m_hMeleeAttack2Ani;
			}
		}
		else
		{
			// very simplistic
			hAni = m_hWalkKnifeAni;
			// original shogo code below
			//hAni = bAttacking ? ((bStrafeLeft ? m_hWalkStrafeLKnifeAttackAni : (bStrafeRight ? m_hWalkStrafeRKnifeAttackAni : (bBackward ? m_hWalkBKnifeAttackAni : m_hWalkKnifeAttackAni)))) : 
			//		((bStrafeLeft ? m_hWalkStrafeLKnifeAni : (bStrafeRight ? m_hWalkStrafeRKnifeAni : (bBackward ? m_hWalkBKnifeAni : m_hWalkKnifeAni))));
		}
	}
	else
	{
		hAni = bAttacking ? ((bStrafeLeft ? m_hWalkStrafeLRifleAttackAni : (bStrafeRight ? m_hWalkStrafeRRifleAttackAni : (bBackward ? m_hWalkBRifleAttackAni : m_hWalkRifleAttackAni)))) : 
				((bStrafeLeft ? m_hWalkStrafeLRifleAni : (bStrafeRight ? m_hWalkStrafeRRifleAni : (bBackward ? m_hWalkBRifleAni : m_hWalkRifleAni))));
	}

	SetAnimation(hAni, bLoop);
}

// ----------------------------------------------------------------------- //
//
//	ROUTINE:	MeleeAI::SetAnimationIndexes()
//
//	PURPOSE:	Initialize model animation indexes
//
// ----------------------------------------------------------------------- //
	
DBOOL MeleeAI::TargetInMeleeRange(HOBJECT target)
{
	if (!m_hTarget)	return DFALSE;
	
	// Distance to target...

	DFLOAT fDist = DistanceToObject(m_hTarget);
	
	if (fDist < m_fMeleeRange + 16.0f)
	{
		return DTRUE;
	}
	return DFALSE;
}
