Check Inventory for 2 specific items

I have a smelting interface that has a few ingots that require 2 types of ores (Alloy) (Bronze = Tin & Copper, Steel = Carbon & Iron)

What I want to do is script the check in a way where I dont have to have a section for each alloy.
The items are created as a scriptableObject.

The only thing I can think of is having 2 variables in the Ingot for the required ores.

    public void ChosenMaterial(int buttonNo)
    {
        //Check for required item/s

        //If True
        //{
            Debug.Log("Added " + PlayerInventory.Inventory[i].ItemName + " to inventory");
            //Remove used ores | PlayerInventory.Inventory.RemoveAt(???);
            //Remove used ores | PlayerInventory.Inventory.RemoveAt(???);
            PlayerInventory.Inventory.Add(ingots[buttonNo]);
        //}

    }

ā€˜buttonNo’ is the button number in the interface (Players choice of ingot to smelt)

BUMP

If im not being clear, dont hesitate to ask :slight_smile:

First of all, please, don’t bump it in a day.

Second, what exactly is your question?

Sounds logical to have 2 variables for the required ore.

You just want to find those 2 in the inventory?

I want to make a check to see if the player has the required ores (single ores are easy) needed to create an alloy without a dedicated section for each alloy.

My question is how can I implement this? (In terms of structure)

Thats the best solution I can think of, but i always think there might be a better solution that the one I thought off.
Correct. I would like to check if the inventory has the required ores for an alloy.

Its not to much of a problem with how to code it but how to do it structure wise.

Im wondering if there maybe a better solution before implementing the 2 variables…

I think I would put the 2 requirements in the ore. Or just have a list for any ore, where single ores have only 1.

Sorry, didn’t realize you’d respond to mine as I was writing … I thought maybe it was a blanket response before.

I’m not sure how you’d know what ores are required, if they weren’t listed. :slight_smile:

If I understand you correctly (which I’m not sure), I would do something like this:

  • create recipes in HashSet -s, like Tin, Copper
  • when the user clicking on metals and they selected two already would iterate over each recipe and check if the HashSet is equal to the HashSet you create out of the button clicks
  • if the recipe is found → create the alloy
  • if the recipe isn’t found, message that it is not a valid compositon

basically what I’m saying is this system is just a filtered inventory of the player with the converting logic on it, so would handle it like that (on this way you don’t have to treat it separately from the inventory)

drawback: you can’t create recipe which needs more than one metal of one type (like 2 copper and 1 whatever)

There are many, many ways of doing this. :slight_smile: I gave one possible example here .

Ok so HashSets & Dictionaries are what I need. I shall head back to school :stuck_out_tongue:

Right. I’ve done my research on Hashtables & Dictionaries.

Lurkingninjadev
I think I understand your response.
You mentioned a Hashtable cannot use "
drawback: you can’t create recipe which needs more than one metal of one type (like 2 copper and 1 whatever)" this is because it cant use <int, string, string>? It has to have a single value datatype. Correct ?

Doug
I don’t understand your code. I’ll explain when I get home

Sure, feel free. But just before you do that, would you try this :

  • Try the code out below.
  • Put a breakpoint on line 10.
  • Once you hit the breakpoint, just keep pressing F11 through til you get to line 11.
  • Did you see how the result was achieved?
  • What happens if you call result = craft.TheseMake("Iron", "Wood"); instead?
public class Test : MonoBehaviour
{
    void Awake()
    {
        craft = new CCrafting();
    }

    void Start()
    {
        var result = craft.TheseMake("Wood", "Iron");
    }

    CCrafting craft;
}

There are so many ways of creating the same result.

My setup would be something like the code below, where a Player monobehaviour has an Inventory and whenever you ā€œuseā€ a recipe you can check the Recipe.CanCraft and give a reference to your inventory.
you can think of some more logic to actually craft the item (in the ItemRecipe).

basically simple logic where if the Inventory.GetItem returns null it means your item is not in the inventory. If it is it will not be null and you can check it’s quantity.
With the ItemRecipe you could write a method Craft that takes the inventory as reference. Then call the Inventory.RemoveItem(Item, Quantity) or something like that. Then Inventory.AddItem(Item, Quantity).

I really love scriptable objects, still figuring out how to use them efficiently :slight_smile:

Edit:

The reason that I put the logic of CanCraft / AddItem / RemoveItem in their ScriptableObject is because then you can easily debug where the call is coming from.
Your logic on how the Data changes will be contained within the ScriptableObject.
So if an NPC would be taking items from the inventory it would have to call the Inventory.RemoveItem of the Player Inventory. You can get a stack trace who is calling the RemoveItem method, you will know who is calling it. If the NPC has that logic to change the Inventory directly you may be searching all over the place where the Inventory is being managed by something. So containing such functionality within the ScriptableObject would be handy for debugging.
So instead of having a Remove method all over the place in several scripts, it is contained within the ScriptableObject. This makes automated testing easier as well, if you have automated tests you don’t need other scripts like an NPC, only the ScriptableObject.

using UnityEngine;

[CreateAssetMenu(menuName = "Scriptable Objects/Item")]
public class Item : ScriptableObject
{
    public int ID;
}

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

[CreateAssetMenu(menuName = "Scriptable Objects/Item Recipe")]
public class ItemRecipe : ScriptableObject
{
    public int ID;
    public List<ItemRequirement> Recipe = new List<ItemRequirement>();
    public ItemOutput OutputItem;

    public bool CanCraft(ref Inventory inventory)
    {
        foreach (var itemRequirement in Recipe)
        {
            var inventoryItem = inventory.GetItem(itemRequirement.Item);
            if (inventoryItem == null || inventoryItem.Quantity < itemRequirement.RequiredQuantity)
                return false;
        }

        return true;
    }
}

[Serializable]
public class ItemRequirement
{
    public Item Item;
    public int RequiredQuantity;
}

[Serializable]
public class ItemOutput
{
    public Item Item;
    public int Quantity;
}

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

[CreateAssetMenu(menuName = "Scriptable Objects/Inventory")]
public class Inventory : ScriptableObject
{
    public List<InventoryItem> Items;

    public InventoryItem GetItem(Item itemReference)
    {
        for (int i = 0; i < Items.Count; i++)
        {
            var inventoryItem = Items[i];
            if (inventoryItem.Item.Equals(itemReference))
                return inventoryItem;
        }

        return null;
    }
}

[Serializable]
public class InventoryItem
{
    public Item Item;
    public int Quantity;
}
1 Like

This is only true to the Sets (HashSet). Since the simplicity of the solution I proposed, it means you can’t have duplicated values in the Set. But you can choose other storage like Dictionary, where you can have duplicates, but for that, you will have to have your own check for equation (duplicates, order of the items matter).

I’ve broken down your code (answer is sword) and I now understand it.

The bits I can’t understand (I’ve Google it, still don’t understand) are the colons, question marks and string.compare.

var b = (String.Compare(s1, s2) < 0);
        return (  (b ? s1 : s2)
                + "/"
                + (b ? s2 : s1));

Can you run me through what’s happening with these please :slight_smile:

Maskedmouse

I’ll have a go at it once I get home. I commute alot (3hrs a day)

Compare returns an int (imagine sorting order).
The ?: is the ternary/conditional operator: ?: operator - the ternary conditional operator - C# reference | Microsoft Learn

It goes like:

 this_expression ? is_true_do_this : otherwise_do_this_instead

Something like that, but you could read the link. :slight_smile:

In the example for you, it’s saying if s1 < s2, make a string : ā€œs1/s2ā€ otherwise make ā€œs2/s1ā€

1 Like

I’ve been busy with creating an inventory system some time ago, opened up the project last night and even got the example I posted here implemented into it (it was better than what I made before). I extended it a little bit but I am at work now, can’t spend too much time hanging on the forum haha.

I extended its functionality by making the Item class abstract, the Item class itself contains the fields that all Items should have (such as an ID, Sprite, Description etc). I made a sub-class StackableItem that extends Item and gave it a MaxStacks integer field. Thats all what StackableItem adds on top of Item, a maximum amount of stacks.

So now when I try to call AddItem of the Inventory class I check whether the passed Item is a StackableItem, if so I check if I have it in the inventory already and if that is the case then try adding quantity +1. If it has reached max stacks I fill up a new slot. if there are no inventory slots left I return.

Eventually I want to hook up the inventory to the UI. On Inventory window open I want it to use the Player Inventory as reference, change the visuals using on the data that resides inside the Inventory ScriptableObject.
For easy debugging I can create a new Inventory asset file with different items and hook that up to the UI. It doesn’t have to be the player inventory for testing purposes. That’s what I love about Scriptable Objects, the UI will not care where the information came from, you give it a reference to an inventory and it will do the magic.

Thanks Methos
One last thing.
I noticed the craftMatrix needs to be part of a function to work and for the defintions to be declared. Why is this?
Does the function reflect the Start() function?

public CCrafting( )
    {
        craftMatrix = new Dictionary<string, string>()
        {   { MakeOrderedKey( "This", "That" ), "Other" }
        ,   { MakeOrderedKey( "Wood", "Iron" ), "Sword" }
        ,   { MakeOrderedKey( "Iron", "Leather" ), "Shield" }
        };
    }