using Invector; using Invector.vCharacterController; using Lean.Pool; using UnityEngine; namespace DemonBoss.Magic { /// /// Component handling damage and collisions for fireball fired by crystals /// Deals damage on collision with player and automatically despawns /// [RequireComponent(typeof(Collider))] public class FireballDamage : MonoBehaviour { [Header("Damage Configuration")] [Tooltip("Damage dealt by fireball")] public float damage = 25f; [Tooltip("Whether fireball can deal damage only once")] public bool damageOnce = true; [Tooltip("Layer mask for targets that can be hit")] public LayerMask targetLayerMask = -1; [Header("Impact Effects")] [Tooltip("Explosion effect prefab on hit")] public GameObject impactEffectPrefab; [Tooltip("Impact sound")] public AudioClip impactSound; [Tooltip("Knockback force on hit")] public float knockbackForce = 5f; [Header("Lifetime")] [Tooltip("Maximum fireball lifetime in seconds (failsafe)")] public float maxLifetime = 5f; [Tooltip("Whether to destroy fireball on terrain collision")] public bool destroyOnTerrain = true; [Tooltip("Layer mask for terrain/obstacles")] public LayerMask terrainLayerMask = 1; [Header("Debug")] [Tooltip("Enable debug logging")] public bool enableDebug = false; // Private variables private bool hasDealtDamage = false; private float spawnTime; private AudioSource audioSource; private Collider fireballCollider; private Rigidbody fireballRigidbody; /// /// Component initialization /// private void Awake() { fireballCollider = GetComponent(); fireballRigidbody = GetComponent(); if (fireballCollider != null && !fireballCollider.isTrigger) { fireballCollider.isTrigger = true; if (enableDebug) Debug.Log("[FireballDamage] Set collider as trigger"); } audioSource = GetComponent(); if (audioSource == null && impactSound != null) { audioSource = gameObject.AddComponent(); audioSource.playOnAwake = false; audioSource.spatialBlend = 1f; } } /// /// Start - note spawn time and start failsafe timer /// private void Start() { spawnTime = Time.time; hasDealtDamage = false; Invoke(nameof(FailsafeDespawn), maxLifetime); if (enableDebug) Debug.Log("[FireballDamage] Fireball initialized"); } /// /// Trigger collision handling - dealing damage /// /// Collider that fireball collides with private void OnTriggerEnter(Collider other) { if (enableDebug) Debug.Log($"[FireballDamage] Collision with: {other.name}"); if (IsValidTarget(other)) { DealDamageToTarget(other); CreateImpactEffect(other.transform.position); DespawnFireball(); return; } if (destroyOnTerrain && IsTerrainCollision(other)) { if (enableDebug) Debug.Log("[FireballDamage] Terrain collision"); CreateImpactEffect(transform.position); DespawnFireball(); return; } } /// /// Checks if collider is valid target /// /// Collider to check /// True if it's valid target private bool IsValidTarget(Collider other) { if ((targetLayerMask.value & (1 << other.gameObject.layer)) == 0) return false; if (damageOnce && hasDealtDamage) return false; var damageReceiver = other.GetComponent(); var healthController = other.GetComponent(); var character = other.GetComponent(); var thirdPersonController = other.GetComponent(); if (damageReceiver == null) damageReceiver = other.GetComponentInParent(); if (healthController == null) healthController = other.GetComponentInParent(); if (character == null) character = other.GetComponentInParent(); if (thirdPersonController == null) thirdPersonController = other.GetComponentInParent(); return damageReceiver != null || healthController != null || character != null || thirdPersonController != null; } /// /// Checks if it's terrain collision /// /// Collider to check /// True if it's terrain private bool IsTerrainCollision(Collider other) { return (terrainLayerMask.value & (1 << other.gameObject.layer)) != 0; } /// /// Deals damage to target - ulepszona wersja /// /// Target collider private void DealDamageToTarget(Collider targetCollider) { if (enableDebug) Debug.Log($"[FireballDamage] Dealing {damage} damage to: {targetCollider.name}"); Vector3 hitPoint = GetClosestPointOnCollider(targetCollider); Vector3 hitDirection = GetHitDirection(targetCollider); vDamage damageInfo = new vDamage(Mathf.RoundToInt(damage)); damageInfo.sender = transform; damageInfo.hitPosition = hitPoint; if (knockbackForce > 0f) { damageInfo.force = hitDirection * knockbackForce; } bool damageDealt = false; var damageReceiver = targetCollider.GetComponent(); if (damageReceiver == null) damageReceiver = targetCollider.GetComponentInParent(); if (damageReceiver != null && !damageDealt) { damageReceiver.TakeDamage(damageInfo); damageDealt = true; hasDealtDamage = true; if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vIDamageReceiver"); } if (!damageDealt) { var healthController = targetCollider.GetComponent(); if (healthController == null) healthController = targetCollider.GetComponentInParent(); if (healthController != null) { healthController.TakeDamage(damageInfo); damageDealt = true; hasDealtDamage = true; if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vHealthController"); } } if (!damageDealt) { var thirdPersonController = targetCollider.GetComponent(); if (thirdPersonController == null) thirdPersonController = targetCollider.GetComponentInParent(); if (thirdPersonController != null) { if (thirdPersonController is Beyond.bThirdPersonController beyondController) { if (!beyondController.GodMode && !beyondController.isImmortal) { thirdPersonController.TakeDamage(damageInfo); damageDealt = true; hasDealtDamage = true; if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through bThirdPersonController"); } else { if (enableDebug) Debug.Log("[FireballDamage] Player is immortal or in God Mode - no damage dealt"); } } else { thirdPersonController.TakeDamage(damageInfo); damageDealt = true; hasDealtDamage = true; if (enableDebug) Debug.Log("[FireballDamage] Damage dealt through vThirdPersonController"); } } } if (!damageDealt) { if (enableDebug) Debug.LogWarning("[FireballDamage] Could not deal damage - no valid damage receiver found!"); } } /// /// Takes the closest point on the collider as the point of impact /// private Vector3 GetClosestPointOnCollider(Collider targetCollider) { return targetCollider.ClosestPoint(transform.position); } /// /// Calculates the direction of impact for knockback /// private Vector3 GetHitDirection(Collider targetCollider) { Vector3 direction; if (fireballRigidbody != null && fireballRigidbody.linearVelocity.magnitude > 0.1f) { direction = fireballRigidbody.linearVelocity.normalized; } else { direction = (targetCollider.transform.position - transform.position).normalized; } direction.y = Mathf.Max(0.2f, direction.y); return direction.normalized; } /// /// Creates impact effect /// /// Impact position private void CreateImpactEffect(Vector3 impactPosition) { if (impactEffectPrefab != null) { GameObject impact = LeanPool.Spawn(impactEffectPrefab, impactPosition, Quaternion.identity); LeanPool.Despawn(impact, 3f); if (enableDebug) Debug.Log("[FireballDamage] Impact effect created"); } if (audioSource != null && impactSound != null) { audioSource.PlayOneShot(impactSound); } } /// /// Despawns fireball from map /// private void DespawnFireball() { if (enableDebug) Debug.Log("[FireballDamage] Despawning fireball"); CancelInvoke(nameof(FailsafeDespawn)); LeanPool.Despawn(gameObject); } /// /// Failsafe despawn - removes fireball after maximum lifetime /// private void FailsafeDespawn() { if (enableDebug) Debug.Log("[FireballDamage] Failsafe despawn - lifetime exceeded"); DespawnFireball(); } /// /// Forces immediate despawn /// public void ForceDespawn() { if (enableDebug) Debug.Log("[FireballDamage] Forced despawn"); DespawnFireball(); } /// /// Checks if fireball has already dealt damage /// /// True if damage was already dealt public bool HasDealtDamage() { return hasDealtDamage; } /// /// Returns fireball lifetime /// /// Time in seconds since spawn public float GetLifetime() { return Time.time - spawnTime; } /// /// Sets new damage (e.g. for different difficulty levels) /// /// New damage public void SetDamage(float newDamage) { damage = newDamage; if (enableDebug) Debug.Log($"[FireballDamage] Set new damage: {damage}"); } /// /// Sets new knockback /// /// New knockback force public void SetKnockback(float newKnockback) { knockbackForce = newKnockback; if (enableDebug) Debug.Log($"[FireballDamage] Set new knockback: {knockbackForce}"); } /// /// Resets fireball state (useful for pooling) /// public void ResetFireball() { hasDealtDamage = false; spawnTime = Time.time; CancelInvoke(); Invoke(nameof(FailsafeDespawn), maxLifetime); if (enableDebug) Debug.Log("[FireballDamage] Fireball reset"); } /// /// Called on spawn by Lean Pool /// private void OnSpawn() { ResetFireball(); } /// /// Called on despawn by Lean Pool /// private void OnDespawn() { CancelInvoke(); } } }