using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EmeraldAI.Utility;
namespace EmeraldAI
{
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/animation-component")]
public class EmeraldAnimation : MonoBehaviour
{
#region Animation States
public AnimatorStateInfo CurrentStateInfo;
public bool InternalDodge; //In order to detect dodges between transitions, a custom bool is needed to avoid it being missed or playing while hit is playing.
public bool InternalBlock; //In order to detect blocks between transitions, a custom bool is needed to avoid it being missed or playing while attack is playing.
public bool InternalHit; //In order to detect hits between transitions, a custom bool is needed to avoid it being missed or playing while dodge is playing.
public bool IsEmoting;
public bool IsIdling;
public bool IsAttacking;
public bool IsStrafing;
public bool IsBlocking;
public bool IsDodging;
public bool IsRecoiling;
public bool IsStunned;
public bool IsGettingHit;
public bool IsEquipping;
public bool IsBackingUp;
public bool IsTurning;
public bool IsTurningLeft, IsTurningRight;
public bool IsSwitchingWeapons;
public bool IsWarning;
public bool IsMoving;
public bool IsDead;
public bool m_IdleAnimaionIndexOverride = false;
#endregion
#region Animation Variables
public EmeraldAI.Utility.AnimationProfile m_AnimationProfile;
public bool AnimatorControllerGenerated = false;
public bool AnimationListsChanged = false;
public bool MissingRuntimeController = false;
public bool AnimationsUpdated = false;
public Animator AIAnimator;
public bool AttackingTracker; //Called right when an attack is generated
public bool AttackTriggered; //Briefly called while an attack is playing
public bool WarningAnimationTriggered = false;
public bool BusyBetweenStates = false;
public AnimationStateTypes CurrentAnimationState = AnimationStateTypes.Idling;
public delegate void GetHitHandler();
public event GetHitHandler OnGetHit;
public delegate void RecoilHandler();
public event RecoilHandler OnRecoil;
public delegate void StartAttackAnimationHandler();
public event StartAttackAnimationHandler OnStartAttackAnimation;
public delegate void EndAttackAnimationHandler();
public event StartAttackAnimationHandler OnEndAttackAnimation;
float LastHitTime;
Coroutine StunnedCoroutine;
EmeraldSystem EmeraldComponent;
#endregion
#region Editor Variables
public string[] Type1AttackEnumAnimations;
public string[] Type2AttackEnumAnimations;
public string[] Type1AttackBlankOptions = { "No Type 1 Attack Animations" };
public string[] Type2AttackBlankOptions = { "No Type 2 Attack Animations" };
public bool HideSettingsFoldout;
public bool AnimationProfileFoldout;
#endregion
void Start()
{
InitailizeAnimations();
SetupAnimator();
}
///
/// Initialize the Animation Component.
///
void InitailizeAnimations()
{
AIAnimator = GetComponent();
AIAnimator.runtimeAnimatorController = m_AnimationProfile.AIAnimator;
EmeraldComponent = GetComponent();
EmeraldComponent.HealthComponent.OnTakeDamage += PlayHitAnimation; //Subscribe to the OnTakeDamage event for Hit Animations
EmeraldComponent.HealthComponent.OnTakeCritDamage += PlayHitAnimation; //Subscribe to the OnTakeCritDamage event for Hit Animations
EmeraldComponent.HealthComponent.OnBlock += PlayHitAnimation; //Subscribe to the OnTakeCritDamage event for Block Hit Animations
EmeraldComponent.HealthComponent.OnDeath += PlayDeathAnimation; //Subscribe to the OnDeath event for Death Animations
EmeraldComponent.MovementComponent.OnReachedWaypoint += PlayIdleAnimation; //Subscribe to the OnReachedWaypoint event for Idle Animations
EmeraldComponent.CombatComponent.OnExitCombat += ReturnToDefaultState; //Subscribe to the OnExitCombat event for ReturnToDefaultState
AIAnimator.cullingMode = m_AnimationProfile.AnimatorCullingMode;
InitializeWeaponTypeAnimationAndSettings();
AIAnimator.updateMode = AnimatorUpdateMode.Normal;
AIAnimator.SetFloat("Offset", Random.Range(0.0f, 1.0f)); //Add a randomized offset so AI sharing animations don't start at the exact same frame.
}
public void AnimationUpdate ()
{
EmeraldComponent.AnimationComponent.CurrentStateInfo = AIAnimator.GetCurrentAnimatorStateInfo(0); //Update the CurrentStateInfo, which is used for getting the current state and tracking the AI's current states and animations.
CheckAnimationStates(); //Keeps track of the current animation state.
//While simple, this is needed to check when the current attack animation finishes. When it does, generate a new attack.
if (IsAttacking && !AttackingTracker)
{
OnStartAttackAnimation?.Invoke();
AttackingTracker = true;
AttackTriggered = true;
Invoke(nameof(StopAttackTrigger), 0.5f);
}
else if (!IsAttacking && AttackingTracker)
{
OnEndAttackAnimation?.Invoke();
EmeraldCombatManager.GenerateNextAttack(EmeraldComponent); //Generate the next attack once the current one has concluded.
}
//Set AttackTriggered to false if an priority state is triggered.
//This can happen if an attack was generated, but another state gets set active before it could trigger through the Animator.
if (AttackTriggered)
{
if (IsMoving || IsTurning || IsStunned || IsStrafing || IsBackingUp || IsBlocking || IsDodging || InternalHit || !AttackingTracker) AttackTriggered = false;
}
}
void StopAttackTrigger()
{
AttackTriggered = false;
}
///
/// Keeps track of the current animation state.
///
public void CheckAnimationStates()
{
if (!EmeraldComponent.CombatComponent.CombatState) IsIdling = CurrentStateInfo.IsName("Movement") && AIAnimator.GetFloat("Speed") < 0.1f && !IsBackingUp || CurrentStateInfo.IsTag("Idle");
if (!EmeraldComponent.CombatComponent.CombatState) IsMoving = CurrentStateInfo.IsName("Movement") && AIAnimator.GetFloat("Speed") >= 0.1f && !IsBackingUp;
if (EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
IsIdling = CurrentStateInfo.IsName("Combat Movement (Type 1)") && AIAnimator.GetFloat("Speed") < 0.1f;
IsMoving = CurrentStateInfo.IsName("Combat Movement (Type 1)") && AIAnimator.GetFloat("Speed") >= 0.1f && !IsBackingUp && !IsAttacking;
}
else if (EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
IsIdling = CurrentStateInfo.IsName("Combat Movement (Type 2)") && AIAnimator.GetFloat("Speed") < 0.1f;
IsMoving = CurrentStateInfo.IsName("Combat Movement (Type 2)") && AIAnimator.GetFloat("Speed") >= 0.1f && !IsBackingUp && !IsAttacking;
}
IsEquipping = CurrentStateInfo.IsTag("Equip");
IsBlocking = CurrentStateInfo.IsTag("Block");
IsRecoiling = CurrentStateInfo.IsTag("Recoil");
IsStunned = CurrentStateInfo.IsTag("Stunned");
IsStrafing = CurrentStateInfo.IsTag("Strafing");
IsDodging = CurrentStateInfo.IsTag("Dodging") || InternalDodge;
IsBackingUp = CurrentStateInfo.IsTag("Backing Up") || AIAnimator.GetBool("Walk Backwards");
IsAttacking = CurrentStateInfo.IsTag("Attack");
IsGettingHit = CurrentStateInfo.IsTag("Hit");
IsWarning = CurrentStateInfo.IsTag("Warning");
IsEmoting = CurrentStateInfo.IsTag("Emote");
//This is used to determine when an AI is in between combat and non-combat states. This stops undsired rotations that happens during these transitions and allows the mechanics to function much smoother.
BusyBetweenStates = AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 1) -> Movement") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 2) -> Movement") ||
AIAnimator.GetAnimatorTransitionInfo(0).IsName("Movement -> Combat Movement (Type 1)") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Movement -> Combat Movement (Type 2)") ||
AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 1) -> Put Away Weapon (Type 1)") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 2) -> Put Away Weapon (Type 2)") ||
AIAnimator.GetAnimatorTransitionInfo(0).IsName("Put Away Weapon (Type 1) -> Movement") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Put Away Weapon (Type 2) -> Movement");
if (IsIdling) CurrentAnimationState = AnimationStateTypes.Idling;
if (IsMoving) CurrentAnimationState = AnimationStateTypes.Moving;
if (IsTurningLeft) CurrentAnimationState = AnimationStateTypes.TurningLeft;
if (IsTurningRight) CurrentAnimationState = AnimationStateTypes.TurningRight;
if (IsEquipping) CurrentAnimationState = AnimationStateTypes.Equipping;
if (IsBlocking) CurrentAnimationState = AnimationStateTypes.Blocking;
if (IsRecoiling) CurrentAnimationState = AnimationStateTypes.Recoiling;
if (IsStunned) CurrentAnimationState = AnimationStateTypes.Stunned;
if (IsStrafing) CurrentAnimationState = AnimationStateTypes.Strafing;
if (IsDodging) CurrentAnimationState = AnimationStateTypes.Dodging;
if (IsBackingUp) CurrentAnimationState = AnimationStateTypes.BackingUp;
if (IsAttacking) CurrentAnimationState = AnimationStateTypes.Attacking;
if (IsGettingHit) CurrentAnimationState = AnimationStateTypes.GettingHit;
if (IsDead) CurrentAnimationState = AnimationStateTypes.Dead;
if (IsEmoting) CurrentAnimationState = AnimationStateTypes.Emoting;
if (IsSwitchingWeapons) CurrentAnimationState = AnimationStateTypes.SwitchingWeapons;
}
public void ResetSettings()
{
//Reapply the AI's Animator Controller settings applied on Start because, when the
//Animator Controller is disabled, they're reset to their default settings.
SetWeaponTypeAnimationState();
AIAnimator.SetBool("Idle Active", false);
InitializeWeaponTypeAnimationAndSettings();
InternalDodge = false;
InternalBlock = false;
InternalHit = false;
}
///
/// Sets up all Animator related settings.
///
public void SetupAnimator ()
{
AIAnimator = GetComponent();
EmeraldComponent = GetComponent(); ;
if (AIAnimator.layerCount >= 2)
AIAnimator.SetLayerWeight(1, 1);
if (GetComponent().MovementType == EmeraldMovement.MovementTypes.RootMotion)
{
EmeraldComponent.m_NavMeshAgent.speed = 0;
AIAnimator.applyRootMotion = true;
}
else
{
AIAnimator.applyRootMotion = false;
}
if (AIAnimator.layerCount >= 2)
{
AIAnimator.SetLayerWeight(1, 1);
}
SetWeaponTypeAnimationState();
AIAnimator.SetInteger("Idle Index", Random.Range(0, m_AnimationProfile.NonCombatAnimations.IdleList.Count));
}
///
/// Sets up all Animator related weapon type settings.
///
public void InitializeWeaponTypeAnimationAndSettings()
{
if (EmeraldComponent.CombatComponent.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
if (EmeraldComponent.CombatComponent.StartingWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
AIAnimator.SetInteger("Weapon Type State", 1);
EmeraldComponent.CombatComponent.CurrentWeaponType = EmeraldCombat.WeaponTypes.Type1;
EmeraldComponent.DetectionComponent.PickTargetType = EmeraldComponent.CombatComponent.Type1PickTargetType;
}
else if (EmeraldComponent.CombatComponent.StartingWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
AIAnimator.SetInteger("Weapon Type State", 2);
EmeraldComponent.CombatComponent.CurrentWeaponType = EmeraldCombat.WeaponTypes.Type2;
EmeraldComponent.DetectionComponent.PickTargetType = EmeraldComponent.CombatComponent.Type2PickTargetType;
}
}
else
{
AIAnimator.SetInteger("Weapon Type State", 1);
EmeraldComponent.CombatComponent.CurrentWeaponType = EmeraldCombat.WeaponTypes.Type1;
EmeraldComponent.DetectionComponent.PickTargetType = EmeraldComponent.CombatComponent.Type1PickTargetType;
}
}
///
/// Controls whether or not Animate Weapon State will be enabled (depending on if the user has applied the equipping and uneuipping animations).
///
void SetWeaponTypeAnimationState ()
{
if (EmeraldComponent.CombatComponent.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.One)
{
if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip == null || m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip == null)
AIAnimator.SetBool("Animate Weapon State", false);
else if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip != null && m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip != null)
AIAnimator.SetBool("Animate Weapon State", true);
}
else if (EmeraldComponent.CombatComponent.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip == null && m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip == null &&
m_AnimationProfile.Type2Animations.PutAwayWeapon.AnimationClip == null && m_AnimationProfile.Type2Animations.PullOutWeapon.AnimationClip == null)
AIAnimator.SetBool("Animate Weapon State", false);
else if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip != null && m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip != null &&
m_AnimationProfile.Type2Animations.PutAwayWeapon.AnimationClip != null && m_AnimationProfile.Type2Animations.PullOutWeapon.AnimationClip != null)
AIAnimator.SetBool("Animate Weapon State", true);
}
}
///
/// Generates a random idle animation index and plays an idle animation.
///
public void PlayIdleAnimation ()
{
if (!EmeraldComponent.AnimationComponent.m_IdleAnimaionIndexOverride && m_AnimationProfile.NonCombatAnimations.IdleList.Count > 0 &&
EmeraldComponent.MovementComponent.WaypointType != EmeraldMovement.WaypointTypes.Loop)
{
AIAnimator.SetInteger("Idle Index", Random.Range(1, m_AnimationProfile.NonCombatAnimations.IdleList.Count+1));
AIAnimator.SetBool("Idle Active", true);
}
}
///
/// Plays an AI's Warning animation for the current Weapon Type.
///
public void PlayWarningAnimation ()
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.IdleWarning.AnimationClip == null ||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.IdleWarning.AnimationClip == null ||
WarningAnimationTriggered)
return;
AIAnimator.SetTrigger("Warning");
WarningAnimationTriggered = true;
}
///
/// Plays a stunned animation depending on the StunnedLength.
///
public void PlayStunnedAnimation (float StunnedLength)
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.Stunned.AnimationClip == null ||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.Stunned.AnimationClip == null) return;
if (!IsStunned && !AIAnimator.GetBool("Blocking") && !AIAnimator.GetBool("Dodge Triggered") && !IsDodging && transform.localScale != Vector3.one * 0.003f)
{
if (StunnedCoroutine != null) StopCoroutine(StunnedCoroutine);
StunnedCoroutine = StartCoroutine(SetStunned(StunnedLength));
}
}
IEnumerator SetStunned(float StunnedLength)
{
yield return new WaitForSeconds(0.5f);
if (IsDodging || IsDead || IsBlocking || IsStunned)
{
AIAnimator.SetBool("Stunned Active", false);
yield break; //If this AI is doding or is dead, don't trigger a stun.
}
AIAnimator.SetBool("Stunned Active", true);
yield return new WaitForSeconds(StunnedLength);
AIAnimator.SetBool("Stunned Active", false);
EmeraldComponent.BehaviorsComponent.IsAiming = false;
}
///
/// Plays a random death animation from the AI's DeathList. If either DeathList is empty, it is assumed ragdoll deaths are being used.
///
public void PlayDeathAnimation ()
{
//Only play a death animation if the current weapon type death animation lists have animations in them.
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.DeathList.Count == 0 ||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.DeathList.Count == 0)
return;
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
AIAnimator.SetInteger("Death Index", Random.Range(1, m_AnimationProfile.Type1Animations.DeathList.Count + 1));
int DeathIndex = AIAnimator.GetInteger("Death Index");
StartCoroutine(DisableAnimator(m_AnimationProfile.Type1Animations.DeathList[DeathIndex-1].AnimationClip.length / m_AnimationProfile.Type1Animations.DeathList[DeathIndex - 1].AnimationSpeed));
}
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
AIAnimator.SetInteger("Death Index", Random.Range(1, m_AnimationProfile.Type2Animations.DeathList.Count + 1));
int DeathIndex = AIAnimator.GetInteger("Death Index");
StartCoroutine(DisableAnimator(m_AnimationProfile.Type2Animations.DeathList[DeathIndex-1].AnimationClip.length / m_AnimationProfile.Type2Animations.DeathList[DeathIndex - 1].AnimationSpeed));
}
AIAnimator.SetTrigger("Dead");
}
///
/// Plays a hit animation depending on the user set Animation State Conditions.
///
public void PlayHitAnimation ()
{
//Get the current hit animation cooldown depending on the weapon type.
float CurrentHitAnimationCooldown = EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 ? m_AnimationProfile.Type1HitAnimationCooldown : m_AnimationProfile.Type2HitAnimationCooldown;
//Don't play a hit animation if an AI is dead, if the CurrentHitAnimationCooldown hasn't passed, or if the odds are met.
if (EmeraldComponent.HealthComponent.CurrentHealth <= 0 || Time.time < (LastHitTime + CurrentHitAnimationCooldown))
return;
LastHitTime = Time.time;
if (!EmeraldComponent.CombatComponent.CombatState)
{
if (m_AnimationProfile.NonCombatAnimations.HitList.Count == 0 && !IsBlocking)
return;
int CurrentIndex = AIAnimator.GetInteger("Hit Index");
AIAnimator.SetInteger("Hit Index", CurrentIndex);
CurrentIndex++;
if (CurrentIndex == m_AnimationProfile.NonCombatAnimations.HitList.Count + 1) CurrentIndex = 1;
AIAnimator.SetInteger("Hit Index", CurrentIndex);
}
else if (EmeraldComponent.CombatComponent.CombatState && !IsBlocking)
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
if (m_AnimationProfile.Type1Animations.HitList.Count == 0 && !IsBlocking)
return;
int CurrentIndex = AIAnimator.GetInteger("Hit Index");
AIAnimator.SetInteger("Hit Index", CurrentIndex);
CurrentIndex++;
if (CurrentIndex >= m_AnimationProfile.Type1Animations.HitList.Count + 1) CurrentIndex = 1;
AIAnimator.SetInteger("Hit Index", CurrentIndex);
}
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
if (m_AnimationProfile.Type2Animations.HitList.Count == 0 && !IsBlocking)
return;
int CurrentIndex = AIAnimator.GetInteger("Hit Index");
AIAnimator.SetInteger("Hit Index", CurrentIndex);
CurrentIndex++;
if (CurrentIndex >= m_AnimationProfile.Type2Animations.HitList.Count + 1) CurrentIndex = 1;
AIAnimator.SetInteger("Hit Index", CurrentIndex);
}
}
//This helps cancel block if an AI doesn't block in time
if (!IsBlocking && AIAnimator.GetBool("Blocking"))
{
AIAnimator.SetBool("Blocking", false);
}
//Only play a hit animation if the conditions are right. Some states are automatically excluded.
if (!IsDodging && !IsSwitchingWeapons && !IsEquipping && !AIAnimator.GetBool("Dodge Triggered") && EmeraldComponent.HealthComponent.CurrentActiveEffects.Count == 0)
{
var Type1Conditions = (((int)m_AnimationProfile.Type1HitConditions) & ((int)CurrentAnimationState)) != 0;
var Type2Conditions = (((int)m_AnimationProfile.Type2HitConditions) & ((int)CurrentAnimationState)) != 0;
if (Type1Conditions && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 || Type2Conditions && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
//In order to bypass issues between frames and triggers, use an internal bool to determine when an AI is hit.
//If an AI was hit within 0.5 seconds, an AI will ignore a dodge if it was triggered.
InternalHit = true;
Invoke(nameof(ResetInternalHit), 0.5f);
AttackTriggered = false;
AIAnimator.SetTrigger("Hit");
OnGetHit?.Invoke();
}
}
AIAnimator.ResetTrigger("Attack");
}
void ResetInternalHit()
{
InternalHit = false;
AttackTriggered = false;
}
///
/// Plays an attack animation depending on the EmeraldComponent.CombatComponent.CurrentAnimationIndex.
///
public void PlayAttackAnimation ()
{
if (!EmeraldComponent.CombatComponent.CurrentAttackData.CooldownIgnored) EmeraldComponent.CombatComponent.CurrentAttackData.CooldownTimeStamp = Time.time;
if (!EmeraldComponent.CombatComponent.CurrentAttackData.AbilityObject.CooldownSettings.Enabled) EmeraldComponent.CombatComponent.CurrentAttackData.CooldownTimeStamp = 0; //Set cooldown values that are disabled to 0
AIAnimator.SetInteger("Attack Index", EmeraldComponent.CombatComponent.CurrentAnimationIndex + 1);
AIAnimator.SetTrigger("Attack");
AttackTriggered = true;
}
///
/// Calculates which turn animations to use while stationary.
///
public void CalculateTurnAnimations(bool ByPassConditions = false)
{
//Lock turning, as soon as the angle threshold is met, for 1 second. This prevents an AI from getting stuck transitioning between two turning animations.
if (!EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.MovementComponent.DestinationAdjustedAngle <= EmeraldComponent.MovementComponent.AngleToTurn && !EmeraldComponent.MovementComponent.LockTurning)
{
EmeraldComponent.MovementComponent.LockTurning = true;
StartCoroutine(LockTurns());
DisableTurning();
}
Vector3 DestinationDirection = EmeraldComponent.MovementComponent.DestinationDirection;
if (ByPassConditions || CanPlayTurningAnimation(DestinationDirection))
{
if (Time.timeSinceLevelLoad < 1f || EmeraldComponent.MovementComponent.LockTurning && !EmeraldComponent.CombatComponent.CombatState || IsBackingUp)
return;
Vector3 cross = Vector3.Cross(transform.forward, Quaternion.LookRotation(DestinationDirection, Vector3.up) * Vector3.forward);
if (cross.y > 0.0f) //Right
{
EmeraldComponent.AnimationComponent.IsTurning = true;
EmeraldComponent.AnimationComponent.IsTurningRight = true;
EmeraldComponent.AnimationComponent.IsTurningLeft = false;
AIAnimator.SetBool("Idle Active", false);
AIAnimator.SetBool("Turn Right", true);
AIAnimator.SetBool("Turn Left", false);
}
else if (cross.y < 0.0f) //Left
{
EmeraldComponent.AnimationComponent.IsTurning = true;
EmeraldComponent.AnimationComponent.IsTurningLeft = true;
EmeraldComponent.AnimationComponent.IsTurningRight = false;
AIAnimator.SetBool("Idle Active", false);
AIAnimator.SetBool("Turn Left", true);
AIAnimator.SetBool("Turn Right", false);
}
}
else if (EmeraldComponent.CombatComponent.CombatState)
{
EmeraldComponent.AnimationComponent.IsTurning = false;
EmeraldComponent.AnimationComponent.IsTurningLeft = false;
EmeraldComponent.AnimationComponent.IsTurningRight = false;
AIAnimator.SetBool("Turn Left", false);
AIAnimator.SetBool("Turn Right", false);
}
}
///
/// Returns true if a turn animation can be played.
///
bool CanPlayTurningAnimation (Vector3 DestinationDirection)
{
if (!EmeraldComponent.CombatComponent.CombatState)
{
return EmeraldComponent.MovementComponent.DestinationAdjustedAngle >= EmeraldComponent.MovementComponent.AngleToTurn && DestinationDirection != Vector3.zero &&
EmeraldComponent.MovementComponent.AIAgentActive && EmeraldComponent.m_NavMeshAgent.remainingDistance > EmeraldComponent.m_NavMeshAgent.stoppingDistance;
}
else
{
return !EmeraldComponent.CombatComponent.DeathDelayActive && EmeraldComponent.MovementComponent.DestinationAdjustedAngle >= EmeraldComponent.MovementComponent.AngleToTurn && DestinationDirection != Vector3.zero &&
EmeraldComponent.MovementComponent.AIAgentActive && !IsAttacking && !IsBlocking && !IsGettingHit && !IsRecoiling && !IsStrafing && !IsDodging && !IsStunned && !IsSwitchingWeapons && !IsEquipping;
}
}
///
/// Lock turning, as soon as the angle threshold is met, for 1 second. This prevents an AI from getting stuck transitioning between two turning animations.
///
IEnumerator LockTurns()
{
yield return new WaitForSeconds(1f);
EmeraldComponent.MovementComponent.LockTurning = false;
}
void DisableTurning()
{
IsTurning = false;
IsTurningLeft = false;
IsTurningRight = false;
AIAnimator.SetBool("Turn Right", false);
AIAnimator.SetBool("Turn Left", false);
}
///
/// Plays a recoil animation when an AI is attacking and their target blocks.
///
public void PlayRecoilAnimation ()
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.Recoil.AnimationClip == null ||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.Recoil.AnimationClip == null) return;
if (EmeraldComponent.CombatTarget != null && EmeraldComponent.CurrentTargetInfo != null && EmeraldComponent.CurrentTargetInfo.CurrentICombat.IsBlocking())
{
AIAnimator.ResetTrigger("Attack");
AIAnimator.SetTrigger("Recoil");
OnRecoil?.Invoke();
}
}
///
/// Sets the Strafe State according to the passed state bool parameter.
///
public void SetStrafeState (bool State)
{
int Direction = AIAnimator.GetInteger("Strafe Direction");
if (State) Direction = Random.Range(0, 2); //Only change the strafe direction if setting the strafe state to true.
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
if (Direction == 0 && m_AnimationProfile.Type1Animations.StrafeLeft.AnimationClip == null) return;
if (Direction == 1 && m_AnimationProfile.Type1Animations.StrafeRight.AnimationClip == null) return;
}
else if(EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
if (Direction == 0 && m_AnimationProfile.Type2Animations.StrafeLeft.AnimationClip == null) return;
if (Direction == 1 && m_AnimationProfile.Type2Animations.StrafeRight.AnimationClip == null) return;
}
AIAnimator.SetBool("Strafe Active", State);
if (State) AIAnimator.SetInteger("Strafe Direction", Direction);
if (State) AIAnimator.SetTrigger("Strafing Triggered");
}
///
/// Sets the Strafe State according to the passed state bool parameter.
///
public void TriggerDodgeState()
{
int Direction = Random.Range(0, 3);
//Override the dodge direction to be equal to the strafe direction, if strafing is active during a dodge.
if (IsStrafing || AIAnimator.GetBool("Strafe Active"))
{
int StrafeDirection = AIAnimator.GetInteger("Strafe Direction");
if (StrafeDirection == 0) Direction = 0;
if (StrafeDirection == 1) Direction = 2;
}
/*
//Override the dodge direction to be backwards, if the AI is currently backing up during a dodge.
else if (IsBackingUp)
{
Direction = 1;
}
*/
//Return if the chosen dodge animation is empty.
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
if (Direction == 0 && m_AnimationProfile.Type1Animations.DodgeLeft.AnimationClip == null) return;
if (Direction == 1 && m_AnimationProfile.Type1Animations.DodgeBack.AnimationClip == null) return;
if (Direction == 2 && m_AnimationProfile.Type1Animations.DodgeRight.AnimationClip == null) return;
}
else if(EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
if (Direction == 0 && m_AnimationProfile.Type2Animations.DodgeLeft.AnimationClip == null) return;
if (Direction == 1 && m_AnimationProfile.Type2Animations.DodgeBack.AnimationClip == null) return;
if (Direction == 2 && m_AnimationProfile.Type2Animations.DodgeRight.AnimationClip == null) return;
}
AIAnimator.SetInteger("Dodge Direction", Direction);
AIAnimator.SetTrigger("Dodge Triggered");
AIAnimator.SetBool("Walk Backwards", false);
}
///
/// Sets the Block State according to the passed state bool parameter.
///
public void PlayBlockAnimation (bool State)
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.BlockIdle.AnimationClip == null ||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.BlockIdle.AnimationClip == null) return;
AIAnimator.SetBool("Blocking", State);
}
///
/// Resets all deafult Animator values before initializing another action. This is to prevent multiple triggers being active at once, which can cause actions to be missed or skipped.
///
public void ResetTriggers(float Delay)
{
StartCoroutine(ResetTriggersInternal(Delay));
}
IEnumerator ResetTriggersInternal(float Delay)
{
yield return new WaitForSeconds(Delay);
EmeraldComponent.AIAnimator.ResetTrigger("Hit");
EmeraldComponent.AIAnimator.ResetTrigger("Attack");
EmeraldComponent.AIAnimator.SetBool("Blocking", false);
EmeraldComponent.AIAnimator.ResetTrigger("Dodge Triggered");
EmeraldComponent.AIAnimator.ResetTrigger("Strafing Triggered");
EmeraldComponent.AIAnimator.SetBool("Strafe Active", false);
EmeraldComponent.AIAnimator.ResetTrigger("Attack Cancelled");
}
///
/// Returns the Animator back to its default (non-combat) state. This is called through the OnExitCombat callback.
///
void ReturnToDefaultState ()
{
EmeraldComponent.AIAnimator.SetBool("Combat State Active", false);
EmeraldComponent.AnimationComponent.WarningAnimationTriggered = false;
}
///
/// Plays an emote animation according to the Animation Clip parameter.
///
public void PlayEmoteAnimation(int EmoteAnimationID)
{
//Look through each animation in the EmoteAnimationList for the appropriate ID.
//Once found, play the animaition of the same index as the found ID.
for (int i = 0; i < m_AnimationProfile.EmoteAnimationList.Count; i++)
{
if (m_AnimationProfile.EmoteAnimationList[i].AnimationID == EmoteAnimationID)
{
AIAnimator.SetInteger("Emote Index", EmoteAnimationID);
AIAnimator.SetTrigger("Emote Trigger");
IsMoving = false;
}
}
}
///
/// Loops an emote animation according to the Animation Clip parameter until it is called to stop. Note: If you are using this during combat,
/// it is important that you handle canceling it or the AI will not be able to return to combat.
///
public void LoopEmoteAnimation(int EmoteAnimationID)
{
//Look through each animation in the EmoteAnimationList for the appropriate ID.
//Once found, play the animaition of the same index as the found ID.
for (int i = 0; i < m_AnimationProfile.EmoteAnimationList.Count; i++)
{
if (m_AnimationProfile.EmoteAnimationList[i].AnimationID == EmoteAnimationID)
{
AIAnimator.SetInteger("Emote Index", EmoteAnimationID);
AIAnimator.SetBool("Emote Loop", true);
IsMoving = false;
}
}
}
///
/// Loops an emote animation according to the Animation Clip parameter until it is called to stop.
///
public void StopLoopEmoteAnimation(int EmoteAnimationID)
{
//Look through each animation in the EmoteAnimationList for the appropriate ID.
//Once found, play the animaition of the same index as the found ID.
for (int i = 0; i < m_AnimationProfile.EmoteAnimationList.Count; i++)
{
if (m_AnimationProfile.EmoteAnimationList[i].AnimationID == EmoteAnimationID)
{
AIAnimator.SetInteger("Emote Index", EmoteAnimationID);
AIAnimator.SetBool("Emote Loop", false);
IsMoving = false;
}
}
}
///
/// Delays the call to disable the Emerald AI components until after the death animation has finished playing.
///
IEnumerator DisableAnimator(float AnimationLength)
{
yield return new WaitForSeconds(AnimationLength);
EmeraldComponent.AIAnimator.enabled = false;
}
}
}