using Kryz.CharacterStats.Examples; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public class Health : Resource { protected CharacterStats character; public ScrollingText scrollingTextPrefab; protected ScrollingText scrollingText; protected AbsorbEffectInstance absorbEffectInstance; protected DamageIncomeModifierEffectInstance damageIncomeModifierEffectInstance; public UnityEvent onMaxHealthChanged = new UnityEvent(); public UnityEvent onDeath = new UnityEvent(); private bool noCost = false; public bool NoCost => noCost; public bool invulnerable = false; public bool Invulnerable => invulnerable; float incomingValue; float percentStatMitigation; float reducedDamage; private bool isDead; public UnityEvent OnInvulnerabilityStateChanged = new UnityEvent(); public UnityEvent OnDodgedSuccessfully = new UnityEvent(); public void SetIsDeadState(bool isDead) { this.isDead = isDead; canRegen = !isDead; } protected virtual void Awake() { character = GetComponent(); absorbEffectInstance = GetComponent(); damageIncomeModifierEffectInstance = GetComponent(); character.onAllStatsUpdated.AddListener(CalculateMaxValueBasedOnStat); } protected override void Start() { baseRegenValue = character.GetStat("healthregen").BaseValue; baseMaxValue = character.GetStat("maxhealth").BaseValue; base.Start(); } public bool EnoughHealth(float cost) { if (noCost || invulnerable) return true; return cost <= currentValue; } public void SetInvulnerabilityState(bool isInvulnerable) { invulnerable = isInvulnerable; OnInvulnerabilityStateChanged.Invoke(invulnerable); } protected void HandleDamageIncomeModifierEffects() { if (incomingValue < 0) { if (damageIncomeModifierEffectInstance.IsActive) { //Debug.Log("Incoming damage b4 mitigation: " + incomingValue); incomingValue = damageIncomeModifierEffectInstance.ModifyDamageIncome(incomingValue); if (incomingValue > 0) //avoid damage ultra mitigated turning into healing incomingValue = 0; } //Debug.Log("Incoming damage after mitigation: " + incomingValue); } } protected bool HasDodged() { return MathHelpers.RollChancePercent(character.GetStat("dodgechance").Value); } protected bool HasBlocked() { return MathHelpers.RollChancePercent(character.GetStat("blockchance").Value); } protected void HandleBlockMitigation() { if (incomingValue >= 0) return; Debug.Log("Blocked!"); if (scrollingTextPrefab != null) { scrollingText = GameObjectPoolManager.Instance.Get(scrollingTextPrefab.gameObject, this.transform.position + new Vector3(Random.Range(-0.5f, 0.5f), 2f, Random.Range(-0.5f, 0.5f)), Quaternion.identity).GetComponent(); scrollingText.ShowBlock("Blocked!"); } percentStatMitigation = MathHelpers.NormalizePercentageDecimal(character.GetStat("blockeffectiveness").Value); percentStatMitigation = Mathf.Clamp(percentStatMitigation, 0, GameConstants.CharacterStatsBalancing.MaximumPercentDamageReductionFromBlock); reducedDamage = incomingValue * percentStatMitigation; incomingValue += Mathf.Abs(reducedDamage); if (incomingValue > 0) //avoid damage ultra mitigated turning into healing incomingValue = 0; } protected void HandleStatMitigation(DamageType dmgType) { if (incomingValue < 0) { switch (dmgType) { case DamageType.Attack: default: { percentStatMitigation = Mathf.Clamp((character.GetStat("armor").Value * GameConstants.CharacterStatsBalancing.PercentArmorIntoDamageReduction), 0,GameConstants.CharacterStatsBalancing.MaximumPercentDamageReductionFromArmor); } break; case DamageType.Spell: { percentStatMitigation = Mathf.Clamp((character.GetStat("magicresistance").Value * GameConstants.CharacterStatsBalancing.PercentMagicResistanceIntoDamageReduction), 0, GameConstants.CharacterStatsBalancing.MaximumPercentDamageReductionFromMagicResistance); } break; } //Debug.Log(gameObject.name + " mitigating = " + percentStatMitigation + " percent"); reducedDamage = incomingValue * percentStatMitigation; incomingValue += Mathf.Abs(reducedDamage); //Debug.Log(gameObject.name + " receiving dmg = " + incomingValue + $" after mitigations from {(dmgType == DamageType.Attack ? nameof(character.Armor) : nameof(character.MagicResistance))}"); if (incomingValue > 0) //avoid damage ultra mitigated turning into healing incomingValue = 0; } } protected void HandleAbsorbEffects() { if (incomingValue < 0) { if (absorbEffectInstance.IsActive) { //Debug.Log("Incoming damage b4 absorbs: " + incomingValue); incomingValue = absorbEffectInstance.AbsorbDamage(incomingValue); if (incomingValue > 0) //avoid complete absorbs turning into healing incomingValue = 0; } //Debug.Log("Incoming damage after absorbs: " + incomingValue); } } public override void ChangeValue(float value) { //Debug.Log("Value to change: " + value); if (isDead) return; incomingValue = value; if (invulnerable && incomingValue < 0) return; HandleDamageIncomeModifierEffects(); HandleAbsorbEffects(); currentValue += incomingValue; currentValue = Mathf.Clamp(currentValue, 0, maxValue); if (currentValue == 0) { //dead; onDeath.Invoke(); } //Debug.Log("CurrentHealth: " + currentValue); onResourceChanged.Invoke(currentValue); } public void ChangeValueDirect(float value) { //Debug.Log("Value to change: " + value); if (isDead) return; incomingValue = value; if (invulnerable && incomingValue < 0) return; currentValue += incomingValue; currentValue = Mathf.Clamp(currentValue, 0, maxValue); if (currentValue == 0) { //dead; onDeath.Invoke(); } //Debug.Log("CurrentHealth: " + currentValue); onResourceChanged.Invoke(currentValue); } public void ChangeValue(float value, int dmgType, bool isCrit) { //Debug.Log("Value to change: " + value); if (isDead) return; incomingValue = value; //Debug.Log(gameObject.name + " receiving dmg = " + incomingValue + " before mitigations " + (DamageType)dmgType); if (invulnerable && incomingValue < 0) { if (scrollingTextPrefab != null) { scrollingText = GameObjectPoolManager.Instance.Get(scrollingTextPrefab.gameObject, this.transform.position + new Vector3(Random.Range(-0.5f, 0.5f), 2f, Random.Range(-0.5f, 0.5f)), Quaternion.identity).GetComponent(); scrollingText.Show("Invulnerable", Color.gray, 1.5f); } return; } if (incomingValue < 0) { HandleNegativeValue(dmgType, isCrit); } else { HandlePositiveValue(); } } protected void HandleNegativeValue(int dmgType, bool isCrit) { if (incomingValue >= 0) return; if (HasDodged()) { Debug.Log("Dodged!"); if (scrollingTextPrefab != null) { scrollingText = GameObjectPoolManager.Instance.Get(scrollingTextPrefab.gameObject, this.transform.position + new Vector3(Random.Range(-0.5f, 0.5f), 2f, Random.Range(-0.5f, 0.5f)), Quaternion.identity).GetComponent(); scrollingText.ShowDodge("Dodge!"); } OnDodgedSuccessfully.Invoke(); return; } HandleDamageIncomeModifierEffects(); if (HasBlocked()) HandleBlockMitigation(); HandleStatMitigation((DamageType)dmgType); HandleAbsorbEffects(); currentValue += incomingValue; currentValue = Mathf.Clamp(currentValue, 0, maxValue); if (scrollingTextPrefab != null) { scrollingText = GameObjectPoolManager.Instance.Get(scrollingTextPrefab.gameObject, this.transform.position + new Vector3(Random.Range(-0.5f, 0.5f), 2f, Random.Range(-0.5f, 0.5f)), Quaternion.identity).GetComponent(); if (isCrit) scrollingText.ShowCrit(incomingValue.ToString("0.0")); else scrollingText.ShowHit(incomingValue.ToString("0.0")); } if (currentValue == 0) { //dead; onDeath.Invoke(); } //Debug.Log("CurrentHealth: " + currentValue); onResourceChanged.Invoke(currentValue); } protected void HandlePositiveValue() { currentValue += incomingValue; currentValue = Mathf.Clamp(currentValue, 0, maxValue); if (scrollingTextPrefab != null && incomingValue != 0) { scrollingText = GameObjectPoolManager.Instance.Get(scrollingTextPrefab.gameObject, this.transform.position + new Vector3(Random.Range(-0.5f, 0.5f), 2f, Random.Range(-0.5f, 0.5f)), Quaternion.identity).GetComponent(); scrollingText.ShowHeal(incomingValue.ToString("0.0")); } if (currentValue == 0) { //dead; onDeath.Invoke(); } //Debug.Log("CurrentHealth: " + currentValue); onResourceChanged.Invoke(currentValue); } public override float GetMaxValue() { return base.GetMaxValue(); } public virtual void CalculateMaxValueBasedOnStat() { maxValue = character.GetStat("maxhealth").Value; CalculateRegenValueBasedOnStat(); onMaxHealthChanged.Invoke(maxValue); } public void CalculateRegenValueBasedOnStat() { flatRegen = character.GetStat("healthregen").Value; } public override void SetMaxValue(float value) { base.SetMaxValue(value); onMaxHealthChanged.Invoke(maxValue); } }