init 1.1.2
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 286a581fb22ff6d44874b36490913c5b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,379 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class AerialProjectile : MonoBehaviour, IAvoidable
|
||||
{
|
||||
#region Variables
|
||||
bool Initialized;
|
||||
bool MinHomingDistMet;
|
||||
AerialProjectileAbility CurrentAbilityData;
|
||||
Transform CurrentTarget;
|
||||
public Transform AbilityTarget { get => CurrentTarget; set => CurrentTarget = value; }
|
||||
Vector3 InitialTargetPosition;
|
||||
ICombat StartingICombat;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
AudioSource m_AudioSource;
|
||||
GameObject m_SoundEffect;
|
||||
Collider m_Collider;
|
||||
SphereCollider m_SphereCollider;
|
||||
List<Collider> IgnoredColliders = new List<Collider>();
|
||||
Rigidbody m_Rigidbody;
|
||||
GameObject Owner;
|
||||
float StartTime;
|
||||
float HomingTimer;
|
||||
|
||||
public List<ProjectileEffectsClass> m_ProjectileObjects = new List<ProjectileEffectsClass>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize the needed components and settings the first time the projectile is used.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
m_AudioSource.loop = true;
|
||||
m_AudioSource.rolloffMode = AudioRolloffMode.Linear;
|
||||
m_AudioSource.spatialBlend = 1;
|
||||
m_AudioSource.maxDistance = 20;
|
||||
|
||||
//If a collider already exists on the projectile object, use it as the projectile's collider.
|
||||
m_Collider = GetComponent<Collider>();
|
||||
if (m_Collider != null)
|
||||
{
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
//If not, create a SphereCollider.
|
||||
else
|
||||
{
|
||||
m_Collider = gameObject.AddComponent<SphereCollider>();
|
||||
m_SphereCollider = gameObject.GetComponent<SphereCollider>();
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
|
||||
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_Rigidbody.useGravity = true;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
m_Rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
m_SoundEffect = Resources.Load("Emerald Sound") as GameObject;
|
||||
|
||||
//Go through all effect objects on Awake and cache them to be used through the Projectile Module.
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(gameObject.GetComponent<ParticleSystemRenderer>(), gameObject));
|
||||
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(child.GetComponent<ParticleSystemRenderer>(), child.gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the projectile with the passed information.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of this projectile.</param>
|
||||
/// <param name="currentTarget">The current target for this projectile.</param>
|
||||
/// <param name="abilityData">The current ability data for this projectile.</param>
|
||||
public void Initialize (GameObject owner, Transform currentTarget, AerialProjectileAbility abilityData)
|
||||
{
|
||||
StartCoroutine(InitializeInternal(owner, currentTarget, abilityData));
|
||||
}
|
||||
|
||||
IEnumerator InitializeInternal (GameObject owner, Transform currentTarget, AerialProjectileAbility abilityData)
|
||||
{
|
||||
Owner = owner;
|
||||
CurrentTarget = currentTarget;
|
||||
if (CurrentTarget != null)
|
||||
{
|
||||
StartingICombat = CurrentTarget.GetComponent<ICombat>();
|
||||
InitialTargetPosition = StartingICombat.DamagePosition();
|
||||
}
|
||||
|
||||
EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
CurrentAbilityData = abilityData;
|
||||
m_Collider.enabled = true;
|
||||
|
||||
//Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
LocationBasedDamage LBDComponent = Owner.GetComponent<LocationBasedDamage>();
|
||||
if (LBDComponent)
|
||||
{
|
||||
IgnoredColliders.Clear();
|
||||
for (int i = 0; i < LBDComponent.ColliderList.Count; i++)
|
||||
{
|
||||
IgnoredColliders.Add(LBDComponent.ColliderList[i].ColliderObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_SphereCollider != null)
|
||||
{
|
||||
m_SphereCollider.radius = abilityData.ColliderSettings.ColliderRadius;
|
||||
m_SphereCollider.center = Vector3.forward * abilityData.ColliderSettings.ZOffet;
|
||||
}
|
||||
|
||||
gameObject.layer = CurrentAbilityData.ColliderSettings.ProjectileLayer;
|
||||
Initialized = false;
|
||||
MinHomingDistMet = false;
|
||||
HomingTimer = 0;
|
||||
|
||||
if (CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count > 0) SetEffectsState(true); //Enable the specified effects by comparing the names from the EffectsToDisable list.
|
||||
InitializeAerialProjectile();
|
||||
|
||||
yield return new WaitForSeconds(CurrentAbilityData.ProjectileSettings.LaunchProjectileDelay);
|
||||
|
||||
if (CurrentTarget != null && CurrentAbilityData.AerialProjectileSettings.AimSource == AbilityData.AerialProjectileData.AimSources.TargetPosition && CurrentTarget.transform.localScale != Vector3.one * 0.003f)
|
||||
transform.LookAt(StartingICombat.DamagePosition());
|
||||
|
||||
StartTime = Time.time;
|
||||
if (CurrentAbilityData.ProjectileSettings.TravelSound != null) m_AudioSource.PlayOneShot(CurrentAbilityData.ProjectileSettings.TravelSound);
|
||||
CurrentAbilityData.ProjectileSettings.SpawnLaunchProjectileEffect(Owner, transform.position);
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
void InitializeAerialProjectile()
|
||||
{
|
||||
//Generates a destination (for projectiles) randomly around their current position with a randomized angle.
|
||||
if (CurrentAbilityData.AerialProjectileSettings.Enabled)
|
||||
{
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(transform.position, Vector3.down, out hit, 20))
|
||||
{
|
||||
InitialTargetPosition = hit.point;
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentAbilityData.AerialProjectileSettings.Enabled && CurrentAbilityData.AerialProjectileSettings.AimSource == EmeraldAI.AbilityData.AerialProjectileData.AimSources.TargetPosition)
|
||||
{
|
||||
transform.LookAt(StartingICombat.DamagePosition());
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 10, Color.red, 5);
|
||||
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(transform.position, Vector3.down, out hit, 30))
|
||||
{
|
||||
transform.LookAt(hit.point);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
CurrentAbilityData.ProjectileSettings.SpawnEffect(Owner, transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to move and track the time since the projectile has been spawned.
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
ProjectileTimeout(); //Track the time since the projectile has been active.
|
||||
MoveProjectile(); //Move the projectile towards its current target.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track the time since the projectile has been active. Once the time ProjectileTimeoutSeconds
|
||||
/// has been met, despawn the projectile and spawn an impact effect from its current position.
|
||||
/// </summary>
|
||||
void ProjectileTimeout ()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
float TimeAlive = Time.time - StartTime;
|
||||
if (TimeAlive > CurrentAbilityData.ProjectileSettings.ProjectileTimeoutSeconds && m_Collider.enabled)
|
||||
{
|
||||
CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position);
|
||||
this.enabled = false;
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the projectile towards its current target, but only if the ability is initialized.
|
||||
/// </summary>
|
||||
void MoveProjectile ()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
var step = CurrentAbilityData.AerialProjectileSettings.ProjectileSpeed * Time.deltaTime;
|
||||
|
||||
if (CurrentTarget != null)
|
||||
{
|
||||
float DistFromTarget = (Vector3.Distance(transform.position, StartingICombat.DamagePosition()));
|
||||
|
||||
if (!MinHomingDistMet && DistFromTarget < CurrentAbilityData.HomingSettings.MinimumHomingDistance) MinHomingDistMet = true;
|
||||
|
||||
if (CurrentAbilityData.HomingSettings.Enabled && HomingTimer < CurrentAbilityData.HomingSettings.HomingSeconds && !MinHomingDistMet)
|
||||
{
|
||||
Vector3 ForwardMovement = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
transform.position = ForwardMovement;
|
||||
float DistFormOwner = Vector3.Distance(Owner.transform.position, transform.position);
|
||||
|
||||
if (DistFormOwner > 1f || DistFromTarget < 1)
|
||||
{
|
||||
HomingTimer += Time.deltaTime;
|
||||
RotateTowardsTarget();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotate towards the projectile's Current Target's position.
|
||||
/// </summary>
|
||||
void RotateTowardsTarget ()
|
||||
{
|
||||
//Determine which direction to rotate towards
|
||||
Vector3 targetDirection = StartingICombat.DamagePosition() - transform.position;
|
||||
|
||||
//The step size is equal to speed times frame time.
|
||||
float singleStep = Time.deltaTime * CurrentAbilityData.HomingSettings.HomingSpeed;
|
||||
|
||||
//Rotate the forward vector towards the target direction by one step
|
||||
Vector3 newDirection = Vector3.RotateTowards(transform.forward, targetDirection, singleStep, 0.0f);
|
||||
|
||||
//Calculate a rotation a step closer to the target and applies rotation to this object
|
||||
transform.rotation = Quaternion.LookRotation(newDirection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the impact functionality, given the collision object is not this or another projectile.
|
||||
/// </summary>
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!this.enabled) return; //Only allow trigger collisions to work if the script is active
|
||||
if (((1 << other.gameObject.layer) & CurrentAbilityData.ColliderSettings.CollidableLayers) != 0 && other.gameObject != Owner && !IgnoredColliders.Contains(other)) Impact(other.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles all impact related functionality.
|
||||
/// </summary>
|
||||
void Impact (GameObject TargetHit)
|
||||
{
|
||||
m_Collider.enabled = false; //Disable the ability's collider so unintended collisions don't happen.
|
||||
Initialized = false; //Disable initialization so the projectile stops operating.
|
||||
CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position); //Spawns the ability's impact effect and impact sound.
|
||||
|
||||
DamageTarget(TargetHit); //Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
Invoke(nameof(ImpactDespawn), CurrentAbilityData.ColliderSettings.CollisionTimeout); //Despawn the projectile according to its CollisionTimeout. This gives the projectile extra time to finish before being despawned.
|
||||
|
||||
if (CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count > 0)
|
||||
{
|
||||
if (!CurrentAbilityData.AerialProjectileSettings.AttachToTarget) SetEffectsState(false); //Disables the specified effects by comparing the names from the EffectsToDisable list.
|
||||
else if (TargetHit.activeSelf) StartCoroutine(SetEffectsStateDelay(false)); //Disables the specified effects by comparing the names from the EffectsToDisable list (with delay).
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delay the SetEffectsState call.
|
||||
/// </summary>
|
||||
IEnumerator SetEffectsStateDelay(bool State)
|
||||
{
|
||||
if (!State) yield return new WaitForSeconds(0.5f);
|
||||
SetEffectsState(State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified effects' state by comparing the names from the EffectsToDisable list.
|
||||
/// </summary>
|
||||
void SetEffectsState(bool State)
|
||||
{
|
||||
for (int i = 0; i < m_ProjectileObjects.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count; j++)
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectObject.name == CurrentAbilityData.ProjectileSettings.EffectsToDisable[j])
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectParticle != null) m_ProjectileObjects[i].EffectParticle.enabled = State;
|
||||
else if (m_ProjectileObjects[i].EffectObject != null && m_ProjectileObjects[i].EffectObject != this.gameObject) m_ProjectileObjects[i].EffectObject.SetActive(State);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target)
|
||||
{
|
||||
LocationBasedDamageArea m_LocationBasedDamageArea = Target.GetComponent<LocationBasedDamageArea>();
|
||||
|
||||
//Only damage layers that are in the AI's DetectionLayerMask or if the target has a LBD component on it.
|
||||
if (!m_LocationBasedDamageArea && ((1 << Target.layer) & EmeraldComponent.DetectionComponent.DetectionLayerMask) == 0) return;
|
||||
|
||||
//Return if the target is teleporting.
|
||||
if (m_LocationBasedDamageArea != null && m_LocationBasedDamageArea.EmeraldComponent.transform.localScale == Vector3.one * 0.003f || Target.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
|
||||
//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;
|
||||
|
||||
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_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)
|
||||
{
|
||||
AttachToCollider(Target.transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the projectile to the passed collider.
|
||||
/// </summary>
|
||||
void AttachToCollider(Transform Target)
|
||||
{
|
||||
transform.SetParent(Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns the impact effect after the CollisionTimeout has been met.
|
||||
/// </summary>
|
||||
void ImpactDespawn ()
|
||||
{
|
||||
this.enabled = false; //Disable the script so it doesn't interfere with other objects that may use the same object through Object Pooling
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c0e369a5b935584787c0d51979578da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Aerial Projectile Ability", menuName = "Emerald AI/Ability/Aerial Projectile Ability")]
|
||||
public class AerialProjectileAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.CreateSettingsData CreateSettings;
|
||||
public AbilityData.ProjectileData ProjectileSettings;
|
||||
public AbilityData.HomingData HomingSettings;
|
||||
public AbilityData.AerialProjectileData AerialProjectileSettings;
|
||||
public AbilityData.TargetTypeData TargetTypeSettings;
|
||||
public AbilityData.ColliderData ColliderSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
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, TargetTypeSettings.TargetType);
|
||||
CreateSettings.SpawnCreateEffect(Owner, AttackTransform);
|
||||
OwnerMonoBehaviour.StartCoroutine(SpawnAerialProjectiles(Owner, Target, AerialProjectileSettings.TimeBetweenProjectiles));
|
||||
}
|
||||
|
||||
IEnumerator SpawnAerialProjectiles(GameObject Owner, Transform Target, float Delay)
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
if (Target == null) Target = Owner.GetComponent<EmeraldSystem>().CombatTarget;
|
||||
//If the target is still null after attempting to get the current target, cancel the ability.
|
||||
if (Target == null) yield break;
|
||||
|
||||
AerialProjectileSettings.SpawnAerialEffect(Owner, Target); //Spawns an aerial effect, given it's enabled.
|
||||
|
||||
Vector3 StartingPosition = GetStartingPosition(Owner, Target);
|
||||
|
||||
yield return new WaitForSeconds(0.25f);
|
||||
|
||||
float theta = Mathf.PI * (3 - Mathf.Sqrt(5));
|
||||
|
||||
for (int i = 0; i < AerialProjectileSettings.TotalProjectiles; i++)
|
||||
{
|
||||
//Continue to get a new target each time a projectile is created
|
||||
if (TargetTypeSettings.TargetType == AbilityData.TargetTypes.MultipleRandomEnemies)
|
||||
{
|
||||
Target = GetTarget(Owner, TargetTypeSettings.TargetType);
|
||||
if (AerialProjectileSettings.SpawnSource != AbilityData.AerialProjectileData.SpawnSources.AboveSelf)
|
||||
AerialProjectileSettings.SpawnAerialEffect(Owner, Target);
|
||||
}
|
||||
|
||||
Vector3 SpawnPosition = GetSpawnPosition(StartingPosition, theta, i);
|
||||
GameObject SpawnedProjectile = EmeraldObjectPool.Spawn(ProjectileSettings.ProjectileEffect, SpawnPosition, ProjectileSettings.ProjectileEffect.transform.rotation);
|
||||
SpawnedProjectile.transform.localScale = ProjectileSettings.ProjectileEffect.transform.localScale;
|
||||
SpawnedProjectile.name = ProjectileSettings.ProjectileEffect.name;
|
||||
SpawnedProjectile.transform.LookAt(Vector3.down);
|
||||
|
||||
Vector3 AimDir = new Vector3(90 + Random.Range(-AerialProjectileSettings.LaunchAngle, AerialProjectileSettings.LaunchAngle), Random.Range(0, 360), 0);
|
||||
SpawnedProjectile.transform.eulerAngles = AimDir;
|
||||
|
||||
AssignScript(SpawnedProjectile).Initialize(Owner, Target, this);
|
||||
|
||||
if (Delay > 0) yield return new WaitForSeconds(Delay);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign the AerialProjectile script on the newly spawned projectile.
|
||||
/// </summary>
|
||||
public AerialProjectile AssignScript(GameObject SpawnedProjectile)
|
||||
{
|
||||
var aerialProjectile = SpawnedProjectile.GetComponent<AerialProjectile>();
|
||||
if (aerialProjectile == null) aerialProjectile = SpawnedProjectile.AddComponent<AerialProjectile>();
|
||||
aerialProjectile.enabled = true;
|
||||
return aerialProjectile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to get the position when the ability is first called (so that if the StartingPosition moves, the projectiles remain above where they were originally cast).
|
||||
/// </summary>
|
||||
Vector3 GetStartingPosition (GameObject Owner, Transform Target)
|
||||
{
|
||||
if (AerialProjectileSettings.SpawnSource == AbilityData.AerialProjectileData.SpawnSources.AboveTarget)
|
||||
{
|
||||
return Target.transform.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Owner.transform.position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Spawn Position depending on the index and the total projectiles. This will allow all positions to be evenly distributed.
|
||||
/// </summary>
|
||||
Vector3 GetSpawnPosition (Vector3 StartingPosition, float theta, int Index)
|
||||
{
|
||||
float r = (AerialProjectileSettings.Radius) * Mathf.Sqrt(Index) / Mathf.Sqrt(AerialProjectileSettings.TotalProjectiles);
|
||||
float a = theta * Index;
|
||||
return StartingPosition + new Vector3(Mathf.Cos(a) * r, AerialProjectileSettings.HeightOffset, Mathf.Sin(a) * r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bb8bef5ae6b3914587fb9a80434e6d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a52091849b6e144c96d5174c01dfaa1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
public class AreaOfEffect : MonoBehaviour
|
||||
{
|
||||
public LayerMask Enemies;
|
||||
AreaOfEffectAbility CurrentAbilityData;
|
||||
GameObject Owner;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
|
||||
public void Initialize (GameObject owner, Transform AttackTransform, AreaOfEffectAbility abilityData)
|
||||
{
|
||||
EmeraldComponent = owner.GetComponent<EmeraldSystem>();
|
||||
Enemies = EmeraldComponent.DetectionComponent.DetectionLayerMask;
|
||||
CurrentAbilityData = abilityData;
|
||||
Owner = owner;
|
||||
IntitailizeInternal(Owner, AttackTransform);
|
||||
}
|
||||
|
||||
void IntitailizeInternal (GameObject Owner, Transform AttackTransform)
|
||||
{
|
||||
List<Collider> DetectedAOETargets = Physics.OverlapSphere(AttackTransform.position, CurrentAbilityData.AreaOfEffectSettings.Radius, Enemies).ToList(); //Only looks for targets that have the same layer as the layers from the AI's DetectionLayerMask.
|
||||
DetectedAOETargets.Remove(Owner.GetComponent<Collider>()); //Remove the owner's collider if it happens to be detected.
|
||||
|
||||
for (int i = 0; i < DetectedAOETargets.Count; i++)
|
||||
{
|
||||
//Only damage targets that the Owner has an Enemy Relation Type with.
|
||||
if (EmeraldAPI.Faction.GetTargetFactionRelation(EmeraldComponent, DetectedAOETargets[i].transform) == "Enemy")
|
||||
{
|
||||
ICombat m_ICombat = DetectedAOETargets[i].GetComponent<ICombat>();
|
||||
|
||||
if (CurrentAbilityData.AreaOfEffectSettings.HitTargetEffect != null)
|
||||
{
|
||||
if (m_ICombat != null && !m_ICombat.IsDodging() && !m_ICombat.IsBlocking() && DetectedAOETargets[i].transform.localScale != Vector3.one * 0.003f)
|
||||
EmeraldObjectPool.SpawnEffect(CurrentAbilityData.AreaOfEffectSettings.HitTargetEffect, DetectedAOETargets[i].GetComponent<ICombat>().DamagePosition(), DetectedAOETargets[i].transform.rotation, CurrentAbilityData.AreaOfEffectSettings.HitTargetEffectTimeoutSeconds);
|
||||
}
|
||||
|
||||
DamageTarget(DetectedAOETargets[i].gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damaes the projectile's StartingTarget, given that it has a IDamageable.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target)
|
||||
{
|
||||
if (Target.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
if (CurrentAbilityData.StunnedSettings.Enabled && CurrentAbilityData.StunnedSettings.RollForStun())
|
||||
{
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
if (m_ICombat != null) m_ICombat.TriggerStun(CurrentAbilityData.StunnedSettings.StunLength);
|
||||
}
|
||||
|
||||
//Only cause damage if it's enabled
|
||||
if (!CurrentAbilityData.DamageSettings.Enabled) return;
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log(Target.gameObject + " is missing a IDamageable and/or ICombat Component, apply one");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00899966b59ef584dbb3699e4352d2e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Area of Effect Ability", menuName = "Emerald AI/Ability/Area of Effect Ability")]
|
||||
public class AreaOfEffectAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.CreateSettingsData CreateSettings;
|
||||
public AbilityData.AreaOfEffectData AreaOfEffectSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
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(StartAOE(Owner, AttackTransform));
|
||||
}
|
||||
|
||||
IEnumerator StartAOE (GameObject Owner, Transform AttackTransform = null)
|
||||
{
|
||||
yield return new WaitForSeconds(AreaOfEffectSettings.Delay);
|
||||
|
||||
Vector3 SpawnPosition = new Vector3(AttackTransform.position.x, Owner.transform.position.y, AttackTransform.position.z) + Vector3.up * AreaOfEffectSettings.HeightOffset;
|
||||
GameObject SpawnedAbility = AreaOfEffectSettings.SpawnAOEEffect(Owner, SpawnPosition);
|
||||
AssignScript(SpawnedAbility).Initialize(Owner, AttackTransform, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign the AreaOfEffect script on the newly spawned AOE effect.
|
||||
/// </summary>
|
||||
public AreaOfEffect AssignScript(GameObject SpawnedAbility)
|
||||
{
|
||||
var areaOfEffect = SpawnedAbility.GetComponent<AreaOfEffect>();
|
||||
if (areaOfEffect == null) areaOfEffect = SpawnedAbility.AddComponent<AreaOfEffect>();
|
||||
return areaOfEffect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bffe9c5d4821ade4f9493c7cb29686d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6597fd517b2f1a439ec0a832f202824
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,309 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class ArrowProjectile : MonoBehaviour, IAvoidable
|
||||
{
|
||||
#region Variables
|
||||
ArrowProjectileAbility CurrentAbilityData;
|
||||
Transform CurrentTarget;
|
||||
public Transform AbilityTarget { get => CurrentTarget; set => CurrentTarget = value; }
|
||||
Vector3 InitialTargetPosition;
|
||||
ICombat StartingICombat;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
AudioSource m_AudioSource;
|
||||
GameObject m_SoundEffect;
|
||||
Collider m_Collider;
|
||||
SphereCollider m_SphereCollider;
|
||||
Rigidbody m_Rigidbody;
|
||||
GameObject Owner;
|
||||
List<Collider> IgnoredColliders = new List<Collider>();
|
||||
float StartTime;
|
||||
bool Initialized;
|
||||
Coroutine DespawnCoroutine;
|
||||
|
||||
//[SerializeField]
|
||||
public List<ProjectileEffectsClass> m_ProjectileObjects = new List<ProjectileEffectsClass>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize the needed components and settings the first time the projectile is used.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
m_AudioSource.loop = true;
|
||||
m_AudioSource.rolloffMode = AudioRolloffMode.Linear;
|
||||
m_AudioSource.spatialBlend = 1;
|
||||
m_AudioSource.maxDistance = 20;
|
||||
|
||||
//If a collider already exists on the projectile object, use it as the projectile's collider.
|
||||
m_Collider = GetComponent<Collider>();
|
||||
if (m_Collider != null)
|
||||
{
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
//If not, create a SphereCollider.
|
||||
else
|
||||
{
|
||||
m_Collider = gameObject.AddComponent<SphereCollider>();
|
||||
m_SphereCollider = gameObject.GetComponent<SphereCollider>();
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_Rigidbody.useGravity = true;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
m_Rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
m_SoundEffect = Resources.Load("Emerald Sound") as GameObject;
|
||||
|
||||
//Go through all effect objects on Awake and cache them to be used through the Projectile Module.
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(gameObject.GetComponent<ParticleSystemRenderer>(), gameObject));
|
||||
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(child.GetComponent<ParticleSystemRenderer>(), child.gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the projectile with the passed information.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of this projectile.</param>
|
||||
/// <param name="currentTarget">The current target for this projectile.</param>
|
||||
/// <param name="abilityData">The current ability data for this projectile.</param>
|
||||
public void Initialize (GameObject owner, Transform currentTarget, ArrowProjectileAbility abilityData)
|
||||
{
|
||||
StartCoroutine(InitializeInternal(owner, currentTarget, abilityData));
|
||||
}
|
||||
|
||||
IEnumerator InitializeInternal (GameObject owner, Transform currentTarget, ArrowProjectileAbility abilityData)
|
||||
{
|
||||
Owner = owner;
|
||||
CurrentTarget = currentTarget;
|
||||
if (CurrentTarget != null)
|
||||
{
|
||||
StartingICombat = CurrentTarget.GetComponent<ICombat>();
|
||||
InitialTargetPosition = StartingICombat.DamagePosition();
|
||||
}
|
||||
|
||||
EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
CurrentAbilityData = abilityData;
|
||||
m_Collider.enabled = false; //Don't enable colliders until after they have launched
|
||||
|
||||
GetLBDColliders(); //Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
|
||||
if (m_SphereCollider != null)
|
||||
{
|
||||
m_SphereCollider.radius = CurrentAbilityData.ColliderSettings.ColliderRadius;
|
||||
m_SphereCollider.center = Vector3.forward * CurrentAbilityData.ColliderSettings.ZOffet;
|
||||
}
|
||||
|
||||
gameObject.layer = CurrentAbilityData.ColliderSettings.ProjectileLayer;
|
||||
Initialized = false;
|
||||
|
||||
if (CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count > 0) SetEffectsState(true); //Enable the specified effects by comparing the names from the EffectsToDisable list.
|
||||
InitializeProjectile(); //Intialize the projectile's settings.
|
||||
|
||||
yield return new WaitForSeconds(CurrentAbilityData.ProjectileSettings.LaunchProjectileDelay);
|
||||
|
||||
StartTime = Time.time;
|
||||
m_Collider.enabled = true; //Enable the collider now this is has launched
|
||||
if (CurrentAbilityData.ProjectileSettings.TravelSound != null) m_AudioSource.PlayOneShot(CurrentAbilityData.ProjectileSettings.TravelSound);
|
||||
CurrentAbilityData.ProjectileSettings.SpawnLaunchProjectileEffect(Owner, transform.position);
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
void GetLBDColliders()
|
||||
{
|
||||
//Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
LocationBasedDamage LBDComponent = Owner.GetComponent<LocationBasedDamage>();
|
||||
if (LBDComponent)
|
||||
{
|
||||
IgnoredColliders.Clear();
|
||||
for (int i = 0; i < LBDComponent.ColliderList.Count; i++)
|
||||
{
|
||||
IgnoredColliders.Add(LBDComponent.ColliderList[i].ColliderObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeProjectile ()
|
||||
{
|
||||
transform.LookAt(InitialTargetPosition);
|
||||
CurrentAbilityData.ProjectileSettings.SpawnEffect(Owner, transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to move and track the time since the projectile has been spawned.
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
ProjectileTimeout(); //Track the time since the projectile has been active.
|
||||
MoveProjectile(); //Move the projectile towards its current target.
|
||||
}
|
||||
/// <summary>
|
||||
/// Track the time since the projectile has been active. Once the time ProjectileTimeoutSeconds
|
||||
/// has been met, despawn the projectile and spawn an impact effect from its current position.
|
||||
/// </summary>
|
||||
void ProjectileTimeout ()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
float TimeAlive = Time.time - StartTime;
|
||||
if (TimeAlive > CurrentAbilityData.ProjectileSettings.ProjectileTimeoutSeconds && m_Collider.enabled)
|
||||
{
|
||||
CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position);
|
||||
this.enabled = false;
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the projectile towards its current target, but only if the ability is initialized.
|
||||
/// </summary>
|
||||
void MoveProjectile ()
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
var step = CurrentAbilityData.ArrowProjectileSettings.ProjectileSpeed * Time.deltaTime;
|
||||
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the impact functionality, given the collision object is not this or another projectile.
|
||||
/// </summary>
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!this.enabled) return; //Only allow trigger collisions to work if the script is active
|
||||
if (((1 << other.gameObject.layer) & CurrentAbilityData.ColliderSettings.CollidableLayers) != 0 && other.gameObject != Owner && !IgnoredColliders.Contains(other)) Impact(other.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles all impact related functionality.
|
||||
/// </summary>
|
||||
void Impact (GameObject TargetHit)
|
||||
{
|
||||
//if (Owner.GetComponent<EmeraldAISystem>().DetectionComponent.GetTargetFaction(TargetHit.GetComponent<LocationBasedDamageArea>().EmeraldComponent.transform) == "Friendly") return;
|
||||
|
||||
m_Collider.enabled = false; //Disable the ability's collider so unintended collisions don't happen.
|
||||
Initialized = false; //Disable initialization so the projectile stops operating.
|
||||
CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position); //Spawns the ability's impact effect and impact sound.
|
||||
Invoke(nameof(ImpactDespawn), CurrentAbilityData.ColliderSettings.CollisionTimeout); //Despawn the projectile according to its CollisionTimeout. This gives the projectile extra time to finish before being despawned.
|
||||
DamageTarget(TargetHit); //Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
|
||||
if (CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count > 0)
|
||||
{
|
||||
if (!CurrentAbilityData.ArrowProjectileSettings.AttachToTarget) SetEffectsState(false); //Disables the specified effects by comparing the names from the EffectsToDisable list.
|
||||
else if (TargetHit.activeSelf) StartCoroutine(SetEffectsStateDelay(false)); //Disables the specified effects by comparing the names from the EffectsToDisable list (with delay).
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delay the SetEffectsState call.
|
||||
/// </summary>
|
||||
IEnumerator SetEffectsStateDelay(bool State)
|
||||
{
|
||||
if (!State) yield return new WaitForSeconds(1);
|
||||
SetEffectsState(State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified effects' state by comparing the names from the EffectsToDisable list.
|
||||
/// </summary>
|
||||
void SetEffectsState(bool State)
|
||||
{
|
||||
for (int i = 0; i < m_ProjectileObjects.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count; j++)
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectObject.name == CurrentAbilityData.ProjectileSettings.EffectsToDisable[j])
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectParticle != null) m_ProjectileObjects[i].EffectParticle.enabled = State;
|
||||
else if (m_ProjectileObjects[i].EffectObject != null && m_ProjectileObjects[i].EffectObject != this.gameObject) m_ProjectileObjects[i].EffectObject.SetActive(State);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target)
|
||||
{
|
||||
LocationBasedDamageArea m_LocationBasedDamageArea = Target.GetComponent<LocationBasedDamageArea>();
|
||||
|
||||
//Only damage layers that are in the AI's DetectionLayerMask or if the target has a LBD component on it.
|
||||
if (!m_LocationBasedDamageArea && ((1 << Target.layer) & EmeraldComponent.DetectionComponent.DetectionLayerMask) == 0) return;
|
||||
|
||||
//Return if the target is teleporting.
|
||||
if (m_LocationBasedDamageArea != null && m_LocationBasedDamageArea.EmeraldComponent.transform.localScale == Vector3.one * 0.003f || Target.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
|
||||
//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;
|
||||
|
||||
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_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.ArrowProjectileSettings.AttachToTarget)
|
||||
{
|
||||
AttachToCollider(Target.transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the projectile to the passed collider.
|
||||
/// </summary>
|
||||
void AttachToCollider(Transform Target)
|
||||
{
|
||||
transform.SetParent(Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns the impact effect after the CollisionTimeout has been met.
|
||||
/// </summary>
|
||||
void ImpactDespawn ()
|
||||
{
|
||||
this.enabled = false; //Disable the script so it doesn't interfere with other objects that may use the same object through Object Pooling
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3caf41c19596655458834bf556fe51f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Arrow Projectile Ability", menuName = "Emerald AI/Ability/Arrow Projectile Ability")]
|
||||
public class ArrowProjectileAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.CreateSettingsData CreateSettings;
|
||||
public AbilityData.ProjectileData ProjectileSettings;
|
||||
public AbilityData.ArrowProjectileData ArrowProjectileSettings;
|
||||
public AbilityData.ColliderData ColliderSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
public override void ChargeAbility(GameObject Owner, Transform AttackTransform = null)
|
||||
{
|
||||
ChargeSettings.SpawnChargeEffect(Owner, AttackTransform);
|
||||
}
|
||||
|
||||
public override void InvokeAbility(GameObject Owner, Transform AttackTransform = null)
|
||||
{
|
||||
CreateSettings.SpawnCreateEffect(Owner, AttackTransform);
|
||||
SpawnProjectiles(Owner, AttackTransform);
|
||||
}
|
||||
|
||||
void SpawnProjectiles (GameObject Owner, Transform AttackTransform)
|
||||
{
|
||||
Transform Target = GetTarget(Owner, AbilityData.TargetTypes.CurrentTarget);
|
||||
|
||||
EmeraldSystem EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
if (EmeraldComponent != null)
|
||||
{
|
||||
if (EmeraldComponent.AnimationComponent.IsDodging || EmeraldComponent.AnimationComponent.IsGettingHit) return;
|
||||
}
|
||||
|
||||
Vector3 SpawnPosition = AttackTransform.position;
|
||||
GameObject SpawnedProjectile = EmeraldObjectPool.Spawn(ProjectileSettings.ProjectileEffect, SpawnPosition, ProjectileSettings.ProjectileEffect.transform.rotation);
|
||||
SpawnedProjectile.transform.localScale = ProjectileSettings.ProjectileEffect.transform.localScale;
|
||||
SpawnedProjectile.name = ProjectileSettings.ProjectileEffect.name;
|
||||
|
||||
AssignScript(SpawnedProjectile).Initialize(Owner, Target, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign the ArrowProjectile script on the newly spawned projectile.
|
||||
/// </summary>
|
||||
public ArrowProjectile AssignScript(GameObject SpawnedProjectile)
|
||||
{
|
||||
var arrowProjectile = SpawnedProjectile.GetComponent<ArrowProjectile>();
|
||||
if (arrowProjectile == null) arrowProjectile = SpawnedProjectile.AddComponent<ArrowProjectile>();
|
||||
arrowProjectile.enabled = true;
|
||||
return arrowProjectile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16bc2d989dda7f945b2f91ce5ca16e6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e83e65c58eb9dce4687d748022e4d1d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,268 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
public class BulletProjectile : MonoBehaviour, IAvoidable
|
||||
{
|
||||
#region Variables
|
||||
BulletProjectileAbility CurrentAbilityData;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
Transform CurrentTarget;
|
||||
public Transform AbilityTarget { get => CurrentTarget; set => CurrentTarget = value; }
|
||||
Vector3 InitialTargetPosition;
|
||||
ICombat StartingICombat;
|
||||
AudioSource m_AudioSource;
|
||||
GameObject m_SoundEffect;
|
||||
GameObject Owner;
|
||||
List<Collider> IgnoredColliders = new List<Collider>();
|
||||
float StartTime;
|
||||
bool Initialized;
|
||||
float TargetAngle;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize the needed components and settings the first time the projectile is used.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
m_AudioSource.loop = true;
|
||||
m_AudioSource.rolloffMode = AudioRolloffMode.Linear;
|
||||
m_AudioSource.spatialBlend = 1;
|
||||
m_AudioSource.maxDistance = 20;
|
||||
m_SoundEffect = Resources.Load("Emerald Sound") as GameObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the projectile with the passed information.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of this projectile.</param>
|
||||
/// <param name="currentTarget">The current target for this projectile.</param>
|
||||
/// <param name="abilityData">The current ability data for this projectile.</param>
|
||||
public void Initialize (GameObject owner, Transform currentTarget, BulletProjectileAbility abilityData)
|
||||
{
|
||||
Owner = owner;
|
||||
CurrentTarget = currentTarget;
|
||||
if (CurrentTarget != null)
|
||||
{
|
||||
StartingICombat = CurrentTarget.GetComponent<ICombat>();
|
||||
InitialTargetPosition = StartingICombat.DamagePosition();
|
||||
}
|
||||
|
||||
EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
CurrentAbilityData = abilityData;
|
||||
|
||||
GetLBDColliders(); //Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
|
||||
Initialized = false;
|
||||
InitializeProjectile(); //Intialize the projectile's settings.
|
||||
|
||||
StartTime = Time.time;
|
||||
}
|
||||
|
||||
void GetLBDColliders()
|
||||
{
|
||||
//Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
LocationBasedDamage LBDComponent = Owner.GetComponent<LocationBasedDamage>();
|
||||
if (LBDComponent)
|
||||
{
|
||||
IgnoredColliders.Clear();
|
||||
for (int i = 0; i < LBDComponent.ColliderList.Count; i++)
|
||||
{
|
||||
IgnoredColliders.Add(LBDComponent.ColliderList[i].ColliderObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeProjectile ()
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.TargetAngle < EmeraldComponent.MovementComponent.CombatAngleToTurn) transform.LookAt(InitialTargetPosition);
|
||||
Initialized = true;
|
||||
StartCoroutine(MoveProjectile());
|
||||
}
|
||||
|
||||
IEnumerator MoveProjectile()
|
||||
{
|
||||
bool Collided = false;
|
||||
float CollisionCheckTimer = 0;
|
||||
Vector3 EndPosition = Vector3.zero;
|
||||
Vector3 Accuracy = new Vector3(Random.Range(-CurrentAbilityData.BulletProjectileSettings.BulletSpreadX, CurrentAbilityData.BulletProjectileSettings.BulletSpreadX) * 0.001f,
|
||||
Random.Range(-CurrentAbilityData.BulletProjectileSettings.BulletSpreadY, CurrentAbilityData.BulletProjectileSettings.BulletSpreadY) * 0.001f, 0);
|
||||
|
||||
while (!Collided)
|
||||
{
|
||||
var step = CurrentAbilityData.BulletProjectileSettings.BulletSpeed * Time.deltaTime;
|
||||
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward + Accuracy, step);
|
||||
CollisionCheckTimer += Time.deltaTime;
|
||||
|
||||
//Only check for collisions every tick according to CollisionCheckSpeed.
|
||||
if (CollisionCheckTimer >= CurrentAbilityData.BulletProjectileSettings.CollisionCheckSpeed)
|
||||
{
|
||||
//In order to have reliable collisions for fast moving bullets, a raycast needs to be cast continiously while it travels towards its target.
|
||||
//It has a forward offset of 0.65 units, which allows for reliable collision detection.
|
||||
RaycastHit hit;
|
||||
//Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 0.5f);
|
||||
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, 0.65f, ~CurrentAbilityData.BulletProjectileSettings.IgnoreLayers))
|
||||
{
|
||||
//When the raycast hits something, call the impact function to check to see if the collider can take damage. Store the end hit.point to be used below.
|
||||
if (hit.collider != null)
|
||||
{
|
||||
EndPosition = hit.point + (transform.TransformDirection(Vector3.forward) * 0.1f);
|
||||
Impact(hit.collider.gameObject, hit.point, hit.normal);
|
||||
Collided = true;
|
||||
}
|
||||
}
|
||||
|
||||
CollisionCheckTimer = 0;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Vector3 StartingPosition = transform.position;
|
||||
float t = 0;
|
||||
bool Complete = false;
|
||||
|
||||
while (!Complete)
|
||||
{
|
||||
//Since the raycast is stopped 0.5 units in front of the hit collider, finish up the rest of the distance
|
||||
//for the bullet to allow the bullet trail to reach the impact position.
|
||||
float Distance = Vector3.Distance(transform.position, EndPosition);
|
||||
t += Time.deltaTime * CurrentAbilityData.BulletProjectileSettings.BulletSpeed;
|
||||
transform.position = Vector3.Lerp(StartingPosition, EndPosition, t);
|
||||
|
||||
if (Distance <= 0)
|
||||
{
|
||||
Complete = true;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to move and track the time since the projectile has been spawned.
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
ProjectileTimeout(); //Track the time since the projectile has been active.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track the time since the projectile has been active. Once the time ProjectileTimeoutSeconds
|
||||
/// has been met, despawn the projectile and spawn an impact effect from its current position.
|
||||
/// </summary>
|
||||
void ProjectileTimeout ()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
float TimeAlive = Time.time - StartTime;
|
||||
if (TimeAlive > CurrentAbilityData.BulletProjectileSettings.BulletObjectTimeout)
|
||||
{
|
||||
this.enabled = false;
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles all impact related functionality.
|
||||
/// </summary>
|
||||
void Impact (GameObject TargetHit, Vector3 HitPosition, Vector3 HitNormal)
|
||||
{
|
||||
if (!this.enabled) return; //Only allow trigger collisions to work if the script is active
|
||||
|
||||
Initialized = false; //Disable initialization so the projectile stops operating.
|
||||
BulletImpact(TargetHit, HitPosition, HitNormal);
|
||||
DamageTarget(TargetHit); //Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
Invoke(nameof(ImpactDespawn), CurrentAbilityData.BulletProjectileSettings.BulletCollisionTimeout);
|
||||
}
|
||||
|
||||
void BulletImpact (GameObject TargetHit, Vector3 HitPosition, Vector3 HitNormal)
|
||||
{
|
||||
if (CurrentAbilityData.BulletProjectileSettings.BulletImpactData.Count == 0)
|
||||
{
|
||||
CurrentAbilityData.BulletProjectileSettings.SpawnDefaultBulletImpact(TargetHit, HitPosition, HitNormal);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < CurrentAbilityData.BulletProjectileSettings.BulletImpactData.Count; i++)
|
||||
{
|
||||
if (TargetHit.CompareTag(CurrentAbilityData.BulletProjectileSettings.BulletImpactData[i].SurfaceTag))
|
||||
{
|
||||
CurrentAbilityData.BulletProjectileSettings.SpawnBulletImpact(TargetHit, CurrentAbilityData.BulletProjectileSettings.BulletImpactData[i], HitPosition, HitNormal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentAbilityData.BulletProjectileSettings.SpawnDefaultBulletImpact(TargetHit, HitPosition, HitNormal);
|
||||
}
|
||||
|
||||
void OnDisable ()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target)
|
||||
{
|
||||
LocationBasedDamageArea m_LocationBasedDamageArea = Target.GetComponent<LocationBasedDamageArea>();
|
||||
|
||||
//Only damage layers that are in the AI's DetectionLayerMask or if the target has a LBD component on it.
|
||||
if (!m_LocationBasedDamageArea && ((1 << Target.layer) & EmeraldComponent.DetectionComponent.DetectionLayerMask) == 0) return;
|
||||
|
||||
//Return if the target is teleporting.
|
||||
if (m_LocationBasedDamageArea != null && m_LocationBasedDamageArea.EmeraldComponent.transform.localScale == Vector3.one * 0.003f || Target.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
|
||||
//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;
|
||||
|
||||
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_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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns the impact effect after the CollisionTimeout has been met.
|
||||
/// </summary>
|
||||
void ImpactDespawn ()
|
||||
{
|
||||
this.enabled = false; //Disable the script so it doesn't interfere with other objects that may use the same object through Object Pooling
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62fb471e2a41e844c80e83f7fed8361b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Bullet Projectile Ability", menuName = "Emerald AI/Ability/Bullet Projectile Ability")]
|
||||
public class BulletProjectileAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.BulletProjectileData BulletProjectileSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
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>();
|
||||
OwnerMonoBehaviour.StartCoroutine(SpawnProjectiles(Owner, AttackTransform, BulletProjectileSettings.TimeBetweenBullets));
|
||||
}
|
||||
|
||||
IEnumerator SpawnProjectiles (GameObject Owner, Transform AttackTransform, float Delay)
|
||||
{
|
||||
Transform Target = GetTarget(Owner, AbilityData.TargetTypes.CurrentTarget);
|
||||
|
||||
yield return new WaitForSeconds(0.005f);
|
||||
|
||||
for (int i = 0; i < BulletProjectileSettings.TotalBullets; i++)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
if (EmeraldComponent != null)
|
||||
{
|
||||
if (EmeraldComponent.AnimationComponent.IsDodging || EmeraldComponent.AnimationComponent.IsGettingHit || EmeraldComponent.AnimationComponent.IsTurning || EmeraldComponent.AnimationComponent.IsDead) { yield break; };
|
||||
}
|
||||
|
||||
Vector3 SpawnPosition = AttackTransform.position;
|
||||
GameObject SpawnedProjectile = EmeraldObjectPool.Spawn(BulletProjectileSettings.BulletObject, SpawnPosition, AttackTransform.rotation);
|
||||
SpawnedProjectile.transform.localScale = BulletProjectileSettings.BulletObject.transform.localScale;
|
||||
SpawnedProjectile.name = BulletProjectileSettings.BulletObject.name;
|
||||
|
||||
//Only play a fire sound once if the TimeBetweenBullets/Delay is 0. This prevents the fire sound from playing multiple times within a single shot (which is most likely unwanted).
|
||||
if (Delay == 0 && i == 0) BulletProjectileSettings.SpawnBulletEffect(Owner, SpawnedProjectile.transform.position, EmeraldComponent.CombatComponent.CurrentAttackTransform);
|
||||
else if (Delay > 0) BulletProjectileSettings.SpawnBulletEffect(Owner, SpawnedProjectile.transform.position, EmeraldComponent.CombatComponent.CurrentAttackTransform);
|
||||
|
||||
AssignScript(SpawnedProjectile).Initialize(Owner, Target, this);
|
||||
|
||||
if (Delay > 0) yield return new WaitForSeconds(Delay);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign the ProjectileMovement script on the newly spawned projectile.
|
||||
/// </summary>
|
||||
public BulletProjectile AssignScript(GameObject SpawnedProjectile)
|
||||
{
|
||||
var bulletProjectile = SpawnedProjectile.GetComponent<BulletProjectile>();
|
||||
if (bulletProjectile == null) bulletProjectile = SpawnedProjectile.AddComponent<BulletProjectile>();
|
||||
bulletProjectile.enabled = true;
|
||||
return bulletProjectile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe2eb82b63b7f2f40acf8a839c8076a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears all points from a Trailer Renderer to stop the previous position from being visible when respawning projectiles that use a TrailRenderer.
|
||||
/// </summary>
|
||||
public class TrailRendererHelper : MonoBehaviour
|
||||
{
|
||||
TrailRenderer m_TrailRenderer;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_TrailRenderer = GetComponent<TrailRenderer>();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
m_TrailRenderer.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9aae1fda1c9f69f44bec95254bb5f1f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f2dd9671cbb93f448d048de6c42be9a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,379 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class GeneralProjectile : MonoBehaviour, IAvoidable
|
||||
{
|
||||
#region Variables
|
||||
GeneralProjectileAbility CurrentAbilityData;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
Transform CurrentTarget;
|
||||
public Transform AbilityTarget { get => CurrentTarget; set => CurrentTarget = value; }
|
||||
Vector3 InitialTargetPosition;
|
||||
ICombat StartingICombat;
|
||||
AudioSource m_AudioSource;
|
||||
GameObject m_SoundEffect;
|
||||
Collider m_Collider;
|
||||
SphereCollider m_SphereCollider;
|
||||
Rigidbody m_Rigidbody;
|
||||
GameObject Owner;
|
||||
List<Collider> IgnoredColliders = new List<Collider>();
|
||||
float StartTime;
|
||||
float HomingTimer;
|
||||
bool Initialized;
|
||||
bool MinHomingDistMet;
|
||||
float TargetAngle;
|
||||
|
||||
[SerializeField]
|
||||
public List<ProjectileEffectsClass> m_ProjectileObjects = new List<ProjectileEffectsClass>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize the needed components and settings the first time the projectile is used.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
m_AudioSource.loop = true;
|
||||
m_AudioSource.rolloffMode = AudioRolloffMode.Linear;
|
||||
m_AudioSource.spatialBlend = 1;
|
||||
m_AudioSource.maxDistance = 20;
|
||||
|
||||
//If a collider already exists on the projectile object, use it as the projectile's collider.
|
||||
m_Collider = GetComponent<Collider>();
|
||||
if (m_Collider != null)
|
||||
{
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
//If not, create a SphereCollider.
|
||||
else
|
||||
{
|
||||
m_Collider = gameObject.AddComponent<SphereCollider>();
|
||||
m_SphereCollider = gameObject.GetComponent<SphereCollider>();
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_Rigidbody.useGravity = true;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
m_Rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
m_SoundEffect = Resources.Load("Emerald Sound") as GameObject;
|
||||
|
||||
//Go through all effect objects on Awake and cache them to be used through the Projectile Module.
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(gameObject.GetComponent<ParticleSystemRenderer>(), gameObject));
|
||||
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(child.GetComponent<ParticleSystemRenderer>(), child.gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the projectile with the passed information.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of this projectile.</param>
|
||||
/// <param name="currentTarget">The current target for this projectile.</param>
|
||||
/// <param name="abilityData">The current ability data for this projectile.</param>
|
||||
public void Initialize (GameObject owner, Transform currentTarget, GeneralProjectileAbility abilityData)
|
||||
{
|
||||
StartCoroutine(InitializeInternal(owner, currentTarget, abilityData));
|
||||
}
|
||||
|
||||
IEnumerator InitializeInternal (GameObject owner, Transform currentTarget, GeneralProjectileAbility abilityData)
|
||||
{
|
||||
Owner = owner;
|
||||
CurrentTarget = currentTarget;
|
||||
if (CurrentTarget != null)
|
||||
{
|
||||
StartingICombat = CurrentTarget.GetComponent<ICombat>();
|
||||
InitialTargetPosition = StartingICombat.DamagePosition();
|
||||
}
|
||||
|
||||
EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
CurrentAbilityData = abilityData;
|
||||
m_Collider.enabled = false; //Don't enable colliders until after they have launched
|
||||
|
||||
GetLBDColliders(); //Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
|
||||
if (m_SphereCollider != null)
|
||||
{
|
||||
m_SphereCollider.radius = CurrentAbilityData.ColliderSettings.ColliderRadius;
|
||||
m_SphereCollider.center = Vector3.forward * CurrentAbilityData.ColliderSettings.ZOffet;
|
||||
}
|
||||
|
||||
gameObject.layer = CurrentAbilityData.ColliderSettings.ProjectileLayer;
|
||||
Initialized = false;
|
||||
MinHomingDistMet = false;
|
||||
HomingTimer = 0;
|
||||
|
||||
if (CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count > 0) SetEffectsState(true); //Enable the specified effects by comparing the names from the EffectsToDisable list.
|
||||
InitializeProjectile(); //Intialize the projectile's settings.
|
||||
|
||||
yield return new WaitForSeconds(CurrentAbilityData.ProjectileSettings.LaunchProjectileDelay);
|
||||
|
||||
StartTime = Time.time;
|
||||
m_Collider.enabled = true; //Enable the collider now this is has launched
|
||||
if (CurrentAbilityData.ProjectileSettings.TravelSound != null) m_AudioSource.PlayOneShot(CurrentAbilityData.ProjectileSettings.TravelSound);
|
||||
CurrentAbilityData.ProjectileSettings.SpawnLaunchProjectileEffect(Owner, transform.position);
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
void GetLBDColliders()
|
||||
{
|
||||
//Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
LocationBasedDamage LBDComponent = Owner.GetComponent<LocationBasedDamage>();
|
||||
if (LBDComponent)
|
||||
{
|
||||
IgnoredColliders.Clear();
|
||||
for (int i = 0; i < LBDComponent.ColliderList.Count; i++)
|
||||
{
|
||||
IgnoredColliders.Add(LBDComponent.ColliderList[i].ColliderObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeProjectile ()
|
||||
{
|
||||
if (CurrentAbilityData.SpreadSettings.Enabled && CurrentAbilityData.SpreadSettings.SpreadType == EmeraldAI.AbilityData.SpreadTypes.HorizontalRadius)
|
||||
transform.position += transform.forward * CurrentAbilityData.SpreadSettings.SpawnDistance;
|
||||
|
||||
TargetAngle = EmeraldCombatManager.TransformAngle(EmeraldComponent, CurrentTarget);
|
||||
|
||||
if (TargetAngle < (CurrentAbilityData.GeneralProjectileSettings.ProjectileMaxLaunchAngle / 2f))
|
||||
{
|
||||
if (!CurrentAbilityData.SpreadSettings.Enabled)
|
||||
{
|
||||
transform.LookAt(InitialTargetPosition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.LookAt(transform.position + Owner.transform.forward);
|
||||
}
|
||||
|
||||
CurrentAbilityData.ProjectileSettings.SpawnEffect(Owner, transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to move and track the time since the projectile has been spawned.
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
ProjectileTimeout(); //Track the time since the projectile has been active.
|
||||
MoveProjectile(); //Move the projectile towards its current target.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track the time since the projectile has been active. Once the time ProjectileTimeoutSeconds
|
||||
/// has been met, despawn the projectile and spawn an impact effect from its current position.
|
||||
/// </summary>
|
||||
void ProjectileTimeout ()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
float TimeAlive = Time.time - StartTime;
|
||||
if (TimeAlive > CurrentAbilityData.ProjectileSettings.ProjectileTimeoutSeconds && m_Collider.enabled)
|
||||
{
|
||||
CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position);
|
||||
this.enabled = false;
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the projectile towards its current target, but only if the ability is initialized.
|
||||
/// </summary>
|
||||
void MoveProjectile ()
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
var step = CurrentAbilityData.GeneralProjectileSettings.ProjectileSpeed * Time.deltaTime;
|
||||
|
||||
if (CurrentTarget != null)
|
||||
{
|
||||
float DistFromTarget = (Vector3.Distance(transform.position, StartingICombat.DamagePosition()));
|
||||
|
||||
if (!MinHomingDistMet && DistFromTarget < CurrentAbilityData.HomingSettings.MinimumHomingDistance) MinHomingDistMet = true;
|
||||
|
||||
if (CurrentAbilityData.HomingSettings.Enabled && HomingTimer < CurrentAbilityData.HomingSettings.HomingSeconds && !MinHomingDistMet && CurrentTarget.transform.localScale != Vector3.one * 0.003f && TargetAngle < (CurrentAbilityData.GeneralProjectileSettings.ProjectileMaxLaunchAngle / 2f))
|
||||
{
|
||||
Vector3 ForwardMovement = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
transform.position = ForwardMovement;
|
||||
float DistFormOwner = Vector3.Distance(Owner.transform.position, transform.position);
|
||||
|
||||
if (DistFormOwner > 1f || DistFromTarget < 1)
|
||||
{
|
||||
HomingTimer += Time.deltaTime;
|
||||
RotateTowardsTarget();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotate towards the projectile's Current Target's position.
|
||||
/// </summary>
|
||||
void RotateTowardsTarget ()
|
||||
{
|
||||
//Determine which direction to rotate towards
|
||||
Vector3 targetDirection = StartingICombat.DamagePosition() - transform.position;
|
||||
//Vector3 targetDirection = CurrentTarget.position - transform.position;
|
||||
|
||||
//The step size is equal to speed times frame time.
|
||||
float singleStep = Time.deltaTime * CurrentAbilityData.HomingSettings.HomingSpeed;
|
||||
|
||||
//Rotate the forward vector towards the target direction by one step
|
||||
Vector3 newDirection = Vector3.RotateTowards(transform.forward, targetDirection, singleStep, 0.0f);
|
||||
|
||||
//Calculate a rotation a step closer to the target and applies rotation to this object
|
||||
transform.rotation = Quaternion.LookRotation(newDirection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the impact functionality, given the collision object is not this or another projectile.
|
||||
/// </summary>
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!this.enabled) return; //Only allow trigger collisions to work if the script is active
|
||||
if (((1 << other.gameObject.layer) & CurrentAbilityData.ColliderSettings.CollidableLayers) != 0 && other.gameObject != Owner && !IgnoredColliders.Contains(other)) Impact(other.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles all impact related functionality.
|
||||
/// </summary>
|
||||
void Impact (GameObject TargetHit)
|
||||
{
|
||||
m_Collider.enabled = false; //Disable the ability's collider so unintended collisions don't happen.
|
||||
Initialized = false; //Disable initialization so the projectile stops operating.
|
||||
CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position); //Spawns the ability's impact effect and impact sound.
|
||||
Invoke(nameof(ImpactDespawn), CurrentAbilityData.ColliderSettings.CollisionTimeout); //Despawn the projectile according to its CollisionTimeout. This gives the projectile extra time to finish before being despawned.
|
||||
DamageTarget(TargetHit); //Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
|
||||
if (CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count > 0)
|
||||
{
|
||||
if (!CurrentAbilityData.GeneralProjectileSettings.AttachToTarget) SetEffectsState(false); //Disables the specified effects by comparing the names from the EffectsToDisable list.
|
||||
else if (TargetHit.activeSelf) StartCoroutine(SetEffectsStateDelay(false)); //Disables the specified effects by comparing the names from the EffectsToDisable list (with delay).
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delay the SetEffectsState call.
|
||||
/// </summary>
|
||||
IEnumerator SetEffectsStateDelay (bool State)
|
||||
{
|
||||
if (!State) yield return new WaitForSeconds(1);
|
||||
SetEffectsState(State);
|
||||
}
|
||||
|
||||
void OnDisable ()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified effects' state by comparing the names from the EffectsToDisable list.
|
||||
/// </summary>
|
||||
void SetEffectsState (bool State)
|
||||
{
|
||||
for (int i = 0; i < m_ProjectileObjects.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count; j++)
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectObject.name == CurrentAbilityData.ProjectileSettings.EffectsToDisable[j])
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectParticle != null) m_ProjectileObjects[i].EffectParticle.enabled = State;
|
||||
else if (m_ProjectileObjects[i].EffectObject != null && m_ProjectileObjects[i].EffectObject != this.gameObject) m_ProjectileObjects[i].EffectObject.SetActive(State);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target)
|
||||
{
|
||||
LocationBasedDamageArea m_LocationBasedDamageArea = Target.GetComponent<LocationBasedDamageArea>();
|
||||
|
||||
//Only damage layers that are in the AI's DetectionLayerMask or if the target has a LBD component on it.
|
||||
if (!m_LocationBasedDamageArea && ((1 << Target.layer) & EmeraldComponent.DetectionComponent.DetectionLayerMask) == 0) return;
|
||||
|
||||
//Return if the target is teleporting.
|
||||
if (m_LocationBasedDamageArea != null && m_LocationBasedDamageArea.EmeraldComponent.transform.localScale == Vector3.one * 0.003f || Target.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
|
||||
//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;
|
||||
|
||||
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_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.GeneralProjectileSettings.AttachToTarget)
|
||||
{
|
||||
AttachToCollider(Target.transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the projectile to the passed collider.
|
||||
/// </summary>
|
||||
void AttachToCollider(Transform Target)
|
||||
{
|
||||
transform.SetParent(Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns the impact effect after the CollisionTimeout has been met.
|
||||
/// </summary>
|
||||
void ImpactDespawn ()
|
||||
{
|
||||
this.enabled = false; //Disable the script so it doesn't interfere with other objects that may use the same object through Object Pooling
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd0921c4ca8d1544081ae0f4a4519821
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "General Projectile Ability", menuName = "Emerald AI/Ability/General Projectile Ability")]
|
||||
public class GeneralProjectileAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.CreateSettingsData CreateSettings;
|
||||
public AbilityData.ProjectileData ProjectileSettings;
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("LinearProjectileSettings")] public AbilityData.GeneralProjectileData GeneralProjectileSettings;
|
||||
public AbilityData.HomingData HomingSettings;
|
||||
public AbilityData.TargetTypeData TargetTypeSettings;
|
||||
public AbilityData.SpreadData SpreadSettings;
|
||||
public AbilityData.ColliderData ColliderSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
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(SpawnProjectiles(Owner, AttackTransform, GeneralProjectileSettings.TimeBetweenProjectiles));
|
||||
}
|
||||
|
||||
IEnumerator SpawnProjectiles (GameObject Owner, Transform AttackTransform, float Delay)
|
||||
{
|
||||
Transform Target = GetTarget(Owner, TargetTypeSettings.TargetType);
|
||||
|
||||
for (int i = 0; i < GeneralProjectileSettings.TotalProjectiles; i++)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
if (EmeraldComponent != null)
|
||||
{
|
||||
if (EmeraldComponent.AnimationComponent.IsDodging || EmeraldComponent.AnimationComponent.IsGettingHit) yield break;
|
||||
}
|
||||
|
||||
//Continue to get a new target each time a projectile is created
|
||||
if (TargetTypeSettings.TargetType == AbilityData.TargetTypes.MultipleRandomEnemies) Target = GetTarget(Owner, TargetTypeSettings.TargetType);
|
||||
|
||||
Vector3 SpawnPosition = AttackTransform.position;
|
||||
|
||||
if (SpreadSettings.Enabled && SpreadSettings.SpreadType == AbilityData.SpreadTypes.HorizontalRadius) SpawnPosition = Owner.transform.position + Owner.transform.localScale.y * Vector3.up;
|
||||
|
||||
GameObject SpawnedProjectile = EmeraldObjectPool.Spawn(ProjectileSettings.ProjectileEffect, SpawnPosition, ProjectileSettings.ProjectileEffect.transform.rotation);
|
||||
SpawnedProjectile.transform.localScale = ProjectileSettings.ProjectileEffect.transform.localScale;
|
||||
SpawnedProjectile.name = ProjectileSettings.ProjectileEffect.name;
|
||||
|
||||
if (SpreadSettings.Enabled)
|
||||
{
|
||||
//Evenly distributed projectiles based on given angle
|
||||
if (SpreadSettings.SpreadType == AbilityData.SpreadTypes.HorizontalRadius)
|
||||
{
|
||||
float AnglePerStepX = ((SpreadSettings.SpreadAngleX / 2f) * 2) / (float)GeneralProjectileSettings.TotalProjectiles;
|
||||
SpawnedProjectile.transform.LookAt(Owner.transform.position + Owner.transform.forward);
|
||||
Vector3 AimDir = new Vector3(-SpreadSettings.TiltAngleY, (-(SpreadSettings.SpreadAngleX / 2f) + AnglePerStepX / 2f) + AnglePerStepX * i, 0);
|
||||
SpawnedProjectile.transform.eulerAngles = SpawnedProjectile.transform.eulerAngles + AimDir;
|
||||
}
|
||||
//Random Spread Offset
|
||||
else if (SpreadSettings.SpreadType == AbilityData.SpreadTypes.Random)
|
||||
{
|
||||
float spreadX = Random.Range(SpreadSettings.MinSpreadX, SpreadSettings.MaxSpreadX);
|
||||
float spreadY = Random.Range(SpreadSettings.MinSpreadY, SpreadSettings.MaxSpreadY);
|
||||
if (Target != null) SpawnedProjectile.transform.LookAt(Target.position);
|
||||
Vector3 AimDir = new Vector3(-spreadY, spreadX, 0);
|
||||
SpawnedProjectile.transform.eulerAngles = SpawnedProjectile.transform.eulerAngles + AimDir;
|
||||
}
|
||||
}
|
||||
|
||||
AssignScript(SpawnedProjectile).Initialize(Owner, Target, this);
|
||||
|
||||
if (Delay > 0) yield return new WaitForSeconds(Delay);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign the ProjectileMovement script on the newly spawned projectile.
|
||||
/// </summary>
|
||||
public GeneralProjectile AssignScript(GameObject SpawnedProjectile)
|
||||
{
|
||||
var generalProjectile = SpawnedProjectile.GetComponent<GeneralProjectile>();
|
||||
if (generalProjectile == null) generalProjectile = SpawnedProjectile.AddComponent<GeneralProjectile>();
|
||||
generalProjectile.enabled = true;
|
||||
return generalProjectile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08f6e2140fad9cc4bb236717104d750e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d02cf7d3825ccd43a4962a1b81f51d7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,251 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
using UnityEngine.Audio;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class Grenade : MonoBehaviour, IAvoidable
|
||||
{
|
||||
#region Variables
|
||||
GrenadeAbility CurrentAbilityData;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
Transform CurrentTarget;
|
||||
public Transform AbilityTarget { get => CurrentTarget; set => CurrentTarget = value; }
|
||||
Vector3 InitialTargetPosition;
|
||||
AudioSource m_AudioSource;
|
||||
GameObject m_SoundEffect;
|
||||
Rigidbody m_Rigidbody;
|
||||
GameObject Owner;
|
||||
Collider GrenadeCollider;
|
||||
float StartTime;
|
||||
bool Initialized;
|
||||
bool HasCollided;
|
||||
float TargetAngle;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize the needed components and settings the first time the projectile is used.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
m_AudioSource.loop = true;
|
||||
m_AudioSource.rolloffMode = AudioRolloffMode.Linear;
|
||||
m_AudioSource.spatialBlend = 1;
|
||||
m_AudioSource.maxDistance = 20;
|
||||
m_SoundEffect = Resources.Load("Emerald Sound") as GameObject;
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_Rigidbody.drag = 0.1f;
|
||||
m_Rigidbody.angularDrag = 0.05f;
|
||||
GrenadeCollider = GetComponent<Collider>();
|
||||
gameObject.layer = LayerMask.NameToLayer("Ignore Raycast");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the projectile with the passed information.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of this projectile.</param>
|
||||
/// <param name="currentTarget">The current target for this projectile.</param>
|
||||
/// <param name="abilityData">The current ability data for this projectile.</param>
|
||||
public void Initialize (GameObject owner, Transform currentTarget, GrenadeAbility abilityData)
|
||||
{
|
||||
Owner = owner;
|
||||
CurrentTarget = currentTarget;
|
||||
EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
CurrentAbilityData = abilityData;
|
||||
GrenadeCollider.enabled = false;
|
||||
gameObject.layer = CurrentAbilityData.GrenadeSettings.GrenadeLayer;
|
||||
Invoke(nameof(EnableCollider), 0.1f);
|
||||
Initialized = false;
|
||||
HasCollided = false;
|
||||
InitializeProjectile(); //Intialize the projectile's settings.
|
||||
StartTime = Time.time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the grenade's collider 0.1f seconds after being thrown. This gives the grenade time to leave the owner's hand so it doesn't interfere with its colliders.
|
||||
/// </summary>
|
||||
void EnableCollider ()
|
||||
{
|
||||
GrenadeCollider.enabled = true;
|
||||
}
|
||||
|
||||
void InitializeProjectile ()
|
||||
{
|
||||
InitialTargetPosition = CurrentTarget.transform.position;
|
||||
transform.LookAt(InitialTargetPosition);
|
||||
Initialized = true;
|
||||
if (CurrentAbilityData.GrenadeSettings.ThrowSound) m_AudioSource.PlayOneShot(CurrentAbilityData.GrenadeSettings.ThrowSound);
|
||||
Vector3 TargetDirection = InitialTargetPosition - transform.position;
|
||||
float Distance = Vector3.Distance(InitialTargetPosition, transform.position);
|
||||
m_Rigidbody.velocity = new Vector3(0f, 0f, 0f);
|
||||
m_Rigidbody.angularVelocity = new Vector3(0f, 0f, 0f);
|
||||
float RandomizedDepth = Random.Range(0.85f, 1f);
|
||||
float ThrowHeightOffset = Mathf.Lerp(1f, 0.5f, CurrentAbilityData.GrenadeSettings.ThrowHeight / 10f);
|
||||
m_Rigidbody.AddForce((TargetDirection.normalized * Distance * (ThrowHeightOffset * RandomizedDepth)) + (Vector3.up * CurrentAbilityData.GrenadeSettings.ThrowHeight), ForceMode.Impulse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to move and track the time since the projectile has been spawned.
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
ProjectileTimeout(); //Track the time since the projectile has been active.
|
||||
}
|
||||
|
||||
void FixedUpdate ()
|
||||
{
|
||||
if (!HasCollided && CurrentAbilityData.GrenadeSettings.RotateOnThrow)
|
||||
{
|
||||
Quaternion deltaRotation = Quaternion.Euler(new Vector3(300, 20, 20) * Time.fixedDeltaTime);
|
||||
m_Rigidbody.MoveRotation(m_Rigidbody.rotation * deltaRotation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track the time since the projectile has been active. Once the time ProjectileTimeoutSeconds
|
||||
/// has been met, despawn the projectile and spawn an impact effect from its current position.
|
||||
/// </summary>
|
||||
void ProjectileTimeout ()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
float TimeAlive = Time.time - StartTime;
|
||||
if (TimeAlive > CurrentAbilityData.GrenadeSettings.ExplosionTime)
|
||||
{
|
||||
Explode();
|
||||
Initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void OnCollisionEnter (Collision col)
|
||||
{
|
||||
if (!HasCollided) HasCollided = true;
|
||||
|
||||
m_AudioSource.pitch = Random.Range(0.85f, 1.15f);
|
||||
m_AudioSource.volume = Random.Range(0.6f, 1f);
|
||||
|
||||
if (col.relativeVelocity.magnitude > 0.4f)
|
||||
m_AudioSource.PlayOneShot(CurrentAbilityData.GrenadeSettings.ImpactSoundsList[Random.Range(0, CurrentAbilityData.GrenadeSettings.ImpactSoundsList.Count)]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this function when you want to damage surrounding AI based on the set public variables within this script.
|
||||
/// </summary>
|
||||
public void Explode()
|
||||
{
|
||||
//Create explosion effect
|
||||
CurrentAbilityData.GrenadeSettings.ExplodeGrenade(EmeraldComponent.gameObject, transform.position);
|
||||
|
||||
Invoke(nameof(Despawn), 0.11f);
|
||||
|
||||
List<Collider> TargetsInRange = Physics.OverlapSphere(transform.position, CurrentAbilityData.GrenadeSettings.ExplosionRadius, EmeraldComponent.DetectionComponent.DetectionLayerMask).ToList();
|
||||
TargetsInRange.Remove(Owner.GetComponent<Collider>()); //Remove the owner's collider if it happens to be detected.
|
||||
|
||||
for (int i = 0; i < TargetsInRange.Count; i++)
|
||||
{
|
||||
//Only damage targets that the Owner has an Enemy Relation Type with. Ignore this setting if CanDamageFriendlies is enabled.
|
||||
if (CurrentAbilityData.GrenadeSettings.CanDamageFriendlies || EmeraldAPI.Faction.GetTargetFactionRelation(EmeraldComponent, TargetsInRange[i].transform) == "Enemy")
|
||||
{
|
||||
ICombat m_ICombat = TargetsInRange[i].GetComponent<ICombat>();
|
||||
DamageTarget(TargetsInRange[i].gameObject, m_ICombat);
|
||||
}
|
||||
}
|
||||
|
||||
List<Collider> RigidbodiesInRange = Physics.OverlapSphere(transform.position, CurrentAbilityData.GrenadeSettings.ExplosionRadius, CurrentAbilityData.GrenadeSettings.RigidbodyLayers).ToList();
|
||||
RigidbodiesInRange.Remove(Owner.GetComponent<Collider>()); //Remove the owner's collider if it happens to be detected.
|
||||
|
||||
for (int i = 0; i < RigidbodiesInRange.Count; i++)
|
||||
{
|
||||
Rigidbody TargetRigidbody = RigidbodiesInRange[i].GetComponent<Rigidbody>();
|
||||
if (TargetRigidbody != null) StartCoroutine(AddRagdollForce(TargetRigidbody));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages each target within range of the explosion, given that it has a IDamageable.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target, ICombat m_ICombat)
|
||||
{
|
||||
if (Target.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
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;
|
||||
|
||||
var m_IDamageable = Target.GetComponent<IDamageable>();
|
||||
if (m_IDamageable != null)
|
||||
{
|
||||
bool IsCritHit = CurrentAbilityData.DamageSettings.GenerateCritHit();
|
||||
int DamageMitigation = Mathf.RoundToInt((1f - Vector3.Distance(Target.transform.position, transform.position) / CurrentAbilityData.GrenadeSettings.ExplosionRadius) * CurrentAbilityData.DamageSettings.GenerateDamage(IsCritHit));
|
||||
m_IDamageable.Damage(DamageMitigation, transform, 0, IsCritHit);
|
||||
CurrentAbilityData.DamageSettings.DamageTargetOverTime(CurrentAbilityData, CurrentAbilityData.DamageSettings, Owner, Target);
|
||||
|
||||
if (m_IDamageable.Health <= 0)
|
||||
{
|
||||
EmeraldSystem DetectedEmeraldAgent = Target.GetComponent<EmeraldSystem>();
|
||||
if (DetectedEmeraldAgent != null && DetectedEmeraldAgent.LBDComponent != null) StartCoroutine(AddRagdollForceAI(DetectedEmeraldAgent));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log(Target.gameObject + " is missing a IDamageable and/or ICombat Component, apply one");
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator AddRagdollForceAI(EmeraldSystem EmeraldTarget)
|
||||
{
|
||||
float t = 0;
|
||||
float ForceMitigation = Mathf.RoundToInt((1f - Vector3.Distance(EmeraldTarget.transform.position, transform.position) / CurrentAbilityData.GrenadeSettings.ExplosionRadius) * CurrentAbilityData.DamageSettings.BaseDamageSettings.RagdollForce) * 4f;
|
||||
|
||||
Rigidbody TargetRigidbody = null;
|
||||
if (EmeraldTarget != null) TargetRigidbody = EmeraldTarget.DetectionComponent.HeadTransform.GetComponent<Rigidbody>();
|
||||
|
||||
if (TargetRigidbody != null)
|
||||
{
|
||||
while (t < 0.1f)
|
||||
{
|
||||
t += Time.fixedDeltaTime;
|
||||
TargetRigidbody.AddForce((transform.position - EmeraldTarget.transform.position).normalized * -ForceMitigation * 1f + (Vector3.up * ForceMitigation * 1f), ForceMode.Acceleration);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator AddRagdollForce(Rigidbody TargetRigidbody)
|
||||
{
|
||||
float t = 0;
|
||||
float ForceMitigation = Mathf.RoundToInt((1f - Vector3.Distance(TargetRigidbody.transform.position, transform.position) / (float)CurrentAbilityData.GrenadeSettings.ExplosionRadius * 1.5f) * CurrentAbilityData.DamageSettings.BaseDamageSettings.RagdollForce) * 4f;
|
||||
|
||||
if (TargetRigidbody != null)
|
||||
{
|
||||
while (t < 0.1f)
|
||||
{
|
||||
t += Time.fixedDeltaTime;
|
||||
TargetRigidbody.AddForce((transform.position - TargetRigidbody.transform.position).normalized * -ForceMitigation * 0.15f + (Vector3.up * ForceMitigation * 0.15f), ForceMode.Acceleration);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Despawn ()
|
||||
{
|
||||
this.enabled = false;
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
|
||||
void OnDisable ()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1170830662d5dd9468097764a1878716
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Grenade Ability", menuName = "Emerald AI/Ability/Grenade Ability")]
|
||||
public class GrenadeAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.GrenadeData GrenadeSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
public override void InvokeAbility(GameObject Owner, Transform AttackTransform = null)
|
||||
{
|
||||
SpawnProjectiles(Owner, AttackTransform);
|
||||
}
|
||||
|
||||
void SpawnProjectiles (GameObject Owner, Transform AttackTransform)
|
||||
{
|
||||
Transform Target = GetTarget(Owner, AbilityData.TargetTypes.CurrentTarget);
|
||||
|
||||
Vector3 SpawnPosition = AttackTransform.position;
|
||||
GameObject SpawnedProjectile = EmeraldObjectPool.Spawn(GrenadeSettings.GrenadeObject, SpawnPosition, GrenadeSettings.GrenadeObject.transform.rotation);
|
||||
SpawnedProjectile.transform.localScale = GrenadeSettings.GrenadeObject.transform.localScale;
|
||||
SpawnedProjectile.name = GrenadeSettings.GrenadeObject.name;
|
||||
|
||||
AssignScript(SpawnedProjectile).Initialize(Owner, Target, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign the ProjectileMovement script on the newly spawned projectile.
|
||||
/// </summary>
|
||||
public Grenade AssignScript(GameObject SpawnedProjectile)
|
||||
{
|
||||
var grenade = SpawnedProjectile.GetComponent<Grenade>();
|
||||
if (grenade == null) grenade = SpawnedProjectile.AddComponent<Grenade>();
|
||||
grenade.enabled = true;
|
||||
return grenade;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe42a88d76ed5f349bdb72cee4b18b62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7a2d08e08198c04e84624f7412e1806
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,380 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class GroundProjectile : MonoBehaviour, IAvoidable
|
||||
{
|
||||
#region Variables
|
||||
GroundProjectileAbility CurrentAbilityData;
|
||||
Transform CurrentTarget;
|
||||
public Transform AbilityTarget { get => CurrentTarget; set => CurrentTarget = value; }
|
||||
Vector3 InitialTargetPosition;
|
||||
ICombat StartingICombat;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
AudioSource m_AudioSource;
|
||||
GameObject m_SoundEffect;
|
||||
Collider m_Collider;
|
||||
SphereCollider m_SphereCollider;
|
||||
Rigidbody m_Rigidbody;
|
||||
GameObject Owner;
|
||||
List<Collider> IgnoredColliders = new List<Collider>();
|
||||
float StartTime;
|
||||
float HomingTimer;
|
||||
bool Initialized;
|
||||
float TravelDistance;
|
||||
Vector3 StartingPosition;
|
||||
bool MinHomingDistMet;
|
||||
Vector3 SurfaceNormal;
|
||||
Quaternion qTarget;
|
||||
Quaternion qGround;
|
||||
Vector3 TargetDirection;
|
||||
|
||||
public List<ProjectileEffectsClass> m_ProjectileObjects = new List<ProjectileEffectsClass>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Used to initialize the needed components and settings the first time the projectile is used.
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
m_AudioSource.loop = true;
|
||||
m_AudioSource.rolloffMode = AudioRolloffMode.Linear;
|
||||
m_AudioSource.spatialBlend = 1;
|
||||
m_AudioSource.maxDistance = 20;
|
||||
|
||||
//If a collider already exists on the projectile object, use it as the projectile's collider.
|
||||
m_Collider = GetComponent<Collider>();
|
||||
if (m_Collider != null)
|
||||
{
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
//If not, create a SphereCollider.
|
||||
else
|
||||
{
|
||||
m_Collider = gameObject.AddComponent<SphereCollider>();
|
||||
m_SphereCollider = gameObject.GetComponent<SphereCollider>();
|
||||
m_Collider.isTrigger = true;
|
||||
m_Collider.enabled = false;
|
||||
}
|
||||
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_Rigidbody.useGravity = true;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
m_Rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
m_SoundEffect = Resources.Load("Emerald Sound") as GameObject;
|
||||
|
||||
//Go through all effect objects on Awake and cache them to be used through the Projectile Module.
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(gameObject.GetComponent<ParticleSystemRenderer>(), gameObject));
|
||||
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
m_ProjectileObjects.Add(new ProjectileEffectsClass(child.GetComponent<ParticleSystemRenderer>(), child.gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the projectile with the passed information.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of this projectile.</param>
|
||||
/// <param name="currentTarget">The current target for this projectile.</param>
|
||||
/// <param name="abilityData">The current ability data for this projectile.</param>
|
||||
public void Initialize(GameObject owner, Transform currentTarget, GroundProjectileAbility abilityData)
|
||||
{
|
||||
StartCoroutine(InitializeInternal(owner, currentTarget, abilityData));
|
||||
}
|
||||
|
||||
IEnumerator InitializeInternal(GameObject owner, Transform currentTarget, GroundProjectileAbility abilityData)
|
||||
{
|
||||
Owner = owner;
|
||||
CurrentTarget = currentTarget;
|
||||
if (CurrentTarget != null)
|
||||
{
|
||||
StartingICombat = CurrentTarget.GetComponent<ICombat>();
|
||||
InitialTargetPosition = StartingICombat.DamagePosition();
|
||||
}
|
||||
|
||||
EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
CurrentAbilityData = abilityData;
|
||||
m_Collider.enabled = true;
|
||||
|
||||
//Get a reference to the Owner's LBD component so internal colliders can be ignored.
|
||||
LocationBasedDamage LBDComponent = Owner.GetComponent<LocationBasedDamage>();
|
||||
if (LBDComponent)
|
||||
{
|
||||
IgnoredColliders.Clear();
|
||||
for (int i = 0; i < LBDComponent.ColliderList.Count; i++)
|
||||
{
|
||||
IgnoredColliders.Add(LBDComponent.ColliderList[i].ColliderObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_SphereCollider != null)
|
||||
{
|
||||
m_SphereCollider.radius = abilityData.ColliderSettings.ColliderRadius;
|
||||
m_SphereCollider.center = Vector3.forward * abilityData.ColliderSettings.ZOffet;
|
||||
}
|
||||
|
||||
gameObject.layer = CurrentAbilityData.ColliderSettings.ProjectileLayer;
|
||||
CurrentAbilityData.GroundProjectileSettings.AllignmentLayers &= ~(1 << CurrentAbilityData.ColliderSettings.ProjectileLayer); //Remove the ProjectileLayer if it was mistakenly added to the AllignmentLayers
|
||||
Initialized = false;
|
||||
MinHomingDistMet = false;
|
||||
HomingTimer = 0;
|
||||
StartTime = Time.time;
|
||||
|
||||
//Reset the rotation and target variables (not doing so causes the starting direction to be incorrect).
|
||||
TargetDirection = Vector3.zero;
|
||||
qGround = new Quaternion();
|
||||
qTarget = new Quaternion();
|
||||
|
||||
SetEffectsState(true); //Disables the specified effects by comparing the names from the EffectsToDisable list.
|
||||
InitializeProjectile(); //Initialize the projectile
|
||||
|
||||
yield return new WaitForSeconds(CurrentAbilityData.ProjectileSettings.LaunchProjectileDelay);
|
||||
|
||||
if (CurrentAbilityData.ProjectileSettings.TravelSound != null) m_AudioSource.PlayOneShot(CurrentAbilityData.ProjectileSettings.TravelSound);
|
||||
CurrentAbilityData.ProjectileSettings.SpawnLaunchProjectileEffect(Owner, transform.position);
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
void InitializeProjectile()
|
||||
{
|
||||
CurrentAbilityData.ProjectileSettings.SpawnEffect(Owner, transform.position);
|
||||
StartingPosition = transform.position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to move and track the time since the projectile has been spawned.
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
ProjectileTimeout(); //Track the time since the projectile has been active.
|
||||
MoveGroundProjectile(); //Move the projectile towards its current target.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Track the time since the projectile has been active. Once the time ProjectileTimeoutSeconds
|
||||
/// has been met, despawn the projectile and spawn an impact effect from its current position.
|
||||
/// </summary>
|
||||
void ProjectileTimeout()
|
||||
{
|
||||
float TimeAlive = Time.time - StartTime;
|
||||
if (TimeAlive > CurrentAbilityData.ProjectileSettings.ProjectileTimeoutSeconds && m_Collider.enabled)
|
||||
{
|
||||
StopGroundProjectile();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the projectile towards its current target, but only if the ability is initialized.
|
||||
/// </summary>
|
||||
void MoveGroundProjectile()
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
TravelDistance = Vector3.Distance(StartingPosition, transform.position);
|
||||
|
||||
if (TravelDistance < CurrentAbilityData.GroundProjectileSettings.MaxTravelDistance || CurrentTarget == null)
|
||||
{
|
||||
var step = CurrentAbilityData.GroundProjectileSettings.ProjectileSpeed * Time.deltaTime;
|
||||
float DistFromTarget = (Vector3.Distance(transform.position, StartingICombat.DamagePosition()));
|
||||
float DistFormOwner = Vector3.Distance(Owner.transform.position, transform.position);
|
||||
if (!MinHomingDistMet && DistFromTarget < CurrentAbilityData.HomingSettings.MinimumHomingDistance) MinHomingDistMet = true;
|
||||
|
||||
if (CurrentAbilityData.HomingSettings.Enabled && HomingTimer < CurrentAbilityData.HomingSettings.HomingSeconds && !MinHomingDistMet && CurrentTarget.transform.localScale != Vector3.one * 0.003f)
|
||||
{
|
||||
if (DistFormOwner > 2f || DistFromTarget < 1)
|
||||
{
|
||||
HomingTimer += Time.deltaTime;
|
||||
GetSurfaceNormal();
|
||||
TargetDirection = StartingICombat.DamagePosition() - transform.position;
|
||||
TargetDirection.y = 0;
|
||||
SetMovementAndRotation(step);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetSurfaceNormal();
|
||||
SetMovementAndRotation(step);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GetSurfaceNormal();
|
||||
SetMovementAndRotation(step);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StopGroundProjectile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the movement and rotation for the ground projectiles. The target source is only used while the ability is actively homing.
|
||||
/// </summary>
|
||||
void SetMovementAndRotation (float step)
|
||||
{
|
||||
qGround = Quaternion.Slerp(qGround, Quaternion.FromToRotation(Vector3.up, SurfaceNormal), Time.deltaTime * CurrentAbilityData.GroundProjectileSettings.AlignmentSpeed);
|
||||
if (TargetDirection != Vector3.zero) qTarget = Quaternion.Slerp(qTarget, Quaternion.LookRotation(TargetDirection, Vector3.up), Time.deltaTime * CurrentAbilityData.HomingSettings.HomingSpeed);
|
||||
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward, step);
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, qGround * qTarget, Time.deltaTime * CurrentAbilityData.GroundProjectileSettings.AlignmentSpeed);
|
||||
|
||||
float SurfaceAngle = Vector3.Angle(SurfaceNormal, Vector3.up);
|
||||
if (SurfaceAngle >= CurrentAbilityData.GroundProjectileSettings.KillAngle) StopGroundProjectile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the current surface normal by casting a ray from the center of the projectile.
|
||||
/// </summary>
|
||||
public Vector3 GetSurfaceNormal()
|
||||
{
|
||||
RaycastHit HitDown;
|
||||
if (Physics.Raycast(transform.position + Vector3.up, -Vector3.up, out HitDown, 2f, CurrentAbilityData.GroundProjectileSettings.AllignmentLayers))
|
||||
{
|
||||
if (HitDown.transform != this.transform)
|
||||
{
|
||||
float MaxNormalAngle = CurrentAbilityData.GroundProjectileSettings.MaxAlignmentAngle * 0.01f;
|
||||
transform.position = new Vector3(transform.position.x, Mathf.Lerp(transform.position.y, HitDown.point.y + CurrentAbilityData.GroundProjectileSettings.HeightOffset, Time.deltaTime * 20), transform.position.z);
|
||||
SurfaceNormal = HitDown.normal;
|
||||
SurfaceNormal.x = Mathf.Clamp(SurfaceNormal.x, -MaxNormalAngle, MaxNormalAngle);
|
||||
SurfaceNormal.z = Mathf.Clamp(SurfaceNormal.z, -MaxNormalAngle, MaxNormalAngle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Stop the projectile if there's no detectable ground. This is to prevent moving on cliffs or edges.
|
||||
StopGroundProjectile();
|
||||
}
|
||||
|
||||
RaycastHit HitForward;
|
||||
if (Physics.Raycast(new Vector3(transform.position.x, transform.position.y + 0.5f, transform.position.z), transform.forward, out HitForward, CurrentAbilityData.ColliderSettings.ColliderRadius, CurrentAbilityData.GroundProjectileSettings.AllignmentLayers))
|
||||
{
|
||||
if (HitForward.transform != this.transform)
|
||||
{
|
||||
float SurfaceAngle = Vector3.Angle(HitForward.normal, Vector3.up);
|
||||
if (SurfaceAngle >= CurrentAbilityData.GroundProjectileSettings.KillAngle)
|
||||
{
|
||||
//CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position); //Spawns the ability's impact effect and impact sound.
|
||||
StopGroundProjectile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SurfaceNormal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the impact functionality, given the collision object is not this or another projectile.
|
||||
/// </summary>
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (((1 << other.gameObject.layer) & CurrentAbilityData.ColliderSettings.CollidableLayers) != 0 && other.gameObject != Owner && !IgnoredColliders.Contains(other)) Impact(other.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles all impact related functionality.
|
||||
/// </summary>
|
||||
void Impact(GameObject TargetHit)
|
||||
{
|
||||
m_Collider.enabled = false; //Disable the ability's collider so unintended collisions don't happen.
|
||||
Initialized = false; //Disable initialization so the projectile stops operating.
|
||||
CurrentAbilityData.ProjectileSettings.SpawnImpactEffect(Owner, transform.position); //Spawns the ability's impact effect and impact sound.
|
||||
DamageTarget(TargetHit); //Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
Invoke(nameof(ImpactDespawn), CurrentAbilityData.ColliderSettings.CollisionTimeout); //Despawn the projectile according to its CollisionTimeout. This gives the projectile extra time to finish before being despawned.
|
||||
SetEffectsState(false); //Disables the specified effects by comparing the names from the EffectsToDisable list.
|
||||
}
|
||||
|
||||
void StopGroundProjectile ()
|
||||
{
|
||||
m_Collider.enabled = false; //Disable the ability's collider so unintended collisions don't happen.
|
||||
Initialized = false; //Disable initialization so the projectile stops operating.
|
||||
m_AudioSource.Stop();
|
||||
Invoke(nameof(ImpactDespawn), CurrentAbilityData.ColliderSettings.CollisionTimeout); //Despawn the projectile according to its CollisionTimeout. This gives the projectile extra time to finish before being despawned.
|
||||
SetEffectsState(false); //Disables the specified effects by comparing the names from the EffectsToDisable list.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages the projectile's Target. If a LocationBasedDamageArea is detected, damage it. If not, damage the target's IDamageable component.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target)
|
||||
{
|
||||
LocationBasedDamageArea m_LocationBasedDamageArea = Target.GetComponent<LocationBasedDamageArea>();
|
||||
|
||||
//Only damage layers that are in the AI's DetectionLayerMask or if the target has a LBD component on it.
|
||||
if (!m_LocationBasedDamageArea && ((1 << Target.layer) & EmeraldComponent.DetectionComponent.DetectionLayerMask) == 0) return;
|
||||
|
||||
//Return if the target is teleporting.
|
||||
if (m_LocationBasedDamageArea != null && m_LocationBasedDamageArea.EmeraldComponent.transform.localScale == Vector3.one * 0.003f || Target.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
|
||||
//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;
|
||||
|
||||
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_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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified effects' state by comparing the names from the EffectsToDisable list.
|
||||
/// </summary>
|
||||
void SetEffectsState(bool State)
|
||||
{
|
||||
if (CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < m_ProjectileObjects.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < CurrentAbilityData.ProjectileSettings.EffectsToDisable.Count; j++)
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectObject.name == CurrentAbilityData.ProjectileSettings.EffectsToDisable[j])
|
||||
{
|
||||
if (m_ProjectileObjects[i].EffectParticle != null) m_ProjectileObjects[i].EffectParticle.enabled = State;
|
||||
else if (m_ProjectileObjects[i].EffectObject != null && m_ProjectileObjects[i].EffectObject != this.gameObject) m_ProjectileObjects[i].EffectObject.SetActive(State);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns the impact effect after the CollisionTimeout has been met.
|
||||
/// </summary>
|
||||
void ImpactDespawn()
|
||||
{
|
||||
EmeraldObjectPool.Despawn(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6b6f2c3a28da36459dc3e199def7b51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Ground Projectile Ability", menuName = "Emerald AI/Ability/Ground Projectile Ability")]
|
||||
public class GroundProjectileAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.CreateSettingsData CreateSettings;
|
||||
public AbilityData.ProjectileData ProjectileSettings;
|
||||
public AbilityData.GroundProjectileData GroundProjectileSettings;
|
||||
public AbilityData.HomingData HomingSettings;
|
||||
public AbilityData.TargetTypeData TargetTypeSettings;
|
||||
public AbilityData.ColliderData ColliderSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
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, TargetTypeSettings.TargetType);
|
||||
CreateSettings.SpawnCreateEffect(Owner, AttackTransform);
|
||||
OwnerMonoBehaviour.StartCoroutine(SpawnProjectiles(Owner, Target, GroundProjectileSettings.TimeBetweenProjectiles));
|
||||
}
|
||||
|
||||
IEnumerator SpawnProjectiles(GameObject Owner, Transform Target, float Delay)
|
||||
{
|
||||
for (int i = 0; i < GroundProjectileSettings.TotalProjectiles; i++)
|
||||
{
|
||||
//Continue to get a new target each time a projectile is created
|
||||
if (TargetTypeSettings.TargetType == AbilityData.TargetTypes.MultipleRandomEnemies) Target = GetTarget(Owner, TargetTypeSettings.TargetType);
|
||||
|
||||
Vector3 SpawnPosition = Owner.transform.position;
|
||||
GameObject SpawnedProjectile = EmeraldObjectPool.Spawn(ProjectileSettings.ProjectileEffect, SpawnPosition, ProjectileSettings.ProjectileEffect.transform.rotation);
|
||||
SpawnedProjectile.transform.localScale = ProjectileSettings.ProjectileEffect.transform.localScale;
|
||||
SpawnedProjectile.name = ProjectileSettings.ProjectileEffect.name;
|
||||
|
||||
float AnglePerStepX = ((GroundProjectileSettings.AngleSpread / 2f) * 2) / (float)GroundProjectileSettings.TotalProjectiles;
|
||||
SpawnedProjectile.transform.LookAt(Owner.transform.position + Owner.transform.forward);
|
||||
Vector3 AimDir = new Vector3(0, (-(GroundProjectileSettings.AngleSpread / 2f) + AnglePerStepX / 2f) + AnglePerStepX * i, 0);
|
||||
SpawnedProjectile.transform.eulerAngles = SpawnedProjectile.transform.eulerAngles + AimDir;
|
||||
|
||||
AssignAbilityScript(SpawnedProjectile).Initialize(Owner, Target, this);
|
||||
|
||||
if (Delay > 0) yield return new WaitForSeconds(Delay);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign the GroundProjectile script on the newly spawned projectile.
|
||||
/// </summary>
|
||||
public GroundProjectile AssignAbilityScript(GameObject SpawnedAbility)
|
||||
{
|
||||
var groundProjectile = SpawnedAbility.GetComponent<GroundProjectile>();
|
||||
if (groundProjectile == null) groundProjectile = SpawnedAbility.AddComponent<GroundProjectile>();
|
||||
return groundProjectile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7682495e72a315f48a60632a06d502ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dac1f7cc858a645488bf1ffc52978aca
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Melee Ability", menuName = "Emerald AI/Ability/Melee Ability")]
|
||||
public class MeleeAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.CreateSettingsData CreateSettings;
|
||||
public AbilityData.MeleeData MeleeSettings;
|
||||
public AbilityData.StunnedData StunnedSettings;
|
||||
public AbilityData.DamageData DamageSettings;
|
||||
|
||||
public override void ChargeAbility(GameObject Owner, Transform AttackTransform = null)
|
||||
{
|
||||
ChargeSettings.SpawnChargeEffect(Owner, AttackTransform);
|
||||
}
|
||||
|
||||
public override void InvokeAbility(GameObject Owner, Transform AttackTransform = null)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
EmeraldWeaponCollision WeaponCollision = EmeraldComponent.CombatComponent.CurrentWeaponCollision;
|
||||
Transform Target = EmeraldComponent.CombatTarget;
|
||||
float TargetAngle = EmeraldComponent.CombatComponent.TargetAngle;
|
||||
float TargetDistance = EmeraldComponent.CombatComponent.DistanceFromTarget;
|
||||
|
||||
//Return if the damage angle or damage distance is not met, but only if there's no currently active Weapon Collider components.
|
||||
if (TargetAngle > MeleeSettings.MaxDamageAngle || TargetDistance > MeleeSettings.MaxDamageDistance || WeaponCollision != null || Target == null) return;
|
||||
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
|
||||
//If stuns are enabled, roll for a stun
|
||||
if (StunnedSettings.Enabled && StunnedSettings.RollForStun())
|
||||
{
|
||||
if (m_ICombat != null) m_ICombat.TriggerStun(StunnedSettings.StunLength);
|
||||
}
|
||||
|
||||
//Only cause damage if it's enabled
|
||||
if (!DamageSettings.Enabled) return;
|
||||
|
||||
var m_IDamageable = Target.GetComponent<IDamageable>();
|
||||
if (m_IDamageable != null)
|
||||
{
|
||||
bool IsCritHit = DamageSettings.GenerateCritHit();
|
||||
m_IDamageable.Damage(DamageSettings.GenerateDamage(IsCritHit), Owner.transform, DamageSettings.BaseDamageSettings.RagdollForce, IsCritHit);
|
||||
DamageSettings.DamageTargetOverTime(this, DamageSettings, Owner, m_ICombat.TargetTransform().gameObject);
|
||||
EmeraldComponent.AnimationComponent.PlayRecoilAnimation();
|
||||
if (EmeraldComponent.CombatComponent.DeathDelayTimer < 0.1f && !m_ICombat.IsBlocking() && !m_ICombat.IsDodging()) AbilityData.SpawnEffectAndSound(Owner, Target.GetComponent<ICombat>().DamagePosition(), MeleeSettings.ImpactEffect, MeleeSettings.ImpactEffectTimeoutSeconds, MeleeSettings.ImpactSoundsList);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log(Target.gameObject + " is missing IDamageable Component, apply one");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called through the EmeraldWeaponCollision during a successful collision with a target. This is the only ability that has a dependency (which is the EmeraldWeaponCollision script).
|
||||
/// </summary>
|
||||
public void MeleeDamage (GameObject Owner, GameObject Target, Transform TargetRoot)
|
||||
{
|
||||
//Return if the target is teleporting.
|
||||
if (TargetRoot.transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
EmeraldSystem EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
LocationBasedDamageArea m_LocationBasedDamageArea = Target.GetComponent<LocationBasedDamageArea>();
|
||||
ICombat m_ICombat = TargetRoot.GetComponent<ICombat>();
|
||||
|
||||
//If stuns are enabled, roll for a stun
|
||||
if (StunnedSettings.Enabled && StunnedSettings.RollForStun())
|
||||
{
|
||||
if (m_ICombat != null) m_ICombat.TriggerStun(StunnedSettings.StunLength);
|
||||
}
|
||||
|
||||
//Only cause damage if it's enabled
|
||||
if (!DamageSettings.Enabled) return;
|
||||
|
||||
if (m_LocationBasedDamageArea == null)
|
||||
{
|
||||
var m_IDamageable = TargetRoot.GetComponent<IDamageable>();
|
||||
|
||||
if (m_IDamageable != null)
|
||||
{
|
||||
bool IsCritHit = DamageSettings.GenerateCritHit();
|
||||
m_IDamageable.Damage(DamageSettings.GenerateDamage(IsCritHit), Owner.transform, DamageSettings.BaseDamageSettings.RagdollForce, IsCritHit);
|
||||
DamageSettings.DamageTargetOverTime(this, DamageSettings, Owner, m_ICombat.TargetTransform().gameObject);
|
||||
EmeraldComponent.AnimationComponent.PlayRecoilAnimation();
|
||||
if (EmeraldComponent.CombatComponent.DeathDelayTimer < 0.1f && !m_ICombat.IsBlocking()) AbilityData.SpawnEffectAndSound(Owner, Target.GetComponent<ICombat>().DamagePosition(), MeleeSettings.ImpactEffect, MeleeSettings.ImpactEffectTimeoutSeconds, MeleeSettings.ImpactSoundsList);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log(Target.gameObject + " is missing IDamageable Component, apply one");
|
||||
}
|
||||
}
|
||||
else if (m_LocationBasedDamageArea != null)
|
||||
{
|
||||
bool IsCritHit = DamageSettings.GenerateCritHit();
|
||||
m_LocationBasedDamageArea.DamageArea(DamageSettings.GenerateDamage(IsCritHit), Owner.transform, DamageSettings.BaseDamageSettings.RagdollForce, IsCritHit);
|
||||
DamageSettings.DamageTargetOverTime(this, DamageSettings, Owner, m_ICombat.TargetTransform().gameObject);
|
||||
EmeraldComponent.AnimationComponent.PlayRecoilAnimation();
|
||||
if (EmeraldComponent.CombatComponent.DeathDelayTimer < 0.1f && !m_ICombat.IsBlocking() && !m_ICombat.IsDodging()) AbilityData.SpawnEffectAndSound(Owner, Target.transform.position, MeleeSettings.ImpactEffect, MeleeSettings.ImpactEffectTimeoutSeconds, MeleeSettings.ImpactSoundsList);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target's root transform. This is used to get a reference to the ICombat interface as well as tracking which targets have been hit.
|
||||
/// </summary>
|
||||
public Transform GetTargetRoot (GameObject Target)
|
||||
{
|
||||
Transform TargetTransform = null;
|
||||
LocationBasedDamageArea m_LocationBasedDamageArea = Target.GetComponent<LocationBasedDamageArea>();
|
||||
if (m_LocationBasedDamageArea != null && m_LocationBasedDamageArea.EmeraldComponent.transform.localScale == Vector3.one * 0.003f || Target.transform.localScale == Vector3.one * 0.003f) return null;
|
||||
var m_ICombat = Target.GetComponentInParent<ICombat>();
|
||||
if (m_ICombat != null) TargetTransform = m_ICombat.TargetTransform();
|
||||
return TargetTransform;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ce636d5aaf539e43af5286dfd2e5a32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a639aad95b6005d4ca49eb04a0d41068
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using UnityEngine.AI;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Teleport Ability", menuName = "Emerald AI/Ability/Teleport Ability")]
|
||||
public class TeleportAbility : EmeraldAbilityObject
|
||||
{
|
||||
public AbilityData.ChargeSettingsData ChargeSettings;
|
||||
public AbilityData.CreateSettingsData CreateSettings;
|
||||
public AbilityData.TeleportData TeleportSettings;
|
||||
|
||||
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(InitializeTeleport(Owner, Target));
|
||||
}
|
||||
|
||||
IEnumerator InitializeTeleport (GameObject Owner, Transform Target)
|
||||
{
|
||||
AbilityData.SpawnEffectAndSound(Owner, Owner.GetComponent<ICombat>().DamagePosition(), TeleportSettings.DisappearEffect, TeleportSettings.DisappearEffectTimeoutSeconds, TeleportSettings.DisappearSoundsList);
|
||||
EmeraldSystem EmeraldComponent = Owner.GetComponent<EmeraldSystem>();
|
||||
EmeraldComponent.m_NavMeshAgent.enabled = false;
|
||||
EmeraldComponent.AIAnimator.speed = 0.2f;
|
||||
|
||||
yield return new WaitForSeconds(0.01f);
|
||||
Vector3 StartingScale = Owner.transform.localScale;
|
||||
Owner.transform.localScale = Vector3.one * 0.003f; //A scale of 0 cannot be used due to a Unity bug, but this value is small enough that the AI cannot be seen
|
||||
|
||||
void ResetSettings()
|
||||
{
|
||||
Owner.transform.localScale = StartingScale;
|
||||
EmeraldComponent.m_NavMeshAgent.enabled = true;
|
||||
EmeraldComponent.AIAnimator.speed = 1;
|
||||
}
|
||||
|
||||
//Wait to reappear until after the TeleportTime has lapsed.
|
||||
yield return new WaitForSeconds(TeleportSettings.TeleportTime - 0.5f);
|
||||
if (EmeraldComponent.CombatComponent == null) { ResetSettings(); yield break; } //If the target is lost while teleporting, cancel it.
|
||||
Vector3 TeleportPosition = GetTeleportPosition(Owner);
|
||||
EmeraldComponent.m_NavMeshAgent.Warp(TeleportPosition);
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
if (TeleportSettings.ReappearTriggersAvoidable) EmeraldComponent.AnimationComponent.AttackTriggered = true;
|
||||
|
||||
AbilityData.SpawnEffectAndSound(Owner, Owner.GetComponent<ICombat>().DamagePosition() + Vector3.up * StartingScale.y, TeleportSettings.ReappearIndicatorEffect, TeleportSettings.ReappearIndicatorEffectTimeoutSeconds, TeleportSettings.ReappearIndicatorSoundsList);
|
||||
yield return new WaitForSeconds(TeleportSettings.ReappearDelay);
|
||||
|
||||
AbilityData.SpawnEffectAndSound(Owner, Owner.GetComponent<ICombat>().DamagePosition() + Vector3.up * StartingScale.y, TeleportSettings.ReappearEffect, TeleportSettings.ReappearEffectTimeoutSeconds, TeleportSettings.ReappearSoundsList);
|
||||
|
||||
EmeraldComponent.MovementComponent.InstantlyRotateTowards(Target.position);
|
||||
|
||||
ResetSettings();
|
||||
if (TeleportSettings.ReappearTriggersAvoidable)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
EmeraldComponent.AnimationComponent.AttackTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
Vector3 TeleportPosition = Owner.transform.position;
|
||||
bool TeleportRight = Random.Range(0f, 1f) <= 0.5f;
|
||||
|
||||
if (TeleportRight)
|
||||
{
|
||||
TeleportPosition = TargetPosition + ((TargetPosition - Owner.transform.position).normalized + (Vector3.Lerp(Owner.transform.right, Owner.transform.forward, RandomDegree) * TeleportSettings.TeleportRadius));
|
||||
}
|
||||
else
|
||||
{
|
||||
TeleportPosition = TargetPosition + ((TargetPosition - Owner.transform.position).normalized + (Vector3.Lerp(-Owner.transform.right, Owner.transform.forward, RandomDegree) * TeleportSettings.TeleportRadius));
|
||||
}
|
||||
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(TeleportPosition, Owner.transform.TransformDirection(Vector3.down), out hit, 10))
|
||||
{
|
||||
TeleportPosition = new Vector3(TeleportPosition.x, hit.point.y, TeleportPosition.z);
|
||||
}
|
||||
|
||||
NavMeshHit navMeshHit;
|
||||
if (NavMesh.SamplePosition(TeleportPosition, out navMeshHit, 10f, NavMesh.AllAreas))
|
||||
{
|
||||
TeleportPosition = navMeshHit.position;
|
||||
}
|
||||
|
||||
return TeleportPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2240349b55d243d4a95d912bba2b26d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user