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

216 lines
8.0 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using NeoSaveGames.Serialization;
using NeoSaveGames;
namespace NeoFPS
{
[HelpURL("https://docs.neofps.com/manual/healthref-mb-healthpickup.html")]
[RequireComponent(typeof(AudioSource))]
public class HealthPickup : MonoBehaviour, IPickup, INeoSerializableComponent
{
[SerializeField, Tooltip("How the heal is applied. Fixed value adds the health. Factor adds amount * max health. MissingFactor adds amount * missing health.")]
private HealType m_HealType = HealType.FixedValue;
[SerializeField, Tooltip("The amount to heal by. If heal type is Factor or MissingFactor, this will be a value between 0 and 1.")]
private float m_HealAmount = 10f;
[SerializeField, Tooltip("An event called when the pickup heals a character.")]
private UnityEvent m_OnHealedCharacter = null;
[SerializeField, Tooltip("If the character needs less healing than the pickup amount, should the pickup still be destroyed or should the remainder be available to use again?")]
private bool m_SingleUse = true;
[SerializeField, Tooltip("What to do to the pickup object once its item has been used (fully, or single use).")]
private ConsumeResult m_ConsumeResult = ConsumeResult.Destroy;
[SerializeField, Tooltip("How long to wait before respawning if the consume result is set to \"Respawn\".")]
private float m_RespawnDuration = 20f;
[SerializeField, Tooltip("The display mesh of the pickup. This should not be the same game object as this, so that if this is disabled the pickup will still respawn if required.")]
private GameObject m_DisplayMesh = null;
private static readonly NeoSerializationKey k_RespawnKey = new NeoSerializationKey("respawn");
private static readonly NeoSerializationKey k_EnabledKey = new NeoSerializationKey("pickupEnabled");
private static readonly NeoSerializationKey k_RemainingKey = new NeoSerializationKey("remaining");
public enum HealType
{
FixedValue,
Factor,
MissingFactor
}
public enum ConsumeResult
{
Destroy,
Disable,
Respawn
}
private float m_Remaining = 0f;
private AudioSource m_AudioSource = null;
private Collider m_Collider = null;
private IEnumerator m_DelayedSpawnCoroutine = null;
private float m_RespawnTimer = 0f;
private void OnValidate()
{
if (m_HealType == HealType.FixedValue)
m_HealAmount = Mathf.Clamp(m_HealAmount, 0f, float.MaxValue);
else
m_HealAmount = Mathf.Clamp01(m_HealAmount);
// Get the display mesh object
if (m_DisplayMesh == null)
{
var mesh = GetComponentInChildren<MeshRenderer>(true);
if (mesh != null && mesh.gameObject != gameObject)
m_DisplayMesh = mesh.gameObject;
}
}
void Awake()
{
m_Collider = GetComponent<Collider>();
m_AudioSource = GetComponent<AudioSource>();
EnablePickup(true);
}
public void Trigger(ICharacter character)
{
// Do nothing if character has no health manager
var healthMgr = character.GetComponent<IHealthManager>();
if (healthMgr == null)
return;
// Do nothing if character is max health
if (Mathf.Approximately(healthMgr.health, healthMgr.healthMax))
return;
m_OnHealedCharacter.Invoke();
// Apply the heal
switch (m_HealType)
{
case HealType.FixedValue:
{
if (m_SingleUse)
{
healthMgr.health += m_HealAmount;
DestroyPickup();
}
else
{
// Record the current health
float before = healthMgr.health;
// Apply the heal
healthMgr.health += m_Remaining;
// If only partially used, set new amount, else destroy
float remainder = m_Remaining - healthMgr.health + before;
if (remainder > 0.01f)
m_Remaining = remainder;
else
DestroyPickup();
}
}
break;
case HealType.Factor:
healthMgr.health += m_HealAmount * healthMgr.healthMax;
DestroyPickup();
break;
case HealType.MissingFactor:
healthMgr.health += m_HealAmount * (healthMgr.healthMax - healthMgr.health);
DestroyPickup();
break;
}
}
void DestroyPickup ()
{
if (m_DelayedSpawnCoroutine != null)
StopCoroutine(m_DelayedSpawnCoroutine);
// NB: The item will have been moved into the inventory heirarchy
switch (m_ConsumeResult)
{
case ConsumeResult.Destroy:
if (m_AudioSource.clip != null)
AudioSource.PlayClipAtPoint(m_AudioSource.clip, transform.position);
Destroy(gameObject);
break;
case ConsumeResult.Disable:
m_AudioSource.Play();
EnablePickup(false);
break;
case ConsumeResult.Respawn:
m_AudioSource.Play();
EnablePickup(false);
m_RespawnTimer = m_RespawnDuration;
m_DelayedSpawnCoroutine = DelayedSpawn();
StartCoroutine(m_DelayedSpawnCoroutine);
break;
}
}
public virtual void EnablePickup(bool value)
{
// Enable the mesh
if (m_DisplayMesh != null)
m_DisplayMesh.SetActive(value);
// Enable the collider
m_Collider.enabled = value;
// Reset the heal amount to full
m_Remaining = m_HealAmount;
}
IEnumerator DelayedSpawn()
{
while (m_RespawnTimer > 0f)
{
yield return null;
m_RespawnTimer -= Time.deltaTime;
}
EnablePickup(true);
m_DelayedSpawnCoroutine = null;
}
public void WriteProperties(INeoSerializer writer, NeoSerializedGameObject nsgo, SaveMode saveMode)
{
if (m_DelayedSpawnCoroutine != null)
writer.WriteValue(k_RespawnKey, m_RespawnTimer);
if (!m_Collider.enabled)
writer.WriteValue(k_EnabledKey, false);
if (!Mathf.Approximately(m_Remaining, m_HealAmount))
writer.WriteValue(k_RemainingKey, m_Remaining);
}
public void ReadProperties(INeoDeserializer reader, NeoSerializedGameObject nsgo)
{
// Enable the pickup
bool pickupEnabled = true;
reader.TryReadValue(k_EnabledKey, out pickupEnabled, true);
EnablePickup(pickupEnabled);
// Respawn timer (start the coroutine if the property is found)
float respawn = 0f;
if (reader.TryReadValue(k_RespawnKey, out respawn, 0f))
{
m_RespawnTimer = respawn;
m_DelayedSpawnCoroutine = DelayedSpawn();
StartCoroutine(m_DelayedSpawnCoroutine);
}
// Remaining heal
reader.TryReadValue(k_RemainingKey, out m_Remaining, m_Remaining);
}
}
}