projectEli/Assets/NeoFPS/Core/Damage/BasicHealthManager.cs
2022-11-06 20:28:33 -05:00

248 lines
8.1 KiB
C#

using System;
using UnityEngine;
using NeoSaveGames.Serialization;
using NeoSaveGames;
using UnityEngine.Events;
namespace NeoFPS
{
[HelpURL("https://docs.neofps.com/manual/healthref-mb-basichealthmanager.html")]
public class BasicHealthManager : MonoBehaviour, IHealthManager, INeoSerializableComponent
{
[Tooltip("The starting health of the character.")]
[Delayed, SerializeField] private float m_Health = 100f;
[Tooltip("The maximum health of the character.")]
[Delayed, SerializeField] private float m_HealthMax = 100f;
[Tooltip("Can the character damage itself (eg with explosives).")]
[SerializeField] private bool m_CanDamageSelf = true;
[Tooltip("An event called whenever the health changes")]
[SerializeField] private FloatEvent m_OnHealthChanged = null;
[Tooltip("An event called whenever the alive state of the health manager changes")]
[SerializeField] private BoolEvent m_OnIsAliveChanged = null;
private static readonly NeoSerializationKey k_HealthKey = new NeoSerializationKey("health");
private static readonly NeoSerializationKey k_HealthMaxKey = new NeoSerializationKey("healthMax");
private static readonly NeoSerializationKey k_IsAliveKey = new NeoSerializationKey("isAlive");
public event HealthDelegates.OnIsAliveChanged onIsAliveChanged;
public event HealthDelegates.OnHealthChanged onHealthChanged;
public event HealthDelegates.OnHealthMaxChanged onHealthMaxChanged;
private IController m_Controller = null;
[Serializable]
public class FloatEvent : UnityEvent<float>
{
}
[Serializable]
public class BoolEvent : UnityEvent<bool>
{
}
private bool m_IsAlive = true;
public bool isAlive
{
get { return m_IsAlive; }
protected set
{
if (m_IsAlive != value)
{
m_IsAlive = value;
OnIsAliveChanged();
}
}
}
public float health
{
get { return m_Health; }
set { SetHealth(value, false, null); }
}
public float healthMax
{
get { return m_HealthMax; }
set
{
if (m_HealthMax != value)
{
float old = m_HealthMax;
// Set value
m_HealthMax = value;
// Check lower limit
if (m_HealthMax < 0f)
m_HealthMax = 0f;
// Fire event
OnMaxHealthChanged(old, m_HealthMax);
// Check health is still valid
if (health > m_HealthMax)
health = m_HealthMax;
}
}
}
public float normalisedHealth
{
get { return health / healthMax; }
set { health = value * healthMax; }
}
protected virtual void OnValidate()
{
m_HealthMax = Mathf.Clamp(m_HealthMax, 1f, 10000f);
m_Health = Mathf.Clamp(m_Health, 1f, m_HealthMax);
}
protected virtual void Awake()
{
var character = GetComponent<ICharacter>();
if (character != null)
{
character.onControllerChanged += OnCharacterControllerChanged;
OnCharacterControllerChanged(character, character.controller);
}
}
protected virtual void OnDestroy()
{
var character = GetComponent<ICharacter>();
if (character != null)
character.onControllerChanged += OnCharacterControllerChanged;
}
void OnCharacterControllerChanged(ICharacter character, IController controller)
{
m_Controller = controller;
}
bool CheckDamageSource(IDamageSource source)
{
if (source == null)
return true;
// Check if damage source is self
if (!m_CanDamageSelf && m_Controller != null && source.controller == m_Controller)
{
// Check against damage types
if (source.outDamageFilter.IsDamageType(DamageType.Fall))
return true;
if (source.outDamageFilter.IsDamageType(DamageType.Drowning))
return true;
return false;
}
return true;
}
protected void SetHealth(float h, bool critical, IDamageSource source)
{
float old = m_Health;
// Set value
m_Health = Mathf.Clamp(h, 0f, m_HealthMax);
// Check if changed
if (m_Health != old)
{
OnHealthChanged(old, m_Health, critical, source);
// Check if dead
if (Mathf.Approximately(m_Health, 0f) && isAlive)
isAlive = false;
}
}
protected virtual void OnHealthChanged(float from, float to, bool critical, IDamageSource source)
{
// Fire event
if (onHealthChanged != null)
onHealthChanged(from, to, critical, source);
m_OnHealthChanged.Invoke(to);
}
protected virtual void OnMaxHealthChanged(float from, float to)
{
// Fire event
if (onHealthMaxChanged != null)
onHealthMaxChanged(from, to);
}
protected virtual void OnIsAliveChanged ()
{
if (onIsAliveChanged != null)
onIsAliveChanged(m_IsAlive);
m_OnIsAliveChanged.Invoke(m_IsAlive);
}
public virtual void AddDamage(float damage)
{
SetHealth(health - damage, false, null);
}
public virtual void AddDamage(float damage, bool critical)
{
SetHealth(health - damage, critical, null);
}
public virtual void AddDamage(float damage, IDamageSource source)
{
if (CheckDamageSource(source))
SetHealth(health - damage, false, source);
}
public virtual void AddDamage(float damage, bool critical, IDamageSource source)
{
if (CheckDamageSource(source))
SetHealth(health - damage, critical, source);
}
public void AddDamage(float damage, bool critical, RaycastHit hit)
{
SetHealth(health - damage, critical, null);
}
public void AddDamage(float damage, bool critical, IDamageSource source, RaycastHit hit)
{
if (CheckDamageSource(source))
SetHealth(health - damage, critical, source);
}
public virtual void AddHealth(float h)
{
SetHealth(health + h, false, null);
// Check if brought back to life
if (!isAlive && health > 0f)
isAlive = true;
}
public virtual void AddHealth(float h, IDamageSource source)
{
// Change health
SetHealth(health + h, false, source);
// Check if brought back to life
if (!isAlive && health > 0f)
isAlive = true;
}
public virtual void WriteProperties(INeoSerializer writer, NeoSerializedGameObject nsgo, SaveMode saveMode)
{
writer.WriteValue(k_HealthMaxKey, healthMax);
writer.WriteValue(k_HealthKey, health);
writer.WriteValue(k_IsAliveKey, isAlive);
}
public virtual void ReadProperties(INeoDeserializer reader, NeoSerializedGameObject nsgo)
{
float floatValue = 0f;
if (reader.TryReadValue(k_HealthMaxKey, out floatValue, healthMax))
healthMax = floatValue;
if (reader.TryReadValue(k_HealthKey, out floatValue, health))
health = floatValue;
bool boolValue = true;
if (reader.TryReadValue(k_IsAliveKey, out boolValue, boolValue))
isAlive = boolValue;
}
}
}