using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 1 — Generic health for players, enemies, bosses, and breakables. (v1.1)
///
/// v1.1 adds SetMaxHealth()/SetCurrentHealth() for CharacterSwapper and
/// SetInvulnerable(seconds) for Dash / MomentumLauncher i-frames.
///
/// SETUP
/// - Add to anything that can take damage. Set Max Health.
/// - Invulnerability Time gives i-frames after a hit (set 0 for enemies).
/// - Hook things up in the Inspector events:
/// OnHealthChanged(current, max) -> HUD health bar
/// OnDamaged -> hurt animation, SFX, screen shake
/// OnDied -> RespawnManager.RespawnPlayer, or Destroy for enemies
/// - "Destroy On Death" is a convenience for enemies/breakables. Leave it OFF for the player.
///
/// From other scripts: GetComponent<Health>().TakeDamage(1);
/// </summary>
public class Health : MonoBehaviour
{
[Header("Stats")]
public int maxHealth = 3;
[Tooltip("Seconds of invulnerability after taking damage. 0 = none.")]
public float invulnerabilityTime = 1f;
[Tooltip("Blink the sprite while invulnerable (needs a SpriteRenderer).")]
public bool flashWhileInvulnerable = true;
[Header("On Death")]
public bool destroyOnDeath = false;
public float destroyDelay = 0f;
[Header("Events")]
public UnityEvent<int, int> OnHealthChanged; // (current, max)
public UnityEvent OnDamaged;
public UnityEvent OnHealed;
public UnityEvent OnDied;
public int CurrentHealth { get; private set; }
public bool IsDead => CurrentHealth <= 0;
public bool IsInvulnerable => invulnTimer > 0f;
private float invulnTimer;
private SpriteRenderer sr;
void Awake()
{
CurrentHealth = maxHealth;
sr = GetComponentInChildren<SpriteRenderer>();
}
void Start()
{
OnHealthChanged?.Invoke(CurrentHealth, maxHealth);
}
void Update()
{
if (invulnTimer > 0f)
{
invulnTimer -= Time.deltaTime;
if (flashWhileInvulnerable && sr != null)
sr.enabled = Mathf.FloorToInt(invulnTimer * 10f) % 2 == 0; // 10 Hz blink
if (invulnTimer <= 0f && sr != null) sr.enabled = true;
}
}
/// <summary>Deal damage. Returns true if damage was actually applied.</summary>
public bool TakeDamage(int amount)
{
if (IsDead || IsInvulnerable || amount <= 0) return false;
CurrentHealth = Mathf.Max(0, CurrentHealth - amount);
OnHealthChanged?.Invoke(CurrentHealth, maxHealth);
OnDamaged?.Invoke();
if (CurrentHealth == 0) Die();
else invulnTimer = invulnerabilityTime;
return true;
}
public void Heal(int amount)
{
if (IsDead || amount <= 0) return;
CurrentHealth = Mathf.Min(maxHealth, CurrentHealth + amount);
OnHealthChanged?.Invoke(CurrentHealth, maxHealth);
OnHealed?.Invoke();
}
/// <summary>Instant kill regardless of i-frames (pits, acid, crushers).</summary>
public void Kill()
{
if (IsDead) return;
CurrentHealth = 0;
OnHealthChanged?.Invoke(CurrentHealth, maxHealth);
Die();
}
/// <summary>Grant i-frames for a duration (Dash, MomentumLauncher, cutscenes).</summary>
public void SetInvulnerable(float seconds)
{
invulnTimer = Mathf.Max(invulnTimer, seconds);
}
public void ClearInvulnerability()
{
invulnTimer = 0f;
if (sr != null) sr.enabled = true;
}
/// <summary>Change max health at runtime (character swap, upgrades). Optionally refill.</summary>
public void SetMaxHealth(int newMax, bool refill)
{
maxHealth = Mathf.Max(1, newMax);
CurrentHealth = refill ? maxHealth : Mathf.Min(CurrentHealth, maxHealth);
OnHealthChanged?.Invoke(CurrentHealth, maxHealth);
}
/// <summary>Set current health directly (CharacterSwapper restores a stored value).</summary>
public void SetCurrentHealth(int value)
{
CurrentHealth = Mathf.Clamp(value, 0, maxHealth);
OnHealthChanged?.Invoke(CurrentHealth, maxHealth);
}
/// <summary>Restore to full. RespawnManager calls this.</summary>
public void ResetHealth()
{
CurrentHealth = maxHealth;
invulnTimer = 0f;
if (sr != null) sr.enabled = true;
OnHealthChanged?.Invoke(CurrentHealth, maxHealth);
}
void Die()
{
OnDied?.Invoke();
if (destroyOnDeath) Destroy(gameObject, destroyDelay);
}
}