| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105 |
- using UnityEngine;
- namespace MazeGen
- {
- /// <summary>
- /// 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).
- /// </summary>
- [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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- public void OnAfterDeserialize()
- {
- _currentHealth = _maxHealth;
- }
- public void OnBeforeSerialize()
- {
- }
- /// <summary>
- /// 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.
- /// </summary>
- public int GetMovementRange()
- {
- return Mathf.Max(1, Mathf.FloorToInt(_speed / 2f));
- }
- /// <summary>Create a copy of this stat block (useful for monster instances).</summary>
- 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;
- }
- /// <summary>Reset health to max (for a fresh combat or new agent spawn).</summary>
- public void RestoreHealth()
- {
- _currentHealth = _maxHealth;
- }
- /// <summary>Take damage (already accounting for defense).</summary>
- public void TakeDamage(float damage)
- {
- CurrentHealth -= Mathf.Max(0, damage);
- }
- public void Heal(float amount)
- {
- CurrentHealth += Mathf.Max(0, amount);
- }
- /// <summary>Calculate effective damage taken after defense reduction.</summary>
- 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);
- }
- }
- }
|