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

@@ -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;