using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class NetworkedProjectileAreaOfEffectOverTimeWithTickEvent : NetworkedAreaOfEffectOverTime { [Header("Visuals")] [SerializeField] private GameObject hitParticlesPrefab; public ProjectileAbility projectileAbility; public UnityEvent> onTickHappened = new UnityEvent>(); public UnityEvent> onTargetHitByProjectile = new UnityEvent>(); NetworkedAntiProjectile possibleBlock; public bool canPierce; public float projectileSpeed; public AnimationCurve speedOverLifetime = AnimationCurve.Linear(0, 1, 1, 1); public bool useSpeedCurve = false; public bool enableCurving = false; public Vector3 curveAxis = Vector3.up; public float curveStrength = 1f; public float curveAmplitude = 45f; public bool enableRicochet = false; public int maxRicochets = 1; public float ricochetSpread = 10f; private float timeAlive = 0f; private int ricochetCount = 0; Vector3 incomingDir = new Vector3(); Vector3 hitNormal = new Vector3(); Vector3 reflected = new Vector3(); protected List processedTargets = new List(); protected List projectileTargets = new List(); public void InitProjectileStats() { } private void Update() { if (waitingForDestroy) return; timeAlive += Time.deltaTime; float lifetimeFraction = Mathf.Clamp01(timeAlive / lifeSpan); float currentSpeed = useSpeedCurve ? projectileSpeed * speedOverLifetime.Evaluate(lifetimeFraction) : projectileSpeed; // Curving logic if (enableCurving) { float curveOffset = Mathf.Sin(timeAlive * curveStrength) * curveAmplitude * Time.deltaTime; transform.Rotate(curveAxis.normalized * curveOffset, Space.World); } transform.position += transform.forward * currentSpeed * Time.deltaTime; } protected override void OnTickPerformed() { if (targets.Count > 0) onTickHappened.Invoke(ownerTag, targets); } private void OnTriggerEnter(Collider other) { if (waitingForDestroy) return; target = other.GetComponentInParent(); if (target == null) return; if (target == ownerTag && !canHitSelf) return; if (projectileAbility == null) return; if (!target.IsValidTarget(projectileAbility.targettingTags)) return; //Debug.Log($"TT[{Time.frameCount}] Past validation checks for {target.name}"); if (processedTargets.Contains(target)) return; processedTargets.Add(target); hitPositionCorrected = target.transform.position; hitPositionCorrected.y = this.transform.position.y; onTargetHit.Invoke(hitPositionCorrected); possibleBlock = target.GetComponentInParent(); if (possibleBlock != null) { waitingForDestroy = true; possibleBlock.SendBlockNotice(); StartCoroutine(DelayedDestroy()); return; } //Debug.Log($"TT[{Time.frameCount}] About to process effects"); projectileTargets.Clear(); projectileTargets.Add(target); foreach (BaseEffect effect in projectileAbility.abilityEffects) { //Debug.Log($"TT[{Time.frameCount}] Applying effect: {effect.name}"); effect.ApplyEffect(ownerTag, projectileTargets); } if(projectileTargets.Count > 0) { //Debug.Log($"TT[{Time.frameCount}] OnProjectileHit {projectileTargets.Count}"); onTargetHitByProjectile.Invoke(ownerTag, projectileTargets); } if (!canPierce) { if (enableRicochet && ricochetCount < maxRicochets) { incomingDir = transform.forward; hitNormal = other.ClosestPoint(transform.position) - transform.position; hitNormal = hitNormal.normalized; reflected = Vector3.Reflect(incomingDir, hitNormal); // Add spread reflected = Quaternion.Euler(Random.Range(-ricochetSpread, ricochetSpread), Random.Range(-ricochetSpread, ricochetSpread), 0) * reflected; transform.forward = reflected; ricochetCount++; return; } waitingForDestroy = true; StartCoroutine(DelayedDestroy()); } } }