193 lines
6.7 KiB
C#
193 lines
6.7 KiB
C#
using UnityEngine;
|
|
using TMPro;
|
|
using Ashwild.Inventory;
|
|
|
|
namespace Ashwild.Storage
|
|
{
|
|
/// <summary>
|
|
/// The chest module of the inventory window: the right-side grid that shows the opened chest's
|
|
/// contents, mirror of the hover-description module it replaces while a chest is open. It is a pure
|
|
/// view driven by <see cref="InventoryUI"/> — it builds its grid of <see cref="SlotUI"/> cells from
|
|
/// the bound chest's replicated view and refreshes them live, but it never routes transfers itself:
|
|
/// every cell reports its drop to <see cref="InventoryUI.HandleSlotDrop"/>, which turns it into the
|
|
/// matching server-authoritative deposit/withdraw/move on the <see cref="Chest"/>.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public class ChestPanelUI : MonoBehaviour
|
|
{
|
|
#region Serialized Fields
|
|
|
|
[Header("Window")]
|
|
[Tooltip("Root holder toggled on/off as the module is shown or hidden.")]
|
|
[SerializeField] private GameObject root;
|
|
[SerializeField] private TextMeshProUGUI titleText;
|
|
|
|
[Header("Grid")]
|
|
[Tooltip("Parent the chest cells are laid out under.")]
|
|
[SerializeField] private Transform slotContainer;
|
|
[Tooltip("Prefab carrying a SlotUI — the same cell used by the inventory grid and the hotbar.")]
|
|
[SerializeField] private GameObject slotPrefab;
|
|
|
|
#endregion
|
|
|
|
#region State
|
|
|
|
private SlotUI[] slots;
|
|
private Chest boundChest;
|
|
|
|
/// <summary>
|
|
/// The chest currently shown (null while the module is hidden). Read by InventoryUI to route
|
|
/// transfers.
|
|
/// </summary>
|
|
public Chest Chest => boundChest;
|
|
|
|
#endregion
|
|
|
|
#region Unity Lifecycle
|
|
|
|
/// <summary>
|
|
/// Parks the module hidden so it only appears once a chest is opened.
|
|
/// </summary>
|
|
private void Awake()
|
|
{
|
|
if (root != null) root.SetActive(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unbinds the chest event if the module is torn down while open.
|
|
/// </summary>
|
|
private void OnDestroy()
|
|
{
|
|
if (boundChest != null) boundChest.SlotViewChanged -= HandleChestSlotChanged;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Show / Hide
|
|
|
|
/// <summary>
|
|
/// Binds a chest and shows the module: (re)builds the grid to the chest's slot count, subscribes
|
|
/// to its replicated view so cells refresh live, sets the title and draws every cell.
|
|
/// </summary>
|
|
public void Bind(Chest chest)
|
|
{
|
|
if (boundChest != null) boundChest.SlotViewChanged -= HandleChestSlotChanged;
|
|
|
|
boundChest = chest;
|
|
if (root != null) root.SetActive(true);
|
|
|
|
BuildGrid();
|
|
|
|
if (boundChest != null)
|
|
{
|
|
boundChest.SlotViewChanged += HandleChestSlotChanged;
|
|
if (titleText != null) titleText.text = boundChest.DisplayName;
|
|
}
|
|
|
|
RefreshAll();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hides the module, unbinds the chest and releases it.
|
|
/// </summary>
|
|
public void Hide()
|
|
{
|
|
if (boundChest != null) boundChest.SlotViewChanged -= HandleChestSlotChanged;
|
|
boundChest = null;
|
|
if (root != null) root.SetActive(false);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Grid Building
|
|
|
|
/// <summary>
|
|
/// Builds (or rebuilds when the size differs) the grid to match the bound chest's slot count, so
|
|
/// opening a small chest then a large one lays out the right number of cells.
|
|
/// </summary>
|
|
private void BuildGrid()
|
|
{
|
|
int count = boundChest != null ? boundChest.SlotCount : 0;
|
|
if (slots != null && slots.Length == count) return;
|
|
|
|
if (slots != null)
|
|
foreach (SlotUI s in slots)
|
|
if (s != null) Destroy(s.gameObject);
|
|
|
|
slots = new SlotUI[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
SlotUI slot = Instantiate(slotPrefab, slotContainer).GetComponent<SlotUI>();
|
|
slot.Initialize(SlotContainer.Chest, i, InventoryUI.HandleSlotDrop, HandleChestSlotClicked);
|
|
slots[i] = slot;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Interaction
|
|
|
|
/// <summary>
|
|
/// Right-click on a chest cell quick-transfers its whole stack into the inventory (auto-placed).
|
|
/// </summary>
|
|
private void HandleChestSlotClicked(SlotContainer container, int index, bool rightClick)
|
|
{
|
|
if (!rightClick || boundChest == null) return;
|
|
|
|
PredictEmptied(index);
|
|
boundChest.RequestQuickTransfer(SlotContainer.Chest, index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a cell as emptied straight away, while its move is still in flight to the server.
|
|
///
|
|
/// Emptying the chest travels on the SyncList (flushed at end of tick) but the matching grant is
|
|
/// a TargetRpc (sent immediately), so the item would otherwise appear in the inventory a moment
|
|
/// *before* leaving the chest — a visible double. This only repaints the cell: the authoritative
|
|
/// model is untouched, so if the server disagrees (a co-op partner grabbed the stack first) the
|
|
/// next replicated update simply paints the item back.
|
|
/// </summary>
|
|
public void PredictEmptied(int index)
|
|
{
|
|
if (slots == null || index < 0 || index >= slots.Length) return;
|
|
slots[index].UpdateVisual((ItemData)null, 0, -1);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Refresh
|
|
|
|
/// <summary>
|
|
/// Refreshes a single cell, or the whole grid when the collection was (re)seeded (index -1).
|
|
/// </summary>
|
|
private void HandleChestSlotChanged(int index)
|
|
{
|
|
if (slots == null) return;
|
|
if (index < 0) { RefreshAll(); return; }
|
|
if (index < slots.Length) RefreshSlot(index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redraws every chest cell.
|
|
/// </summary>
|
|
private void RefreshAll()
|
|
{
|
|
if (slots == null) return;
|
|
for (int i = 0; i < slots.Length; i++) RefreshSlot(i);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws one cell from the chest's replicated view, or empty when the slot holds nothing.
|
|
/// </summary>
|
|
private void RefreshSlot(int index)
|
|
{
|
|
if (boundChest != null && boundChest.TryGetSlot(index, out ItemData item, out int qty, out int uses))
|
|
slots[index].UpdateVisual(item, qty, uses);
|
|
else
|
|
slots[index].UpdateVisual((ItemData)null, 0, -1);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|