chenxin
2020-11-09 172d8ffbf5fe3bdd60b3d71a3a60feeed1cb1762
Assets/Scripts/Core/Health/Damageable.cs
@@ -3,251 +3,347 @@
namespace Core.Health
{
   /// <summary>
   /// Damageable class for handling health using events
   /// Could be used on Players or enemies or even destructable world objects
   /// </summary>
   [Serializable]
   public class Damageable
   {
      /// <summary>
      /// The max health of this instance
      /// </summary>
      public float maxHealth;
      public float startingHealth;
    /// <summary>
    /// Damageable class for handling health using events
    /// Could be used on Players or enemies or even destructable world objects
    /// </summary>
    [Serializable]
    public class Damageable
    {
        /// <summary>
        /// The max health of this instance
        /// </summary>
        public float maxHealth;
      /// <summary>
      /// The alignment of the damager
      /// </summary>
      public SerializableIAlignmentProvider alignment;
        public float startingHealth;
      // events
      public event Action reachedMaxHealth;
        /// <summary>
        /// The alignment of the damager
        /// </summary>
        public SerializableIAlignmentProvider alignment;
      // 当前的Entity用于响应各种血量相关的事件。
      public event Action<HealthChangeInfo> damaged, healed, died, healthChanged;
        // events
        public event Action reachedMaxHealth;
        // 当前的Entity用于响应各种血量相关的事件。
        public event Action<HealthChangeInfo> damaged, healed, died, healthChanged;
        /// <summary>
        /// 魔法护盾值改变
        /// </summary>
        public event Action<HealthChangeInfo> ShieldWallHealthChanged;
        /// <summary>
        /// Gets the current health.
        /// </summary>
        public float currentHealth { protected set; get; }
        /// <summary>
        /// 设置无敌状态.
        /// </summary>
        public bool bInvincible { get; set; }
        /// <summary>
        /// 是否存在魔法护盾
        /// </summary>
        public bool IsExistShieldWall { get; set; }
        /// <summary>
        /// 0 -> 和agent的生命周期一致,直到死亡, > 0 (单位s)*时间内生效
        /// </summary>
        public float ShieldWallEffectiveTime { get; set; }
        /// <summary>
        /// 魔法护盾生效剩余时间,这个只针对 ShieldWallEffectTime > 0 才生效
        /// </summary>
        public float ShieldWallRemainTime { get; set; }
        /// <summary>
        /// 魔法护盾最大生命值,即初始生命值
        /// </summary>
        public float ShieldWallMaxHealth { get; set; }
        /// <summary>
        /// 魔法护盾当前生命值,当 <= 0时直接移除护盾效果
        /// </summary>
        public float ShieldWallCurrentHealth { get; set; }
        /// <summary>
        /// Gets the normalised health.
        /// </summary>
        public float normalisedHealth
        {
            get
            {
                if (Math.Abs(maxHealth) <= Mathf.Epsilon)
                {
                    Debug.LogError("Max Health is 0");
                    maxHealth = 1f;
                }
                return currentHealth / maxHealth;
            }
        }
        /// <summary>
        /// 清理魔法护盾
        /// </summary>
        public void ClearShieldWall()
        {
            IsExistShieldWall = false;
            ShieldWallMaxHealth = 0;
            ShieldWallCurrentHealth = 0;
            ShieldWallRemainTime = 0;
            ShieldWallEffectiveTime = 0;
        }
        /// <summary>
        /// Gets the <see cref="IAlignmentProvider"/> of this instance
        /// </summary>
        public IAlignmentProvider alignmentProvider
        {
            get
            {
                return alignment != null ? alignment.GetInterface() : null;
            }
        }
        /// <summary>
        /// Gets whether this instance is dead.
        /// </summary>
        public bool isDead
        {
            get { return currentHealth < 1.0f; }
        }
        /// <summary>
        /// Gets whether this instance is at max health.
        /// </summary>
        public bool isAtMaxHealth
        {
            get { return Mathf.Approximately(currentHealth, maxHealth); }
        }
      /// <summary>
      /// Gets the current health.
      /// </summary>
      public float currentHealth { protected set; get; }
        /// <summary>
        /// Init this instance
        /// </summary>
        public virtual void Init()
        {
            currentHealth = startingHealth;
        }
      /// <summary>
      /// 设置无敌状态.
      /// </summary>
      public bool bInvincible { get; set; }
        /// <summary>
        /// Sets the max health and starting health to the same value
        /// </summary>
        public void SetMaxHealth(float health)
        {
            if (health <= 0)
            {
                return;
            }
            maxHealth = startingHealth = health;
        }
      /// <summary>
      /// Gets the normalised health.
      /// </summary>
      public float normalisedHealth
      {
         get
         {
            if (Math.Abs(maxHealth) <= Mathf.Epsilon)
            {
               Debug.LogError("Max Health is 0");
               maxHealth = 1f;
            }
            return currentHealth / maxHealth;
         }
      }
        /// <summary>
        /// Sets the max health and starting health separately
        /// </summary>
        public void SetMaxHealth(float health, float startingHealth)
        {
            if (health <= 0)
            {
                return;
            }
            maxHealth = health;
            this.startingHealth = startingHealth;
        }
      /// <summary>
      /// Gets the <see cref="IAlignmentProvider"/> of this instance
      /// </summary>
      public IAlignmentProvider alignmentProvider
      {
         get
         {
            return alignment != null ? alignment.GetInterface() : null;
         }
      }
        /// <summary>
        /// Sets this instance's health directly.
        /// </summary>
        /// <param name="health">
        /// The value to set <see cref="currentHealth"/> to
        /// </param>
        public void SetHealth(float health)
        {
            var info = new HealthChangeInfo
            {
                damageable = this,
                newHealth = health,
                oldHealth = currentHealth
            };
      /// <summary>
      /// Gets whether this instance is dead.
      /// </summary>
      public bool isDead
      {
         get { return currentHealth < 1.0f; }
      }
            currentHealth = health;
            info.oldHealth = health;
      /// <summary>
      /// Gets whether this instance is at max health.
      /// </summary>
      public bool isAtMaxHealth
      {
         get { return Mathf.Approximately(currentHealth, maxHealth); }
      }
            if (healthChanged != null)
            {
                healthChanged(info);
            }
        }
        /// <summary>
        /// 获得一个魔法护盾
        /// </summary>
        /// <param name="maxHealth">护盾的最大生命值</param>
        /// <param name="effectiveTime">生效时间 0 -> 和Agent生命周期一致,> 0 *(单位ms)时间内生效</param>
        public void AddShieldWall(float maxHealth, float effectiveTime)
        {
            // 如果重复获得护盾,直接全部重置数据,移除之前的护盾效果,重新获得一个护盾
            IsExistShieldWall = true;
            ShieldWallEffectiveTime = effectiveTime;
      /// <summary>
      /// Init this instance
      /// </summary>
      public virtual void Init()
      {
         currentHealth = startingHealth;
      }
            if (effectiveTime > 0)
                ShieldWallRemainTime = effectiveTime / 1000f;
      /// <summary>
      /// Sets the max health and starting health to the same value
      /// </summary>
      public void SetMaxHealth(float health)
      {
         if (health <= 0)
         {
            return;
         }
         maxHealth = startingHealth = health;
      }
            ShieldWallMaxHealth = ShieldWallCurrentHealth = maxHealth;
      /// <summary>
      /// Sets the max health and starting health separately
      /// </summary>
      public void SetMaxHealth(float health, float startingHealth)
      {
         if (health <= 0)
         {
            return;
         }
         maxHealth = health;
         this.startingHealth = startingHealth;
      }
            HealthChangeInfo info = new HealthChangeInfo
            {
                damageable = this,
                ShieldWallMaxHealth = maxHealth,
                ShieldWallCurrentHealth = maxHealth,
                IsExistShieldWall = true
            };
      /// <summary>
      /// Sets this instance's health directly.
      /// </summary>
      /// <param name="health">
      /// The value to set <see cref="currentHealth"/> to
      /// </param>
      public void SetHealth(float health)
      {
         var info = new HealthChangeInfo
         {
            damageable = this,
            newHealth = health,
            oldHealth = currentHealth
         };
         currentHealth = health;
         info.oldHealth = health;
            if (ShieldWallHealthChanged != null)
            {
                ShieldWallHealthChanged(info);
            }
        }
         if (healthChanged != null)
         {
            healthChanged(info);
         }
      }
        /// <summary>
        /// Use the alignment to see if taking damage is a valid action
        /// </summary>
        /// <param name="damage">
        /// The damage to take
        /// </param>
        /// <param name="damageAlignment">
        /// The alignment of the other combatant
        /// </param>
        /// <param name="output">
        /// The output data if there is damage taken
        /// </param>
        /// <returns>
        /// <value>true if this instance took damage</value>
        /// <value>false if this instance was already dead, or the alignment did not allow the damage</value>
        /// </returns>
        public bool TakeDamage(float damage, IAlignmentProvider damageAlignment, ref HealthChangeInfo output)
        {
      /// <summary>
      /// Use the alignment to see if taking damage is a valid action
      /// </summary>
      /// <param name="damage">
      /// The damage to take
      /// </param>
      /// <param name="damageAlignment">
      /// The alignment of the other combatant
      /// </param>
      /// <param name="output">
      /// The output data if there is damage taken
      /// </param>
      /// <returns>
      /// <value>true if this instance took damage</value>
      /// <value>false if this instance was already dead, or the alignment did not allow the damage</value>
      /// </returns>
      public bool TakeDamage(float damage, IAlignmentProvider damageAlignment, ref HealthChangeInfo output)
      {
         /*output = new HealthChangeInfo
            /*output = new HealthChangeInfo
         {
            damageAlignment = damageAlignment, damageable = this,
            newHealth = currentHealth, oldHealth = currentHealth
         };*/
         output.damageAlignment = damageAlignment;
         output.damageable = this;
         output.newHealth = currentHealth;
         output.oldHealth = currentHealth;
            output.damageAlignment = damageAlignment;
            output.damageable = this;
            output.newHealth = currentHealth;
            output.oldHealth = currentHealth;
         // 无敌状态下,不可伤害.
         if (this.bInvincible)
            return false;
            // 无敌状态下,不可伤害.
            if (this.bInvincible)
                return false;
         bool canDamage = damageAlignment == null || alignmentProvider == null ||
                          damageAlignment.CanHarm(alignmentProvider);
         if (isDead || !canDamage)
         {
            return false;
         }
            bool canDamage = damageAlignment == null || alignmentProvider == null ||
                             damageAlignment.CanHarm(alignmentProvider);
         ChangeHealth(-damage, ref output);
         SafelyDoAction(damaged, output);
         if (isDead)
         {
            SafelyDoAction(died, output);
         }
         return true;
      }
            if (isDead || !canDamage)
            {
                return false;
            }
      /// <summary>
      /// Logic for increasing the health.
      /// </summary>
      /// <param name="health">Health.</param>
      public HealthChangeInfo IncreaseHealth(float health)
      {
         var info = new HealthChangeInfo {damageable = this};
         ChangeHealth(health, ref info);
         SafelyDoAction(healed, info);
         if (isAtMaxHealth)
         {
            SafelyDoAction(reachedMaxHealth);
         }
            ChangeHealth(-damage, ref output);
            SafelyDoAction(damaged, output);
            if (isDead)
            {
                SafelyDoAction(died, output);
            }
            return true;
        }
         return info;
      }
        /// <summary>
        /// Logic for increasing the health.
        /// </summary>
        /// <param name="health">Health.</param>
        public HealthChangeInfo IncreaseHealth(float health)
        {
            var info = new HealthChangeInfo { damageable = this };
            ChangeHealth(health, ref info);
            SafelyDoAction(healed, info);
            if (isAtMaxHealth)
            {
                SafelyDoAction(reachedMaxHealth);
            }
      /// <summary>
      /// Changes the health.
      /// </summary>
      /// <param name="healthIncrement">Health increment.</param>
      /// <param name="info">HealthChangeInfo for this change</param>
      protected void ChangeHealth(float healthIncrement, ref HealthChangeInfo info)
      {
         info.oldHealth = currentHealth;
         currentHealth += healthIncrement;
         currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth);
         info.newHealth = currentHealth;
         if (healthChanged != null)
         {
            healthChanged(info);
         }
      }
            return info;
        }
      /// <summary>
      /// A helper method for null checking actions
      /// </summary>
      /// <param name="action">Action to be done</param>
      protected void SafelyDoAction(Action action)
      {
         if (action != null)
         {
            action();
         }
      }
        /// <summary>
        /// Changes the health.
        /// </summary>
        /// <param name="healthIncrement">Health increment.</param>
        /// <param name="info">HealthChangeInfo for this change</param>
        protected void ChangeHealth(float healthIncrement, ref HealthChangeInfo info)
        {
            bool changeHealth = false;
            float increment = healthIncrement;
      /// <summary>
      /// A helper method for null checking actions
      /// </summary>
      /// <param name="action">Action to be done</param>
      /// <param name="info">The HealthChangeInfo to be passed to the Action</param>
      protected void SafelyDoAction(Action<HealthChangeInfo> action, HealthChangeInfo info)
      {
         if (action != null)
         {
            action(info);
         }
      }
   }
            if (IsExistShieldWall)
            {
                info.ShieldWallOldHealth = ShieldWallCurrentHealth;
                ShieldWallCurrentHealth += healthIncrement;
                if (ShieldWallCurrentHealth == 0)
                    info.IsExistShieldWall = IsExistShieldWall = false;
                else if (ShieldWallCurrentHealth < 0)
                {
                    changeHealth = true;
                    info.IsExistShieldWall = IsExistShieldWall = false;
                    // 护盾血量不够减
                    increment = ShieldWallCurrentHealth;
                    info.ShieldWallCurrentHealth = ShieldWallCurrentHealth = 0;
                }
            }
            else
                changeHealth = true;
            if (changeHealth)
            {
                info.oldHealth = currentHealth;
                currentHealth += increment;
                currentHealth = Mathf.Clamp(currentHealth, 0f, maxHealth);
                info.newHealth = currentHealth;
                if (healthChanged != null)
                {
                    healthChanged(info);
                }
            }
        }
        /// <summary>
        /// A helper method for null checking actions
        /// </summary>
        /// <param name="action">Action to be done</param>
        protected void SafelyDoAction(Action action)
        {
            if (action != null)
            {
                action();
            }
        }
        /// <summary>
        /// A helper method for null checking actions
        /// </summary>
        /// <param name="action">Action to be done</param>
        /// <param name="info">The HealthChangeInfo to be passed to the Action</param>
        protected void SafelyDoAction(Action<HealthChangeInfo> action, HealthChangeInfo info)
        {
            if (action != null)
            {
                action(info);
            }
        }
    }
}