Enemy Drops the Item 2 times

So I am having the weird problem, that after defeating an Enemy he actually drops the potential Quest Item (Walking Stick) and it is exactly right just one prefab. But when I actually pick up the Quest Item I have it two times in my Inventory and I am asking myself why? What is the actual problem with it?



why 3

Paste in your RandomDropper.cs, and your DropLibrary.cs and we’ll take a look. Paste in the text, not screenshots.

The RandomDropper.cs

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

namespace RPG.EquipedInventories
{
    //Script that deals with the actual random drop of an item, depending on the base stat "level" of the player

    public class RandomDropper : ItemDropper
    {
        [Tooltip("How far can the pickups be scatteres from the dropper.")]
        [SerializeField] float scatterDistance = 1;
        [SerializeField] DropLibrary dropLibrary;
        const int ATTEMPTS = 30;

        public void RandomDrop()
        {
            var baseStats = GetComponent<BaseStats>();
            
            var drops = dropLibrary.GetRandomDrops(baseStats.GetLevel());
            foreach (var drop in drops)
            {
                DropItem(drop.item, drop.number);
            }    
        }

        protected override Vector3 GetDropLocation()
        {
            for (int i =0; i < ATTEMPTS; i++)
            {
                Vector3 randomPoint = transform.position + Random.insideUnitSphere * scatterDistance;
                NavMeshHit hit;
                if(NavMesh.SamplePosition(randomPoint,out hit, 0.1f, NavMesh.AllAreas))
                {
                    return hit.position;
                }
            }
            return transform.position;
        }
    }
}



And the DropLibrary.cs

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

//ScriptableObject for a drop library with different items that can be dropped from the enemy 

namespace RPG.EquipedInventories
{

    [CreateAssetMenu(menuName = ("RPG/Inventory/Drop Library"))]
    public class DropLibrary : ScriptableObject
    {
        [SerializeField]
        DropConfig[] potentialDrops;
        [SerializeField] float[] dropChancePercentage;
        [SerializeField] int[] minDrops;
        [SerializeField] int[] maxDrops;

        [System.Serializable]
        class DropConfig
        {
            public InventoryItem item;
            public float[] relativeChance;
            public int[] minNumber;
            public int[] maxNumber;
            public int GetRandomNumber(int level)
            {
                if (!item.IsStackable())
                {
                    return 1;
                }
                int min = GetByLevel(minNumber, level);
                int max = GetByLevel(maxNumber, level);
                return UnityEngine.Random.Range(min, max + 1);
            }
        }

        public struct Dropped
        {
            public InventoryItem item;
            public int number;
        }

        public IEnumerable<Dropped> GetRandomDrops(int level)
        {
            if (!ShouldRandomDrop(level))
            {
                yield break;
            }

            List<DropConfig> potentialDropInstance = potentialDrops.ToList();
            for (int i = 0; i < GetRandomNumberOfDrops(level); i++)
            {
                yield return GetRandomDrop(level, potentialDropInstance);
            }
        }

        bool ShouldRandomDrop(int level)
        {
            return Random.Range(0, 100) < GetByLevel(dropChancePercentage, level);
        }

        int GetRandomNumberOfDrops(int level)
        {
            int min = GetByLevel(minDrops, level);
            int max = GetByLevel(maxDrops, level);
            return Random.Range(min, max);
        }

        Dropped GetRandomDrop(int level, List<DropConfig> potentialDropInstance)
        {
            var drop = SelectRandomItem(level, potentialDropInstance);
            if (!drop.item.IsStackable())
            {
                potentialDropInstance.Remove(drop);
            }
            var result = new Dropped();
            result.item = drop.item;
            result.number = drop.GetRandomNumber(level);
            return result;
        }

        DropConfig SelectRandomItem(int level, List<DropConfig> potentialDropInstance)
        {
            float totalChance = GetTotalChance(level, potentialDropInstance);
            float randomRoll = Random.Range(0, totalChance);
            float chanceTotal = 0;
            foreach (var drop in potentialDropInstance)
            {
                chanceTotal += GetByLevel(drop.relativeChance, level);
                if (chanceTotal > randomRoll)
                {
                    return drop;
                }
            }
            return null;
        }

        float GetTotalChance(int level, List<DropConfig> potentialDropInstance)
        {
            float total = 0;
            foreach (var drop in potentialDropInstance)
            {
                total += GetByLevel(drop.relativeChance, level);
            }
            return total;
        }

        static T GetByLevel<T>(T[] values, int level)
        {
            if (values.Length == 0)
            {
                return default;
            }
            if (level > values.Length)
            {
                return values[values.Length - 1];
            }
            if (level <= 0)
            {
                return default;
            }
            return values[level - 1];
        }
    }
}

This is odd, from what I’m seeing between the actual DropConfig and the scripts, you should be getting just one…
Let’s add some Debugs to get a picture of what might be going on: In RandomDropper.RandomDrop()

            Debug.Log($"{name} RandomDrop()}");
            foreach (var drop in drops)
            {
                Debug.Log($"{gameObject.name} is dropping {drop.item.GetDisplayName()} qty {drop.number};
                DropItem(drop.item, drop.number);
            }

In DropLibrary.GetRandomDrops()

            int d = GetRandomNumberOfDrops(level);
            Debug.Log($"Should be dropping {d} items");
            List<DropConfig> potentialDropInstance = potentialDrops.ToList();
            for (int i = 0; i < GetRandomNumberOfDrops(level); i++)
            {
               
                Dropped result = GetRandomDrop(level, potentialDropInstance);
                Debug.Log($"DropConfig result {i}) {result.item.GetDisplayName()} qty {result.number}");
                yield return GetRandomDrop(level, potentialDropInstance);
            }

Well besides your Debug.Logs, there were also other code snippets I didnt had in my code for example

 int d = GetRandomNumberOfDrops(level);

or
  Dropped result = GetRandomDrop(level, potentialDropInstance);

So now the enemy is not dying anymore and also dont drop anything… but it might be cause of the error but the console shoots the following:

Yes, those were in there so that we could log the result, since return GetRandomNumberOfDrops(level) isn’t loggable.

Paste the GetRandomDrop method as you have it now, and mark the line (79) that the error is pointing to.

Dropped GetRandomDrop(int level, List<DropConfig> potentialDropInstance)
        {
            var drop = SelectRandomItem(level, potentialDropInstance);
            if (!drop.item.IsStackable())
            {
                potentialDropInstance.Remove(drop);
            }
            var result = new Dropped();
            result.item = drop.item;
            result.number = drop.GetRandomNumber(level);
            return result;
        }

Line 79 is the start of the if statement

That would seem to indicate that there is an entry in the Drop Library (the asset) with a null Inventory item…

I dont really get it what that means? My drop Library of the Enemy has all the information it needs. When I click on the Error message it always refers to my player.

I may have misunderstood you… I took it mean the if statement

if(!drop.item.IsStackable())

The only thing that could cause a null reference there is if the item is null (because we’re referencing the IsStackable() method on the item.

Let’s insert a filter here to check:
before if(!drop.Item.IsStackable())

if(drop.item==null)
{
    Debug.Log("A null item was dropped in GetRandomDrop");
}

Well he is shooting the same error,now line 79 is the start of your new if (drop.item==null) Debug log :/. I dont understand whats wrong here.

Oh, I wasn’t expecting that at all, the DropConfig (drop) is null…
Actually, that makes sense, as SelectRandomItem does end in return null… i.e. in the unlikely event that there is nothing to drop… Generally, it should never be reaching that return null, though…

This moves us to investigating SelectRandomItem…
Before the Foreach Loop in SelectRandomItem, let’s add another Debug:

Debug.Log($"totalChance = {totalChance} / randomRoll = {randomRoll}");

Then in the if statement after the chanceTotal+= line

Debug.Log($"chanceTotal = {chanceTotal} / randomRoll = {randomRoll}");

So after that he is giving me those messages.

Sorry, power went out due to the heat wave…
So we’re getting TWO drop attempts, the first attempt delivers the walking stick, which is then removed from the available drops, leaving no available drops… I don’t think Sam considered the possibility of running out of drops… Interestingly enough, your config looks like there should be only one drop happening, so I’m not sure what’s happening there…

In any event, let’s fix the possibility of running out of drops with a Nullable type return…

Dropped? GetRandomDrop(int level, List<DropConfig> potentialDropInstance) //Note the ?
{
    var drop = SelectRandomItem(level, potentialDropInstance);
    if(drop==null) return null;
    //Continue method as normal

Now in the IEnumerable GetRandomDrops, inside the for loop change the contents

Dropped? randomDrop = GetRandomDrop(level, potentialDropInstance);
if(randomDrop!=null) yield return randomDrop;

This should deal with running out of Random Drops. I’m still not sure why we’re trying to drop 2 items, as Random.Range(1, 1+1) should yield exactly 1 every time.

LOL, I just figured out why we were trying 2 drops all of a sudden… It was my on the fly Debug solution!
This should have been

yield return result;

I am soory Brian but I am a little bit confused of what you did there. First you said to change the contents of the IEnumerable to this:

Dropped? randomDrop = GetRandomDrop(level, potentialDropInstance);
if(randomDrop!=null) yield return randomDrop;

but then on the next post it is still the old code and your changing yield return to result instead of randomDrop. Soory if I am a bit confused there. Is this the right Code that I have there right now?

 public IEnumerable<Dropped> GetRandomDrops(int level)
        {
            if (!ShouldRandomDrop(level))
            {
                yield break;
            }

            int d = GetRandomNumberOfDrops(level);
            Debug.Log($"Should be dropping {d} items");
            List<DropConfig> potentialDropInstance = potentialDrops.ToList();
            for (int i = 0; i < GetRandomNumberOfDrops(level); i++)
            {
                Dropped? randomDrop = GetRandomDrop(level, potentialDropInstance);
                if(randomDrop!=null) yield return randomDrop;   
            }
        }

Cause he is shooting me a red error on the randomDrop, saying that it is not possible to convert this, cause there is already a explicit conversion.

Oooh, forgot about the conversion back to a not-nullable…
(If anything, you’re getting a 1st hand look at the joys of cascading refactors)

public IEnumerable<Dropped> GetRandomDrops(int level)
        {
            if (!ShouldRandomDrop(level))
            {
                yield break;
            }

            int d = GetRandomNumberOfDrops(level);
            Debug.Log($"Should be dropping {d} items");
            List<DropConfig> potentialDropInstance = potentialDrops.ToList();
            for (int i = 0; i < GetRandomNumberOfDrops(level); i++)
            {
                Dropped? randomDrop = GetRandomDrop(level, potentialDropInstance);
                if(randomDrop!=null) 
                {
                       Dropped actualDrop = new Dropped();
                       actualDrop.item = randomDrop.item;
                       actualDrop.number = randomDrop.number;
                       yield return actualDrop;   
                }
            }
        }

Well seems like he dont like that either lol^^

huz

?? What error is it throwing?
I’ll have to put this into a copy of the project and see if I can figure out a good solution.

It says that DropLibrary.Dropped? has no definition for item and number .

Privacy & Terms