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