ArgumentNullException: Value cannot be null

Hi All,

The game crashes when trying to load.

I am returning the following error. I think its to do with the dictionary not allowing a null value?

When I start a game (with no save), move to the shop and buy something, then save it all works. If I exit out of play mode and try to go back in, I get this error.

Can I please have some help?

Error:

ArgumentNullException: Value cannot be null.
Parameter name: key
System.Collections.Generic.Dictionary2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) (at <6073cf49ed704e958b8a66d540dea948>:0) System.Collections.Generic.Dictionary2[TKey,TValue].set_Item (TKey key, TValue value) (at <6073cf49ed704e958b8a66d540dea948>:0)
RPG.Shops.Shop.RestoreState (System.Object state) (at Assets/Scripts/Shops/Shop.cs:390)
GameDevTV.Saving.SaveableEntity.RestoreState (System.Object state) (at Assets/Scripts/Saving/SaveableEntity.cs:64)
GameDevTV.Saving.SavingSystem.RestoreState (System.Collections.Generic.Dictionary`2[TKey,TValue] state) (at Assets/Scripts/Saving/SavingSystem.cs:106)
GameDevTV.Saving.SavingSystem+d__0.MoveNext () (at Assets/Scripts/Saving/SavingSystem.cs:34)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <40205bb2cb25478a9cb0f5e54cf11441>:0)

This is my code:
using System;

using System.Collections;

using System.Collections.Generic;

using GameDevTV.Inventories;

using GameDevTV.Saving;

using RPG.Control;

using RPG.Inventories;

using RPG.Stats;

using UnityEngine;

namespace RPG.Shops

{

public class Shop : MonoBehaviour, IRaycastable, ISaveable

{

    [SerializeField] string shopName;

    [Range(0, 100)]

    [SerializeField] float sellingPercentage = 80f;

    // Stock Config

    // Item:

    // InventoryItem

    // Initial Stock

    // buyingDiscount

    [SerializeField]

    StockItemConfig[] stockConfig;

    [System.Serializable]

    class StockItemConfig

    {

        public InventoryItem item;

        public int initialStock;

        [Range(0, 100)]

        public float buyingDiscountPercentage;

        public int levelToUnlock = 0;

    }

    Dictionary<InventoryItem, int> transaction = new Dictionary<InventoryItem, int>();

    Dictionary<InventoryItem, int> stockSold = new Dictionary<InventoryItem, int>();

    Shopper currentShopper = null;

    bool isBuyingMode = true;

    ItemCategory filter = ItemCategory.None;

    public event Action onChange;

    public void SetShopper(Shopper shopper)

    {

        currentShopper = shopper;

    }

    public IEnumerable<ShopItem> GetFilteredItems()

    {

        foreach (ShopItem shopItem in GetAllItems())

        {

            InventoryItem item = shopItem.GetInventoryItem();

            if (filter == ItemCategory.None || item.GetCategory() == filter)

            {

                yield return shopItem;

            }

        }

    }

    public IEnumerable<ShopItem> GetAllItems()

    {

        Dictionary<InventoryItem, float> prices = GetPrices();

        Dictionary<InventoryItem, int> availabilities = GetAvailabilities();

        foreach (InventoryItem item in availabilities.Keys)

        {

            if (availabilities[item] <= 0) continue;

            float price = prices[item];

            int quantityInTransaction = 0;

            transaction.TryGetValue(item, out quantityInTransaction);

            int availability = availabilities[item];

            yield return new ShopItem(item, availability, price, quantityInTransaction);

        }

    }

    public void SelectFilter(ItemCategory category)

    {

        filter = category;

        print(category);

        if (onChange != null)

        {

            onChange();

        }

    }

    public ItemCategory GetFilter()

    {

        return filter;

    }

    public void SelectMode(bool isBuying)

    {

        isBuyingMode = isBuying;

        if (onChange != null)

        {

            onChange();

        }

    }

    public bool IsBuyingMode()

    {

        return isBuyingMode;

    }

    public bool CanTransact()

    {

        if (IsTransactionEmpty()) return false;

        if (!HasSufficientFunds()) return false;

        if (!HasInventorySpace()) return false;

        return true;

    }

    public bool HasSufficientFunds()

    {

        if (!isBuyingMode) return true;

        Purse purse = currentShopper.GetComponent<Purse>();

        if (purse == null) return false;

        return purse.GetBalance() >= TransactionTotal();

    }

    public bool IsTransactionEmpty()

    {

        return transaction.Count == 0;

    }

    public bool HasInventorySpace()

    {

        if (!isBuyingMode) return true;

        Inventory shopperInventory = currentShopper.GetComponent<Inventory>();

        if (shopperInventory == null) return false;

        List<InventoryItem> flatItems = new List<InventoryItem>();

        foreach (ShopItem shopItem in GetAllItems())

        {

            InventoryItem item = shopItem.GetInventoryItem();

            int quantity = shopItem.GetQuantityInTransaction();

            for (int i = 0; i < quantity; i++)

            {

                flatItems.Add(item);

            }

        }

        return shopperInventory.HasSpaceFor(flatItems);

    }

    public void ConfirmTransaction()

    {

        Inventory shopperInventory = currentShopper.GetComponent<Inventory>();

        Purse shopperPurse = currentShopper.GetComponent<Purse>();

        if (shopperInventory == null || shopperPurse == null) return;

        // Transfer to or from the inventory

        foreach (ShopItem shopItem in GetAllItems())

        {

            InventoryItem item = shopItem.GetInventoryItem();

            int quantity = shopItem.GetQuantityInTransaction();

            float price = shopItem.GetPrice();

            for (int i = 0; i < quantity; i++)

            {

                if (isBuyingMode)

                {

                    BuyItem(shopperInventory, shopperPurse, item, price);

                }

                else

                {

                    SellItem(shopperInventory, shopperPurse, item, price);

                }

            }

        }

        // Removal from transaction

        // Debting or Crediting of funds

        if (onChange != null)

        {

            onChange();

        }

    }

    public float TransactionTotal()

    {

        float total = 0;

        foreach (ShopItem item in GetAllItems())

        {

            total += item.GetPrice() * item.GetQuantityInTransaction();

        }

        return total;

    }

    public string GetShopName()

    {

        return shopName;

    }

    public void AddToTransaction(InventoryItem item, int quantity)

    {

        if (!transaction.ContainsKey(item))

        {

            transaction[item] = 0;

        }

        var availabilities = GetAvailabilities();

        int availability = availabilities[item];

        if (transaction[item] + quantity > availability)

        {

            transaction[item] = availability;

        }

        else

        {

            transaction[item] += quantity;

        }

        if (transaction[item] <= 0)

        {

            transaction.Remove(item);

        }

        if (onChange != null)

        {

            onChange();

        }

    }

    public CursorType GetCursorType()

    {

        return CursorType.Shop;

    }

    public bool HandleRaycast(PlayerController callingController)

    {

        if (Input.GetMouseButtonDown(0))

        {

            callingController.GetComponent<Shopper>().SetActiveShop(this);

        }

        return true;

    }

    private int CountItemsInInventory(InventoryItem item)

    {

        Inventory inventory = currentShopper.GetComponent<Inventory>();

        if (inventory == null) return 0;

        int total = 0;

        for (int i = 0; i < inventory.GetSize(); i++)

        {

            if (inventory.GetItemInSlot(i) == item)

            {

                total += inventory.GetNumberInSlot(i);

            }

        }

        return total;

    }

    private Dictionary<InventoryItem, int> GetAvailabilities()

    {

        Dictionary<InventoryItem, int> availabilities = new Dictionary<InventoryItem, int>();

        foreach (var config in GetAvailableConfigs())

        {

            if (isBuyingMode)

            {

                if (!availabilities.ContainsKey(config.item))

                {

                    int sold = 0;

                    stockSold.TryGetValue(config.item, out sold);

                    availabilities[config.item] = -sold;

                }

                availabilities[config.item] += config.initialStock;

            }

            else

            {

                availabilities[config.item] = CountItemsInInventory(config.item);

            }

        }

        return availabilities;

    }

    private Dictionary<InventoryItem, float> GetPrices()

    {

        Dictionary<InventoryItem, float> prices = new Dictionary<InventoryItem, float>();

        foreach (var config in GetAvailableConfigs())

        {

            if (isBuyingMode)

            {

                if (!prices.ContainsKey(config.item))

                {

                    prices[config.item] = config.item.GetPrice();

                }

                prices[config.item] *= (1 - config.buyingDiscountPercentage / 100);

            }

            else

            {

                prices[config.item] = config.item.GetPrice() * (sellingPercentage / 100);

            }

        }

        return prices;

    }

    private IEnumerable<StockItemConfig> GetAvailableConfigs()

    {

        int shopperLevel = GetShopperLevel();

        foreach (var config in stockConfig)

        {

            if (config.levelToUnlock > shopperLevel) continue;

            yield return config;

        }

    }

    private void SellItem(Inventory shopperInventory, Purse shopperPurse, InventoryItem item, float price)

    {

        int slot = FindFirstItemSlot(shopperInventory, item);

        if (slot == -1) return;

        AddToTransaction(item, -1);

        shopperInventory.RemoveFromSlot(slot, 1);

        if (!stockSold.ContainsKey(item))

        {

            stockSold[item] = 0;

        }

        stockSold[item]--;

        shopperPurse.UpdateBalance(price);

    }

    private void BuyItem(Inventory shopperInventory, Purse shopperPurse, InventoryItem item, float price)

    {

        if (shopperPurse.GetBalance() < price) return;

        bool success = shopperInventory.AddToFirstEmptySlot(item, 1);

        if (success)

        {

            AddToTransaction(item, -1);

            if (!stockSold.ContainsKey(item))

            {

                stockSold[item] = 0;

            }

            stockSold[item]++;

            shopperPurse.UpdateBalance(-price);

        }

    }

    private int FindFirstItemSlot(Inventory shopperInventory, InventoryItem item)

    {

        for (int i = 0; i < shopperInventory.GetSize(); i++)

        {

            if (shopperInventory.GetItemInSlot(i) == item)

            {

                return i;

            }

        }

        return -1;

    }

    private int GetShopperLevel()

    {

        BaseStats stats = currentShopper.GetComponent<BaseStats>();

        if (stats == null) return 0;

        return stats.GetLevel();

    }

    public object CaptureState()

    {

        Dictionary<string, int> saveObject = new Dictionary<string, int>();

        foreach (var pair in stockSold)

        {

            saveObject[pair.Key.GetItemID()] = pair.Value;

        }

        return saveObject;

    }

    public void RestoreState(object state)

    {

        Dictionary<string, int> saveObject = (Dictionary<string, int>)state;

        stockSold.Clear();

        foreach (var pair in saveObject)

        {

            stockSold[InventoryItem.GetFromID(pair.Key)] = pair.Value;

        }

    }

}

}

So I used the below code to effectively skip over the Item if it had a Null ID:

public void RestoreState(object state)

    {

        Dictionary<string, int> saveObject = (Dictionary<string, int>)state;

        stockSold.Clear();

        foreach (var pair in saveObject)

        {

            var inventoryItem = InventoryItem.GetFromID(pair.Key);

            if (inventoryItem != null)

            {

                stockSold[inventoryItem] = pair.Value;

            }

        }

    }

however this is causing the icon to not load when I reload the game and it cannot be dragged into the action bar? It can be dragged into the action bar before I save:

Before Save:
image

After Save and reload:
image

image

So it looks like your inventory item with that specific ID cannot be retrieved from the item database. Add a Debug.Log in RestoreState to print the pair.Key to the console and check if it matches what you expect.

The likelyhood is that your item is not in a folder named Resources.
All Inventory Items (an Ability is an Inventory Item) must be in or under a folder named Resources (spelling and capitalization matters).
For example, these folders work:

Game/Resources
Game/Resources/Abilities
Game/Abilities/Resources

But these directories won’t

Game
Game/Abilities

Amazing! this was only not working for my health potions, all other items were in resources which is why they worked. Thank you!

My only other issue is the projectile has seemed to stop working, on collision it returns the issue. I have lifted the projectile script from this lecture and still get the error.

I assume however we will be fixing this generally as part of the abilities section?

image

It looks like you’re acting on the prefab, and not the instantiated projectile.
Paste in your SpawnProjectileEffect.cs and we’ll take a look.

Thanks Brian, you were totally correct - all fixed now.

I would very much like to get to the point where I can troubleshoot these types of problems!

You will, with experience.

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

Privacy & Terms