2026-09-03 16:35:05 +01:00
using System.Collections.Generic ;
using UnityEngine ;
namespace MedievalShopkeeper
{
public class Shelf : Interactable
{
[Header("Shelf Settings")]
[Tooltip("The maximum amount of items this shelf can hold.")]
[SerializeField] private int maxCapacity = 10 ;
[Tooltip("Transforms where visual prefabs should be spawned. The array size visually dictates capacity layout (can be fewer than maxCapacity, just overlaps/stacks visually).")]
[SerializeField] private Transform [ ] displaySpots ;
[Header("Current Status (Read-Only)")]
[SerializeField] private ItemData assignedItem ;
[SerializeField] private int currentStock = 0 ;
private List < GameObject > visualPrefabs = new List < GameObject > ( ) ;
public ItemData AssignedItem = > assignedItem ;
public int CurrentStock = > currentStock ;
public override string InteractionPrompt
{
get
{
if ( assignedItem ! = null )
{
return $"Restock {assignedItem.itemName} ({currentStock}/{maxCapacity})" ;
}
return "Stock Shelf (Empty)" ;
}
}
public override void Interact ( PlayerController player )
{
base . Interact ( player ) ;
if ( UIManager . Instance ! = null )
{
UIManager . Instance . OpenShelfMenu ( this , player ) ;
}
else
{
// Fallback to auto-stocking if no UI manager exists
StockFromPlayer ( player ) ;
}
}
public void StockFromPlayer ( PlayerController player )
{
if ( ! player . IsCarryingItem )
{
Debug . Log ( "[Shelf] Player is not carrying any stock box." ) ;
return ;
}
GameObject carriedObj = player . CurrentCarriedItem ;
StockBox stockBox = carriedObj . GetComponent < StockBox > ( ) ;
if ( stockBox = = null )
{
Debug . LogWarning ( "[Shelf] Carried item is not a StockBox!" ) ;
return ;
}
// Dynamically assign item if empty
if ( currentStock = = 0 )
{
assignedItem = stockBox . itemData ;
}
// Check if items match
if ( assignedItem ! = stockBox . itemData )
{
Debug . Log ( $"[Shelf] Mismatch! Shelf holds {assignedItem.itemName}, but box contains {stockBox.itemData.itemName}." ) ;
return ;
}
// Calculate how much to transfer
int spaceAvailable = maxCapacity - currentStock ;
if ( spaceAvailable < = 0 )
{
Debug . Log ( "[Shelf] Shelf is already full!" ) ;
return ;
}
int transferAmount = Mathf . Min ( spaceAvailable , stockBox . quantity ) ;
// Transfer
currentStock + = transferAmount ;
stockBox . RemoveQuantity ( transferAmount ) ;
Debug . Log ( $"[Shelf] Restocked {transferAmount}x {assignedItem.itemName}. Shelf is now at {currentStock}/{maxCapacity}." ) ;
UpdateVisuals ( ) ;
// If box is empty, drop and destroy it
if ( stockBox . quantity < = 0 )
{
GameObject dropped = player . DropCarriedItem ( ) ;
if ( dropped ! = null ) Destroy ( dropped ) ;
}
}
public void EmptyShelf ( )
{
if ( currentStock < = 0 | | assignedItem = = null )
{
Debug . Log ( "[Shelf] Shelf is already empty." ) ;
return ;
}
// Return items to virtual Shop Inventory
if ( ShopInventory . Instance ! = null )
{
ShopInventory . Instance . AddStock ( assignedItem , currentStock ) ;
Debug . Log ( $"[Shelf] Emptied {currentStock}x {assignedItem.itemName} back to Shop Inventory." ) ;
}
else
{
Debug . LogWarning ( "[Shelf] No ShopInventory found to return items to! Items lost." ) ;
}
currentStock = 0 ;
assignedItem = null ;
UpdateVisuals ( ) ;
}
2026-09-04 16:33:41 +01:00
public bool isBroken { get ; private set ; }
public bool HasStock = > ! isBroken & & currentStock > 0 & & assignedItem ! = null ;
2026-09-03 16:35:05 +01:00
/// <summary>
/// Called by NPCs when they want to take an item to put in their basket.
/// </summary>
public bool TryTakeItem ( out ItemData purchasedItem )
{
purchasedItem = null ;
2026-09-04 16:33:41 +01:00
if ( HasStock )
2026-09-03 16:35:05 +01:00
{
purchasedItem = assignedItem ;
currentStock - - ;
2026-09-04 16:33:41 +01:00
if ( currentStock < = 0 )
2026-09-03 16:35:05 +01:00
{
2026-09-04 16:33:41 +01:00
assignedItem = null ;
2026-09-03 16:35:05 +01:00
}
UpdateVisuals ( ) ;
return true ;
}
return false ;
}
2026-09-04 16:33:41 +01:00
public void BreakShelf ( )
{
if ( isBroken ) return ;
isBroken = true ;
currentStock = 0 ;
assignedItem = null ;
Debug . Log ( $"[Shelf] The {gameObject.name} was smashed by a Raider!" ) ;
UpdateVisuals ( ) ;
}
public void RepairShelf ( )
{
if ( ! isBroken ) return ;
isBroken = false ;
Debug . Log ( $"[Shelf] The {gameObject.name} was repaired!" ) ;
}
2026-09-03 16:35:05 +01:00
private void UpdateVisuals ( )
{
// Clear old visuals
foreach ( var vis in visualPrefabs )
{
if ( vis ! = null ) Destroy ( vis ) ;
}
visualPrefabs . Clear ( ) ;
if ( assignedItem = = null | | assignedItem . displayPrefab = = null | | displaySpots = = null | | displaySpots . Length = = 0 ) return ;
2026-09-04 16:33:41 +01:00
for ( int i = 0 ; i < currentStock ; i + + )
2026-09-03 16:35:05 +01:00
{
2026-09-04 16:33:41 +01:00
int spotIndex = i % displaySpots . Length ;
int stackIndex = i / displaySpots . Length ; // 0 for front row, 1 for back row, etc.
Transform spot = displaySpots [ spotIndex ] ;
GameObject vis = Instantiate ( assignedItem . displayPrefab , spot . position , spot . rotation , spot ) ;
// Shift subsequent rows backwards/upwards.
// Z is usually backward, Y is upward. You can adjust this offset.
vis . transform . localPosition + = new Vector3 ( 0 , stackIndex * 0.15f , stackIndex * - 0.2f ) ;
2026-09-03 16:35:05 +01:00
vis . transform . localScale = assignedItem . displayScale ;
// Destroy any colliders on the visual so it doesn't block rays
foreach ( var col in vis . GetComponentsInChildren < Collider > ( ) )
{
Destroy ( col ) ;
}
visualPrefabs . Add ( vis ) ;
}
}
}
}