using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class NetworkedProjectile : MonoBehaviour { [Header("Visuals")] [SerializeField] private GameObject hitParticlesPrefab; [SerializeField] private GameObject visuals; [Header("Set by code")] public Taggable ownerTag; public BaseAbility ability; public float speed; public float lifeSpan; public bool canPierce; public bool canHitSelf; 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 Taggable target; private bool waitingForDestroy = false; public UnityEvent onTargetHit = new UnityEvent(); private List hitSpawnedVFXs = new List(); private GameObject hitSpawnedVFX; private Vector3 hitPositionCorrected; protected List processedTargets = new List(); NetworkedAntiProjectile possibleBlock; private float timeAlive = 0f; private int ricochetCount = 0; Vector3 incomingDir = new Vector3(); Vector3 hitNormal = new Vector3(); Vector3 reflected = new Vector3(); private void Awake() { onTargetHit.AddListener(SpawnHitParticleVFX); } public void Init() { waitingForDestroy = false; StartCoroutine(SelfDestruct()); } private void SpawnHitParticleVFX(Vector3 position) { if (hitParticlesPrefab == null) return; hitSpawnedVFX = Instantiate(hitParticlesPrefab, position, this.transform.rotation); //hitSpawnedVFX.transform.localScale = visuals.transform.localScale; hitSpawnedVFXs.Add(hitSpawnedVFX); } private void Update() { if (waitingForDestroy) return; timeAlive += Time.deltaTime; float lifetimeFraction = Mathf.Clamp01(timeAlive / lifeSpan); float currentSpeed = useSpeedCurve ? speed * speedOverLifetime.Evaluate(lifetimeFraction) : speed; // 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; } private void OnTriggerEnter(Collider other) { if (waitingForDestroy) return; if (other.GetComponent() != null) { other.GetComponent().Hit(); onTargetHit.Invoke(this.transform.position); waitingForDestroy = true; StartCoroutine(DelayedDestroy()); } target = other.GetComponentInParent(); if (target == null) return; if (target == ownerTag && !canHitSelf) return; if (!target.IsValidTarget(ability.targettingTags)) return; 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; } foreach (BaseEffect effect in ability.abilityEffects) { effect.ApplyEffect(ownerTag, new List { target }); } try { target.GetComponentInChildren()?.SetTrigger("hurt"); } catch (System.Exception) { Debug.Log("No Hurt trigger"); } 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()); } } IEnumerator SelfDestruct() { yield return new WaitForSeconds(lifeSpan); waitingForDestroy = true; StartCoroutine(DelayedDestroy()); } IEnumerator DelayedDestroy() { visuals.SetActive(false); yield return new WaitForSeconds(1.5f); for (int i = hitSpawnedVFXs.Count - 1; i >= 0; i--) { if (hitSpawnedVFXs[i] != null) { Destroy(hitSpawnedVFXs[i]); } } yield return new WaitForSeconds(1f); Destroy(this.gameObject); } }