using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using TMPro; using Ashwild.Inventory; namespace Ashwild.Storage { /// /// Which container a chest-window cell belongs to, so a drop can be routed to the right transfer. /// public enum ChestSlotContainer { Inventory, Chest } /// /// One draggable cell in the chest window, used for both sides — the player's inventory (left) and /// the chest (right). Unlike the inventory's SlotUI it carries which container and index it maps to, /// so a drop hands the pair to the owning , which turns it into a deposit, /// withdraw, move-within or inventory swap. It only renders and reports drags; every real mutation /// goes through the chest's server-authoritative RPCs. /// public class ChestSlotUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler, IPointerClickHandler { #region Serialized Fields [Header("References")] [SerializeField] private Image iconImage; [SerializeField] private TextMeshProUGUI quantityText; [Header("Uses Bar")] [Tooltip("Root of the uses/durability bar — shown only for items that track uses.")] [SerializeField] private GameObject usageBarRoot; [Tooltip("Filled image whose fillAmount maps to the remaining uses (Image Type = Filled).")] [SerializeField] private Image usageBarFill; [SerializeField] private Color normalTint = Color.white; [SerializeField] private Color depletedTint = Color.red; #endregion #region State private ChestUI owner; private ChestSlotContainer container; private int index; public ChestSlotContainer Container => container; public int Index => index; // Static drag state shared across every chest-window cell (both grids). private static ChestSlotUI draggedSlot; private static GameObject ghostObject; private static Image ghostIcon; private static TextMeshProUGUI ghostQuantity; #endregion #region Setup /// /// Wires the single shared drag ghost used by every cell in the window. /// public static void SetupGhost(GameObject ghost, Image icon, TextMeshProUGUI qty) { ghostObject = ghost; ghostIcon = icon; ghostQuantity = qty; if (ghostObject != null) ghostObject.SetActive(false); } /// /// Binds this cell to a container and index within the owning window. /// public void Initialize(ChestUI ownerUI, ChestSlotContainer slotContainer, int slotIndex) { owner = ownerUI; container = slotContainer; index = slotIndex; } #endregion #region Rendering /// /// Redraws the cell from whichever container it belongs to (the local inventory or the chest's /// replicated view). /// public void Refresh() { if (container == ChestSlotContainer.Inventory) { PlayerInventory inv = PlayerInventory.Instance; InventorySlot slot = inv != null ? inv.GetSlot(index) : null; if (slot == null || slot.IsEmpty) { RenderEmpty(); return; } Render(slot.ItemData, slot.Quantity, slot.ItemData.HasUses ? slot.CurrentUses : -1); } else { if (owner != null && owner.Chest != null && owner.Chest.TryGetSlot(index, out ItemData item, out int qty, out int uses)) Render(item, qty, uses); else RenderEmpty(); } } /// /// Clears the cell to its empty look. /// private void RenderEmpty() { iconImage.gameObject.SetActive(false); quantityText.gameObject.SetActive(false); if (usageBarRoot != null) usageBarRoot.SetActive(false); } /// /// Draws an item's icon, quantity and uses bar. Keeps the mid-drag fade on the cell currently /// being dragged, but otherwise forces full opacity. /// private void Render(ItemData item, int quantity, int uses) { iconImage.gameObject.SetActive(true); iconImage.sprite = item.Icon; bool showQty = quantity > 1; quantityText.gameObject.SetActive(showQty); if (showQty) quantityText.text = quantity.ToString(); bool hasUses = item.HasUses; if (usageBarRoot != null) usageBarRoot.SetActive(hasUses); bool depleted = hasUses && uses <= 0; Color tint = depleted ? depletedTint : normalTint; tint.a = draggedSlot == this ? iconImage.color.a : 1f; iconImage.color = tint; if (hasUses && usageBarFill != null && item.MaxUses > 0) usageBarFill.fillAmount = (float)Mathf.Max(0, uses) / item.MaxUses; } #endregion #region Drag & Drop /// /// Starts dragging this cell's stack (left button, non-empty only), showing the shared ghost. /// public void OnBeginDrag(PointerEventData eventData) { if (eventData.button != PointerEventData.InputButton.Left) return; if (!HasItem()) return; draggedSlot = this; if (ghostObject != null) { ghostObject.SetActive(true); ghostIcon.sprite = iconImage.sprite; ghostIcon.gameObject.SetActive(true); bool showQty = quantityText.gameObject.activeSelf; ghostQuantity.gameObject.SetActive(showQty); if (showQty) ghostQuantity.text = quantityText.text; ghostObject.transform.position = eventData.position; } Color c = iconImage.color; c.a = 0.4f; iconImage.color = c; } /// /// Moves the ghost with the pointer. /// public void OnDrag(PointerEventData eventData) { if (draggedSlot != this) return; if (ghostObject != null) ghostObject.transform.position = eventData.position; } /// /// Ends the drag: hides the ghost and restores the source cell's opacity. /// public void OnEndDrag(PointerEventData eventData) { if (draggedSlot != this) return; if (ghostObject != null) ghostObject.SetActive(false); Color c = iconImage.color; c.a = 1f; iconImage.color = c; draggedSlot = null; } /// /// Drop target: hands the dragged cell and this cell to the window to route the transfer. /// public void OnDrop(PointerEventData eventData) { if (draggedSlot == null || draggedSlot == this) return; if (owner != null) owner.HandleTransfer(draggedSlot, this); } /// /// Right-click: quick-transfers this cell to the other container (deposit or withdraw). /// public void OnPointerClick(PointerEventData eventData) { if (eventData.dragging) return; if (eventData.button == PointerEventData.InputButton.Right && owner != null) owner.HandleQuickTransfer(this); } #endregion #region Internal Helpers /// /// Whether this cell currently holds an item in its container. /// private bool HasItem() { if (container == ChestSlotContainer.Inventory) { PlayerInventory inv = PlayerInventory.Instance; InventorySlot s = inv != null ? inv.GetSlot(index) : null; return s != null && !s.IsEmpty; } return owner != null && owner.Chest != null && owner.Chest.TryGetSlot(index, out _, out _, out _); } #endregion } }