update 1.2.0

This commit is contained in:
2025-07-20 10:05:55 +08:00
parent 2afbbf9be4
commit 5396d3e4b8
102 changed files with 93826 additions and 357 deletions

View File

@@ -0,0 +1,98 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldDecals))]
[CanEditMultipleObjects]
public class EmeraldDecalsEditor : Editor
{
GUIStyle FoldoutStyle;
Texture EventsEditorIcon;
SerializedProperty HideSettingsFoldout, DecalsFoldout, BloodEffects, BloodSpawnHeight, BloodSpawnDelay, BloodSpawnRadius, BloodDespawnTime, OddsForBlood;
void OnEnable()
{
if (EventsEditorIcon == null) EventsEditorIcon = Resources.Load("Editor Icons/EmeraldDecals") as Texture;
InitializeProperties();
}
void InitializeProperties()
{
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
DecalsFoldout = serializedObject.FindProperty("DecalsFoldout");
BloodEffects = serializedObject.FindProperty("BloodEffects");
BloodSpawnHeight = serializedObject.FindProperty("BloodSpawnHeight");
BloodSpawnDelay = serializedObject.FindProperty("BloodSpawnDelay");
BloodSpawnRadius = serializedObject.FindProperty("BloodSpawnRadius");
BloodDespawnTime = serializedObject.FindProperty("BloodDespawnTime");
OddsForBlood = serializedObject.FindProperty("OddsForBlood");
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldDecals self = (EmeraldDecals)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Decals", EventsEditorIcon, new GUIContent(), HideSettingsFoldout);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
DecalSettings(self);
EditorGUILayout.Space();
}
CustomEditorProperties.EndScriptHeader();
serializedObject.ApplyModifiedProperties();
}
void DecalSettings(EmeraldDecals self)
{
DecalsFoldout.boolValue = EditorGUILayout.Foldout(DecalsFoldout.boolValue, "Decal Settings", true, FoldoutStyle);
if (DecalsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Decal Settings", "The decal settings gives control over how decal prefabs will be spawned and positioned when an AI is damaged.", true);
if (UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline == null && !self.MessageDismissed)
{
CustomEditorProperties.DisplayImportantMessage("This componenent is intended to be used with URP's or HDRP's decal systems (or if you have your own solution). This component does not create decals, but spawns and positions prefab decals. You will need to ensure decals are enabled through your Render Pipeline Asset.");
if (GUILayout.Button(new GUIContent("Dismiss Message", "Stops this message from being displayed."), GUILayout.Height(20)))
{
self.MessageDismissed = true;
}
GUILayout.Space(15);
}
//if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset.GetType().Name == )
EditorGUILayout.PropertyField(BloodSpawnHeight);
CustomEditorProperties.CustomHelpLabelField("Controls the spawning height of the decal. Additional height can help if decals are clipping through sloped terrains.", true);
EditorGUILayout.PropertyField(BloodSpawnRadius);
CustomEditorProperties.CustomHelpLabelField("Controls the radius in which the decal can be spawned from the AI's position.", true);
EditorGUILayout.PropertyField(BloodSpawnDelay);
CustomEditorProperties.CustomHelpLabelField("Controls the delay (in seconds) for a decal to spawn after being successfully damaged.", true);
EditorGUILayout.PropertyField(BloodDespawnTime);
CustomEditorProperties.CustomHelpLabelField("Controls the length (in seconds) it takes for the decal to despawn.", true);
GUILayout.Space(15);
CustomEditorProperties.CustomHelpLabelField("A list of possible decals that can be spawned.", false);
CustomEditorProperties.BeginIndent(15);
EditorGUILayout.PropertyField(BloodEffects);
CustomEditorProperties.EndIndent();
CustomEditorProperties.EndFoldoutWindowBox();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c657656153adede498c04dabbcf02587
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Events;
namespace EmeraldAI
{
/// <summary>
/// A component for spawning and positioning decals.
/// </summary>
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/decal-component")]
[RequireComponent(typeof(EmeraldEvents))]
public class EmeraldDecals : MonoBehaviour
{
#region Decals Variables
public List<GameObject> BloodEffects = new List<GameObject>();
[Range(0, 3f)]
public float BloodSpawnHeight = 0.33f;
[Range(0, 3f)]
public float BloodSpawnDelay = 0;
[Range(0f, 3f)]
public float BloodSpawnRadius = 0.6f;
[Range(3f, 60f)]
public int BloodDespawnTime = 16;
[Range(1, 100)]
public int OddsForBlood = 100;
EmeraldEvents EmeraldEventsComponent;
EmeraldSystem EmeraldComponent;
#endregion
#region Editor Variables
public bool HideSettingsFoldout;
public bool DecalsFoldout;
public bool MessageDismissed;
#endregion
void Start()
{
Initialize();
}
/// <summary>
/// Initialize the Events Component.
/// </summary>
void Initialize ()
{
EmeraldComponent = GetComponent<EmeraldSystem>();
EmeraldEventsComponent = GetComponent<EmeraldEvents>();
EmeraldEventsComponent.OnTakeDamageEvent.AddListener(() => { CreateBloodSplatter(); });
}
public void CreateBloodSplatter()
{
Invoke("DelayCreateBloodSplatter", BloodSpawnDelay);
}
void DelayCreateBloodSplatter()
{
var Odds = Random.Range(0, 101);
if (Odds <= OddsForBlood && EmeraldComponent != null && !EmeraldComponent.AnimationComponent.IsBlocking)
{
GameObject BloodEffect = EmeraldAI.Utility.EmeraldObjectPool.SpawnEffect(BloodEffects[Random.Range(0, BloodEffects.Count)], transform.position + Random.insideUnitSphere * BloodSpawnRadius, Quaternion.identity, BloodDespawnTime);
BloodEffect.transform.position = new Vector3(BloodEffect.transform.position.x, transform.position.y, BloodEffect.transform.position.z);
BloodEffect.transform.rotation = Quaternion.AngleAxis(Random.Range(55, 125), Vector3.right) * Quaternion.AngleAxis(Random.Range(10, 350), Vector3.forward);
BloodEffect.transform.localScale = Vector3.one * Random.Range(0.8f, 1f);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: adefc3a65ee419b43a955e6c321eec10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -112,7 +112,7 @@ namespace EmeraldAI
if (hit.collider != null)
{
//Get the tag of detected raycast collider.
var StepData = FootstepSurfaces.Find(step => step.SurfaceType == FootstepSurfaceObject.SurfaceTypes.Tag && step.SurfaceTag == hit.collider.tag);
var StepData = FootstepSurfaces.Find(step => step.SurfaceType == FootstepSurfaceObject.SurfaceTypes.Tag && hit.collider.CompareTag(step.SurfaceTag));
//If the StepData is null, check for a terrain. If a terrain is found, get the most dominant texture for the footstep's position.
//Use this texture to determine the Surface Data that should be used for this footstep.

View File

@@ -350,6 +350,7 @@ namespace EmeraldAI
}
m_RigBuilder.enabled = true;
this.enabled = true;
}
/// <summary>
@@ -363,6 +364,7 @@ namespace EmeraldAI
}
m_RigBuilder.enabled = false;
this.enabled = false;
}
}
}

View File

@@ -300,16 +300,44 @@ namespace EmeraldAI
{
for (int i = 0; i < Type1EquippableWeapons.Count; i++)
{
if (Type1EquippableWeapons[i].HeldObject != null) Type1EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
if (Type1EquippableWeapons[i].HolsteredObject != null) Type1EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
if (EmeraldComponent.AnimationComponent.m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip != null && EmeraldComponent.AnimationComponent.m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip != null)
{
if (Type1EquippableWeapons[i].HeldObject != null && Type1EquippableWeapons[i].HolsteredObject != null)
{
Type1EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
Type1EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
}
else
{
if (Type1EquippableWeapons[i].HeldObject) Type1EquippableWeapons[i].HeldObject.SetActive(true); //Enable the held weapon
}
}
else
{
if (Type1EquippableWeapons[i].HeldObject) Type1EquippableWeapons[i].HeldObject.SetActive(true); //Enable the held weapon
}
}
}
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
{
for (int i = 0; i < Type2EquippableWeapons.Count; i++)
{
if (Type2EquippableWeapons[i].HeldObject != null) Type2EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
if (Type2EquippableWeapons[i].HolsteredObject != null) Type2EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
if (EmeraldComponent.AnimationComponent.m_AnimationProfile.Type2Animations.PullOutWeapon.AnimationClip != null && EmeraldComponent.AnimationComponent.m_AnimationProfile.Type2Animations.PutAwayWeapon.AnimationClip != null)
{
if (Type2EquippableWeapons[i].HeldObject != null && Type2EquippableWeapons[i].HolsteredObject != null)
{
Type2EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
Type2EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
}
else
{
if (Type2EquippableWeapons[i].HeldObject) Type2EquippableWeapons[i].HeldObject.SetActive(true); //Enable the held weapon
}
}
else
{
if (Type2EquippableWeapons[i].HeldObject) Type2EquippableWeapons[i].HeldObject.SetActive(true); //Enable the held weapon
}
}
}
}

View File

@@ -89,7 +89,8 @@ namespace EmeraldAI
/// </summary>
void DamageTarget(GameObject Target)
{
var m_MeleeAbility = (MeleeAbility)EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility;
MeleeAbility m_MeleeAbility = EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility as MeleeAbility;
if (m_MeleeAbility != null)
{
Transform TargetRoot = m_MeleeAbility.GetTargetRoot(Target);

View File

@@ -126,11 +126,13 @@ namespace EmeraldAI
/// <summary>
/// Resets the rigidbody and joint components. The helps prevent the ragdoll from becoming unstable after being reused in a different location.
/// </summary>
IEnumerator Reset (LocationBasedDamageClass LBDC)
IEnumerator Reset(LocationBasedDamageClass LBDC)
{
Rigidbody ColliderRigidbody = LBDC.ColliderObject.GetComponent<Rigidbody>();
ColliderRigidbody.useGravity = true;
ColliderRigidbody.isKinematic = true;
ColliderRigidbody.velocity = Vector3.zero;
ColliderRigidbody.angularVelocity = Vector3.zero;
if (SetCollidersLayerAndTag) LBDC.ColliderObject.gameObject.layer = LBDComponentsLayer;
yield return new WaitForSeconds(0.05f);
@@ -140,9 +142,12 @@ namespace EmeraldAI
yield return new WaitForSeconds(0.05f);
Joint ColliderJoint = LBDC.ColliderObject.GetComponent<Joint>();
ColliderJoint.autoConfigureConnectedAnchor = false;
yield return new WaitForSeconds(0.05f);
ColliderJoint.autoConfigureConnectedAnchor = true;
if (ColliderJoint)
{
ColliderJoint.autoConfigureConnectedAnchor = false;
yield return new WaitForSeconds(0.05f);
ColliderJoint.autoConfigureConnectedAnchor = true;
}
yield return new WaitForSeconds(0.05f);
LBDC.ColliderObject.gameObject.SetActive(false);

View File

@@ -77,7 +77,7 @@ namespace EmeraldAI.Utility
if (BehaviorSettingsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Behavior Settings", "Choose from 1 of the 3 available base behavior types. Companion and Pet options are avaialble witin these options by setting a Target to Follow.", true);
CustomEditorProperties.TextTitleWithDescription("Behavior Settings", "Choose from 1 of the 3 available base behavior types. Companion and Pet options are avaialble within these options by setting a Target to Follow.", true);
EditorGUILayout.Space();
EditorGUILayout.Space();

View File

@@ -110,97 +110,9 @@ namespace EmeraldAI.Utility
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 3f, rect.width, EditorGUIUtility.singleLineHeight), element, new GUIContent("Attack Transform " + (index + 1), AttackTransformTooltip));
};
//TODO: Make into function so it can be used with type 1 and type 2
//Type 1 Attacks
Type1Attacks = new ReorderableList(serializedObject, serializedObject.FindProperty("Type1Attacks").FindPropertyRelative("AttackDataList"), true, true, true, true);
Type1Attacks.drawHeaderCallback = rect => {
EditorGUI.LabelField(rect, "Type 1 Attacks", EditorStyles.boldLabel);
};
Type1Attacks.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
var element = Type1Attacks.serializedProperty.GetArrayElementAtIndex(index);
Type1Attacks.elementHeight = EditorGUIUtility.singleLineHeight * 7.5f;
if (self.Type1Attacks.AttackDataList.Count > 0 && EmeraldAnimation.Type1AttackEnumAnimations != null)
{
CustomEditorProperties.CustomListPopup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), new GUIContent(), element.FindPropertyRelative("AttackAnimation"), "Attack Animation", EmeraldAnimation.Type1AttackEnumAnimations);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation", "The animation that will be used for this attack.\n\nNote: Animations are based off of your AI's Attack Animation List within its Animation Profile."));
}
else
{
EditorGUI.Popup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), 0, EmeraldAnimation.Type1AttackBlankOptions);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation"));
}
if (isActive)
{
CurrentAttackDistance = element.FindPropertyRelative("AttackDistance").floatValue;
CurrentTooCloseDistance = element.FindPropertyRelative("TooCloseDistance").floatValue;
DrawDistanceActive = true;
}
if (element.FindPropertyRelative("AttackDistance").floatValue == 0) element.FindPropertyRelative("AttackDistance").floatValue = 2;
if (element.FindPropertyRelative("TooCloseDistance").floatValue == 0) element.FindPropertyRelative("TooCloseDistance").floatValue = 0.5f;
EditorGUI.ObjectField(new Rect(rect.x, rect.y + 35, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AbilityObject"), new GUIContent("Ability Object", "The Ability Object that will be used for this attack."));
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 60, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Distance", "Controls the distance that this attack can happen within."), element.FindPropertyRelative("AttackDistance"), 0.5f, 75f);
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 85, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Too Close Distance", "Controls the distance for when an AI will backup. This is useful for AI keeping their distance from attackers."), element.FindPropertyRelative("TooCloseDistance"), 0f, 35f);
EditorGUI.BeginDisabledGroup(self.Type1Attacks.AttackPickType != AttackPickTypes.Odds);
CustomEditorProperties.CustomListIntSlider(new Rect(rect.x, rect.y + 110, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Odds", "The odds that this attack will be used (when using the Odds Pick type)."), element.FindPropertyRelative("AttackOdds"), 1, 100);
EditorGUI.EndDisabledGroup();
};
Type1Attacks.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 1 Attacks", EditorStyles.boldLabel);
};
//Type 2 Attacks
Type2Attacks = new ReorderableList(serializedObject, serializedObject.FindProperty("Type2Attacks").FindPropertyRelative("AttackDataList"), true, true, true, true);
Type2Attacks.drawHeaderCallback = rect => {
EditorGUI.LabelField(rect, "Type 2 Attacks", EditorStyles.boldLabel);
};
Type2Attacks.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
var element = Type2Attacks.serializedProperty.GetArrayElementAtIndex(index);
Type2Attacks.elementHeight = EditorGUIUtility.singleLineHeight * 7.5f;
if (self.Type2Attacks.AttackDataList.Count > 0 && EmeraldAnimation.Type2AttackEnumAnimations != null)
{
CustomEditorProperties.CustomListPopup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), new GUIContent(), element.FindPropertyRelative("AttackAnimation"), "Attack Animation", EmeraldAnimation.Type2AttackEnumAnimations);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation", "The animation that will be used for this attack.\n\nNote: Animations are based off of your AI's Attack Animation List within its Animation Profile."));
}
else
{
EditorGUI.Popup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), 0, EmeraldAnimation.Type1AttackBlankOptions);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation"));
}
if (isActive)
{
CurrentAttackDistance = element.FindPropertyRelative("AttackDistance").floatValue;
CurrentTooCloseDistance = element.FindPropertyRelative("TooCloseDistance").floatValue;
DrawDistanceActive = true;
}
EditorGUI.ObjectField(new Rect(rect.x, rect.y + 35, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AbilityObject"), new GUIContent("Ability Object", "The Ability Object that will be used for this attack."));
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 60, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Distance", "Controls the distance that this attack can happen within"), element.FindPropertyRelative("AttackDistance"), 0.5f, 75f);
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 85, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Too Close Distance", "Controls the distance for when an AI will backup. This is useful for AI keeping their distance from attackers."), element.FindPropertyRelative("TooCloseDistance"), 0f, 35f);
EditorGUI.BeginDisabledGroup(self.Type2Attacks.AttackPickType != AttackPickTypes.Odds);
CustomEditorProperties.CustomListIntSlider(new Rect(rect.x, rect.y + 110, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Odds", "The odds that this attack will be used (when using the Odds Pick type)."), element.FindPropertyRelative("AttackOdds"), 1, 100);
EditorGUI.EndDisabledGroup();
};
Type2Attacks.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 2 Attacks", EditorStyles.boldLabel);
};
DrawType1Attacks(self);
DrawType2Attacks(self);
//Type 1 Action List
Type1ActionsList = new ReorderableList(serializedObject, serializedObject.FindProperty("Type1CombatActions"), true, true, true, true);
@@ -545,6 +457,139 @@ namespace EmeraldAI.Utility
DrawCombatRadii(self);
}
void DrawType1Attacks(EmeraldCombat self)
{
//Type 1 Attacks
Type1Attacks = new ReorderableList(serializedObject, serializedObject.FindProperty("Type1Attacks").FindPropertyRelative("AttackDataList"), true, true, true, true);
Type1Attacks.drawHeaderCallback = rect => {
EditorGUI.LabelField(rect, "Type 1 Attacks", EditorStyles.boldLabel);
};
Type1Attacks.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
var element = Type1Attacks.serializedProperty.GetArrayElementAtIndex(index);
if (self.Type1Attacks.AttackDataList[index].AbilityObject != null && self.Type1Attacks.AttackDataList[index].AbilityObject.ConditionSettings.Enabled)
{
Rect helpBoxRect = new Rect(rect.x, rect.y + 133, rect.width, EditorGUIUtility.singleLineHeight * 1.5f);
GUIContent content = new GUIContent(" Condition Module Active (See tooltip)", EditorGUIUtility.IconContent("console.warnicon").image, "Abilities that use a Condition Module will need to have their condition met in order to be triggered. " +
"Conditions that are High Priorty will ignore an AI's Pick Type and be picked first, given the condition is met.\n\nNote: If the condition is not met, this ability will be skipped.");
EditorGUI.LabelField(helpBoxRect, content, EditorStyles.label);
}
if (self.Type1Attacks.AttackDataList.Count > 0 && EmeraldAnimation.Type1AttackEnumAnimations != null)
{
CustomEditorProperties.CustomListPopup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), new GUIContent(), element.FindPropertyRelative("AttackAnimation"), "Attack Animation", EmeraldAnimation.Type1AttackEnumAnimations);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation", "The animation that will be used for this attack.\n\nNote: Animations are based off of your AI's Attack Animation List within its Animation Profile."));
}
else
{
EditorGUI.Popup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), 0, EmeraldAnimation.Type1AttackBlankOptions);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation"));
}
if (isActive)
{
CurrentAttackDistance = element.FindPropertyRelative("AttackDistance").floatValue;
CurrentTooCloseDistance = element.FindPropertyRelative("TooCloseDistance").floatValue;
DrawDistanceActive = true;
}
if (element.FindPropertyRelative("AttackDistance").floatValue == 0) element.FindPropertyRelative("AttackDistance").floatValue = 2;
if (element.FindPropertyRelative("TooCloseDistance").floatValue == 0) element.FindPropertyRelative("TooCloseDistance").floatValue = 0.5f;
EditorGUI.ObjectField(new Rect(rect.x, rect.y + 35, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AbilityObject"), new GUIContent("Ability Object", "The Ability Object that will be used for this attack."));
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 60, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Distance", "Controls the distance that this attack can happen within."), element.FindPropertyRelative("AttackDistance"), 0.5f, 75f);
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 85, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Too Close Distance", "Controls the distance for when an AI will backup. This is useful for AI keeping their distance from attackers."), element.FindPropertyRelative("TooCloseDistance"), 0f, 35f);
EditorGUI.BeginDisabledGroup(self.Type1Attacks.AttackPickType != AttackPickTypes.Odds);
CustomEditorProperties.CustomListIntSlider(new Rect(rect.x, rect.y + 110, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Odds", "The odds that this attack will be used (when using the Odds Pick type)."), element.FindPropertyRelative("AttackOdds"), 1, 100);
EditorGUI.EndDisabledGroup();
};
//Modify the heights of each element to create a cleaner reorderable list. This allows the element with a condition module active to have room to display the icon and info.
Type1Attacks.elementHeightCallback = (int index) =>
{
SerializedProperty element = Type1Attacks.serializedProperty.GetArrayElementAtIndex(index);
float height = EditorGUIUtility.singleLineHeight * 7.5f;
if (self.Type1Attacks.AttackDataList[index].AbilityObject && self.Type1Attacks.AttackDataList[index].AbilityObject.ConditionSettings.Enabled)
height += 26f;
return height;
};
Type1Attacks.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 1 Attacks", EditorStyles.boldLabel);
};
}
void DrawType2Attacks(EmeraldCombat self)
{
//Type 2 Attacks
Type2Attacks = new ReorderableList(serializedObject, serializedObject.FindProperty("Type2Attacks").FindPropertyRelative("AttackDataList"), true, true, true, true);
Type2Attacks.drawHeaderCallback = rect => {
EditorGUI.LabelField(rect, "Type 2 Attacks", EditorStyles.boldLabel);
};
Type2Attacks.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
var element = Type2Attacks.serializedProperty.GetArrayElementAtIndex(index);
if (self.Type2Attacks.AttackDataList[index].AbilityObject != null && self.Type2Attacks.AttackDataList[index].AbilityObject.ConditionSettings.Enabled)
{
Rect helpBoxRect = new Rect(rect.x, rect.y + 133, rect.width, EditorGUIUtility.singleLineHeight * 1.5f);
GUIContent content = new GUIContent(" Condition Module Active (See tooltip)", EditorGUIUtility.IconContent("console.warnicon").image, "Abilities that use a Condition Module will need to have their condition met in order to be triggered. " +
"Conditions that are High Priorty will ignore an AI's Pick Type and be picked first, given the condition is met.\n\nNote: If the condition is not met, this ability will be skipped.");
EditorGUI.LabelField(helpBoxRect, content, EditorStyles.label);
}
if (self.Type2Attacks.AttackDataList.Count > 0 && EmeraldAnimation.Type2AttackEnumAnimations != null)
{
CustomEditorProperties.CustomListPopup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), new GUIContent(), element.FindPropertyRelative("AttackAnimation"), "Attack Animation", EmeraldAnimation.Type2AttackEnumAnimations);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation", "The animation that will be used for this attack.\n\nNote: Animations are based off of your AI's Attack Animation List within its Animation Profile."));
}
else
{
EditorGUI.Popup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), 0, EmeraldAnimation.Type1AttackBlankOptions);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation"));
}
if (isActive)
{
CurrentAttackDistance = element.FindPropertyRelative("AttackDistance").floatValue;
CurrentTooCloseDistance = element.FindPropertyRelative("TooCloseDistance").floatValue;
DrawDistanceActive = true;
}
EditorGUI.ObjectField(new Rect(rect.x, rect.y + 35, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AbilityObject"), new GUIContent("Ability Object", "The Ability Object that will be used for this attack."));
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 60, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Distance", "Controls the distance that this attack can happen within"), element.FindPropertyRelative("AttackDistance"), 0.5f, 75f);
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 85, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Too Close Distance", "Controls the distance for when an AI will backup. This is useful for AI keeping their distance from attackers."), element.FindPropertyRelative("TooCloseDistance"), 0f, 35f);
EditorGUI.BeginDisabledGroup(self.Type2Attacks.AttackPickType != AttackPickTypes.Odds);
CustomEditorProperties.CustomListIntSlider(new Rect(rect.x, rect.y + 110, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Odds", "The odds that this attack will be used (when using the Odds Pick type)."), element.FindPropertyRelative("AttackOdds"), 1, 100);
EditorGUI.EndDisabledGroup();
};
//Modify the heights of each element to create a cleaner reorderable list. This allows the element with a condition module active to have room to display the icon and info.
Type2Attacks.elementHeightCallback = (int index) =>
{
SerializedProperty element = Type2Attacks.serializedProperty.GetArrayElementAtIndex(index);
float height = EditorGUIUtility.singleLineHeight * 7.5f;
if (self.Type2Attacks.AttackDataList[index].AbilityObject && self.Type2Attacks.AttackDataList[index].AbilityObject.ConditionSettings.Enabled)
height += 26f;
return height;
};
Type2Attacks.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 2 Attacks", EditorStyles.boldLabel);
};
}
void DrawCombatRadii (EmeraldCombat self)
{
if (DrawDistanceActive)

View File

@@ -18,7 +18,7 @@ namespace EmeraldAI.Utility
SerializedProperty UseHitEffectProp;
//Bool
SerializedProperty HideSettingsFoldout, HealthFoldout, HitEffectFoldout, ImmortalProp;
SerializedProperty HideSettingsFoldout, HealthFoldout, HitEffectFoldout, ImmortalProp, AttachHitEffectsProp;
//Float
SerializedProperty HitEffectTimeoutSecondsProp;
@@ -46,6 +46,7 @@ namespace EmeraldAI.Utility
HealthFoldout = serializedObject.FindProperty("HealthFoldout");
HitEffectFoldout = serializedObject.FindProperty("HitEffectFoldout");
ImmortalProp = serializedObject.FindProperty("Immortal");
AttachHitEffectsProp = serializedObject.FindProperty("AttachHitEffects");
//Float
HitEffectTimeoutSecondsProp = serializedObject.FindProperty("HitEffectTimeoutSeconds");
@@ -171,6 +172,9 @@ namespace EmeraldAI.Utility
{
CustomEditorProperties.BeginIndent();
CustomEditorProperties.CustomPropertyField(AttachHitEffectsProp, "Attach Hit Effects", "Controls whether or not the Hit Effects will be attached to the AI when it's damaged.", true);
EditorGUILayout.Space();
CustomEditorProperties.CustomHelpLabelField("The hit effect that will appear when this AI receives damage.", true);
HitEffectsList.DoLayoutList();
EditorGUILayout.Space();

View File

@@ -189,6 +189,9 @@ namespace EmeraldAI
SetWeaponTypeAnimationState();
AIAnimator.SetBool("Idle Active", false);
InitializeWeaponTypeAnimationAndSettings();
InternalDodge = false;
InternalBlock = false;
InternalHit = false;
}
/// <summary>
@@ -318,7 +321,11 @@ namespace EmeraldAI
IEnumerator SetStunned(float StunnedLength)
{
yield return new WaitForSeconds(0.5f);
if (IsDodging || IsDead) yield break; //If this AI is doding or is dead, don't trigger a stun.
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);
@@ -359,7 +366,7 @@ namespace EmeraldAI
//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 or if the CurrentHitAnimationCooldown hasn't passed.
//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;

View File

@@ -97,7 +97,7 @@ namespace EmeraldAI
if (CurrentBehaviorType == BehaviorTypes.Passive)
{
if (gameObject.tag != "Untagged")
if (!gameObject.CompareTag("Untagged"))
{
gameObject.tag = "Untagged";
}
@@ -201,7 +201,9 @@ namespace EmeraldAI
EmeraldComponent.m_NavMeshAgent.SetDestination(transform.position + transform.forward * 2);
}
EmeraldComponent.CombatComponent.UpdateActions(); //Updates an AI's list of Combat Actions, but only while in the Aggressive state (Combat State).
//Updates an AI's list of Combat Actions, but only while in the Aggressive state (Combat State).
//Don't allow Combat Actions to update for 2 seconds after being enabled or spawned to avoid them triggering before the AI is fully initialized.
if (Time.time - EmeraldComponent.TimeSinceEnabled > 2f) EmeraldComponent.CombatComponent.UpdateActions();
Attack(); //Continuously check to see if the conditions are right to trigger an attack, given the AI is in the Aggressive State.
@@ -294,12 +296,21 @@ namespace EmeraldAI
if (EmeraldCombatManager.AllowedToAttack(EmeraldComponent) && EnterConditions && !IsAiming && AttackTimer >= EmeraldComponent.CombatComponent.CurrentAttackCooldown)
{
EmeraldComponent.AnimationComponent.IsMoving = false;
EmeraldComponent.CombatComponent.AdjustCooldowns();
EmeraldComponent.CombatComponent.AttackPosition = EmeraldComponent.CombatTarget.position - transform.position;
EmeraldComponent.CombatComponent.AttackPosition.y = 0;
EmeraldComponent.AnimationComponent.PlayAttackAnimation();
AttackTimer = 0;
EmeraldCombatManager.CheckAttackHeight(EmeraldComponent);
if (!EmeraldComponent.CombatComponent.TargetOutOfHeightRange)
{
EmeraldComponent.AnimationComponent.IsMoving = false;
EmeraldComponent.CombatComponent.AdjustCooldowns();
EmeraldComponent.CombatComponent.AttackPosition = EmeraldComponent.CombatTarget.position - transform.position;
EmeraldComponent.CombatComponent.AttackPosition.y = 0;
EmeraldComponent.AnimationComponent.PlayAttackAnimation();
AttackTimer = 0;
}
else
{
AttackTimer = 0;
}
}
//Cancel the attack if it's triggered and the target is out of range

View File

@@ -90,9 +90,11 @@ namespace EmeraldAI
public float DeathDelayTimer;
public int CurrentAnimationIndex = 0;
public bool TargetDetectionActive;
public bool TargetOutOfHeightRange;
public float TooCloseDistance = 1;
public float AttackDistance = 2.5f;
public EmeraldAbilityObject CurrentEmeraldAIAbility;
public List<AttackClass.AttackData> AvailableConditionAbilities = new List<AttackClass.AttackData>();
EmeraldSystem EmeraldComponent;
public AttackClass.AttackData CurrentAttackData;
public Transform LastAttacker;
@@ -327,7 +329,7 @@ namespace EmeraldAI
DeathDelay = Random.Range(MinResumeWander, MaxResumeWander + 1);
DeathDelayActive = true;
EmeraldComponent.m_NavMeshAgent.ResetPath();
Invoke(nameof(ClearTarget), 0.75f);
Invoke(nameof(ClearTarget), 0.01f); //Was 0.75
}
}
}

View File

@@ -35,6 +35,8 @@ namespace EmeraldAI
public ObstructedTypes ObstructionType = ObstructedTypes.None;
public List<Collider> LineOfSightTargets = new List<Collider>();
public List<Transform> CurrentFollowers = new List<Transform>();
public List<EmeraldSystem> NearbyAllies = new List<EmeraldSystem>();
public List<EmeraldSystem> LowHealthAllies = new List<EmeraldSystem>();
public delegate void OnDetectTargetHandler();
public event OnDetectTargetHandler OnDetectionUpdate;
public delegate void OnEnemyTargetDetectedHandler();
@@ -104,6 +106,7 @@ namespace EmeraldAI
EmeraldComponent = GetComponent<EmeraldSystem>();
EmeraldComponent.CombatComponent.OnExitCombat += ReturnToDefaultState; //Subscribe the ReturnToDefaultState function to the OnExitCombat delegate
EmeraldComponent.HealthComponent.OnDeath += ClearTargetToFollow; //Subscribe the RemoveTargetToFollow function to the OnDeath delegate
EmeraldComponent.HealthComponent.OnDeath += NearbyAllyDeathHandler; //Subscribe the NearbyAllyDeathHandler function to the OnDeath delegate
OnNullTarget += NullNonCombatTarget; //Subscribe the NullNonCombatTarget function to the OnNullTarget delegate
if (FactionData == null) FactionData = Resources.Load("Faction Data") as EmeraldFactionData;
@@ -263,6 +266,8 @@ namespace EmeraldAI
{
if (LineOfSightTargets.Count > 0) LineOfSightTargetsDistanceCheck();
if (NearbyAllies.Count > 0) NearbyAlliesDistanceCheck();
Collider[] CurrentlyDetectedTargets = Physics.OverlapSphere(transform.position, DetectionRadius, DetectionLayerMask);
foreach (Collider C in CurrentlyDetectedTargets)
@@ -282,11 +287,23 @@ namespace EmeraldAI
if (IgnoredTargetsList.Contains(Target))
return;
if (Target != EmeraldComponent.TargetToFollow && !CurrentFollowers.Contains(Target) && IsEnemyTarget(Target) && EmeraldComponent.BehaviorsComponent.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Passive)
//Clean up
//if (Target != EmeraldComponent.TargetToFollow && !CurrentFollowers.Contains(Target) && IsEnemyTarget(Target) && EmeraldComponent.BehaviorsComponent.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Passive)
//if (Target != EmeraldComponent.TargetToFollow && !CurrentFollowers.Contains(Target) && EmeraldComponent.BehaviorsComponent.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Passive)
if (EmeraldComponent.BehaviorsComponent.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Passive)
{
CurrentDetectionState = DetectionStates.Alert;
if (!LineOfSightTargets.Contains(Target.GetComponent<Collider>()))
LineOfSightTargets.Add(Target.GetComponent<Collider>());
if (IsEnemyTarget(Target))
{
CurrentDetectionState = DetectionStates.Alert;
if (!LineOfSightTargets.Contains(Target.GetComponent<Collider>()))
LineOfSightTargets.Add(Target.GetComponent<Collider>());
}
//Get nearby allies (Allies can only be other AI).
else if (IsFriendlyTarget(Target))
{
EmeraldSystem AllyEmeraldComponent = Target.GetComponent<EmeraldSystem>();
if (AllyEmeraldComponent && !NearbyAllies.Contains(AllyEmeraldComponent)) NearbyAllies.Add(AllyEmeraldComponent);
}
}
if (EmeraldComponent.LookAtTarget == null && EmeraldComponent.CombatTarget == null)
@@ -319,7 +336,9 @@ namespace EmeraldAI
float angle = Vector3.Angle(new Vector3(direction.x, 0, direction.z), transform.forward);
//Only check targets that are within the AI's line of sight.
if (angle < FieldOfViewAngle * 0.5f)
//if (angle < FieldOfViewAngle * 0.5f)
float fieldOfView = EmeraldComponent.CombatComponent.CombatState ? 360f : FieldOfViewAngle * 0.5f;
if (angle < fieldOfView)
{
if (!EmeraldComponent.CombatComponent.CombatState)
{
@@ -334,9 +353,10 @@ namespace EmeraldAI
}
}
}
else if (EmeraldComponent.CombatComponent.CombatState)
else if (EmeraldComponent.CombatComponent.CombatState && LineOfSightTargets.Count > 0)
{
SearchForTarget(PickTargetType);
break;
}
}
}
@@ -360,7 +380,7 @@ namespace EmeraldAI
{
if (hit.collider != null && LineOfSightTargets.Contains(hit.collider))
{
if (!VisibleTargets.Contains(hit.collider.transform) && EmeraldComponent.CombatTarget != hit.collider.transform || hit.collider.CompareTag("Player"))
if (!VisibleTargets.Contains(hit.collider.transform) && EmeraldComponent.CombatTarget != hit.collider.transform || hit.collider.CompareTag(PlayerTag))
{
VisibleTargets.Add(hit.collider.transform);
}
@@ -495,8 +515,8 @@ namespace EmeraldAI
{
for (int i = 0; i < LineOfSightTargets.Count; i++)
{
//Remove any targets that become null during the distance check.
if (LineOfSightTargets[i] == null)
//Remove any targets that become null or who's dead during the distance check.
if (LineOfSightTargets[i] == null || LineOfSightTargets[i].GetComponent<IDamageable>().Health <=0 )
{
LineOfSightTargets.RemoveAt(i);
}
@@ -511,6 +531,29 @@ namespace EmeraldAI
}
}
/// <summary>
/// Check that each LineOfSightTarget is within the AI's DetectionRadius. If not, remove it from the list.
/// </summary>
void NearbyAlliesDistanceCheck()
{
for (int i = 0; i < NearbyAllies.Count; i++)
{
//Remove any targets that become null during the distance check.
if (NearbyAllies[i] == null)
{
NearbyAllies.RemoveAt(i);
}
else
{
float distance = Vector3.Distance(NearbyAllies[i].transform.position, transform.position);
//If the distance of the detected friendly is greater than the DetectionRadius, remove it from the NearbyAllies list.
if (distance > DetectionRadius)
NearbyAllies.Remove(NearbyAllies[i]);
}
}
}
/// <summary>
/// Check that the LookAtTarget is within the AI's DetectionRadius. If not, remove it as the current LookAtTarget.
/// </summary>
@@ -536,6 +579,20 @@ namespace EmeraldAI
GetTargetInfo(EmeraldComponent.LookAtTarget);
}
/// <summary>
/// Removes this AI from all of its allies NearbyAllies lists (called through the OnDeath callback).
/// </summary>
void NearbyAllyDeathHandler ()
{
if (NearbyAllies.Count > 0)
{
for (int i = 0; i < NearbyAllies.Count; i++)
{
if (NearbyAllies[i].DetectionComponent.NearbyAllies.Contains(EmeraldComponent)) NearbyAllies[i].DetectionComponent.NearbyAllies.Remove(EmeraldComponent);
}
}
}
/// <summary>
/// Gets the Faction Relation name of the passed target and this AI in the form of a string (Enemy, Neutral, or Friendly). If a faction cannot be found, or if it is not a valid target, you will receive a value of Invalid Target.
/// </summary>
@@ -576,7 +633,7 @@ namespace EmeraldAI
if (Target == EmeraldComponent.CombatTarget) EmeraldComponent.CombatComponent.ClearTarget();
if (EmeraldComponent.CombatComponent.CombatState) EmeraldComponent.CombatComponent.DeathDelayActive = true;
//EmeraldComponent.m_NavMeshAgent.stoppingDistance = EmeraldComponent.MovementComponent.FollowingStoppingDistance;
EmeraldComponent.TargetToFollow = Target;
EmeraldComponent.BehaviorsComponent.TargetToFollow = Target;
EmeraldComponent.BehaviorsComponent.ResetState();
@@ -584,6 +641,37 @@ namespace EmeraldAI
EmeraldComponent.MovementComponent.WanderType = EmeraldMovement.WanderTypes.Stationary;
}
/// <summary>
/// Initializes a summoned target.
/// </summary>
public void InitializeSummonTarget(Transform Target, bool CopyFactionData = true)
{
EmeraldSystem TargetEmeraldComponent = Target.GetComponent<EmeraldSystem>(); //Attempt to get the Target's EmeraldComponent
if (TargetEmeraldComponent != null)
{
if (TargetEmeraldComponent.CombatTarget == transform) TargetEmeraldComponent.CombatComponent.ClearTarget(); //If the Target is another AI, clear its targets
TargetEmeraldComponent.DetectionComponent.CurrentFollowers.Add(transform); //Add this AI as a follower of the leader AI
//Copies the Target to Follow's Faction Data so it will react the same way the follower does to detected targets.
if (CopyFactionData)
{
CurrentFaction = TargetEmeraldComponent.DetectionComponent.CurrentFaction; //Make the Current Faction the same as the AI's new Target to Follow
AIFactionsList = TargetEmeraldComponent.DetectionComponent.AIFactionsList; //Make the Faction List the same as the AI's new Target to Follow
FactionRelations = TargetEmeraldComponent.DetectionComponent.FactionRelations; //Make the Faction Relations the same as the AI's new Target to Follow
FactionRelationsList = TargetEmeraldComponent.DetectionComponent.FactionRelationsList; //Make the Faction Relations List the same as the AI's new Target to Follow
}
}
if (Target == EmeraldComponent.CombatTarget) EmeraldComponent.CombatComponent.ClearTarget();
EmeraldComponent.TargetToFollow = Target;
EmeraldComponent.BehaviorsComponent.TargetToFollow = Target;
EmeraldComponent.BehaviorsComponent.ResetState();
EmeraldComponent.MovementComponent.CurrentMovementState = EmeraldMovement.MovementStates.Run;
EmeraldComponent.MovementComponent.WanderType = EmeraldMovement.WanderTypes.Stationary;
Invoke(nameof(UpdateAIDetection), 0.1f); //Manually update UpdateAIDetection in case something happens before the Detection Update tick.
}
/// <summary>
/// Clears the AI's Target to Follow transform so it will be no longer following it. This will also stop the AI from being a Companion AI.
/// </summary>
@@ -618,6 +706,15 @@ namespace EmeraldAI
return AIFactionsList.Contains(ReceivedFaction) && FactionRelations[AIFactionsList.IndexOf(ReceivedFaction)] == 0;
}
/// <summary>
/// Returns true if the currently passed transform is an enemy target.
/// </summary>
bool IsFriendlyTarget(Transform Target)
{
int ReceivedFaction = Target.GetComponent<IFaction>().GetFaction();
return AIFactionsList.Contains(ReceivedFaction) && FactionRelations[AIFactionsList.IndexOf(ReceivedFaction)] == 2 || ReceivedFaction == CurrentFaction;
}
/// <summary>
/// Returns true if the currently passed transform is a valid look at target.
/// </summary>

View File

@@ -16,6 +16,7 @@ namespace EmeraldAI
public int CurrentHealth = 50;
public int StartingHealth = 50;
public int HealRate = 0;
public bool AttachHitEffects = false;
public bool Immortal = false;
public List<string> CurrentActiveEffects;
public bool HitEffectFoldout;
@@ -37,6 +38,8 @@ namespace EmeraldAI
public event DeathHandler OnDeath;
public delegate void HealRateTickHandler();
public event HealRateTickHandler OnHealRateTick;
public delegate void OnHealingReceivedHandler();
public event OnHealingReceivedHandler OnHealingReceived;
public delegate void HealthChangeHandler();
public event HealthChangeHandler OnHealthChange;
EmeraldSystem EmeraldComponent;
@@ -55,7 +58,7 @@ namespace EmeraldAI
{
CurrentHealth = StartingHealth;
EmeraldComponent = GetComponent<EmeraldSystem>();
EmeraldComponent.CombatComponent.OnExitCombat += StartHealing; //Subscribe to the OnExitCombat event for StartHealing
EmeraldComponent.CombatComponent.OnExitCombat += RecoverHealth; //Subscribe to the OnExitCombat event for RecoverHealth
}
/// <summary>
@@ -182,21 +185,23 @@ namespace EmeraldAI
/// </summary>
public void KillAI()
{
EmeraldAPI.Combat.KillAI(EmeraldComponent);
EmeraldComponent.CombatComponent.ReceivedRagdollForceAmount = 1;
Health = 0;
Death();
}
/// <summary>
/// Called through the OnExitCombat callback when an AI exits combat. This heals the AI over time according to its HealRate.
/// </summary>
void StartHealing ()
void RecoverHealth ()
{
StartCoroutine(StartHealingInternal());
StartCoroutine(RecoverHealthInternal());
}
/// <summary>
/// Called through the StartHealing function which increases the AI's health each HealRate. This gets canceled if the target enters combat.
/// </summary>
IEnumerator StartHealingInternal ()
IEnumerator RecoverHealthInternal()
{
float t = 0;
@@ -219,6 +224,16 @@ namespace EmeraldAI
CurrentHealth = StartingHealth;
}
public void UpdateHealTick ()
{
OnHealRateTick?.Invoke();
}
public void UpdateHealingReceived()
{
OnHealingReceived?.Invoke();
}
/// <summary>
/// Updates the AI's Max Health and Current Health.
/// </summary>
@@ -240,7 +255,7 @@ namespace EmeraldAI
if (RandomBloodEffect != null)
{
GameObject SpawnedBlood = EmeraldObjectPool.SpawnEffect(RandomBloodEffect, Vector3.zero, EmeraldComponent.transform.rotation, EmeraldComponent.HealthComponent.HitEffectTimeoutSeconds) as GameObject;
SpawnedBlood.transform.SetParent(EmeraldComponent.transform);
if (AttachHitEffects) SpawnedBlood.transform.SetParent(EmeraldComponent.transform);
SpawnedBlood.transform.position = EmeraldComponent.CombatComponent.DamagePosition() + EmeraldComponent.HealthComponent.HitEffectPosOffset;
}
}

View File

@@ -27,7 +27,7 @@ namespace EmeraldAI
{
#region Target Info
//Since these are evenly used across multiple components, these are kept in the main Emerald System script.
[HideInInspector] public Transform CombatTarget;
public Transform CombatTarget;
[HideInInspector] public Transform TargetToFollow;
[HideInInspector] public Transform LookAtTarget;
[HideInInspector] [SerializeField] public CurrentTargetInfoClass CurrentTargetInfo = null;
@@ -46,6 +46,7 @@ namespace EmeraldAI
[HideInInspector] public NavMeshAgent m_NavMeshAgent;
[HideInInspector] public BoxCollider AIBoxCollider;
[HideInInspector] public Animator AIAnimator;
[HideInInspector] public float TimeSinceEnabled;
#endregion
#region AI Components
@@ -94,6 +95,8 @@ namespace EmeraldAI
void OnEnable()
{
TimeSinceEnabled = Time.time; //Time stamp when the AI was enabled.
//When the AI is enabled, and it has been killed, reset the AI to its default settings.
//This is intended for being used with Object Pooling or spawning systems such as Crux.
if (AnimationComponent.IsDead)

View File

@@ -2,11 +2,54 @@
using UnityEditor;
using UnityEditor.Animations;
using System.Linq;
using System.IO;
namespace EmeraldAI.Utility
{
public class EmeraldAnimatorGenerator
{
/// <summary>
/// Creates a unique copy of the passed Animation Profile and clears the copy's Animator Controller.
/// </summary>
public static void CopyAnimationProfile (AnimationProfile AnimationProfileRef)
{
var SerializedReferenceAP = new SerializedObject(AnimationProfileRef);
SerializedReferenceAP.Update();
//Get the path of the selected asset
string assetPath = AssetDatabase.GetAssetPath(AnimationProfileRef);
string assetDirectory = Path.GetDirectoryName(assetPath);
string assetName = Path.GetFileNameWithoutExtension(assetPath);
string assetExtension = Path.GetExtension(assetPath);
//Create a unique path for the duplicate asset
string duplicateAssetPath = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(assetDirectory, assetName + " Copy" + assetExtension));
//Duplicate the asset
AssetDatabase.CopyAsset(assetPath, duplicateAssetPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
//Select the duplicated asset in the Project window
UnityEngine.Object duplicateAsset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(duplicateAssetPath);
Selection.activeObject = duplicateAsset;
//Clear the copy's Animator Controller.
AnimationProfile AnimationProfileCopy = AssetDatabase.LoadAssetAtPath<AnimationProfile>(duplicateAssetPath);
var SerializedCopyAP = new SerializedObject(AnimationProfileCopy);
SerializedCopyAP.Update();
AnimationProfileCopy.AIAnimator = null;
AnimationProfileCopy.AnimatorControllerGenerated = false;
AnimationProfileCopy.FilePath = "";
SerializedCopyAP.ApplyModifiedProperties();
SerializedReferenceAP.ApplyModifiedProperties();
EditorUtility.SetDirty(AnimationProfileCopy);
AssetDatabase.Refresh();
}
/// <summary>
/// Generates an Animator Controller using the passed EmeraldAnimation values. This is done by searching each state by name and applying the proper animation, animation speed, and mirroring values.
/// </summary>

View File

@@ -27,6 +27,16 @@ namespace EmeraldAI
EmeraldComponent.DetectionComponent.SetTargetToFollow(Target, CopyFactionData);
}
/// <summary>
/// Initializes a summoned target.
/// </summary>
/// <param name="EmeraldComponent"> The AI's EmeraldSystem of the summoned AI.</param>
/// <param name="Target"> The target the summoned AI will follow and protect.</param>
public static void InitializeSummonTarget(EmeraldSystem EmeraldComponent, Transform Target)
{
EmeraldComponent.DetectionComponent.InitializeSummonTarget(Target, true);
}
/// <summary>
/// Clears the AI's Target to Follow transform so it will be no longer following it. This will also stop the AI from being a Companion or Pet AI.
/// </summary>
@@ -100,6 +110,23 @@ namespace EmeraldAI
/// </summary>
public class Combat
{
/// <summary>
/// Overrides the AI's current ability and attack distance so the ability can be used when called (given within distance).
/// </summary>
/// <param name="EmeraldComponent">The AI's EmeraldSystem who will use this API call.</param>
/// <param name="AbilityObject">The Ability Object that will be used and override the AI's current one.</param>
/// <param name="AttackDistance">The new distance needed to trigger the ability. If high enough, the AI will trigger it right from their current position.</param>
/// <param name="AttackAnimationIndex">The AI's attack animation index (from an AI's Animation Profile's Attack List). If the attack animation index does not exist within the AI's Attack List, there will be an error.</param>
public static void TriggerAbility(EmeraldSystem EmeraldComponent, EmeraldAbilityObject AbilityObject, int AttackDistance, int AttackAnimationIndex)
{
EmeraldComponent.CombatComponent.CurrentAnimationIndex = AttackAnimationIndex;
EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility = AbilityObject;
EmeraldComponent.CombatComponent.CancelAllCombatActions();
EmeraldComponent.CombatComponent.AttackDistance = AttackDistance;
EmeraldComponent.m_NavMeshAgent.stoppingDistance = AttackDistance;
EmeraldComponent.AnimationComponent.PlayAttackAnimation();
}
/// <summary>
/// Instantly kills this AI.
/// </summary>
@@ -108,7 +135,7 @@ namespace EmeraldAI
{
if (!EmeraldComponent.AnimationComponent.IsDead)
{
EmeraldComponent.HealthComponent.Damage(9999999);
EmeraldComponent.HealthComponent.KillAI();
}
}

View File

@@ -17,7 +17,7 @@ namespace EmeraldAI.Utility
Coroutine C;
EmeraldSystem EmeraldComponent;
EmeraldUI EmeraldUI;
EmeraldHealth EmeraldHeath;
EmeraldHealth EmeraldHealth;
Coroutine CoroutineTransitionDamage;
#endregion
@@ -34,7 +34,7 @@ namespace EmeraldAI.Utility
//Since the transform order of the health bar is always the same, get the parent's parent to get all need Emerald AI Components.
canvas = GetComponent<Canvas>();
EmeraldUI = transform.parent.parent.GetComponent<EmeraldUI>();
EmeraldHeath = transform.parent.parent.GetComponent<EmeraldHealth>();
EmeraldHealth = transform.parent.parent.GetComponent<EmeraldHealth>();
EmeraldComponent = transform.parent.parent.GetComponent<EmeraldSystem>();
if (m_Camera == null) m_Camera = GameObject.FindGameObjectWithTag(EmeraldUI.CameraTag).GetComponent<Camera>(); //Get a reference to the camera via the EmeraldUI.CameraTag.
@@ -44,11 +44,11 @@ namespace EmeraldAI.Utility
AINameUI = transform.Find("AI Name Text").GetComponent<Text>();
AILevelUI = transform.Find("AI Level Text").GetComponent<Text>();
EmeraldHeath.OnDeath += FadeOutUI; //Subscribe FadeOutUI to the OnDeath delegate.
EmeraldHeath.OnTakeDamage += TransitionDamage; //Subscribe TransitionDamage to the OnTakeDamage delegate.
EmeraldHeath.OnTakeCritDamage += TransitionDamage; //Subscribe TransitionDamage to the OnTakeCritDamage delegate.
EmeraldHeath.OnHealRateTick += TransitionHealing; //Subscribe TransitionHealing to the OnHealRateTick delegate.
EmeraldHeath.OnHealthChange += UpdateHealthUI; //Subscribe UpdateHealthUI to the OnHealthChange delegate.
EmeraldHealth.OnDeath += FadeOutUI; //Subscribe FadeOutUI to the OnDeath delegate.
EmeraldHealth.OnTakeDamage += TransitionDamage; //Subscribe TransitionDamage to the OnTakeDamage delegate.
EmeraldHealth.OnTakeCritDamage += TransitionDamage; //Subscribe TransitionDamage to the OnTakeCritDamage delegate.
EmeraldHealth.OnHealRateTick += TransitionHealing; //Subscribe TransitionHealing to the OnHealRateTick delegate.
EmeraldHealth.OnHealthChange += UpdateHealthUI; //Subscribe UpdateHealthUI to the OnHealthChange delegate.
}
void Update()
@@ -97,7 +97,7 @@ namespace EmeraldAI.Utility
IEnumerator FadeOutUIInternal(float DesiredValue, float TransitionTime)
{
HealthBar.fillAmount = ((float)EmeraldHeath.Health / (float)EmeraldHeath.StartHealth);
HealthBar.fillAmount = ((float)EmeraldHealth.Health / (float)EmeraldHealth.StartHealth);
float alpha = CG.alpha;
float t = 0;
@@ -123,14 +123,14 @@ namespace EmeraldAI.Utility
}
else
{
HealthBar.fillAmount = ((float)EmeraldHeath.Health / (float)EmeraldHeath.StartHealth);
HealthBar.fillAmount = ((float)EmeraldHealth.Health / (float)EmeraldHealth.StartHealth);
HealthBarDamage.fillAmount = HealthBar.fillAmount;
}
}
IEnumerator TransitionDamageInternal ()
{
HealthBar.fillAmount = ((float)EmeraldHeath.Health / (float)EmeraldHeath.StartHealth);
HealthBar.fillAmount = ((float)EmeraldHealth.Health / (float)EmeraldHealth.StartHealth);
float Start = HealthBarDamage.fillAmount;
float t = 0;
yield return new WaitForSeconds(0.75f);
@@ -151,7 +151,7 @@ namespace EmeraldAI.Utility
}
else
{
HealthBar.fillAmount = ((float)EmeraldHeath.Health / (float)EmeraldHeath.StartHealth);
HealthBar.fillAmount = ((float)EmeraldHealth.Health / (float)EmeraldHealth.StartHealth);
HealthBarDamage.fillAmount = HealthBar.fillAmount;
}
}
@@ -161,13 +161,13 @@ namespace EmeraldAI.Utility
/// </summary>
void UpdateHealthUI ()
{
HealthBar.fillAmount = ((float)EmeraldHeath.Health / (float)EmeraldHeath.StartHealth);
HealthBar.fillAmount = ((float)EmeraldHealth.Health / (float)EmeraldHealth.StartHealth);
HealthBarDamage.fillAmount = HealthBar.fillAmount;
}
IEnumerator TransitionHealingInternal()
{
float HealAmount = ((float)EmeraldHeath.Health / (float)EmeraldHeath.StartHealth);
float HealAmount = ((float)EmeraldHealth.Health / (float)EmeraldHealth.StartHealth);
float Start = HealthBar.fillAmount;
float t = 0;

View File

@@ -0,0 +1,10 @@
namespace EmeraldAI
{
public enum ConditionTypes
{
SelfLowHealth,
AllyLowHealth,
DistanceFromTarget,
NoCurrentSummons,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c80c9cdf23a2d6c4a8f4206b5aa48945
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -18,7 +18,7 @@ namespace EmeraldAI
if (DamageMultiplier > 1) DamageDealt = Mathf.RoundToInt(DamageAmount * DamageMultiplier);
IDamageable m_IDamageable = EmeraldComponent.GetComponent<IDamageable>();
m_IDamageable.Damage(DamageDealt, AttackerTransform, RagdollForce, CriticalHit);
if (!EmeraldComponent.AnimationComponent.IsBlocking) CreateImpactEffect(transform.position, true);
if (!EmeraldComponent.AnimationComponent.IsBlocking) CreateImpactEffect(transform.position, EmeraldComponent.HealthComponent.AttachHitEffects);
if (m_IDamageable.Health <= 0)
EmeraldComponent.CombatComponent.RagdollTransform = transform;
}

View File

@@ -31,13 +31,22 @@ namespace EmeraldAI.Utility
{
if (!AvailableAttacks.Contains(SentAttackClass.AttackDataList[i]))
{
AvailableAttacks.Add(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 && 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.");
@@ -56,10 +65,59 @@ namespace EmeraldAI.Utility
}
else if (SentAttackClass.AttackPickType == AttackPickTypes.Order) //Pick an ability by going through the list in order
{
SetAttackValues(EmeraldComponent, SentAttackClass.AttackDataList[SentAttackClass.AttackListIndex], true);
float CooldownTime = 0;
AttackClass.AttackData AbilityDataRef = SentAttackClass.AttackDataList[SentAttackClass.AttackListIndex];
SentAttackClass.AttackListIndex++;
if (SentAttackClass.AttackListIndex == SentAttackClass.AttackDataList.Count) SentAttackClass.AttackListIndex = 0;
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
{
@@ -70,25 +128,80 @@ namespace EmeraldAI.Utility
//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)
{
//TODO: This section will be improved with an update.
/*
float min = SentAttackClass.AttackDataList.Min(attack => attack.TooCloseDistance);
var ClosestAttacks = SentAttackClass.AttackDataList.Where(attack => attack.TooCloseDistance == min).ToList();
var RandomCloseAttack = ClosestAttacks[Random.Range(0, ClosestAttacks.Count)];
Debug.Log(EmeraldComponent.name + " " + ClosestAttacks.Count);
int AttackIndex = SentAttackClass.AttackDataList.FindIndex(attack => attack == RandomCloseAttack);
SentAttackClass.AttackListIndex = AttackIndex;
EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility = SentAttackClass.AttackDataList[AttackIndex].AbilityObject;
EmeraldComponent.CombatComponent.CurrentAnimationIndex = SentAttackClass.AttackDataList[AttackIndex].AttackAnimation;
SetAttackValues(EmeraldComponent, AvailableAttacks[AttackIndex], true);
*/
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>
@@ -215,8 +328,7 @@ namespace EmeraldAI.Utility
/// </summary>
public static void DisableComponents(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.GetComponent<SoundDetection.EmeraldSoundDetector>() != null)
EmeraldComponent.GetComponent<SoundDetection.EmeraldSoundDetector>().enabled = false;
if (EmeraldComponent.SoundDetectorComponent != null) EmeraldComponent.SoundDetectorComponent.enabled = false;
if (EmeraldComponent.OptimizationComponent != null && EmeraldComponent.OptimizationComponent.m_VisibilityCheck != null)
{
@@ -272,8 +384,7 @@ namespace EmeraldAI.Utility
/// </summary>
public static void EnableComponents(EmeraldSystem EmeraldComponent)
{
if (EmeraldComponent.GetComponent<SoundDetection.EmeraldSoundDetector>() != null)
EmeraldComponent.GetComponent<SoundDetection.EmeraldSoundDetector>().enabled = true;
if (EmeraldComponent.SoundDetectorComponent != null) EmeraldComponent.SoundDetectorComponent.enabled = true;
if (EmeraldComponent.OptimizationComponent != null && EmeraldComponent.OptimizationComponent.m_VisibilityCheck != null)
{
@@ -281,9 +392,8 @@ namespace EmeraldAI.Utility
EmeraldComponent.OptimizationComponent.m_VisibilityCheck.enabled = true;
}
if (EmeraldComponent.GetComponent<EmeraldInverseKinematics>() != null) EmeraldComponent.GetComponent<EmeraldInverseKinematics>().EnableInverseKinematics();
if (EmeraldComponent.LBDComponent != null) EmeraldComponent.LBDComponent.ResetLBDComponents();
if (EmeraldComponent.ItemsComponent != null) EmeraldComponent.ItemsComponent.ResetSettings();
if (EmeraldComponent.InverseKinematicsComponent != null) EmeraldComponent.InverseKinematicsComponent.EnableInverseKinematics();
EmeraldComponent.m_NavMeshAgent.enabled = true;
EmeraldComponent.AIBoxCollider.enabled = true;
EmeraldComponent.DetectionComponent.enabled = true;
@@ -293,7 +403,9 @@ namespace EmeraldAI.Utility
EmeraldComponent.BehaviorsComponent.enabled = true;
EmeraldComponent.AIAnimator.enabled = true;
EmeraldComponent.AIAnimator.Rebind();
EmeraldComponent.AnimationComponent.InitializeWeaponTypeAnimationAndSettings();
EmeraldComponent.AnimationComponent.ResetSettings();
if (EmeraldComponent.LBDComponent != null) EmeraldComponent.LBDComponent.ResetLBDComponents();
if (EmeraldComponent.ItemsComponent != null) EmeraldComponent.ItemsComponent.ResetSettings();
}
/// <summary>
@@ -404,14 +516,47 @@ namespace EmeraldAI.Utility
/// </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;
if (!WithinStoppingDistanceOfTarget(EmeraldComponent) || !WithinDistanceOfTarget(EmeraldComponent)) return false;
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;
if (!EmeraldComponent.CombatComponent.TargetWithinAngleLimit() || EmeraldComponent.CurrentTargetInfo.CurrentIDamageable.Health <= 0) return false;
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>

View File

@@ -321,36 +321,41 @@ namespace EmeraldAI
var m_ICombat = Target.GetComponentInParent<ICombat>();
//If stuns are enabled, roll for a stun
if (CurrentAbilityData.StunnedSettings.Enabled && CurrentAbilityData.StunnedSettings.RollForStun())
//Do not allow Friendlies to be damaged or stunned
bool IsFriendlyTarget = EmeraldAPI.Faction.GetTargetFactionName(m_ICombat.TargetTransform()) == EmeraldAPI.Faction.GetTargetFactionName(EmeraldComponent.transform) || EmeraldAPI.Faction.GetTargetFactionRelation(EmeraldComponent, m_ICombat.TargetTransform()) == "Friendly";
if (!IsFriendlyTarget)
{
if (m_ICombat != null) m_ICombat.TriggerStun(CurrentAbilityData.StunnedSettings.StunLength);
}
//If stuns are enabled, roll for a stun
if (CurrentAbilityData.StunnedSettings.Enabled && CurrentAbilityData.StunnedSettings.RollForStun())
{
if (m_ICombat != null) m_ICombat.TriggerStun(CurrentAbilityData.StunnedSettings.StunLength);
}
//Only cause damage if it's enabled
if (!CurrentAbilityData.DamageSettings.Enabled) return;
//Only cause damage if it's enabled
if (!CurrentAbilityData.DamageSettings.Enabled) return;
if (m_LocationBasedDamageArea == null)
{
var m_IDamageable = Target.GetComponent<IDamageable>();
if (m_IDamageable != null)
if (m_LocationBasedDamageArea == null)
{
var m_IDamageable = Target.GetComponent<IDamageable>();
if (m_IDamageable != null)
{
bool IsCritHit = CurrentAbilityData.DamageSettings.GenerateCritHit();
m_IDamageable.Damage(CurrentAbilityData.DamageSettings.GenerateDamage(IsCritHit), Owner.transform, CurrentAbilityData.DamageSettings.BaseDamageSettings.RagdollForce, IsCritHit);
CurrentAbilityData.DamageSettings.DamageTargetOverTime(CurrentAbilityData, CurrentAbilityData.DamageSettings, Owner, Target);
m_AudioSource.Stop();
}
else
{
Debug.Log(Target.gameObject + " is missing IDamageable Component, apply one");
}
}
else if (m_LocationBasedDamageArea != null)
{
bool IsCritHit = CurrentAbilityData.DamageSettings.GenerateCritHit();
m_IDamageable.Damage(CurrentAbilityData.DamageSettings.GenerateDamage(IsCritHit), Owner.transform, CurrentAbilityData.DamageSettings.BaseDamageSettings.RagdollForce, IsCritHit);
CurrentAbilityData.DamageSettings.DamageTargetOverTime(CurrentAbilityData, CurrentAbilityData.DamageSettings, Owner, Target);
m_LocationBasedDamageArea.DamageArea(CurrentAbilityData.DamageSettings.GenerateDamage(IsCritHit), Owner.transform, CurrentAbilityData.DamageSettings.BaseDamageSettings.RagdollForce, IsCritHit);
CurrentAbilityData.DamageSettings.DamageTargetOverTime(CurrentAbilityData, CurrentAbilityData.DamageSettings, Owner, m_ICombat.TargetTransform().gameObject);
m_AudioSource.Stop();
}
else
{
Debug.Log(Target.gameObject + " is missing IDamageable Component, apply one");
}
}
else if (m_LocationBasedDamageArea != null)
{
bool IsCritHit = CurrentAbilityData.DamageSettings.GenerateCritHit();
m_LocationBasedDamageArea.DamageArea(CurrentAbilityData.DamageSettings.GenerateDamage(IsCritHit), Owner.transform, CurrentAbilityData.DamageSettings.BaseDamageSettings.RagdollForce, IsCritHit);
CurrentAbilityData.DamageSettings.DamageTargetOverTime(CurrentAbilityData, CurrentAbilityData.DamageSettings, Owner, m_ICombat.TargetTransform().gameObject);
m_AudioSource.Stop();
}
if (CurrentAbilityData.AerialProjectileSettings.AttachToTarget)

View File

@@ -46,7 +46,7 @@ namespace EmeraldAI
}
/// <summary>
/// Damaes the projectile's StartingTarget, given that it has a IDamageable.
/// Damages the projectile's StartingTarget, given that it has a IDamageable.
/// </summary>
void DamageTarget(GameObject Target)
{

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: faae736a8c3155044907390e8ec8c91d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,195 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EmeraldAI.Utility;
using System.Linq;
namespace EmeraldAI
{
[CreateAssetMenu(fileName = "Healing Ability", menuName = "Emerald AI/Ability/Healing Ability")]
public class HealingAbility : EmeraldAbilityObject
{
public AbilityData.ChargeSettingsData ChargeSettings;
public AbilityData.CreateSettingsData CreateSettings;
public AbilityData.HealingData HealingSettings;
public override void ChargeAbility(GameObject Owner, Transform AttackTransform = null)
{
ChargeSettings.SpawnChargeEffect(Owner, AttackTransform);
}
public override void InvokeAbility(GameObject Owner, Transform AttackTransform = null)
{
MonoBehaviour OwnerMonoBehaviour = Owner.GetComponent<MonoBehaviour>();
Transform Target = GetTarget(Owner, AbilityData.TargetTypes.CurrentTarget);
CreateSettings.SpawnCreateEffect(Owner, AttackTransform);
OwnerMonoBehaviour.StartCoroutine(StartHeals(Owner, AttackTransform));
}
IEnumerator StartHeals (GameObject Owner, Transform AttackTransform = null)
{
yield return new WaitForSeconds(HealingSettings.Delay);
Vector3 EffectPosition = new Vector3(AttackTransform.position.x, AttackTransform.position.y, AttackTransform.position.z) + Vector3.up * HealingSettings.EffectHeightOffset;
GameObject SpawnedEffect = HealingSettings.SpawnHealingEffect(Owner, EffectPosition, HealingSettings.HealingEffect, HealingSettings.HealingEffectTimeoutSeconds, HealingSettings.HealingSoundsList);
if (HealingSettings.TargetType == AbilityData.HealingData.TargetTypes.Area) IntitailizeAreaHealing(Owner.GetComponent<EmeraldSystem>(), AttackTransform);
else if (HealingSettings.TargetType == AbilityData.HealingData.TargetTypes.Self) IntitailizeSelfHealing(Owner.GetComponent<EmeraldSystem>(), AttackTransform);
else if (HealingSettings.TargetType == AbilityData.HealingData.TargetTypes.Target) IntitailizeTargetHealing(Owner.GetComponent<EmeraldSystem>(), AttackTransform);
}
void IntitailizeAreaHealing(EmeraldSystem OwnerEmeraldComponent, Transform AttackTransform)
{
OwnerEmeraldComponent.DetectionComponent.LowHealthAllies.Clear();
OwnerEmeraldComponent.DetectionComponent.LowHealthAllies.Add(OwnerEmeraldComponent); //Add the caster to the list of allies to heal
for (int i = 0; i < OwnerEmeraldComponent.DetectionComponent.NearbyAllies.Count; i++)
{
//Only heal targets in range
if (Vector3.Distance(OwnerEmeraldComponent.transform.position, OwnerEmeraldComponent.DetectionComponent.NearbyAllies[i].transform.position) < HealingSettings.Radius)
{
OwnerEmeraldComponent.DetectionComponent.LowHealthAllies.Add(OwnerEmeraldComponent.DetectionComponent.NearbyAllies[i]);
}
}
for (int i = 0; i < OwnerEmeraldComponent.DetectionComponent.LowHealthAllies.Count; i++)
{
EmeraldSystem TargetEmeraldComponent = OwnerEmeraldComponent.DetectionComponent.LowHealthAllies[i].GetComponent<EmeraldSystem>();
if (TargetEmeraldComponent != null)
{
//Only damage targets that the Owner has an Enemy Relation Type with.
if (EmeraldAPI.Faction.GetTargetFactionRelation(OwnerEmeraldComponent, OwnerEmeraldComponent.DetectionComponent.LowHealthAllies[i].transform) == "Friendly" ||
EmeraldAPI.Faction.GetTargetFactionName(OwnerEmeraldComponent.DetectionComponent.LowHealthAllies[i].transform) == EmeraldAPI.Faction.GetTargetFactionName(OwnerEmeraldComponent.transform))
{
if (HealingSettings.HealTargetEffect != null)
{
EmeraldObjectPool.SpawnEffect(HealingSettings.HealTargetEffect, OwnerEmeraldComponent.DetectionComponent.LowHealthAllies[i].GetComponent<ICombat>().DamagePosition(), OwnerEmeraldComponent.DetectionComponent.LowHealthAllies[i].transform.rotation, HealingSettings.HealTargetEffectTimeoutSeconds);
}
HealTarget(TargetEmeraldComponent);
}
}
}
}
void IntitailizeSelfHealing(EmeraldSystem OwnerEmeraldComponent, Transform AttackTransform)
{
if (HealingSettings.HealTargetEffect != null)
{
EmeraldObjectPool.SpawnEffect(HealingSettings.HealTargetEffect, OwnerEmeraldComponent.GetComponent<ICombat>().DamagePosition(), OwnerEmeraldComponent.transform.rotation, HealingSettings.HealTargetEffectTimeoutSeconds);
}
HealTarget(OwnerEmeraldComponent);
}
void IntitailizeTargetHealing(EmeraldSystem OwnerEmeraldComponent, Transform AttackTransform)
{
OwnerEmeraldComponent.DetectionComponent.LowHealthAllies.Clear();
for (int i = 0; i < OwnerEmeraldComponent.DetectionComponent.NearbyAllies.Count; i++)
{
//Only look for AI that are not dead.
if (!OwnerEmeraldComponent.DetectionComponent.NearbyAllies[i].AnimationComponent.IsDead)
{
OwnerEmeraldComponent.DetectionComponent.LowHealthAllies.Add(OwnerEmeraldComponent.DetectionComponent.NearbyAllies[i]);
}
}
//Search through nearby allies and only heal the ally target with the lowest health
if (OwnerEmeraldComponent.DetectionComponent.LowHealthAllies.Count > 0)
{
EmeraldSystem TargetEmeraldComponent = null;
float lowestHealth = float.MaxValue;
foreach (var Ally in OwnerEmeraldComponent.DetectionComponent.LowHealthAllies)
{
float AllyHealth = (float)Ally.HealthComponent.CurrentHealth / (float)Ally.HealthComponent.StartingHealth;
if (AllyHealth < lowestHealth)
{
lowestHealth = AllyHealth;
TargetEmeraldComponent = Ally;
}
}
if (TargetEmeraldComponent && HealingSettings.HealTargetEffect != null)
{
EmeraldObjectPool.SpawnEffect(HealingSettings.HealTargetEffect, TargetEmeraldComponent.GetComponent<ICombat>().DamagePosition(), TargetEmeraldComponent.transform.rotation, HealingSettings.HealTargetEffectTimeoutSeconds);
}
if (TargetEmeraldComponent)
{
HealTarget(TargetEmeraldComponent);
}
}
}
/// <summary>
/// Heals each detected friendly target within the AI's healing radius.
/// </summary>
void HealTarget(EmeraldSystem TargetEmeraldComponent)
{
HealAI(TargetEmeraldComponent, HealingSettings.BaseHealAmount);
if (HealingSettings.HealingType == AbilityData.HealingData.HealingTypes.OverTime)
{
HealAIOverTime(TargetEmeraldComponent);
}
}
/// <summary>
/// Heals an AI instantly according to the HealAmount. This is also called through healing abilities to heal an AI.
/// </summary>
public void HealAI(EmeraldSystem TargetEmeraldComponent, int HealAmount)
{
EmeraldHealth HealthRef = TargetEmeraldComponent.HealthComponent;
HealthRef.CurrentHealth = HealthRef.CurrentHealth + HealAmount;
//Don't allow the heals to heal more than the AI's Starting Health.
if (HealthRef.CurrentHealth >= HealthRef.StartingHealth) HealthRef.CurrentHealth = HealthRef.StartingHealth;
CombatTextSystem.Instance.CreateCombatTextAI(HealAmount, TargetEmeraldComponent.CombatComponent.DamagePosition(), false, true);
HealthRef.UpdateHealingReceived();
HealthRef.UpdateHealTick();
}
/// <summary>
/// Heals an AI over time. This is also called through heal over time abilities to heal an AI.
/// </summary>
public void HealAIOverTime(EmeraldSystem TargetEmeraldComponent)
{
TargetEmeraldComponent.GetComponent<MonoBehaviour>().StartCoroutine(HealAIOverTimeInternal(TargetEmeraldComponent));
}
IEnumerator HealAIOverTimeInternal(EmeraldSystem TargetEmeraldComponent)
{
float t = 0;
float LapsedTime = 0;
EmeraldHealth HealthRef = TargetEmeraldComponent.HealthComponent;
HealthRef.UpdateHealingReceived();
while (LapsedTime <= HealingSettings.HealOverTimeLength)
{
t += Time.deltaTime;
LapsedTime += Time.deltaTime;
if (t >= HealingSettings.TickRate)
{
HealthRef.CurrentHealth = HealthRef.CurrentHealth + HealingSettings.HealsPerTick;
Vector3 RefPosition = TargetEmeraldComponent.CombatComponent.DamagePosition();
CombatTextSystem.Instance.CreateCombatTextAI(HealingSettings.HealsPerTick, RefPosition, false, true);
HealingSettings.SpawnHealingEffect(TargetEmeraldComponent.gameObject, RefPosition, HealingSettings.HealTargetEffect, HealingSettings.HealTargetEffectTimeoutSeconds, HealingSettings.HealTickSounds);
HealthRef.UpdateHealTick();
t = 0;
}
//Stop healing over time if the healing target dies.
if (TargetEmeraldComponent.HealthComponent.CurrentHealth <= 0) yield break;
//Don't allow the heals to heal more than the AI's Starting Health.
if (HealthRef.CurrentHealth >= HealthRef.StartingHealth) HealthRef.CurrentHealth = HealthRef.StartingHealth;
yield return null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a935740a62e270749bc63b5d2a643bc4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fab798d12765658448a96e75b9eb42dc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EmeraldAI.Utility;
using System.Linq;
namespace EmeraldAI
{
[CreateAssetMenu(fileName = "Summon Ability", menuName = "Emerald AI/Ability/Summon Ability")]
public class SummonAbility : EmeraldAbilityObject
{
public AbilityData.ChargeSettingsData ChargeSettings;
public AbilityData.CreateSettingsData CreateSettings;
public AbilityData.SummonData SummonSettings;
public override void ChargeAbility(GameObject Owner, Transform AttackTransform = null)
{
ChargeSettings.SpawnChargeEffect(Owner, AttackTransform);
}
public override void InvokeAbility(GameObject Owner, Transform AttackTransform = null)
{
MonoBehaviour OwnerMonoBehaviour = Owner.GetComponent<MonoBehaviour>();
CreateSettings.SpawnCreateEffect(Owner, AttackTransform);
OwnerMonoBehaviour.StartCoroutine(IntitailizeSummon(Owner, OwnerMonoBehaviour, AttackTransform));
}
IEnumerator IntitailizeSummon(GameObject Owner, MonoBehaviour OwnerMonoBehaviour, Transform AttackTransform = null)
{
yield return new WaitForSeconds(SummonSettings.SummonDelay);
Vector3 EffectPosition = new Vector3(AttackTransform.position.x, AttackTransform.position.y, AttackTransform.position.z);
SummonSettings.SpawnEffect(Owner, EffectPosition, SummonSettings.CastEffect, SummonSettings.CastEffectTimeoutSeconds, SummonSettings.CastSounds, false);
for (int i = 0; i < SummonSettings.SummonAmount; i++)
{
//Calculate the angle for each object
float angle = i * Mathf.PI * 2f / SummonSettings.SummonAmount;
//Get the summon position (depending on the setting)
Vector3 SummonPosition = Vector3.zero;
if (SummonSettings.SummonPosition == AbilityData.SummonData.SummonPositions.Self) SummonPosition = Owner.transform.position;
else if (SummonSettings.SummonPosition == AbilityData.SummonData.SummonPositions.Target)
{
EmeraldSystem EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
if (EmeraldComponent.CombatTarget != null) SummonPosition = EmeraldComponent.CombatTarget.position;
else SummonPosition = Owner.transform.position; //If for some reason the CombatTarget is null, fallback to summoning around the caster.
}
//Calculate the position based on the angle
Vector3 SpawnPosition = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * SummonSettings.SummonRadius + SummonPosition;
//Get a random index from the AIPrefabs list
int RandomIndex = Random.Range(0, SummonSettings.AIPrefabs.Count);
//Spawn the AI prefab - Skipp the summon sound after the first index to avoid too many sounds playing at once.
if (i == 0) OwnerMonoBehaviour.StartCoroutine(SummonAlly(Owner, RandomIndex, SpawnPosition, false));
else OwnerMonoBehaviour.StartCoroutine(SummonAlly(Owner, RandomIndex, SpawnPosition, true));
}
}
/// <summary>
/// Summons an AI for the caster.
/// </summary>
IEnumerator SummonAlly(GameObject Owner, int RandomIndex, Vector3 SpawnPosition, bool SkipSummonSound)
{
yield return new WaitForSeconds(SummonSettings.SummonDelay);
EmeraldSystem AllyEmeraldComponent = EmeraldObjectPool.Spawn(SummonSettings.AIPrefabs[RandomIndex], SpawnPosition, Quaternion.identity).GetComponent<EmeraldSystem>();
yield return new WaitForSeconds(0.01f);
EmeraldAPI.Detection.InitializeSummonTarget(AllyEmeraldComponent, Owner.transform);
SummonSettings.SpawnEffect(Owner, AllyEmeraldComponent.GetComponent<ICombat>().DamagePosition() + (Vector3.up * SummonSettings.SummonEffectHeightOffset), SummonSettings.SummonEffect, SummonSettings.SummonEffectTimeoutSeconds, SummonSettings.SummonSounds, SkipSummonSound);
if (SummonSettings.IsTimedSummon)
{
yield return new WaitForSeconds(SummonSettings.SummonLength);
EmeraldAPI.Combat.KillAI(AllyEmeraldComponent);
}
if (SummonSettings.IsTimedSummon)
{
yield return new WaitForSeconds(SummonSettings.DespawnLength);
EmeraldObjectPool.Despawn(AllyEmeraldComponent.gameObject);
}
else
{
yield return new WaitUntil(() => AllyEmeraldComponent.AnimationComponent.IsDead);
yield return new WaitForSeconds(SummonSettings.DespawnLength);
EmeraldObjectPool.Despawn(AllyEmeraldComponent.gameObject);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5da1d82dcb6e5849886825764f8df9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -66,11 +66,13 @@ namespace EmeraldAI
}
}
Vector3 GetTeleportPosition (GameObject Owner)
Vector3 GetTeleportPosition(GameObject Owner)
{
//Generate a random position (within 180 degrees) behind the specified target within the set radius.
float RandomDegree = Random.Range(0f, 1f);
Vector3 TargetPosition = GetTarget(Owner, AbilityData.TargetTypes.CurrentTarget).position;
Transform Target = GetTarget(Owner, AbilityData.TargetTypes.CurrentTarget);
if (Target == null) Target = Owner.transform;
Vector3 TargetPosition = Target.position;
Vector3 TeleportPosition = Owner.transform.position;
bool TeleportRight = Random.Range(0f, 1f) <= 0.5f;

View File

@@ -124,6 +124,9 @@ namespace EmeraldAI
[Tooltip("Controls the max distance allowed to deal damage with this ability.\n\nNote: If your melee attack animation is using Weapon Collision Events, " +
"this setting will be ignored and this ability will rely on a successful collision from the weapon instead.")]
public float MaxDamageDistance = 4;
[Range(1, 20)]
[Tooltip("Controls the max allowed height the AI can attack with this ability.\n\nNote: If this height is exceeded, the AI will wait to attack the current target until they are within range.")]
public float MaxAttackHeight = 5;
[Range(5, 360)]
[Tooltip("Controls the max angle allowed to deal damage with this ability.\n\nNote: If your melee attack animation is using Weapon Collision Events, " +
"this setting will be ignored and this ability will rely on a successful collision from the weapon instead.")]
@@ -742,6 +745,214 @@ namespace EmeraldAI
}
}
[System.Serializable]
public class SummonData
{
[HideInInspector] public bool Foldout = true;
[HideInInspector] public bool Enabled = true;
[Tooltip("Controls the visual effect used when casting the ability.")]
public GameObject CastEffect;
[Range(0.5f, 10)]
[Tooltip("Controls the time (in seconds) the Summon Effect will be disabled after it has been spawned.")]
public float CastEffectTimeoutSeconds = 4;
[Tooltip("Controls the sound that will play when the abiliy is casted.")]
public List<AudioClip> CastSounds = new List<AudioClip>();
[Tooltip("Controls the visual effect used when the summong ability is first created.")]
public GameObject SummonEffect;
[Range(0.5f, 10)]
[Tooltip("Controls the time (in seconds) the Summon Effect will be disabled after it has been spawned.")]
public float SummonEffectTimeoutSeconds = 4;
[Range(-3f, 3f)]
[Tooltip("Controls the Y position offset for the SummonEffect.")]
public float SummonEffectHeightOffset = 0f;
[Tooltip("Controls the sound that will play when an AI is summoned.")]
public List<AudioClip> SummonSounds = new List<AudioClip>();
[Tooltip("Controls how many AI will be summoned when using this ability.")]
[Range(1, 6)]
public int SummonAmount = 1;
public enum SummonPositions { Self, Target };
[Tooltip("Controls the position in which the AI will be summoned to." +
"\n\nSelf - Summons AI around the caster." +
"\n\nTarget - Summons AI around the caster's current target.")]
public SummonPositions SummonPosition = SummonPositions.Self;
[Tooltip("Controls the radius in which summoned AI will spawn around the Summon Position.")]
[Range(2f, 12f)]
public float SummonRadius = 4f;
[Range(0f, 1f)]
[Tooltip("Controls the delay length (in seconds) of when the AI will be summoned after the ability has been casted.")]
public float SummonDelay = 0;
[Tooltip("A list of possible AI prefabs that will be picked when the caster is using this ability. The total amount of AI spawned is determined by the Summon Amount.")]
public List<GameObject> AIPrefabs = new List<GameObject>();
[Tooltip("Controls whether or not this AI will be automatically killed after a certain amount of time.")]
public bool IsTimedSummon = false;
[Range(10, 180)]
[Tooltip("Controls the length (in seconds) until the AI is killed.")]
public int SummonLength = 20;
[Tooltip("Controls whether or not this AI's body (after it has died) will be despawned (through Object Pooling) so it can be reused. The AI object will automatically be reset to its starting state during this process.")]
public bool DespawnAfterKilled = false;
[Range(5, 120)]
[Tooltip("Controls the length (in seconds) until the AI is despawned back to the Object Pool after it has been killed.")]
public int DespawnLength = 20;
/// <summary>
/// A universal function for spawning various sounds and effects for this ability.
/// </summary>
/// <param name="Owner">The owner of the ability.</param>
/// <param name="SpawnPosition">The spawn position for the effect.</param>
public GameObject SpawnEffect(GameObject Owner, Vector3 SpawnPosition, GameObject VisualEffect, float TimeoutSeconds, List<AudioClip> EffectSounds, bool SkipSound)
{
GameObject SpawnedVisualEffect = null;
if (Enabled)
{
if (VisualEffect != null)
{
SpawnedVisualEffect = EmeraldObjectPool.SpawnEffect(VisualEffect, SpawnPosition, VisualEffect.transform.rotation, TimeoutSeconds);
SpawnedVisualEffect.name = VisualEffect.name;
SpawnedVisualEffect.transform.localScale = VisualEffect.transform.localScale;
}
if (EffectSounds.Count > 0 && !SkipSound)
{
AudioClip Clip = EffectSounds[Random.Range(0, EffectSounds.Count)];
if (Clip)
{
AudioSource TempSound = EmeraldObjectPool.SpawnEffect(Resources.Load("Emerald Sound") as GameObject, SpawnPosition, Quaternion.identity, Clip.length).GetComponent<AudioSource>();
TempSound.volume = Random.Range(0.75f, 1f);
TempSound.pitch = Random.Range(0.9f, 1.1f);
TempSound.PlayOneShot(Clip);
}
}
}
return SpawnedVisualEffect;
}
}
[System.Serializable]
public class HealingData
{
[HideInInspector] public bool Foldout = true;
[HideInInspector] public bool Enabled = true;
[Tooltip("Controls the visual effect used when the healing ability is first created.")]
public GameObject HealingEffect;
[Range(0.5f, 15)]
[Tooltip("Controls the time (in seconds) the Healing Effect will be disabled after it has been spawned.")]
public float HealingEffectTimeoutSeconds = 6;
public enum TargetTypes { Self, Target, Area };
[Tooltip("Controls the type of target this ability will affect." +
"\n\nSelf - The healing will only affect the caster." +
"\n\nTarget - The healing will affect a single friendly target with the lowest amount of health within radius of the caster." +
"\n\nArea - The healing will affect any friendly targets within radius of the caster. The layer for this is based off of the caster's Detection Layers from its Detection Component.")]
public TargetTypes TargetType = TargetTypes.Self;
public enum HealingTypes { Instant, OverTime};
[Tooltip("Controls the type of healing this ability will use." +
"\n\nInstant - Instantly heals the target according to the Base Heal Amount." +
"\n\nOver Time - Instantly heals the target according to the Base Heal Amount AND over time based on the amount and rate. " +
"Base Heal Amount can be set to 0 if no initial heal is desired.")]
public HealingTypes HealingType = HealingTypes.Instant;
//Base Heals
[Tooltip("Controls the base healing or initial healing amount for this ability.")]
public int BaseHealAmount = 20;
//Base Heals
//Heals Over Time
[Tooltip("Controls the length (in seconds) how often Heal Over Time is applied.")]
[Range(0.1f, 10f)]
public float TickRate = 1;
[Tooltip("Controls the amount of heals that happens per Tick Rate.")]
public int HealsPerTick = 5;
[Range(0f, 10f)]
[Tooltip("Controls the length (in seconds) the Heal Over Time effect will last.")]
public float HealOverTimeLength = 3;
[Tooltip("Controls the sound that will play each heal tick.")]
public List<AudioClip> HealTickSounds = new List<AudioClip>();
//Heals Over Time
[Range(-5, 5)]
[Tooltip("Controls the height offset the Healing Effect will be spawned.")]
public float EffectHeightOffset = 0;
[Range(1, 20)]
[Tooltip("Controls the healing radius of the healing ability.")]
public float Radius = 3;
[Range(0f, 5f)]
[Tooltip("Controls the delay it takes for the target detection to trigger for area healing.")]
public float Delay = 0;
[Tooltip("The visual effect that will happen when a valid target is healed with this ability.")]
public GameObject HealTargetEffect;
[Range(0.5f, 15)]
public float HealTargetEffectTimeoutSeconds = 2;
[Tooltip("The list of possible sounds that will play when the Visual Effect is spawned.")]
public List<AudioClip> HealingSoundsList = new List<AudioClip>();
/// <summary>
/// A universal function for spawning various sounds and effects for healing abilities.
/// </summary>
/// <param name="Owner">The owner of the ability.</param>
/// <param name="SpawnPosition">The spawn position of the AOE Effect.</param>
public GameObject SpawnHealingEffect(GameObject Owner, Vector3 SpawnPosition, GameObject VisualEffect, float TimeoutSeconds, List<AudioClip> EffectSounds)
{
GameObject SpawnedVisualEffect = null;
if (Enabled)
{
if (VisualEffect != null)
{
SpawnedVisualEffect = EmeraldObjectPool.SpawnEffect(VisualEffect, SpawnPosition, VisualEffect.transform.rotation, TimeoutSeconds);
SpawnedVisualEffect.name = VisualEffect.name;
SpawnedVisualEffect.transform.localScale = VisualEffect.transform.localScale;
}
if (EffectSounds.Count > 0)
{
AudioClip Clip = EffectSounds[Random.Range(0, EffectSounds.Count)];
if (Clip)
{
AudioSource TempSound = EmeraldObjectPool.SpawnEffect(Resources.Load("Emerald Sound") as GameObject, SpawnPosition, Quaternion.identity, Clip.length).GetComponent<AudioSource>();
TempSound.volume = Random.Range(0.75f, 1f);
TempSound.pitch = Random.Range(0.9f, 1.1f);
TempSound.PlayOneShot(Clip);
}
}
}
return SpawnedVisualEffect;
}
}
[System.Serializable]
public class ConditionData
{
[HideInInspector] public bool Foldout = true;
[HideInInspector] public bool Enabled;
[Tooltip("Controls the condition that is needed in order for this ability to trigger. Enable High Priority to allow this ability to be picked before other abilities." +
"\n\nSelf Low Health - Triggers when an AI's own health reaches the Low Health Percentage threshold." +
"\n\nTarget Low Health - Triggers when a nearby Friendly AI reaches the Low Health Percentage threshold." +
"\n\nNo Current Summons - Triggers when an AI has 0 currently summoned AI.")]
public ConditionTypes ConditionType = ConditionTypes.SelfLowHealth;
public enum ValueCompareTypes { GreaterThan, LessThan };
[Tooltip("Controls the type of value comparison.")]
public ValueCompareTypes ValueCompareType = ValueCompareTypes.LessThan;
[Tooltip("Controls whether or not this condition is High Priority. A High Priority ability will take priority over other abilities and will ignore an AI's Pick Type, given the condition is met. This is especially helpful for support abiltiies such as healing." +
"\n\nNote: If High Priority is disabled, this ability will follow an AI's Pick Type, but the condition will need to be met. If the condition is not met, the ability will be skipped.")]
public bool HighPriority = false;
[Range(1, 40)]
[Tooltip("Controls the distance threshold needed to meet the Distance from Target condition.")]
public float DistanceFromTarget = 3f;
[Range(1, 99)]
[Tooltip("Controls the low health amount (in percentage) needed to trigger this condition.")]
public float LowHealthPercentage = 60f;
}
[System.Serializable]
public class ColliderData
{
@@ -802,8 +1013,8 @@ namespace EmeraldAI
public class CooldownData
{
[HideInInspector] public bool Enabled;
[Range(0, 30)]
[Tooltip("Controls the length (in seconds) it will take before this ability can be used again. This is useful to help keep certain abilities from being used too often or in a row when using Random or Odds Pick Types." +
[Range(0, 60)]
[Tooltip("Controls the length (in seconds) it will take before this ability can be used again. This is useful to help keep certain abilities from being used too often or in a row." +
"\n\nNote: This setting is ignored if an ability is overriden through an Create Ability (Animation Event).")]
public float CooldownLength = 1;
@@ -969,7 +1180,7 @@ namespace EmeraldAI
[System.Serializable]
public class DamageOverTimeClass
{
[Tooltip("Controls the effect that is spawned each Amount Per Second.")]
[Tooltip("Controls the effect that is spawned each tick.")]
public GameObject DamageOverTimeEffect;
[Range(0.5f, 5)]
public float OverTimeEffectTimeOutSeconds = 1.5f;
@@ -981,7 +1192,7 @@ namespace EmeraldAI
[Range(0f, 10f)]
[Tooltip("Controls the length (in seconds) the Damage Over Time effect will last.")]
public float DamageOverTimeLength = 3;
[Tooltip("Controls the sound that will play each Amount Per Second.")]
[Tooltip("Controls the sound that will play each tick.")]
public List<AudioClip> OverTimeSounds = new List<AudioClip>();
}
@@ -1033,15 +1244,6 @@ namespace EmeraldAI
}
}
//TODO: Will add with update
[System.Serializable]
public class SummonAllyData
{
public GameObject AIPrefab;
public bool IsTimedSummon;
public int SummonLength;
}
public enum TargetSources { Enemy, Ally }
public enum TargetTypes

View File

@@ -22,6 +22,9 @@ namespace EmeraldAI
/// </summary>
public AbilityData.CooldownData CooldownSettings;
//Testing
public AbilityData.ConditionData ConditionSettings;
public virtual void ChargeAbility(GameObject Owner, Transform AttackTransform = null) { }
public virtual void InvokeAbility(GameObject Owner, Transform AttackTransform = null) { }

View File

@@ -13,12 +13,12 @@ namespace EmeraldAI.Utility
{
GUIStyle FoldoutStyle;
FieldInfo[] CustomFields;
SerializedProperty AbilityName, AbilityIcon, DerivedSettingsFoldout, InfoSettingsFoldout, HideSettingsFoldout, ModularSettingsFoldout, CooldownSettings;
SerializedProperty AbilityName, AbilityIcon, DerivedSettingsFoldout, InfoSettingsFoldout, HideSettingsFoldout, ModularSettingsFoldout, CooldownSettings, ConditionSettings, SummonSettings;
SerializedProperty MeleeSettings, ProjectileSettings, ArrowProjectileSettings, GrenadeSettings, GeneralProjectileSettings, BulletProjectileSettings, AerialProjectileSettings, GroundProjectileSettings, BarrageProjectileSettings, TeleportSettings, HomingSettings, TargetTypeSettings,
SpreadSettings, ColliderSettings, CreateSettings, ChargeSettings, AreaOfEffectSettings, DamageSettings, StunnedSettings;
SpreadSettings, ColliderSettings, CreateSettings, ChargeSettings, AreaOfEffectSettings, DamageSettings, StunnedSettings, HealingSettings;
string ChargeSettingsTooltip = "Allows the ability to play an effect and/or sound when it is charging. The location of this effect is determined by the Attack Transform " +
"name passed through the ChargeEffect animation event. In order to use this setting, it must be set to true.\n\nNote: An ChargeEffect animation event is required for this to trigger. This should be done through the AI's attack animations before the EmeraldAttack animation event is called.";
"name passed through the ChargeEffect animation event. In order to use this setting, it must be set to true.\n\nNote: A ChargeEffect animation event is required for this to trigger. This should be done through the AI's attack animations before the EmeraldAttack animation event is called.";
string CreateSettingsTooltip = "Allows the ability to play an effect and/or sound when it is created. The location of this effect is determined by the Attack Transform " +
"name passed through the CreateAbility. In order to use this setting, it must be set to true.";
string MeleeSettingsTooltip = "Allows an ability to deal damage within the specified angle and distance.\n\nNote: " +
@@ -39,8 +39,12 @@ namespace EmeraldAI.Utility
string AreaOfEffectSettingsTooltip = "Allows an ability to affect the specified area with";
string StunSettingsTooltip = "Allows abilities the chance to stun a successfully hit target.";
string DamageSettingsTooltip = "Allows abilities to deal damage to a successfully hit target.";
string HealSettingsTooltip = "Allows abilities to heal friendly AI targets.";
string GrenadeSettingsTooltip = "Controls the settings for the Grenade Ability.";
string CooldownSettingsTooltip = "Allows abilities to have cooldowns so they can't be used more than what's set by the Cooldown Length amount.\n\nNote: The Cooldown Module only works with the Random and Odds Attack Pick Types (from the Combat Component).";
string CooldownSettingsTooltip = "Allows abilities to have cooldowns so they can't be used more than what's set by the Cooldown Length amount.";
string ConditionSettingsTooltip = "Abilities that use a Condition Module will need to have their condition met in order to be triggered. " +
"Conditions that are High Priorty will ignore an AI's Pick Type and be picked first, given the condition is met.\n\nNote: If the condition is not met, this ability will be skipped.";
string SummonSettingsTooltip = "Controls the settings for the Summong Ability.\n\nNote: The Summon Ability can only spawn Emerald AI agents.";
void OnEnable()
@@ -54,6 +58,7 @@ namespace EmeraldAI.Utility
AbilityName = serializedObject.FindProperty("AbilityName");
AbilityIcon = serializedObject.FindProperty("AbilityIcon");
CooldownSettings = serializedObject.FindProperty("CooldownSettings");
ConditionSettings = serializedObject.FindProperty("ConditionSettings");
MeleeSettings = serializedObject.FindProperty("MeleeSettings");
ProjectileSettings = serializedObject.FindProperty("ProjectileSettings");
BulletProjectileSettings = serializedObject.FindProperty("BulletProjectileSettings");
@@ -73,6 +78,8 @@ namespace EmeraldAI.Utility
AreaOfEffectSettings = serializedObject.FindProperty("AreaOfEffectSettings");
DamageSettings = serializedObject.FindProperty("DamageSettings");
StunnedSettings = serializedObject.FindProperty("StunnedSettings");
HealingSettings = serializedObject.FindProperty("HealingSettings");
SummonSettings = serializedObject.FindProperty("SummonSettings");
//Get all variables that are not part of the parent class.
CustomFields = target.GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
@@ -149,6 +156,10 @@ namespace EmeraldAI.Utility
if (CooldownSettings != null) DrawModule(CooldownSettings, "Cooldown Module", CooldownSettingsTooltip, false);
if (ConditionSettings != null) DrawConditionModule(ConditionSettings, "Condition Module", ConditionSettingsTooltip, false);
if (SummonSettings != null) DrawSummonModule(SummonSettings, "Summon Module", SummonSettingsTooltip, true);
if (MeleeSettings != null) DrawModule(MeleeSettings, "Melee Module", MeleeSettingsTooltip, true);
if (ColliderSettings != null) DrawModule(ColliderSettings, "Collider Module", ColliderSettingsTooltip, true);
@@ -169,6 +180,7 @@ namespace EmeraldAI.Utility
if (SpreadSettings != null) DrawModule(SpreadSettings, "Spread Module", SpreadSettingsTooltip);
if (DamageSettings != null) DrawDamageModule(DamageSettings, "Damage Module", DamageSettingsTooltip);
if (StunnedSettings != null) DrawModule(StunnedSettings, "Stunned Module", StunSettingsTooltip);
if (HealingSettings != null) DrawHealingModule(HealingSettings, "Heal Module", HealSettingsTooltip, true);
foreach (FieldInfo field in CustomFields)
@@ -353,6 +365,204 @@ namespace EmeraldAI.Utility
GUILayout.Space(2.5f);
}
void DrawHealingModule(SerializedProperty property, string Name, string Tooltip, bool Required = false)
{
CustomEditorProperties.BeginFoldoutWindowBox();
GUILayout.BeginHorizontal();
{
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginDisabledGroup(true);
property.FindPropertyRelative("Enabled").boolValue = EditorGUILayout.Toggle(property.FindPropertyRelative("Enabled").boolValue, GUILayout.Width(28));
property.FindPropertyRelative("Foldout").boolValue = EditorGUILayout.Foldout(property.FindPropertyRelative("Foldout").boolValue, new GUIContent(Name, "(Required) " + Tooltip), true);
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
}
GUILayout.Space(5);
GUILayout.EndHorizontal();
if (property.FindPropertyRelative("Foldout").boolValue)
{
CustomEditorProperties.BeginIndent(45);
EditorGUILayout.PropertyField(property.FindPropertyRelative("TargetType"));
GUILayout.Space(2.5f);
CustomEditorProperties.BeginIndent(15);
if (property.FindPropertyRelative("TargetType").intValue == 0) //Self
{
}
else if (property.FindPropertyRelative("TargetType").intValue == 1) //Target
{
EditorGUILayout.PropertyField(property.FindPropertyRelative("Radius"));
}
else if (property.FindPropertyRelative("TargetType").intValue == 2) //Area
{
EditorGUILayout.PropertyField(property.FindPropertyRelative("Radius"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("Delay"));
}
CustomEditorProperties.EndIndent();
GUILayout.Space(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealingEffect"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealingEffectTimeoutSeconds"));
if (property.FindPropertyRelative("TargetType").intValue == 2) //Area
{
EditorGUILayout.PropertyField(property.FindPropertyRelative("EffectHeightOffset"));
}
GUILayout.Space(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealingType"));
GUILayout.Space(2.5f);
CustomEditorProperties.BeginIndent(15);
if (property.FindPropertyRelative("HealingType").intValue == 0)
{
EditorGUILayout.PropertyField(property.FindPropertyRelative("BaseHealAmount"));
}
else if (property.FindPropertyRelative("HealingType").intValue == 1)
{
EditorGUILayout.PropertyField(property.FindPropertyRelative("BaseHealAmount"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealsPerTick"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("TickRate"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealOverTimeLength"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealTickSounds"));
}
CustomEditorProperties.EndIndent();
GUILayout.Space(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealTargetEffect"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealTargetEffectTimeoutSeconds"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("HealingSoundsList"));
CustomEditorProperties.EndIndent();
}
GUILayout.Space(2.5f);
CustomEditorProperties.EndFoldoutWindowBox();
GUILayout.Space(2.5f);
}
void DrawSummonModule(SerializedProperty property, string Name, string Tooltip, bool Required = false)
{
CustomEditorProperties.BeginFoldoutWindowBox();
GUILayout.BeginHorizontal();
{
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginDisabledGroup(true);
property.FindPropertyRelative("Enabled").boolValue = EditorGUILayout.Toggle(property.FindPropertyRelative("Enabled").boolValue, GUILayout.Width(28));
property.FindPropertyRelative("Foldout").boolValue = EditorGUILayout.Foldout(property.FindPropertyRelative("Foldout").boolValue, new GUIContent(Name, "(Required) " + Tooltip), true);
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
}
GUILayout.Space(5);
GUILayout.EndHorizontal();
if (property.FindPropertyRelative("Foldout").boolValue)
{
CustomEditorProperties.BeginIndent(45);
EditorGUILayout.PropertyField(property.FindPropertyRelative("CastEffect"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("CastEffectTimeoutSeconds"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("CastSounds"));
GUILayout.Space(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonEffect"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonEffectTimeoutSeconds"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonEffectHeightOffset"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonSounds"));
GUILayout.Space(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonAmount"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonPosition"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonRadius"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonDelay"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("AIPrefabs"));
GUILayout.Space(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("IsTimedSummon"));
GUILayout.Space(2.5f);
if (property.FindPropertyRelative("IsTimedSummon").boolValue)
{
CustomEditorProperties.BeginIndent(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("SummonLength"));
CustomEditorProperties.EndIndent();
EditorGUILayout.BeginVertical();
GUILayout.Space(5f);
EditorGUILayout.EndVertical();
}
EditorGUILayout.PropertyField(property.FindPropertyRelative("DespawnAfterKilled"));
GUILayout.Space(2.5f);
if (property.FindPropertyRelative("DespawnAfterKilled").boolValue)
{
CustomEditorProperties.BeginIndent(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("DespawnLength"));
CustomEditorProperties.EndIndent();
}
GUILayout.Space(15);
CustomEditorProperties.EndIndent();
}
GUILayout.Space(2.5f);
CustomEditorProperties.EndFoldoutWindowBox();
GUILayout.Space(2.5f);
}
void DrawConditionModule(SerializedProperty property, string Name, string Tooltip, bool Required = false)
{
CustomEditorProperties.BeginFoldoutWindowBox();
GUILayout.BeginHorizontal();
{
EditorGUILayout.BeginHorizontal();
property.FindPropertyRelative("Enabled").boolValue = EditorGUILayout.Toggle(property.FindPropertyRelative("Enabled").boolValue, GUILayout.Width(28));
property.FindPropertyRelative("Foldout").boolValue = EditorGUILayout.Foldout(property.FindPropertyRelative("Foldout").boolValue, new GUIContent(Name, "(Optional) " + Tooltip), true);
EditorGUILayout.EndHorizontal();
}
GUILayout.Space(5);
GUILayout.EndHorizontal();
if (property.FindPropertyRelative("Foldout").boolValue)
{
CustomEditorProperties.BeginIndent(45);
EditorGUILayout.PropertyField(property.FindPropertyRelative("HighPriority"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("ConditionType"));
GUILayout.Space(2.5f);
if ((ConditionTypes)property.FindPropertyRelative("ConditionType").enumValueIndex == ConditionTypes.DistanceFromTarget)
{
CustomEditorProperties.BeginIndent(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("ValueCompareType"));
EditorGUILayout.PropertyField(property.FindPropertyRelative("DistanceFromTarget"));
CustomEditorProperties.EndIndent();
EditorGUILayout.BeginVertical();
GUILayout.Space(5f);
EditorGUILayout.EndVertical();
}
else if ((ConditionTypes)property.FindPropertyRelative("ConditionType").enumValueIndex == ConditionTypes.AllyLowHealth ||
(ConditionTypes)property.FindPropertyRelative("ConditionType").enumValueIndex == ConditionTypes.SelfLowHealth)
{
CustomEditorProperties.BeginIndent(15);
EditorGUILayout.PropertyField(property.FindPropertyRelative("LowHealthPercentage"));
CustomEditorProperties.EndIndent();
EditorGUILayout.BeginVertical();
GUILayout.Space(5f);
EditorGUILayout.EndVertical();
}
CustomEditorProperties.EndIndent();
}
GUILayout.Space(2.5f);
CustomEditorProperties.EndFoldoutWindowBox();
GUILayout.Space(2.5f);
}
void SetModule (SerializedProperty property, bool State)
{
property.FindPropertyRelative("Enabled").boolValue = State;

View File

@@ -708,7 +708,7 @@ namespace EmeraldAI.Utility
"Hit animations will be blended with the Stunned Animation while in this state (this feature can be disabled by excluding the Stunned state from the Hit Conditions below).", 2, false, false);
CustomEditorProperties.CustomPropertyField(Type1HitConditionsProp, "Hit Conditions", "Controls which states are allowed to be canceled to play a hit animation. Note: Dodge and Equipping are automatically excluded.", false);
CustomEditorProperties.CustomFloatSliderPropertyField(Type1HitAnimationCooldownProp, "Hit Animation Cooldown", "Controls the time (in seconds) that need to pass in order to play another hit animation.", 0f, 1f, true);
CustomEditorProperties.CustomFloatSliderPropertyField(Type1HitAnimationCooldownProp, "Hit Animation Cooldown", "Controls the time (in seconds) that need to pass in order to play another hit animation.", 0f, 4f, true);
EditorGUILayout.LabelField("Combat Hit Animations", EditorStyles.boldLabel);
CustomEditorProperties.CustomHelpLabelField("Controls the animations that will play when an AI receives damage when not in combat.", false);
@@ -742,7 +742,7 @@ namespace EmeraldAI.Utility
"Hit animations will be blended with the Stunned Animation while in this state (this feature can be disabled by excluding the Stunned state from the Hit Conditions below).", 2, false, false);
CustomEditorProperties.CustomPropertyField(Type2HitConditionsProp, "Hit Conditions", "Controls which states are allowed to be canceled to play a hit animation. Note: Dodge and Equipping are automatically excluded.", false);
CustomEditorProperties.CustomFloatSliderPropertyField(Type2HitAnimationCooldownProp, "Hit Animation Cooldown", "Controls the time (in seconds) that need to pass in order to play another hit animation.", 0f, 1f, true);
CustomEditorProperties.CustomFloatSliderPropertyField(Type2HitAnimationCooldownProp, "Hit Animation Cooldown", "Controls the time (in seconds) that need to pass in order to play another hit animation.", 0f, 4f, true);
EditorGUILayout.LabelField("Combat Hit Animations", EditorStyles.boldLabel);
CustomEditorProperties.CustomHelpLabelField("Controls the animations that will play when an AI receives damage when not in combat.", false);
@@ -1012,6 +1012,7 @@ namespace EmeraldAI.Utility
CustomEditorProperties.CustomHelpLabelField("Use this to copy all non-combat animations (idles, turns, movement, hits, and death) so the same animations don't have to be reassigned as the combat animations." +
" Note: This will only work for the Type 1 Weapon Type and is practical for AI who use the same animations for the non-combat animations and their Type 1 Combat Animations.", true);
CopyAnimationProfileButton(self);
RegenerateAnimatorControllerButton(self);
ClearAnimatorControllerButton(self);
@@ -1147,6 +1148,12 @@ namespace EmeraldAI.Utility
self.Type1Animations.HitList.Add(new AnimationClass(self.NonCombatAnimations.HitList[i].AnimationSpeed, self.NonCombatAnimations.HitList[i].AnimationClip, self.NonCombatAnimations.HitList[i].Mirror));
}
self.Type1Animations.DeathList.Clear();
for (int i = 0; i < self.NonCombatAnimations.DeathList.Count; i++)
{
self.Type1Animations.DeathList.Add(new AnimationClass(self.NonCombatAnimations.DeathList[i].AnimationSpeed, self.NonCombatAnimations.DeathList[i].AnimationClip, self.NonCombatAnimations.DeathList[i].Mirror));
}
serializedObject.Update();
AnimationsUpdatedProp.boolValue = true;
serializedObject.ApplyModifiedProperties();
@@ -1177,6 +1184,12 @@ namespace EmeraldAI.Utility
self.Type2Animations.HitList.Add(new AnimationClass(self.NonCombatAnimations.HitList[i].AnimationSpeed, self.NonCombatAnimations.HitList[i].AnimationClip, self.NonCombatAnimations.HitList[i].Mirror));
}
self.Type2Animations.DeathList.Clear();
for (int i = 0; i < self.NonCombatAnimations.DeathList.Count; i++)
{
self.Type2Animations.DeathList.Add(new AnimationClass(self.NonCombatAnimations.DeathList[i].AnimationSpeed, self.NonCombatAnimations.DeathList[i].AnimationClip, self.NonCombatAnimations.DeathList[i].Mirror));
}
serializedObject.Update();
AnimationsUpdatedProp.boolValue = true;
serializedObject.ApplyModifiedProperties();
@@ -1197,6 +1210,19 @@ namespace EmeraldAI.Utility
self.EmeraldAnimationComponent.AIAnimator.runtimeAnimatorController = self.EmeraldAnimationComponent.m_AnimationProfile.AIAnimator;
}
void CopyAnimationProfileButton(AnimationProfile self)
{
EditorGUILayout.Space();
CustomEditorProperties.CustomHelpLabelFieldWithType("Creates a copy of this Animation Profile (keeping all of the applied animations) and clears its Animator Controller so a new one can be created.", false, new Color(0.25f, 2f, 0f, 0.75f), MessageType.Info);
GUI.backgroundColor = new Color(0.1f, 1.2f, 0f, 0.5f);
if (GUILayout.Button("Copy Animation Profile", HelpButtonStyle, GUILayout.Height(23)) && EditorUtility.DisplayDialog("Copy Animation Profile?", "Are you sure you want to copy this Animation Profile? This process cannot be undone.", "Yes", "Cancel"))
{
EmeraldAnimatorGenerator.CopyAnimationProfile(self);
}
GUI.backgroundColor = Color.white;
}
void ClearAnimatorControllerButton(AnimationProfile self)
{
EditorGUILayout.Space();

View File

@@ -36,7 +36,7 @@ namespace EmeraldAI
/// </summary>
void SwitchTarget (EmeraldSystem EmeraldComponent, ActionsClass ActionClass)
{
if (EmeraldComponent.AnimationComponent.IsAttacking || EmeraldComponent.AIAnimator.GetBool("Attack"))
if (EmeraldComponent.AnimationComponent.IsAttacking || EmeraldComponent.AIAnimator.GetBool("Attack") || EmeraldComponent.DetectionComponent.LineOfSightTargets.Count <= 1)
return;
EmeraldComponent.DetectionComponent.SearchForTarget(PickTargetType);