Healthy Hat won't add any health bonus

Hello!

So I have the weird issue, that I have actually no errors in my console and everything from picking up and dragging the healthy hat to the inventory works just fine but it actually won’t set my health up and also not showing it in the UI. Any Ideas in which part of the code it could be wrong?

Also I just noticed that my equipped Weapons are not making any damage anymore and he is just taking the damage from the progression

Did you attach the Equipment or the StatsEquipment component to your player? The Equipment one by itself isn’t an IModifierProvider, and won’t be picked up in the gathering of stat bonuses.

I attached the Stats Equipment to the player but for some strange reason the health won’t go up and also attached weapons are not making any damage anymore :confused:

Paste in your StatsEquipment.cs script and we’ll have a look.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Inventories;
using RPG.Stats;


namespace RPG.EquipedInventories
{
    public class StatsEquipment : Equipment, IModefierProvider
    {
        IEnumerable<float> IModefierProvider.GetAdditiveModifiers(Stat stat)
        {
            foreach (var slot in GetAllPopulatedSlots())
            {
                var item = GetItemInSlot(slot) as IModefierProvider;
                if (item == null) continue;
                

                foreach (float modifier in item.GetAdditiveModifiers(stat))
                {
                    yield return modifier;
                }
            }
        }

        IEnumerable<float> IModefierProvider.GetPercentageModifiers(Stat stat)
        {
            foreach (var slot in GetAllPopulatedSlots())
            {
                var item = GetItemInSlot(slot) as IModefierProvider;
                if (item == null) continue;

                foreach (float modifier in item.GetPercentageModifiers(stat))
                {
                    yield return modifier;
                }
            }
        }

    }
}


I need to mention, that I changed the names of the namespaces a bit during the process.

So what for you was " using GameDevTV.Inventories" is now RPG.Inventories for me and that’s why I also have a different namespace (EquipedInventories) in this case. Just to make it clear :slight_smile:

P.S Also my IModifier Script is in my case written with an e, so just if you wonder if I did a typo in this script here :slight_smile:

Neither of those things should matter.

This means we need to look at your StatsEquipableItem and WeaponConfig scripts to see what’s going on there, as well, as your StatsEquipment looks correct.

Stats EquipableItem

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Inventories;
using RPG.Stats;

namespace RPG.EquipedInventories
{
    [CreateAssetMenu(menuName = ("RPG/Inventory/Equipable Item"))]
    public class StatsEquipableItem : EquipableItem, IModefierProvider
    {
        [SerializeField]
        Modifier[] additiveModifiers;
        [SerializeField]
        Modifier[] percentageModifiers;

        [System.Serializable]
        struct Modifier
        {
            public Stat stat;
            public float value;
        }   

        public IEnumerable<float> GetAdditiveModifiers(Stat stat)
        {
            foreach (var modifier in additiveModifiers)
            {
                if (modifier.stat == stat)
                {
                    yield return modifier.value;
                }
            }
        }

        public IEnumerable<float> GetPercentageModifiers(Stat stat)
        {
            foreach (var modifier in percentageModifiers)
            {
                if (modifier.stat == stat)
                {
                    yield return modifier.value;
                }
            }
        }
    }
}

StatsEquipment

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Inventories;
using RPG.Stats;


namespace RPG.EquipedInventories
{
    public class StatsEquipment : Equipment, IModefierProvider
    {
        IEnumerable<float> IModefierProvider.GetAdditiveModifiers(Stat stat)
        {
            foreach (var slot in GetAllPopulatedSlots())
            {
                var item = GetItemInSlot(slot) as IModefierProvider;
                if (item == null) continue;
                

                foreach (float modifier in item.GetAdditiveModifiers(stat))
                {
                    yield return modifier;
                }
            }
        }

        IEnumerable<float> IModefierProvider.GetPercentageModifiers(Stat stat)
        {
            foreach (var slot in GetAllPopulatedSlots())
            {
                var item = GetItemInSlot(slot) as IModefierProvider;
                if (item == null) continue;

                foreach (float modifier in item.GetPercentageModifiers(stat))
                {
                    yield return modifier;
                }
            }
        }

    }
}

WeaponConfig

using UnityEngine;
using RPG.Core;
using RPG.Attributes;
using RPG.Inventories;
using RPG.Stats;
using System.Collections.Generic;

namespace RPG.Combat
{
    [CreateAssetMenu(fileName = "Weapon", menuName = "Weapons/Make New Weapon", order = 0)]
    public class WeaponConfig : EquipableItem, IModefierProvider
    {
        [SerializeField] AnimatorOverrideController animatorOverride = null;
        [SerializeField] Weapon equippedPrefab = null;
        [SerializeField] float weaponDamage = 5f;
        [SerializeField] float percentageBonus = 0;
        [SerializeField] float weaponRange = 2f;
        [SerializeField] bool isRightHanded = true;
        [SerializeField] Projectile projectile = null;

        const string weaponName = "Weapon";

        public Weapon Spawn(Transform rightHand, Transform leftHand, Animator animator)
        {
            DestroyOldWeapon(rightHand, leftHand);

            Weapon weapon = null;

            if (equippedPrefab != null)
            {
                Transform handTransform = GetTransform(rightHand, leftHand);
                weapon = Instantiate(equippedPrefab, handTransform);
                weapon.gameObject.name = weaponName;
            }

            var overrideController = animator.runtimeAnimatorController as AnimatorOverrideController;
            if (animatorOverride != null)
            {
                animator.runtimeAnimatorController = animatorOverride; 
            }
            else if (overrideController != null)
            {
                animator.runtimeAnimatorController = overrideController.runtimeAnimatorController;
            }

            return weapon;
        }

        private void DestroyOldWeapon(Transform rightHand, Transform leftHand)
        {
            Transform oldWeapon = rightHand.Find(weaponName);
            if (oldWeapon == null)
            {
                oldWeapon = leftHand.Find(weaponName);
            }
            if (oldWeapon == null) return;

            oldWeapon.name = "DESTROYING";
            Destroy(oldWeapon.gameObject);
        }

        private Transform GetTransform(Transform rightHand, Transform leftHand)
        {
            Transform handTransform;
            if (isRightHanded) handTransform = rightHand;
            else handTransform = leftHand;
            return handTransform;
        }

        public bool HasProjectile()
        {
            return projectile != null;
        }

        public void LaunchProjectile(Transform rightHand, Transform leftHand, Health target, GameObject instigator, float calculatedDamage)
        {
            Projectile projectileInstance = Instantiate(projectile, GetTransform(rightHand, leftHand).position, Quaternion.identity);
            projectileInstance.SetTarget(target, instigator, calculatedDamage);
        }

        public float GetDamage()
        {
            return weaponDamage;
        }

        public float GetPercentageBonus()
        {
            return percentageBonus;
        }

        public float GetRange()
        {
            return weaponRange;
        }

        public IEnumerable<float> GetAdditiveModifiers(Stat stat)
        {
            if (stat == Stat.Damage)
            {
                yield return weaponDamage;
            }
        }

        public IEnumerable<float> GetPercentageModifiers(Stat stat)
        {
            if (stat == Stat.Damage)
            {
                yield return percentageBonus;
            }
        }
    }
}

I hope its just a stupid mistake I did and that you could help me with that :)!

P.s Also the healthy hat won’t saved when changing scenes so I am super confused what could be wrong :confused:

The scripts appear correct, so far. Trying to follow the chain of function calls that make up gathering the information. Next would be BaseStats.cs

Base Stats

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine;
using RPG.Utils;


namespace RPG.Stats
{
    public class BaseStats : MonoBehaviour
    {
        [Range(1, 99)]
        [SerializeField] int startingLevel = 1;
        [SerializeField] CharacterClass characterClass;
        [SerializeField] Progression progression = null;
        [SerializeField] GameObject levelUpParticleEffect = null;
        [SerializeField] bool shouldUseModifiers = false;

        public event Action onLevelUp;

        LazyValue<int> currentLevel;

        Experience experience;

        private void Awake() {
            experience = GetComponent<Experience>();
            currentLevel = new LazyValue<int>(CalculateLevel);
        }

        private void Start() 
        {
            currentLevel.ForceInit();
        }

        private void OnEnable() {
            if (experience != null)
            {
                experience.onExperienceGained += UpdateLevel;
            }
        }

        private void OnDisable() {
            if (experience != null)
            {
                experience.onExperienceGained -= UpdateLevel;
            }
        }

        private void UpdateLevel() 
        {
            int newLevel = CalculateLevel();
            if (newLevel > currentLevel.value)
            {
                currentLevel.value = newLevel;
                LevelUpEffect();
                onLevelUp();
            }
        }

        private void LevelUpEffect()
        {
            Instantiate(levelUpParticleEffect, transform);
        }

        public float GetStat(Stat stat)
        {
            return (GetBaseStat(stat) + GetAdditiveModifier(stat)) * (1 + GetPercentageModifier(stat)/100);
        }

        private float GetBaseStat(Stat stat)
        {
            return progression.GetStat(stat, characterClass, GetLevel());
        }

        public int GetLevel()
        {
            return currentLevel.value;
        }

        private float GetAdditiveModifier(Stat stat)
        {
            if (!shouldUseModifiers) return 0;

            float total = 0;
            foreach (IModefierProvider provider in GetComponents<IModefierProvider>())
            {
                foreach (float modifier in provider.GetAdditiveModifiers(stat))
                {
                    total += modifier;
                }
            }
            return total;
        }

        private float GetPercentageModifier(Stat stat)
        {
            if (!shouldUseModifiers) return 0;

            float total = 0;
            foreach (IModefierProvider provider in GetComponents<IModefierProvider>())
            {
                foreach (float modifier in provider.GetPercentageModifiers(stat))
                {
                    total += modifier;
                }
            }
            return total;
        }

        private int CalculateLevel()
        {
            Experience experience = GetComponent<Experience>();
            if (experience == null) return startingLevel;

            float currentXP = experience.GetPoints();
            int penultimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass);
            for (int level = 1; level <= penultimateLevel; level++)
            {
                float XPToLevelUp = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level);
                if (XPToLevelUp > currentXP)
                {
                    return level;
                }
            }

            return penultimateLevel + 1;
        }
    }
}

Maybe also my IModefier doesn’t work properly? Well this is the Base Stats Script

Do you have the shouldUseModifiers checked on your Player’s BaseStats?

No I don’t cause I thought I only need it, when I want to activate like special bonus damage to the weapons? :o

P.s This did the trick with the healthy hat! The Healthy hat works now but only in one scene. If I change the scene then it gets stuck in the white fading screen with this error:

ArgumentNullException: Value cannot be null.
Parameter name: key

And my weapon still doesn’t make the right amount of damage

Check the Item ID of all of your InventoryItems. It’s possible that one is null.

Is the weapon the default weapon or one that is equipped into StatsEquipment?

The Default Weapon won’t add to the stats, as it’s not actually equipped into StatsEquipment, generally for the player this is Unarmed, so the damage should reflect that bare minimum for Unarmed. Since the Enemies don’t use stat bonuses, the damage should reflect the damage for their weapon of choice.

As my default weapon on the fighter script I have my unarmed weapon. When I pick up the sword and equip it in the inventory he actually takes the sword in his hand but he dont actually change any damage value and it only stays the damage from the current experience level

Also in case this is my fighter script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Movement;
using RPG.Core;
using RPG.Saving;
using RPG.Attributes;
using RPG.Stats;
using RPG.Inventories;
using RPG.Utils;
using System;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction, ISaveable 
    {
        
        [SerializeField]  float timeBetweenAttacks = 1f;
        [SerializeField] Transform rightHandTransform = null;
        [SerializeField] Transform leftHandTransform = null;
        [SerializeField] WeaponConfig defaultWeapon = null;
        
      
        
        
        Health target;
        Equipment equipment;
        float timeSinceLastAttack = Mathf.Infinity;
        WeaponConfig currentWeaponConfig;
        LazyValue<Weapon> currentWeapon;

        private void Awake()
        {
            currentWeaponConfig = defaultWeapon;
            currentWeapon = new LazyValue<Weapon>(SetupDefaultWeapon);
            equipment = GetComponent<Equipment>();
            if(equipment)
            {
                equipment.equipmentUpdated += UpdateWeapon;
            } 
        }

        private Weapon SetupDefaultWeapon()
        {
            return AttachWeapon(defaultWeapon);
        }

        private void Start()
        {
            
            currentWeapon.ForceInit(); 
        }

        private void Update()
        {
            timeSinceLastAttack += Time.deltaTime;

            if (target == null) return;
            if(target.IsDead()) return;
            
            
            if (!GetIsInRange(target.transform))
            {
                GetComponent<Mover>().MoveTo(target.transform.position, 1f);
            }
            else
            {
                GetComponent<Mover>().Cancel();
                AttackBehaviour();
                //target = null;
            }
        }

        public void EquipWeapon(WeaponConfig weapon)
        {
           currentWeaponConfig = weapon;
           currentWeapon.value = AttachWeapon(weapon);
           
        }

        private void UpdateWeapon()
        {
            var weapon = equipment.GetItemInSlot(EquipLocation.Weapon) as WeaponConfig;
            if (weapon == null)
            {
                EquipWeapon(defaultWeapon);
            }
            else
            {
                EquipWeapon(weapon);
            }
        }

        private Weapon AttachWeapon(WeaponConfig weapon)
        {
            Animator animator = GetComponent<Animator>();
            return weapon.Spawn(rightHandTransform, leftHandTransform, animator);
        }

        public Health GetTarget()
        {
            return target;
        }

        private void AttackBehaviour()
        {
            transform.LookAt(target.transform);
            if (timeSinceLastAttack > timeBetweenAttacks)
            {
                //This will trigger the Hit() event.
                TriggerAttack();
                timeSinceLastAttack = 0;
            }

        }

        private void TriggerAttack()
        {
            GetComponent<Animator>().ResetTrigger("stopAttack");
            GetComponent<Animator>().SetTrigger("attack");
        }

         // Animation Event!
        void Hit()
        {
            if(target == null) {return;}

            float damage = GetComponent<BaseStats>().GetStat(Stat.Damage);
            if (currentWeapon.value != null)
            {
                currentWeapon.value.OnHit();
            }
            if (currentWeaponConfig.HasProjectile())
            {
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, target, gameObject, damage);
            }
            else
            {
                
                target.TakeDamage(gameObject, damage);
            }   
        }

         //Calls Hit Method for Bow
        void Shoot()
        {
            Hit();
        }

        
        private bool GetIsInRange(Transform targetTransform)
        {
            return Vector3.Distance(transform.position, targetTransform.position) < currentWeaponConfig.GetRange();
        }

        public bool CanAttack(GameObject combatTarget)
        {
            if(combatTarget == null) {return false;}
            if (!GetComponent<Mover>().CanMoveTo(combatTarget.transform.position) &&
                !GetIsInRange(combatTarget.transform))
            {
                return false;
            } 
            Health targetToTest = combatTarget.GetComponent<Health>();
            return targetToTest != null && !targetToTest.IsDead();
        }

        public void Attack(GameObject combatTarget)
        {
            GetComponent<ActionSchedular>().StartAction(this);
            target = combatTarget.GetComponent<Health>();
        }

        public void Cancel()
        {
            StopAttack();
            target = null;
            GetComponent<Mover>().Cancel();
        }

        private void StopAttack()
        {
            GetComponent<Animator>().ResetTrigger("attack");
            GetComponent<Animator>().SetTrigger("stopAttack");
        }

        public object CaptureState()
        {
            return currentWeaponConfig.name;
        }
        public void RestoreState(object state)
        {
            string weaponName = (string)state;
            WeaponConfig weapon = UnityEngine.Resources.Load<WeaponConfig>(weaponName);
            EquipWeapon(weapon);
        }
    }
}

Let’s add some Debugs to see if we can figure out why the Weapon is not adding (since it is not the default)

In WeaponConfig GetAdditiveModifiers()

if(stat == Stat.Damage)
{
    Debug.Log($"WeaponConfig GetAdditiveModifiers for {GetDisplayName()} returning {weaponDamage}");
    yield return weaponDamage;
}

In StatsEquipment’s GetAdditiveModifiers
Before the foreach (float modifier in item.GetAdditiveModifiers(stat)):

if(stat == Stat.Damage) Debug.Log($"StatsEquipment testing weapon {item.GetDisplayName} for damage modifiers");

And within the foreach(float modifier in item.GetModifiers(stat))

if(stat==Stat.Damage) Debug.Log($"{item.GetDisplayName} returned {modifier} damage bonus");

Be sure to turn off Collapse messages in the Console, or they will stack and it will be hard to tell what’s going on.

I am really sorry Brian with so many questions and bothering but it seems like I am doing something wrong even with some DebugLog messages ha.


did I understood you wrong about where to actually put the Debug.Logs?

Ack, that was my fault, I wrote that one at work, IModifierProvider doesn’t have a GetDisplayName function, and what we really needed was just to use the GetItemInSlot…
Let’s rewrite those debugs:

if(stat == Stat.Damage) Debug.Log($"StatsEquipment testing {GetItemInSlot(slot).GetDisplayName()} for damage modifiers");

and in the foreach

if(stat ==Stat.Damage) Debug.Log($"{GetItemInSlot(slot).GetDisplayName()} returned {modifier} damage bonus");

Hello Brian! I am really sorry but I found the solution for the problem and it was actually so easy and dumb in the same way. So you told me to tik the box with “Use modifiers” on the base stats. Well I did that in the prefab of the player but right now I just found out, that it was actually not activated when I was running the game. So I activated it also on this level and now everything is just working fine. The healthy hat is a healthy hat now and the weapons all making the right amount of damage. Sorry for bothering you with such a simple solution but at least found it out now.

Also I have to say that this was my first question here in the community and I just wanted to say that you do such a great job and I am super happy with your clear and understandable answers! Also that you respond so damn fast and not after like a few days is just such a high notch service! I am super happy about it and thanks again.

Greetings from Germany
Nils

I’m glad you got this sorted. Feel free to keep asking questions, the answers aren’t always obvious (for example, I didn’t think of checking shouldUseModifiers until we’d exhausted several other avenues!). There are a lot of factors when debugging in Unity, especially since we have not only our codebase, but settings in the inspector or prefabs that can be causing trouble. The trick is to go through each possibility, one at a time until we find the culprit.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms