Check if all elements in a loop meet a condition

I want crafting recipes to show only if a list (the inventory) contains all the items of a recipe. if an Apple requires 1 Log and 1 Apple and you have the required items you will get two Apples in the crafting menu

var recipeIndex = 0;
		var itemIndex = 0;
		foreach (Recipes recipe in available)
		{
			foreach (string item in recipe.items)
			{
				Debug.Log(recipe.name + " items: " + item);
				
				if (!inventoryItems.Contains(item))
				{	
					return;
				}
			
				if (inventoryItems.Contains(item))
				{
						craftable.Add(recipe);
						//craftable = new HashSet<Recipes>(available).ToList();				
				}
				itemIndex++;
			}
			recipeIndex++;
		}
		
		for (int a = 0; a < craftable.Count; a++)
		{
			instantiateSlotsScript.craftingSlots[a].GetComponent<SlotCrafting>().item[0] = craftable[a].name;
		}
		foreach (Recipes recipe in available)
			foreach (string item in recipe.items)
				if (inventoryItems.Contains(item))
						craftable.Add(recipe);
                        
        for (int a = 0; a < craftable.Count; a++)
			instantiateSlotsScript.craftingSlots[a].GetComponent<SlotCrafting>().item[0] = craftable[a].name;

I used return so I will not end up with multiple of the same recipe in the crafting menu. This works fine but if I have 2 Logs now the Apple recipe is gone. This is when I have a second recipe like an Axe that requires 2 Logs as well

Well, we don’t know all details about your inventory and if items can actually be “stacked” or if recipes can require multiple of the same item. However if you really just have a list ot items required and the pure existance is enough, you should do

    craftable.Clear();
    foreach (Recipes recipe in available)
    {
        bool allItemsAvailable = true;
        foreach (string item in recipe.items)
        {
            if (!inventoryItems.Contains(item))
            {
                allItemsAvailable = false;
                break;
            }
        }
        if (allItemsAvailable)
            craftable.Add(recipe);
    }

This will fill the craftable List / hashtable only with recipes where all necessary items are available.

Instead of a string, you’ll want to use an object to represent the required item so that you can store not just a name/identifier, but also an amount to go along with it.

1 Like

Yes. Games like Minecraft have the concept of an “ItemStack”. So an inventory slot actually stores item stacks which is an item reference as well as an amount. Still no string required. The actual item types can be ScriptableObject instances for example.

I still get two of the same recipe in the crafting menu because if I have 1 Log the Apple will show up and then if I have 1 Log and 1 Apple, two Apple recipes will show

This is my code. I have been trying to do this for a week

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

public class Crafting : MonoBehaviour
{
	public List<string> item;
	
	public List<Recipes> recipes;
	
	private MainHand mainHandScript;
	private InstantiateSlots instantiateSlotsScript;
	
	public List<Recipes> available;
	public List<Recipes> craftable;
	
	public List<int> inventoryAmounts;
	public List<string> inventoryItems;
	
    void Update()
    {
		if (item.Count == 0)
		{
			item.Add("");
		}
		
		if (mainHandScript == null)
		{
			mainHandScript = GameObject.Find("Player").GetComponent<MainHand>();
		}
		
		if (instantiateSlotsScript == null)
		{
			instantiateSlotsScript = GameObject.Find("Scripts").GetComponent<InstantiateSlots>();
		}
		
		if (mainHandScript.selectedSlot.Count != 0)
		{
			if (item[0] != mainHandScript.selectedSlot[0].GetComponent<SlotMainHand>().item[0])
			{
				foreach (GameObject slot in instantiateSlotsScript.craftingSlots)
				{
					if (slot.GetComponent<SlotCrafting>().item.Count != 0)
					{
						if (slot.GetComponent<SlotCrafting>().item[0] != "")
						{
							slot.GetComponent<SlotCrafting>().item[0] = "";
							Destroy(slot.GetComponent<SlotCrafting>().slotObject);
						}
					}
				}
				
				item[0] = mainHandScript.selectedSlot[0].GetComponent<SlotMainHand>().item[0];
				DisplayRecipes();
			}
		}
    }
	
	void DisplayRecipes()
	{	
		available = new List<Recipes>();
		craftable = new List<Recipes>();
		
		inventoryItems = new List<string>();
		inventoryAmounts = new List<int>();
		
		for (int i = 0; i < recipes.Count; i++)
		{
			if (mainHandScript.selectedSlot[0].GetComponent<SlotMainHand>().item[0] == recipes[i].items[0]
			//mainHandScript.selectedSlot[0].GetComponent<SlotMainHand>().amount >= recipes[i].amounts[0]
			)
			{
				available.Add(recipes[i]);
				inventoryAmounts = instantiateSlotsScript.inventoryAmounts;
				inventoryItems = instantiateSlotsScript.inventoryItems;
			}
		}
		
		var recipeIndex = 0;
		var itemIndex = 0;
		foreach (Recipes recipe in available)
		{
			bool allItemsAvailable = true;
			
			foreach (string item in recipe.items)
			{
				Debug.Log(recipe.name + " items: " + item);
				
				if (!inventoryItems.Contains(item))
				{	
					allItemsAvailable = false;
					break;
				}
				
				if (inventoryAmounts[inventoryItems.FindIndex(inventoryItem => inventoryItem.Contains(item))] < recipe.amounts[recipe.items.FindIndex(item => item.Contains(item))])
				{
					allItemsAvailable = false;
					break;
				}
			
				if (allItemsAvailable == true)
				{
						craftable.Add(recipe);
						//craftable = new HashSet<Recipes>(available).ToList();				
				}
				itemIndex++;
			}
			recipeIndex++;
		}
		
		for (int a = 0; a < craftable.Count; a++)
		{
			instantiateSlotsScript.craftingSlots[a].GetComponent<SlotCrafting>().item[0] = craftable[a].name;
		}
	}
}

I changed the loops

for (int i = 0; i < available.Count; i++)
{
	bool allItemsAvailable = true;
			
	for (int b = 0; b < available[i].items.Count; b++)
	{

then to only add craftables when b equals items.Count

if (allItemsAvailable == true)
{
	if (b == available[i].items.Count - 1)
	{
		craftable.Add(available[i]);
		craftable = new HashSet<Recipes>(craftable).ToList();	
	}
}