Sell Items that aren't in shop stock

I have changed the current selling system, so that you can also sell items which haven’t been in the shop before.

I swapped the stockConfig-Array into a list and added a new List SellingList:

[SerializeField] List<StockItemConfig> stockConfig = new List<StockItemConfig>();
List<InventoryItem> sellingList = new List<InventoryItem>();

I added a constructor to the StockConfig Class:

[System.Serializable]
        class StockItemConfig
        {
            public StockItemConfig(InventoryItem item, int initialStock)
            {
                this.item = item;
                this.initialStock = initialStock;
                buyingDiscountPercentage = 0;
            }
            public InventoryItem item;
            public int initialStock;
            [Range(0, 100)] public float buyingDiscountPercentage;
        }

I also changed GetAllItems:

public IEnumerable<ShopItem> GetAllItems()
        {
            sellingList.Clear();
            if (isBuyingMode)
            {
                foreach (StockItemConfig config in stockConfig)
                {
                    float price = GetPrice(config);

                    int quantityInTransaction = 0;
                    transaction.TryGetValue(config.item, out quantityInTransaction);
                    int availibility = GetAvailability(config.item);
                    yield return new ShopItem(config.item, availibility, price, quantityInTransaction);
                }
            }
            else
            {
                Inventory shopperInventory = currentShopper.GetComponent<Inventory>();
                for (int i = 0; i < shopperInventory.GetSize(); i++)
                {
                    InventoryItem item = shopperInventory.GetItemInSlot(i);
                    if (item != null)
                    {
                        int quantityInTransaction = 0;
                        transaction.TryGetValue(item, out quantityInTransaction);
                        float price = 0;
                        if (stock.ContainsKey(item))
                        {
                            foreach (StockItemConfig config in stockConfig)
                            {
                                if (config.item == item)
                                {
                                    price = GetPrice(config);
                                }
                            }
                        }
                        else
                        {
                            price = item.GetPrice() * 0.3f;
                        }
                        if (!sellingList.Contains(item))
                        {
                            sellingList.Add(item);
                            yield return new ShopItem(item, GetAvailability(item), price, quantityInTransaction);
                        }
                    }
                }
            }

It now checks wether we buy or sell. When we sell something, GetAllItems configures a new ShopItem. If the item already exists in the stock it uses the regular discount for this item. Otherwise the selling price amounts 30% of the original price. You need to update the sellingList otherwise the item could occur multiple times inGame in the selling table.

I changed SellItem so that when the stock already contains the item it will be raised in the StockDictionary as usual. If that’s not applying then the Dictionary and the StockConfig list get a new entry (therefore the array was changed into a list and a new constructor was added).

private void SellItem(Purse shopperPurse, Inventory shopperInventory, InventoryItem item, float price)
        {
            int slot = FindFirstItemSlot(shopperInventory, item);
            if (slot == -1) return;
            AddToTransaction(item, -1);
            shopperInventory.RemoveFromSlot(slot, 1);
            if (stock.ContainsKey(item))
            {
                stock[item]++;
            }
            else
            {
                stockConfig.Add(new StockItemConfig(item, 1));
                stock[item] = 1;
            }
            shopperPurse.UpdateBalance(price);
        }

When you want to rebuy that item you can buy it for the original price. However if you want you can set a discount percentage inside the constructor of StockItemConfig.

Hope it helps. Feel free to use and happy coding :slight_smile:

5 Likes

Excellent work! This looks like a good solution to the selling problem.

I haven’t implemented this yet. but this is also what I need, to be able to sell items my player looted in the world which is not present in the merchant shop.

I will finish this shop course first. before trying your method cause I’m new to coding. I’m afraid I might break something if I implement this now.

I just want to know what do you mean by this:

Thank you.

First of all, thank you for this sharing. This issue was something I wanted to solve, too. I have try to implemented this but I couldn’t move beyond the mistakes. Unfortunately, I’m still very new to writing code, but I’m doing my best :slight_smile:

GetPrice(config) and GetAvailability(config.item) gives red lines (errors) on my code. Maybe rest of my code is different? I would be very happy if someone could point me in the right direction.

Here is my code;

using RPG.Control;
using RPG.Inventories;
using RPG.Movement;
using RPG.Saving;
using RPG.Stats;
using RPG.StatsInventories;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Localization;
using Newtonsoft.Json.Linq;

namespace RPG.Shops
{
    public class Shop : MonoBehaviour, IRaycastable, ISaveable, IJsonSaveable
    {
        [SerializeField] LocalizedString shopName;

        [SerializeField] float playerDistance;
        [SerializeField] float shopDistance = 1.5f;        

        [Range(0, 100f)]
        [SerializeField] float sellingPercentage = 50f;
        [Range(0, 100f)]
        [SerializeField] float maximumBarterDiscount = 80f;

        [SerializeField] StockItemConfig[] stockConfig;

        Pickup pickup;
        GameObject player;
        Mover mover;
        PlayerController playerController;
        Controls myControls;

        [System.Serializable]
        class StockItemConfig
        {
            public InventoryItem item;
            public int initialStock;
            [Range(0, 100f)] //for higher price than normal, change 0 to negative values
            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;

        private void Awake()
        {
            myControls = new Controls();
            pickup = GetComponent<Pickup>();
            player = GameObject.FindGameObjectWithTag("Player");
            mover = player.GetComponent<Mover>();
            playerController = player.GetComponent<PlayerController>();

        }
        private void OnEnable()
        {
            myControls.Enable();
        }

        private void OnDisable()
        {
            myControls.Disable();
        }

        private void Update()
        {
            playerDistance = Vector3.Distance(player.transform.position, transform.position);

            if (playerDistance <= shopDistance && playerController.shopFocus == this)
            {
                player.GetComponent<Shopper>().SetActiveShop(this);
                playerController.shopFocus = null;
                mover.Cancel();                
            }
        }

        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> availabilites = GetAvailabilities();

            foreach (InventoryItem item in availabilites.Keys)
            {
                if (availabilites[item] <= 0) continue;

                float price = prices[item];
                int quantityInTransaction = 0;
                transaction.TryGetValue(item, out quantityInTransaction);
                int availability = availabilites[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;
                        
            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);
                    }

                }
            }

            if (onChange != null)
            {
                onChange();
            }
        }        

        public float TransactionTotal() 
        {
            float total = 0f;

            foreach (ShopItem item in GetAllItems())
            {
                total += item.GetPrice() * item.GetQuantityInTransaction();
            }
            return total;
        }

        public string GetShopName()
        {
            return shopName.GetLocalizedString();
        }

        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 (myControls.PlayerNormal.MouseLeftClick.WasPressedThisFrame())
            {
                playerController.SetShopFocus(this);

                if (playerDistance > shopDistance)
                {
                    mover.StartMoveAction(transform.position, 1f);
                }                
            }
            return true;
        }    
        
        public void DialogueShopOpen()
        {
            playerController.SetShopFocus(this);
        }

        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() * GetBarterDiscount();
                    }

                    prices[config.item] *= (1 - config.buyingDiscountPercentage / 100);
                }
                else
                {
                    prices[config.item] = config.item.GetPrice() * (sellingPercentage / 100);
                }
            }

            return prices;
        }

        private float GetBarterDiscount()
        {
            BaseStats baseStats = currentShopper.GetComponent<BaseStats>();
            float discount = baseStats.GetStat(Stat.BuyingDiscountPercentage);
            return (1 - Mathf.Min(discount, maximumBarterDiscount) / 100);
        }

        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;
            }
        }

        private void OnDrawGizmosSelected()
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawWireSphere(transform.position, shopDistance);
        }

        public JToken CaptureAsJToken()
        {
            JObject state = new JObject();
            IDictionary<string, JToken> stateDict = state;
            foreach (KeyValuePair<InventoryItem, int> pair in stockSold)
            {
                stateDict[pair.Key.GetItemID()] = JToken.FromObject(pair.Value);
            }
            return state;
        }

        public void RestoreFromJToken(JToken state)
        {
            if (state is JObject stateObject)
            {
                IDictionary<string, JToken> stateDict = stateObject;
                stockSold.Clear();
                foreach (KeyValuePair<string, JToken> pair in stateDict)
                {
                    InventoryItem item = InventoryItem.GetFromID(pair.Key);
                    if (item)
                    {
                        stockSold[item] = pair.Value.ToObject<int>();
                    }
                }
            }
        }

    }
}

Privacy & Terms