using UnityEngine; namespace MazeGen { /// /// Core character/monster statistics. Stores both base stats and current values /// (health can be reduced in combat, speed affects action order, attack/defense/magic /// are used in combat resolution). /// [System.Serializable] public class CharacterStats : ISerializationCallbackReceiver { [SerializeField] private float _maxHealth = 100f; [SerializeField] private float _speed = 10f; // Initiative and movement speed [SerializeField] private float _attack = 10f; // Physical damage output [SerializeField] private float _defense = 5f; // Physical damage reduction [SerializeField] private float _magic = 5f; // Magical power (casting damage/resistance) private float _currentHealth; public float MaxHealth => _maxHealth; public float CurrentHealth { get => _currentHealth; set => _currentHealth = Mathf.Clamp(value, 0, _maxHealth); } public float Speed => _speed; public float Attack => _attack; public float Defense => _defense; public float Magic => _magic; public bool IsAlive => _currentHealth > 0; public CharacterStats() { _currentHealth = _maxHealth; } /// /// Unity deserializes nested [Serializable] classes by running the default constructor /// first (which sets _currentHealth from _maxHealth's *default field value*, 100), and /// only afterwards overwrites the serialized fields (like _maxHealth) with the saved /// asset data. That leaves _currentHealth stale (e.g. 100/20) for any template asset whose /// real MaxHealth differs from the field default. _currentHealth isn't [SerializeField], /// so nothing else corrects it - fix it up here now that _maxHealth holds its real value. /// public void OnAfterDeserialize() { _currentHealth = _maxHealth; } public void OnBeforeSerialize() { } /// /// Calculate how many grid squares this character can move per turn during combat. /// Movement range is based on Speed: faster characters move further. /// Formula: Speed / 2, minimum 1 square per turn. /// public int GetMovementRange() { return Mathf.Max(1, Mathf.FloorToInt(_speed / 2f)); } /// Create a copy of this stat block (useful for monster instances). public CharacterStats Clone() { var clone = new CharacterStats(); // Manually copy all fields (can't use object initializer for private fields) clone._maxHealth = _maxHealth; clone._speed = _speed; clone._attack = _attack; clone._defense = _defense; clone._magic = _magic; clone._currentHealth = _currentHealth; return clone; } /// Reset health to max (for a fresh combat or new agent spawn). public void RestoreHealth() { _currentHealth = _maxHealth; } /// Take damage (already accounting for defense). public void TakeDamage(float damage) { CurrentHealth -= Mathf.Max(0, damage); } public void Heal(float amount) { CurrentHealth += Mathf.Max(0, amount); } /// Calculate effective damage taken after defense reduction. public float CalculateDamageReduction(float incomingDamage) { // Simple formula: defense reduces damage by 10% per defense point, up to 90% reduction float reductionPercent = Mathf.Clamp01(_defense * 0.1f); return incomingDamage * (1f - reductionPercent); } } }