CharacterStats.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. using UnityEngine;
  2. namespace MazeGen
  3. {
  4. /// <summary>
  5. /// Core character/monster statistics. Stores both base stats and current values
  6. /// (health can be reduced in combat, speed affects action order, attack/defense/magic
  7. /// are used in combat resolution).
  8. /// </summary>
  9. [System.Serializable]
  10. public class CharacterStats : ISerializationCallbackReceiver
  11. {
  12. [SerializeField] private float _maxHealth = 100f;
  13. [SerializeField] private float _speed = 10f; // Initiative and movement speed
  14. [SerializeField] private float _attack = 10f; // Physical damage output
  15. [SerializeField] private float _defense = 5f; // Physical damage reduction
  16. [SerializeField] private float _magic = 5f; // Magical power (casting damage/resistance)
  17. private float _currentHealth;
  18. public float MaxHealth => _maxHealth;
  19. public float CurrentHealth
  20. {
  21. get => _currentHealth;
  22. set => _currentHealth = Mathf.Clamp(value, 0, _maxHealth);
  23. }
  24. public float Speed => _speed;
  25. public float Attack => _attack;
  26. public float Defense => _defense;
  27. public float Magic => _magic;
  28. public bool IsAlive => _currentHealth > 0;
  29. public CharacterStats()
  30. {
  31. _currentHealth = _maxHealth;
  32. }
  33. /// <summary>
  34. /// Unity deserializes nested [Serializable] classes by running the default constructor
  35. /// first (which sets _currentHealth from _maxHealth's *default field value*, 100), and
  36. /// only afterwards overwrites the serialized fields (like _maxHealth) with the saved
  37. /// asset data. That leaves _currentHealth stale (e.g. 100/20) for any template asset whose
  38. /// real MaxHealth differs from the field default. _currentHealth isn't [SerializeField],
  39. /// so nothing else corrects it - fix it up here now that _maxHealth holds its real value.
  40. /// </summary>
  41. public void OnAfterDeserialize()
  42. {
  43. _currentHealth = _maxHealth;
  44. }
  45. public void OnBeforeSerialize()
  46. {
  47. }
  48. /// <summary>
  49. /// Calculate how many grid squares this character can move per turn during combat.
  50. /// Movement range is based on Speed: faster characters move further.
  51. /// Formula: Speed / 2, minimum 1 square per turn.
  52. /// </summary>
  53. public int GetMovementRange()
  54. {
  55. return Mathf.Max(1, Mathf.FloorToInt(_speed / 2f));
  56. }
  57. /// <summary>Create a copy of this stat block (useful for monster instances).</summary>
  58. public CharacterStats Clone()
  59. {
  60. var clone = new CharacterStats();
  61. // Manually copy all fields (can't use object initializer for private fields)
  62. clone._maxHealth = _maxHealth;
  63. clone._speed = _speed;
  64. clone._attack = _attack;
  65. clone._defense = _defense;
  66. clone._magic = _magic;
  67. clone._currentHealth = _currentHealth;
  68. return clone;
  69. }
  70. /// <summary>Reset health to max (for a fresh combat or new agent spawn).</summary>
  71. public void RestoreHealth()
  72. {
  73. _currentHealth = _maxHealth;
  74. }
  75. /// <summary>Take damage (already accounting for defense).</summary>
  76. public void TakeDamage(float damage)
  77. {
  78. CurrentHealth -= Mathf.Max(0, damage);
  79. }
  80. public void Heal(float amount)
  81. {
  82. CurrentHealth += Mathf.Max(0, amount);
  83. }
  84. /// <summary>Calculate effective damage taken after defense reduction.</summary>
  85. public float CalculateDamageReduction(float incomingDamage)
  86. {
  87. // Simple formula: defense reduces damage by 10% per defense point, up to 90% reduction
  88. float reductionPercent = Mathf.Clamp01(_defense * 0.1f);
  89. return incomingDamage * (1f - reductionPercent);
  90. }
  91. }
  92. }