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();
}
}
}