using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; namespace Player { [System.Serializable] public class VitalStat { [System.Serializable] public class FloatEvent : UnityEvent { } [System.Serializable] public class ThresholdRule { [Range(0f, 1f)] public float normalizedThreshold = 0.5f; public UnityEvent onEnter = new UnityEvent(); public UnityEvent onExit = new UnityEvent(); [HideInInspector] public bool isBelow; } public string id; public float minValue = 0f; public float maxValue = 100f; public float currentValue = 100f; [FormerlySerializedAs("decayPerSecond")] public float decayPerMinute = 0f; public FloatEvent onValueChanged = new FloatEvent(); public FloatEvent onNormalizedChanged = new FloatEvent(); public List thresholds = new List(); public float Normalized { get { if (Mathf.Approximately(minValue, maxValue)) return 0f; return Mathf.InverseLerp(minValue, maxValue, currentValue); } } public void Initialize(bool invokeEvents = true) { currentValue = Mathf.Clamp(currentValue, minValue, maxValue); UpdateThresholdStates(invokeEvents: false); if (invokeEvents) InvokeAll(); } public void Tick(float deltaTime) { if (decayPerMinute == 0f) return; Add(-(decayPerMinute / 60f) * deltaTime); } public void Add(float delta) { SetCurrent(currentValue + delta); } public void SetCurrent(float value) { float clamped = Mathf.Clamp(value, minValue, maxValue); if (Mathf.Approximately(clamped, currentValue)) return; currentValue = clamped; InvokeAll(); UpdateThresholdStates(invokeEvents: true); } public void SetRange(float min, float max, bool preserveNormalized = true) { float normalized = Normalized; minValue = min; maxValue = max; if (preserveNormalized) { currentValue = Mathf.Lerp(minValue, maxValue, normalized); } else { currentValue = Mathf.Clamp(currentValue, minValue, maxValue); } InvokeAll(); UpdateThresholdStates(invokeEvents: true); } void InvokeAll() { onValueChanged.Invoke(currentValue); onNormalizedChanged.Invoke(Normalized); } void UpdateThresholdStates(bool invokeEvents) { float normalized = Normalized; for (int i = 0; i < thresholds.Count; i++) { ThresholdRule rule = thresholds[i]; bool below = normalized <= rule.normalizedThreshold; if (invokeEvents) { if (!rule.isBelow && below) rule.onEnter.Invoke(); else if (rule.isBelow && !below) rule.onExit.Invoke(); } rule.isBelow = below; } } } }