using UnityEngine;
using DG.Tweening;
using Ashwild.Player;
namespace Ashwild.Inventory
{
public class HotbarUI : MonoBehaviour
{
[Header("References")]
[SerializeField] private SlotUI[] hotbarSlot;
[SerializeField] private CanvasGroup canvasGroup;
[Header("Animation")]
[SerializeField] private float idleScale = 0.7f;
[SerializeField] private float activeScale = 1f;
[SerializeField] private float idleAlpha = 0.5f;
[SerializeField] private float activeAlpha = 1f;
[SerializeField] private float zoomInDuration = 0.15f;
[SerializeField] private float zoomOutDuration = 0.4f;
[SerializeField] private float activeHoldDuration = 1.5f;
[SerializeField] private Ease zoomInEase = Ease.OutBack;
[SerializeField] private Ease zoomOutEase = Ease.InQuad;
[Header("Context Menu")]
[SerializeField] private SlotContextMenu contextMenu;
private PlayerInventory inventory;
private Tweener scaleTween;
private Tweener alphaTween;
private float zoomOutTimer;
private bool inventoryLocked;
private bool bound;
///
/// Waits for the networked local player to spawn before binding to its inventory.
///
private void OnEnable()
{
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
}
///
/// Unsubscribes — mirrors OnEnable exactly.
///
private void OnDisable()
{
PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned;
}
private void Start()
{
// Visual setup that does not need the player.
for (int i = 0; i < hotbarSlot.Length; i++)
hotbarSlot[i].Initialize(i, InventoryUI.OnSwapRequested, OnSlotClicked);
transform.localScale = Vector3.one * idleScale;
if (canvasGroup != null)
canvasGroup.alpha = idleAlpha;
// The player may already exist (late UI init); otherwise we wait for the spawn event.
if (PlayerInventory.Instance != null)
BindToInventory();
}
///
/// Binds to the inventory as soon as the local player has spawned on the network.
///
private void HandleLocalPlayerSpawned() => BindToInventory();
///
/// Hooks the hotbar UI onto the local player's inventory; safe to call more than once.
///
private void BindToInventory()
{
if (bound) return;
inventory = PlayerInventory.Instance;
if (inventory == null) return;
bound = true;
inventory.onSlotChanged.AddListener(OnSlotChanged);
inventory.onSelectedSlotChanged.AddListener(OnSelectedSlotChanged);
RefreshAll();
UpdateSelection(inventory.SelectedHotbarIndex);
}
private void OnDestroy()
{
scaleTween?.Kill();
alphaTween?.Kill();
if (inventory != null)
{
inventory.onSlotChanged.RemoveListener(OnSlotChanged);
inventory.onSelectedSlotChanged.RemoveListener(OnSelectedSlotChanged);
}
}
private void Update()
{
if (inventoryLocked || zoomOutTimer <= 0f) return;
zoomOutTimer -= Time.deltaTime;
if (zoomOutTimer <= 0f)
ZoomOut();
}
private void OnSelectedSlotChanged(int selectedIndex)
{
UpdateSelection(selectedIndex);
ZoomIn();
}
private void ZoomIn()
{
zoomOutTimer = activeHoldDuration;
scaleTween?.Kill();
scaleTween = transform.DOScale(activeScale, zoomInDuration).SetEase(zoomInEase);
if (canvasGroup != null)
{
alphaTween?.Kill();
alphaTween = canvasGroup.DOFade(activeAlpha, zoomInDuration);
}
}
private void ZoomOut()
{
scaleTween?.Kill();
scaleTween = transform.DOScale(idleScale, zoomOutDuration).SetEase(zoomOutEase);
if (canvasGroup != null)
{
alphaTween?.Kill();
alphaTween = canvasGroup.DOFade(idleAlpha, zoomOutDuration);
}
}
public void OnInventoryOpen()
{
inventoryLocked = true;
zoomOutTimer = 0f;
ZoomIn();
}
public void OnInventoryClose()
{
inventoryLocked = false;
ZoomOut();
}
private void OnSlotClicked(int index, bool rightClick)
{
if (rightClick && PlayerEvents.IsInventoryOpen)
{
if (contextMenu != null)
contextMenu.Show(index);
else
inventory.UseItem(index);
}
}
private void OnSlotChanged(int index)
{
if (index >= 0 && index < hotbarSlot.Length)
hotbarSlot[index].UpdateVisual(inventory.GetSlot(index));
}
private void UpdateSelection(int selectedIndex)
{
for (int i = 0; i < hotbarSlot.Length; i++)
hotbarSlot[i].SetSelected(i == selectedIndex);
}
private void RefreshAll()
{
for (int i = 0; i < hotbarSlot.Length; i++)
hotbarSlot[i].UpdateVisual(inventory.GetSlot(i));
}
}
}