Files
EmeraidAI/Runtime/Scripts/Managers/EmeraldCombatManager.cs
2025-07-20 10:05:55 +08:00

676 lines
36 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
namespace EmeraldAI.Utility
{
/// <summary>
/// Handles all Emerald AI combat related functionality.
/// </summary>
public static class EmeraldCombatManager
{
/// <summary>
/// Handles the generation of all Emerald AI attacks.
/// </summary>
public static void GenerateAttack(EmeraldSystem EmeraldComponent, AttackClass SentAttackClass, bool OverridePickType = false)
{
if (SentAttackClass.AttackDataList.Count > 0)
{
List<AttackClass.AttackData> AvailableAttacks = new List<AttackClass.AttackData>();
//Go through the attack list and get all cooled down attacks. Note: This is ignored for the Ordered Pick Type.
for (int i = 0; i < SentAttackClass.AttackDataList.Count; i++)
{
SentAttackClass.AttackDataList[i].CooldownIgnored = false; //Reset before checking cooldown again
float CooldownTime = 0;
if (SentAttackClass.AttackDataList[i].AbilityObject != null) CooldownTime = (SentAttackClass.AttackDataList[i].CooldownTimeStamp + SentAttackClass.AttackDataList[i].AbilityObject.CooldownSettings.CooldownLength);
if (Time.time >= CooldownTime || SentAttackClass.AttackDataList[i].CooldownTimeStamp == 0 || SentAttackClass.AttackDataList[i].AbilityObject != null && !SentAttackClass.AttackDataList[i].AbilityObject.CooldownSettings.Enabled)
{
if (!AvailableAttacks.Contains(SentAttackClass.AttackDataList[i]))
{
if (SentAttackClass.AttackDataList[i].AbilityObject == null)
{
AvailableAttacks.Add(SentAttackClass.AttackDataList[i]);
}
else if (!SentAttackClass.AttackDataList[i].AbilityObject.ConditionSettings.Enabled ||
SentAttackClass.AttackDataList[i].AbilityObject.ConditionSettings.Enabled && !SentAttackClass.AttackDataList[i].AbilityObject.ConditionSettings.HighPriority && CheckAbilityConditions(EmeraldComponent, SentAttackClass.AttackDataList[i]))
{
AvailableAttacks.Add(SentAttackClass.AttackDataList[i]);
}
}
}
}
//If there are no available attacks, ignore the cooldowns and pick a random ability to avoid an error.
//if (AvailableAttacks.Count == 0 && SentAttackClass.AttackPickType != AttackPickTypes.Order)
if (AvailableAttacks.Count == 0)
{
SetAttackValues(EmeraldComponent, SentAttackClass.AttackDataList[Random.Range(0, SentAttackClass.AttackDataList.Count)], true);
Debug.Log("All cooldowns are busy with the: '" + EmeraldComponent.gameObject.name + "' AI so the a random attack within its Attack List was used. To avoid this, add more attacks or decrease some of this AI's cooldowns times within the Ability Objects.");
return;
}
if (SentAttackClass.AttackPickType == AttackPickTypes.Odds) //Pick an ability by using the odds for each available ability
{
List<float> OddsList = new List<float>();
for (int i = 0; i < AvailableAttacks.Count; i++)
{
OddsList.Add(AvailableAttacks[i].AttackOdds);
}
int OddsIndex = (int)GenerateProbability(OddsList.ToArray());
SetAttackValues(EmeraldComponent, AvailableAttacks[OddsIndex], false);
}
else if (SentAttackClass.AttackPickType == AttackPickTypes.Order) //Pick an ability by going through the list in order
{
float CooldownTime = 0;
AttackClass.AttackData AbilityDataRef = SentAttackClass.AttackDataList[SentAttackClass.AttackListIndex];
if (AbilityDataRef.AbilityObject == null || AbilityDataRef.AbilityObject != null && !AbilityDataRef.AbilityObject.CooldownSettings.Enabled && !AbilityDataRef.AbilityObject.ConditionSettings.Enabled)
{
SetAttackValues(EmeraldComponent, SentAttackClass.AttackDataList[SentAttackClass.AttackListIndex], false);
SentAttackClass.AttackListIndex++;
if (SentAttackClass.AttackListIndex == SentAttackClass.AttackDataList.Count) SentAttackClass.AttackListIndex = 0;
}
else
{
//Start the loop to find the next available attack
bool attackFound = false;
while (!attackFound)
{
AbilityDataRef = SentAttackClass.AttackDataList[SentAttackClass.AttackListIndex];
if (AbilityDataRef.AbilityObject != null)
CooldownTime = (AbilityDataRef.CooldownTimeStamp + AbilityDataRef.AbilityObject.CooldownSettings.CooldownLength);
//If no AbilityObject is found, play the attack animation
if (AbilityDataRef.AbilityObject == null)
{
SetAttackValues(EmeraldComponent, AbilityDataRef, false);
SentAttackClass.AttackListIndex++;
if (SentAttackClass.AttackListIndex == SentAttackClass.AttackDataList.Count) SentAttackClass.AttackListIndex = 0;
attackFound = true;
}
else if (Time.time >= CooldownTime && !AbilityDataRef.AbilityObject.ConditionSettings.Enabled || AbilityDataRef.CooldownTimeStamp == 0) //Check cooldown conditions
{
//Check Condition Module conditions
if (!AbilityDataRef.AbilityObject.ConditionSettings.Enabled || AbilityDataRef.AbilityObject.ConditionSettings.Enabled && !AbilityDataRef.AbilityObject.ConditionSettings.HighPriority && CheckAbilityConditions(EmeraldComponent, AbilityDataRef))
{
//If the ability is ready, set the attack values and break the loop
SetAttackValues(EmeraldComponent, AbilityDataRef, false);
SentAttackClass.AttackListIndex++;
if (SentAttackClass.AttackListIndex == SentAttackClass.AttackDataList.Count) SentAttackClass.AttackListIndex = 0;
attackFound = true;
}
else
{
//If the ability is not ready, move to the next one
SentAttackClass.AttackListIndex++;
if (SentAttackClass.AttackListIndex == SentAttackClass.AttackDataList.Count) SentAttackClass.AttackListIndex = 0;
}
}
else
{
//If the ability is not ready, move to the next one
SentAttackClass.AttackListIndex++;
if (SentAttackClass.AttackListIndex == SentAttackClass.AttackDataList.Count) SentAttackClass.AttackListIndex = 0;
}
}
}
}
else if (SentAttackClass.AttackPickType == AttackPickTypes.Random) //Pick a random ability from the list
{
int RandomIndex = Random.Range(0, AvailableAttacks.Count);
SetAttackValues(EmeraldComponent, AvailableAttacks[RandomIndex], false);
}
//Overrides the current pick type by picking the closest attack. So far, this is done to force a random attack from the AI's attack list.
if (OverridePickType)
{
int RandomIndex = Random.Range(0, AvailableAttacks.Count);
SetAttackValues(EmeraldComponent, AvailableAttacks[RandomIndex], false);
}
CheckConditionalAbiitities(EmeraldComponent, SentAttackClass, OverridePickType);
}
}
/// <summary>
/// Check for conditional abilities and add them to the AvailableConditionAbilities if their condition is met.
/// </summary>
public static void CheckConditionalAbiitities(EmeraldSystem EmeraldComponent, AttackClass SentAttackClass, bool OverridePickType = false)
{
EmeraldComponent.CombatComponent.AvailableConditionAbilities.Clear();
foreach (var attackData in SentAttackClass.AttackDataList)
{
//Only get abilities that are using the Condition Module
if (attackData.AbilityObject != null && attackData.AbilityObject.ConditionSettings.Enabled)
{
//Only get abilities that are not on cooldown
if ((Time.time >= attackData.CooldownTimeStamp + attackData.AbilityObject.CooldownSettings.CooldownLength || attackData.CooldownTimeStamp == 0))
{
//Lastly, check ability conditions then add to AvailableConditionAbilities list
if (CheckAbilityConditions(EmeraldComponent, attackData)) EmeraldComponent.CombatComponent.AvailableConditionAbilities.Add(attackData);
}
}
}
if (EmeraldComponent.CombatComponent.AvailableConditionAbilities.Count > 0)
{
EmeraldComponent.CombatComponent.CancelAllCombatActions();
EmeraldComponent.CombatComponent.AdjustCooldowns();
int RandomUseableAbility = Random.Range(0, EmeraldComponent.CombatComponent.AvailableConditionAbilities.Count);
SetAttackValues(EmeraldComponent, EmeraldComponent.CombatComponent.AvailableConditionAbilities[RandomUseableAbility], false);
}
}
/// <summary>
/// Check each condition for the passed ability to see if they can be used.
/// </summary>
static bool CheckAbilityConditions (EmeraldSystem EmeraldComponent, AttackClass.AttackData AttackData)
{
if (AttackData.AbilityObject.ConditionSettings.ConditionType == ConditionTypes.SelfLowHealth)
{
return ((float)EmeraldComponent.HealthComponent.CurrentHealth / (float)EmeraldComponent.HealthComponent.StartingHealth) <= (AttackData.AbilityObject.ConditionSettings.LowHealthPercentage * 0.01f);
}
else if (AttackData.AbilityObject.ConditionSettings.ConditionType == ConditionTypes.AllyLowHealth)
{
if (EmeraldComponent.DetectionComponent.NearbyAllies.Count > 0)
{
for (int j = 0; j < EmeraldComponent.DetectionComponent.NearbyAllies.Count; j++)
{
bool HasLowHealth = ((float)EmeraldComponent.DetectionComponent.NearbyAllies[j].HealthComponent.CurrentHealth / (float)EmeraldComponent.DetectionComponent.NearbyAllies[j].HealthComponent.StartingHealth) <= (AttackData.AbilityObject.ConditionSettings.LowHealthPercentage * 0.01f);
if (HasLowHealth && !EmeraldComponent.DetectionComponent.NearbyAllies[j].AnimationComponent.IsDead)
{
return true;
}
}
}
}
else if (AttackData.AbilityObject.ConditionSettings.ConditionType == ConditionTypes.NoCurrentSummons)
{
return EmeraldComponent.DetectionComponent.CurrentFollowers.Count == 0;
}
else if (AttackData.AbilityObject.ConditionSettings.ConditionType == ConditionTypes.DistanceFromTarget)
{
return EmeraldComponent.CombatComponent.DistanceFromTarget <= AttackData.AbilityObject.ConditionSettings.DistanceFromTarget && AttackData.AbilityObject.ConditionSettings.ValueCompareType == AbilityData.ConditionData.ValueCompareTypes.LessThan ||
EmeraldComponent.CombatComponent.DistanceFromTarget >= AttackData.AbilityObject.ConditionSettings.DistanceFromTarget && AttackData.AbilityObject.ConditionSettings.ValueCompareType == AbilityData.ConditionData.ValueCompareTypes.GreaterThan;
}
return false;
}
/// <summary>
/// Sets the data of the currently generated attack.
/// </summary>
static void SetAttackValues(EmeraldSystem EmeraldComponent, AttackClass.AttackData AttackData, bool CooldownIgnored)
{
EmeraldComponent.CombatComponent.CurrentAttackData = AttackData;
AttackData.CooldownIgnored = CooldownIgnored;
EmeraldComponent.CombatComponent.CurrentAnimationIndex = AttackData.AttackAnimation;
EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility = AttackData.AbilityObject;
EmeraldComponent.CombatComponent.AttackDistance = AttackData.AttackDistance;
//Don't allow the attack distance from being higher than the detection distance.
if (EmeraldComponent.CombatComponent.AttackDistance > EmeraldComponent.DetectionComponent.DetectionRadius) EmeraldComponent.CombatComponent.AttackDistance = EmeraldComponent.DetectionComponent.DetectionRadius;
EmeraldComponent.CombatComponent.TooCloseDistance = AttackData.TooCloseDistance;
if (EmeraldComponent.CombatComponent.CombatState) EmeraldComponent.m_NavMeshAgent.stoppingDistance = EmeraldComponent.CombatComponent.AttackDistance;
}
/// <summary>
/// Generate the next attack using the current attack class and the AI's Pick Type.
/// </summary>
public static void GenerateNextAttack (EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
GenerateAttack(EmeraldComponent, EmeraldComponent.CombatComponent.Type1Attacks);
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
GenerateAttack(EmeraldComponent, EmeraldComponent.CombatComponent.Type2Attacks);
EmeraldComponent.AnimationComponent.AttackingTracker = false;
}
/// <summary>
/// Overrides the currently generated attack with the closest attack within an AI's Attack List.
/// </summary>
public static void GenerateClosestAttack(EmeraldSystem EmeraldComponent)
{
EmeraldComponent.AnimationComponent.AttackingTracker = false;
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
GenerateAttack(EmeraldComponent, EmeraldComponent.CombatComponent.Type1Attacks, true);
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
GenerateAttack(EmeraldComponent, EmeraldComponent.CombatComponent.Type2Attacks, true);
}
/// <summary>
/// Updates the AI's current attack and weapon transforms based on the sent AttackTransformName from an EmeraldAttackEvent Animation Event.
/// This is done by searching for a matching name within the Attack Transform list. If no match is found, the AI's head transfrom will be used instead.
/// </summary>
public static void UpdateAttackTransforms (EmeraldSystem EmeraldComponent, string AttackTransformName)
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
Transform AttackTransform = EmeraldComponent.CombatComponent.WeaponType1AttackTransforms.Find(x => x != null && x.name == AttackTransformName);
if (AttackTransform != null)
{
EmeraldComponent.CombatComponent.CurrentAttackTransform = AttackTransform;
}
else
{
EmeraldComponent.CombatComponent.CurrentAttackTransform = EmeraldComponent.DetectionComponent.HeadTransform;
}
}
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
Transform AttackTransform = EmeraldComponent.CombatComponent.WeaponType2AttackTransforms.Find(x => x.name == AttackTransformName);
if (AttackTransform != null)
{
EmeraldComponent.CombatComponent.CurrentAttackTransform = AttackTransform;
}
else
{
EmeraldComponent.CombatComponent.CurrentAttackTransform = EmeraldComponent.DetectionComponent.HeadTransform;
}
}
}
/// <summary>
/// Returns the weapon transforms based on the sent AttackTransformName from an EmeraldChargeAttack Animation Event.
/// This is done by searching for a matching name within the Attack Transform list. If no match is found, the EmeraldChargeAttack event will not fire.
/// </summary>
public static Transform GetAttackTransform(EmeraldSystem EmeraldComponent, string AttackTransformName)
{
Transform WeaponTransform = null;
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
{
WeaponTransform = EmeraldComponent.CombatComponent.WeaponType1AttackTransforms.Find(x => x != null && x.name == AttackTransformName);
}
else
{
WeaponTransform = EmeraldComponent.CombatComponent.WeaponType2AttackTransforms.Find(x => x.name == AttackTransformName);
}
return WeaponTransform;
}
/// <summary>
/// Generates the AI's weapon type swap time.
/// </summary>
public static void ResetWeaponSwapTime (EmeraldSystem EmeraldComponent)
{
EmeraldComponent.CombatComponent.SwitchWeaponTime = Random.Range((float)EmeraldComponent.CombatComponent.SwitchWeaponTimeMin, (float)EmeraldComponent.CombatComponent.SwitchWeaponTimeMax + 1);
EmeraldComponent.CombatComponent.SwitchWeaponTimer = 0;
}
/// <summary>
/// Activates the AI's Combat State.
/// </summary>
public static void ActivateCombatState(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.CombatComponent.CombatState)
return;
EmeraldComponent.AIAnimator.ResetTrigger("Hit");
EmeraldComponent.CombatComponent.CombatState = true;
EmeraldComponent.AIAnimator.SetBool("Idle Active", false);
EmeraldComponent.AIAnimator.SetBool("Combat State Active", true);
EmeraldComponent.MovementComponent.CurrentMovementState = EmeraldMovement.MovementStates.Run;
}
/// <summary>
/// Disables an AI's components (called when an AI dies).
/// </summary>
public static void DisableComponents(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.SoundDetectorComponent != null) EmeraldComponent.SoundDetectorComponent.enabled = false;
if (EmeraldComponent.OptimizationComponent != null && EmeraldComponent.OptimizationComponent.m_VisibilityCheck != null)
{
EmeraldComponent.OptimizationComponent.enabled = false;
EmeraldComponent.OptimizationComponent.m_VisibilityCheck.enabled = false;
}
EmeraldComponent.CombatComponent.ExitCombat();
EmeraldComponent.AIBoxCollider.enabled = false;
EmeraldComponent.DetectionComponent.enabled = false;
EmeraldComponent.AnimationComponent.enabled = false;
EmeraldComponent.CombatComponent.enabled = false;
EmeraldComponent.MovementComponent.enabled = false;
EmeraldComponent.BehaviorsComponent.enabled = false;
EmeraldComponent.m_NavMeshAgent.ResetPath();
EmeraldComponent.m_NavMeshAgent.enabled = false;
EmeraldComponent.StartCoroutine(AlignAIOnDeath(EmeraldComponent)); //Align the AI on death, even if the alignment feature is disabled.
}
/// <summary>
/// Aligns the AI when the AI dies.
/// </summary>
static IEnumerator AlignAIOnDeath (EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && EmeraldComponent.AnimationComponent.m_AnimationProfile.Type1Animations.DeathList.Count == 0 ||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && EmeraldComponent.AnimationComponent.m_AnimationProfile.Type2Animations.DeathList.Count == 0)
yield break;
Vector3 SurfaceNormal = Vector3.zero;
while (EmeraldComponent.AIAnimator.enabled)
{
RaycastHit HitDown;
if (Physics.Raycast(new Vector3(EmeraldComponent.transform.position.x, EmeraldComponent.transform.position.y + 0.25f, EmeraldComponent.transform.position.z), -Vector3.up, out HitDown, 2f, EmeraldComponent.MovementComponent.AlignmentLayerMask))
{
if (HitDown.transform != EmeraldComponent.transform)
{
float m_MaxNormalAngle = EmeraldComponent.MovementComponent.MaxNormalAngle * 0.01f;
SurfaceNormal = HitDown.normal;
SurfaceNormal.x = Mathf.Clamp(SurfaceNormal.x, -m_MaxNormalAngle, m_MaxNormalAngle);
SurfaceNormal.z = Mathf.Clamp(SurfaceNormal.z, -m_MaxNormalAngle, m_MaxNormalAngle);
}
}
EmeraldComponent.transform.rotation = Quaternion.Slerp(EmeraldComponent.transform.rotation, Quaternion.FromToRotation(EmeraldComponent.transform.up, SurfaceNormal) * EmeraldComponent.transform.rotation, Time.deltaTime * 5);
yield return null;
}
}
/// <summary>
/// Enables an AI's components (called when an AI is reset).
/// </summary>
public static void EnableComponents(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.SoundDetectorComponent != null) EmeraldComponent.SoundDetectorComponent.enabled = true;
if (EmeraldComponent.OptimizationComponent != null && EmeraldComponent.OptimizationComponent.m_VisibilityCheck != null)
{
EmeraldComponent.OptimizationComponent.enabled = false;
EmeraldComponent.OptimizationComponent.m_VisibilityCheck.enabled = true;
}
if (EmeraldComponent.InverseKinematicsComponent != null) EmeraldComponent.InverseKinematicsComponent.EnableInverseKinematics();
EmeraldComponent.m_NavMeshAgent.enabled = true;
EmeraldComponent.AIBoxCollider.enabled = true;
EmeraldComponent.DetectionComponent.enabled = true;
EmeraldComponent.AnimationComponent.enabled = true;
EmeraldComponent.CombatComponent.enabled = true;
EmeraldComponent.MovementComponent.enabled = true;
EmeraldComponent.BehaviorsComponent.enabled = true;
EmeraldComponent.AIAnimator.enabled = true;
EmeraldComponent.AIAnimator.Rebind();
EmeraldComponent.AnimationComponent.ResetSettings();
if (EmeraldComponent.LBDComponent != null) EmeraldComponent.LBDComponent.ResetLBDComponents();
if (EmeraldComponent.ItemsComponent != null) EmeraldComponent.ItemsComponent.ResetSettings();
}
/// <summary>
/// Disable colliders and rigidbodies, given they're detected.
/// </summary>
public static void DisableRagdoll(EmeraldSystem EmeraldComponent)
{
//Return if a LocationBasedDamage component is detected as colliders need to stay active for this feature.
if (EmeraldComponent.GetComponent<LocationBasedDamage>() != null)
return;
foreach (Rigidbody R in EmeraldComponent.GetComponentsInChildren<Rigidbody>())
{
R.isKinematic = true;
}
if (EmeraldComponent.LBDComponent != null)
return;
foreach (Collider C in EmeraldComponent.GetComponentsInChildren<Collider>())
{
C.enabled = false;
}
EmeraldComponent.GetComponent<BoxCollider>().enabled = true;
}
/// <summary>
/// Enable colliders and rigidbodies inside the AI, given they're detected.
/// </summary>
public static void EnableRagdoll(EmeraldSystem EmeraldComponent)
{
//Only enable the ragdoll components if the current weapon type death animation lists don't have animations in them.
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && EmeraldComponent.AnimationComponent.m_AnimationProfile.Type1Animations.DeathList.Count > 0 ||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && EmeraldComponent.AnimationComponent.m_AnimationProfile.Type2Animations.DeathList.Count > 0)
return;
EmeraldComponent.AIAnimator.enabled = false;
if (EmeraldComponent.LBDComponent == null)
{
foreach (Collider C in EmeraldComponent.GetComponentsInChildren<Collider>())
{
if (C.transform != EmeraldComponent.transform)
{
C.enabled = true;
}
}
foreach (Rigidbody R in EmeraldComponent.GetComponentsInChildren<Rigidbody>())
{
R.isKinematic = false;
R.useGravity = true;
}
}
else
{
for (int i = 0; i < EmeraldComponent.LBDComponent.ColliderList.Count; i++)
{
if (EmeraldComponent.LBDComponent.ColliderList[i].ColliderObject != null)
EmeraldComponent.LBDComponent.ColliderList[i].ColliderObject.enabled = true;
}
for (int i = 0; i < EmeraldComponent.LBDComponent.ColliderList.Count; i++)
{
Rigidbody ColliderRigidbody = EmeraldComponent.LBDComponent.ColliderList[i].ColliderObject.GetComponent<Rigidbody>();
if (ColliderRigidbody != null)
{
ColliderRigidbody.isKinematic = false;
ColliderRigidbody.useGravity = true;
}
EmeraldComponent.LBDComponent.ColliderList[i].ColliderObject.enabled = true;
}
}
EmeraldComponent.StartCoroutine(AddRagdollForceInternal(EmeraldComponent)); //Add force to the ragdoll after enable its components
}
static IEnumerator AddRagdollForceInternal(EmeraldSystem EmeraldComponent)
{
float t = 0;
//Used to provide the force to the ragdoll in the opposite direction of the last target to hit the AI.
Transform LastAttacker = EmeraldComponent.CombatComponent.LastAttacker;
float Force = EmeraldComponent.CombatComponent.ReceivedRagdollForceAmount;
Rigidbody m_Rigidbody = null;
//Use the HeadTransform for the default rigidbody force on death.
if (EmeraldComponent.CombatComponent.RagdollTransform == null) m_Rigidbody = EmeraldComponent.DetectionComponent.HeadTransform.GetComponent<Rigidbody>();
//If the RagdollTransform is not null, add the force for the ragdoll death to this as an AI was damaged through a LBD area.
else m_Rigidbody = EmeraldComponent.CombatComponent.RagdollTransform.GetComponent<Rigidbody>();
if (m_Rigidbody != null && LastAttacker != null)
{
while (t < 0.2f)
{
t += Time.fixedDeltaTime;
m_Rigidbody.AddForce((EmeraldComponent.transform.position - LastAttacker.position).normalized * Force + (Vector3.up * Force * 0.05f), ForceMode.Acceleration);
yield return null;
}
}
}
/// <summary>
/// Returns true if the conditions are right to allow the AI to trigger an attack.
/// </summary>
public static bool AllowedToAttack(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.DetectionComponent.TargetObstructed || EmeraldComponent.CombatComponent.DeathDelayActive || EmeraldComponent.MovementComponent.DefaultMovementPaused || EmeraldComponent.AnimationComponent.InternalHit || EmeraldComponent.AnimationComponent.InternalDodge || EmeraldComponent.AnimationComponent.InternalBlock)
{
return false;
}
else if (!WithinStoppingDistanceOfTarget(EmeraldComponent) || !WithinDistanceOfTarget(EmeraldComponent))
{
return false;
}
else if (EmeraldComponent.AIAnimator.GetBool("Hit") || EmeraldComponent.AIAnimator.GetBool("Strafe Active") || EmeraldComponent.AIAnimator.GetBool("Dodge Triggered") || EmeraldComponent.AIAnimator.GetBool("Blocking") || EmeraldComponent.AnimationComponent.IsSwitchingWeapons ||
EmeraldComponent.AnimationComponent.IsBackingUp || EmeraldComponent.AnimationComponent.IsBlocking || EmeraldComponent.AnimationComponent.IsAttacking || EmeraldComponent.AnimationComponent.IsRecoiling || EmeraldComponent.AnimationComponent.IsStrafing || EmeraldComponent.AnimationComponent.IsDodging || EmeraldComponent.AnimationComponent.IsGettingHit)
{
return false;
}
else if (!EmeraldComponent.CombatComponent.TargetWithinAngleLimit() || EmeraldComponent.CurrentTargetInfo.CurrentIDamageable.Health <= 0)
{
return false;
}
//The AI has passed all checks and can trigger an attack.
return true;
}
public static void CheckAttackHeight(EmeraldSystem EmeraldComponent)
{
if (!EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility) return;
//This puts a height cap on the MeleeAbility to only allow it to trigger an attack if the target is within the MaxAttackHeight range.
MeleeAbility m_MeleeAbility = EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility as MeleeAbility;
if (m_MeleeAbility && EmeraldComponent.CombatTarget)
{
float heightDifference = Mathf.Abs(EmeraldComponent.transform.position.y - EmeraldComponent.CombatTarget.position.y);
if (heightDifference > m_MeleeAbility.MeleeSettings.MaxAttackHeight) EmeraldComponent.CombatComponent.TargetOutOfHeightRange = true;
else EmeraldComponent.CombatComponent.TargetOutOfHeightRange = false;
}
else
{
EmeraldComponent.CombatComponent.TargetOutOfHeightRange = false;
}
}
/// <summary>
/// Checks if the AI is within stopping distance of its current target (using Nav Mesh remainingDistance).
/// </summary>
static bool WithinStoppingDistanceOfTarget(EmeraldSystem EmeraldComponent)
{
return (EmeraldComponent.m_NavMeshAgent.remainingDistance <= EmeraldComponent.m_NavMeshAgent.stoppingDistance);
}
/// <summary>
/// Checks if the AI is within stopping distance of its current target (using Vector3.Distance).
/// </summary>
static bool WithinDistanceOfTarget(EmeraldSystem EmeraldComponent)
{
return (EmeraldComponent.CombatComponent.DistanceFromTarget <= EmeraldComponent.m_NavMeshAgent.stoppingDistance);
}
/// <summary>
/// Returns the height between the AI and its current target (only calculating using the Y axis).
/// </summary>
public static float GetTargetHeight (EmeraldSystem EmeraldComponent)
{
Vector3 m_TargetPos = EmeraldComponent.CombatTarget.position;
m_TargetPos.x = 0;
m_TargetPos.z = 0;
Vector3 m_CurrentPos = EmeraldComponent.transform.position;
m_CurrentPos.x = 0;
m_CurrentPos.z = 0;
return Vector3.Distance(m_TargetPos, m_CurrentPos);
}
/// <summary>
/// Generate the probability for the Random Pick Type.
/// </summary>
public static float GenerateProbability(float[] probs)
{
float total = 0;
foreach (float elem in probs)
{
total += elem;
}
float randomPoint = Random.value * total;
for (int i = 0; i < probs.Length; i++)
{
if (randomPoint < probs[i])
{
return i;
}
else
{
randomPoint -= probs[i];
}
}
return probs.Length - 1;
}
/// <summary>
/// Returns the current angle from the passed Transform.
/// </summary>
public static float TransformAngle(EmeraldSystem EmeraldComponent, Transform Target)
{
if (Target == null)
return 180;
Vector3 Direction = new Vector3(Target.position.x, 0, Target.position.z) - new Vector3(EmeraldComponent.transform.position.x, 0, EmeraldComponent.transform.position.z);
float angle = Vector3.Angle(EmeraldComponent.transform.forward, Direction);
float RotationDifference = EmeraldComponent.transform.localEulerAngles.x;
RotationDifference = (RotationDifference > 180) ? RotationDifference - 360 : RotationDifference;
float AdjustedAngle = Mathf.Abs(angle) - Mathf.Abs(RotationDifference);
return AdjustedAngle;
}
/// <summary>
/// Returns the current angle from the AI's target.
/// </summary>
public static float TargetAngle(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.CombatTarget == null)
return 360;
Vector3 Direction = new Vector3(EmeraldComponent.CombatTarget.position.x, 0, EmeraldComponent.CombatTarget.position.z) - new Vector3(EmeraldComponent.transform.position.x, 0, EmeraldComponent.transform.position.z);
float angle = Vector3.Angle(EmeraldComponent.transform.forward, Direction);
float RotationDifference = EmeraldComponent.transform.localEulerAngles.x;
RotationDifference = (RotationDifference > 180) ? RotationDifference - 360 : RotationDifference;
return Mathf.Abs(angle) - Mathf.Abs(RotationDifference);
}
/// <summary>
/// Gets the distance between this AI and its current target.
/// </summary>
public static float GetDistanceFromTarget(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.CombatTarget == null) return 0;
Vector3 CurrentTargetPos = EmeraldComponent.CombatTarget.position;
CurrentTargetPos.y = 0;
Vector3 CurrentPos = EmeraldComponent.transform.position;
CurrentPos.y = 0;
return Vector3.Distance(CurrentTargetPos, CurrentPos);
}
/// <summary>
/// Gets the distance between this AI and its current look at target.
/// </summary>
public static float GetDistanceFromLookTarget(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.LookAtTarget == null) return 0;
Vector3 CurrentTargetPos = EmeraldComponent.LookAtTarget.position;
CurrentTargetPos.y = 0;
Vector3 CurrentPos = EmeraldComponent.transform.position;
CurrentPos.y = 0;
return Vector3.Distance(CurrentTargetPos, CurrentPos);
}
}
}