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?
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);
}
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;
}
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.
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:
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
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.
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.