Merge branch origin/codex/add-economy-features-with-wallet-and-trading into main

This commit is contained in:
Alexa Amundson
2025-11-28 23:15:39 -06:00
3 changed files with 272 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
using UnityEngine;
using BlackRoad.Worldbuilder.Interaction;
using BlackRoad.Worldbuilder.Items;
namespace BlackRoad.Worldbuilder.Economy
{
/// <summary>
/// Simple merchant that can buy and sell specific items from/to the player.
/// Currently uses a fixed price list and does a single transaction per interaction.
/// (You can expand this into a full UI later.)
/// </summary>
public class Merchant : Interactable
{
[System.Serializable]
public class TradeItem
{
public ItemDefinition item;
[Tooltip("Price in coins per unit when player BUYS.")]
public int buyPrice = 5;
[Tooltip("Price in coins per unit when player SELLS.")]
public int sellPrice = 3;
}
[Header("Inventory / Wallet")]
[SerializeField] private Inventory merchantInventory;
[SerializeField] private PlayerWallet merchantWallet;
[SerializeField] private TradeItem[] tradeItems;
[Header("Mode")]
[Tooltip("If true, this interaction attempts to SELL one unit of selected item to player.\nIf false, attempts to BUY one unit from player.")]
[SerializeField] private bool defaultSellToPlayer = true;
private void Awake()
{
#if UNITY_EDITOR
// Set display info for Interactable UI
var so = new UnityEditor.SerializedObject(this);
so.FindProperty("displayName").stringValue = "Merchant";
so.FindProperty("verb").stringValue = "Trade";
so.ApplyModifiedPropertiesWithoutUndo();
#endif
}
public override void Interact(GameObject interactor)
{
var playerInventory = interactor.GetComponent<Inventory>();
var playerWallet = interactor.GetComponent<PlayerWallet>();
if (playerInventory == null || playerWallet == null)
{
Debug.LogWarning("[Merchant] Player missing Inventory or PlayerWallet.");
return;
}
// For now we just trade the first configured item.
if (tradeItems == null || tradeItems.Length == 0)
{
Debug.LogWarning("[Merchant] No trade items configured.");
return;
}
var tradeItem = tradeItems[0];
if (tradeItem.item == null)
{
Debug.LogWarning("[Merchant] Trade item has no ItemDefinition assigned.");
return;
}
if (defaultSellToPlayer)
{
SellToPlayer(tradeItem, playerInventory, playerWallet);
}
else
{
BuyFromPlayer(tradeItem, playerInventory, playerWallet);
}
}
private void SellToPlayer(TradeItem trade, Inventory playerInventory, PlayerWallet playerWallet)
{
int price = trade.buyPrice;
if (price <= 0)
{
Debug.LogWarning("[Merchant] Invalid buyPrice.");
return;
}
if (!playerWallet.CanAfford(price))
{
Debug.Log("[Merchant] Player cannot afford this item.");
return;
}
// Optionally check merchant inventory; for now assume infinite stock
int added = playerInventory.AddItem(trade.item, 1);
if (added > 0)
{
playerWallet.TrySpend(price);
if (merchantWallet != null)
merchantWallet.AddCoins(price);
Debug.Log($"[Merchant] Player bought 1x {trade.item.DisplayName} for {price} coins.");
}
}
private void BuyFromPlayer(TradeItem trade, Inventory playerInventory, PlayerWallet playerWallet)
{
int price = trade.sellPrice;
if (price <= 0)
{
Debug.LogWarning("[Merchant] Invalid sellPrice.");
return;
}
int count = playerInventory.GetCount(trade.item);
if (count <= 0)
{
Debug.Log("[Merchant] Player has none of that item to sell.");
return;
}
int removed = playerInventory.RemoveItem(trade.item, 1);
if (removed > 0)
{
// Pay player
playerWallet.AddCoins(price);
if (merchantWallet != null && merchantWallet.CanAfford(price))
merchantWallet.TrySpend(price);
Debug.Log($"[Merchant] Player sold 1x {trade.item.DisplayName} for {price} coins.");
}
}
}
}

View File

@@ -0,0 +1,37 @@
using UnityEngine;
namespace BlackRoad.Worldbuilder.Economy
{
/// <summary>
/// Simple currency holder for the player.
/// </summary>
public class PlayerWallet : MonoBehaviour
{
[SerializeField] private int startingCoins = 0;
public int Coins { get; private set; }
private void Awake()
{
Coins = startingCoins;
}
public bool CanAfford(int amount)
{
return amount >= 0 && Coins >= amount;
}
public bool TrySpend(int amount)
{
if (!CanAfford(amount)) return false;
Coins -= amount;
return true;
}
public void AddCoins(int amount)
{
if (amount > 0)
Coins += amount;
}
}
}

View File

@@ -0,0 +1,101 @@
using System.Collections.Generic;
using UnityEngine;
using BlackRoad.Worldbuilder.Items;
namespace BlackRoad.Worldbuilder.Economy
{
/// <summary>
/// Periodically produces items into an internal stockpile.
/// Merchants or players can later claim from this stockpile.
/// Example: farm producing berries, mine producing ore.
/// </summary>
public class ResourceProducer : MonoBehaviour
{
[System.Serializable]
public class Output
{
public ItemDefinition item;
[Tooltip("How many units produced per cycle.")]
public int amountPerCycle = 1;
}
[Header("Production")]
[SerializeField] private Output[] outputs;
[SerializeField] private float cycleSeconds = 30f;
[SerializeField] private int maxStoredPerItem = 50;
private readonly Dictionary<ItemDefinition, int> _stock = new Dictionary<ItemDefinition, int>();
private float _timer;
public int GetStock(ItemDefinition item)
{
return item != null && _stock.TryGetValue(item, out var count)
? count
: 0;
}
private void Update()
{
if (outputs == null || outputs.Length == 0) return;
_timer += Time.deltaTime;
if (_timer >= cycleSeconds)
{
_timer -= cycleSeconds;
RunCycle();
}
}
private void RunCycle()
{
foreach (var o in outputs)
{
if (o.item == null || o.amountPerCycle <= 0) continue;
if (!_stock.TryGetValue(o.item, out var count))
count = 0;
int newCount = Mathf.Min(count + o.amountPerCycle, maxStoredPerItem);
_stock[o.item] = newCount;
}
}
/// <summary>
/// Consume up to amount from stock, returns actual removed.
/// </summary>
public int TakeFromStock(ItemDefinition item, int amount)
{
if (item == null || amount <= 0) return 0;
if (!_stock.TryGetValue(item, out var count) || count <= 0)
return 0;
int taken = Mathf.Min(count, amount);
int remaining = count - taken;
if (remaining <= 0)
_stock.Remove(item);
else
_stock[item] = remaining;
return taken;
}
/// <summary>
/// Optional: give all current stock to a target inventory.
/// </summary>
public void TransferAllToInventory(Inventory inventory)
{
if (inventory == null) return;
var keys = new List<ItemDefinition>(_stock.Keys);
foreach (var item in keys)
{
int count = _stock[item];
if (count <= 0) continue;
int added = inventory.AddItem(item, count);
TakeFromStock(item, added);
}
}
}
}