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);
}
}
}