297 lines
9.4 KiB
C#
297 lines
9.4 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine.EventSystems;
|
|
using DG.Tweening;
|
|
using UnityEngine.InputSystem;
|
|
|
|
namespace Ashwild.Inventory
|
|
{
|
|
public class SlotContextMenu : MonoBehaviour
|
|
{
|
|
[Header("References")]
|
|
[SerializeField] private RectTransform panelRect;
|
|
[SerializeField] private CanvasGroup canvasGroup;
|
|
[SerializeField] private Transform buttonContainer;
|
|
[SerializeField] private GameObject buttonPrefab;
|
|
|
|
[Header("Settings")]
|
|
[SerializeField] private Vector2 offset = new Vector2(10f, -10f);
|
|
|
|
[Header("Animation")]
|
|
[SerializeField] private float showDuration = 0.2f;
|
|
[SerializeField] private float hideDuration = 0.15f;
|
|
[SerializeField] private Ease showEase = Ease.OutBack;
|
|
[SerializeField] private Ease hideEase = Ease.InQuad;
|
|
|
|
private int currentSlotIndex = -1;
|
|
private readonly List<GameObject> activeButtons = new List<GameObject>();
|
|
private Canvas rootCanvas;
|
|
private bool isVisible;
|
|
private Tweener scaleTween;
|
|
private Tweener fadeTween;
|
|
|
|
private void Awake()
|
|
{
|
|
rootCanvas = GetComponentInParent<Canvas>();
|
|
SetHidden();
|
|
}
|
|
|
|
private void SetHidden()
|
|
{
|
|
canvasGroup.gameObject.SetActive(false);
|
|
canvasGroup.alpha = 0f;
|
|
canvasGroup.interactable = false;
|
|
canvasGroup.blocksRaycasts = false;
|
|
panelRect.localScale = Vector3.zero;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
scaleTween?.Kill();
|
|
fadeTween?.Kill();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!isVisible) return;
|
|
|
|
Mouse mouse = Mouse.current;
|
|
if (mouse == null) return;
|
|
|
|
if (mouse.leftButton.wasPressedThisFrame || mouse.rightButton.wasPressedThisFrame)
|
|
{
|
|
if (!IsPointerOverMenu())
|
|
Hide();
|
|
}
|
|
}
|
|
|
|
public void Show(int slotIndex)
|
|
{
|
|
PlayerInventory inv = PlayerInventory.Instance;
|
|
InventorySlot slot = inv.GetSlot(slotIndex);
|
|
if (slot == null || slot.IsEmpty)
|
|
return;
|
|
|
|
// Kill any running animation and clear previous buttons
|
|
scaleTween?.Kill();
|
|
fadeTween?.Kill();
|
|
ClearButtons();
|
|
|
|
currentSlotIndex = slotIndex;
|
|
|
|
bool isHotbar = slotIndex < inv.HotbarSize;
|
|
bool canSplit = slot.Quantity > 1;
|
|
|
|
if (isHotbar)
|
|
AddButton("Move to Inventory", OnMoveToInventory);
|
|
else
|
|
AddButton("Equip", OnEquipToHotbar);
|
|
|
|
if (slot.ItemData.ItemType == ItemType.Consumable)
|
|
AddButton("Use", OnUseItem);
|
|
|
|
AddButton("Drop", OnDrop);
|
|
|
|
if (canSplit)
|
|
AddButton("Split", OnSplit);
|
|
|
|
// Activate and position
|
|
canvasGroup.gameObject.SetActive(true);
|
|
Vector2 mousePos = Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero;
|
|
PositionMenu(mousePos);
|
|
|
|
// Animate in
|
|
isVisible = true;
|
|
panelRect.localScale = Vector3.one * 0.5f;
|
|
canvasGroup.alpha = 0f;
|
|
|
|
scaleTween?.Kill();
|
|
fadeTween?.Kill();
|
|
|
|
scaleTween = panelRect.DOScale(1f, showDuration).SetEase(showEase).SetUpdate(true);
|
|
fadeTween = canvasGroup.DOFade(1f, showDuration).SetUpdate(true).OnComplete(() =>
|
|
{
|
|
canvasGroup.interactable = true;
|
|
canvasGroup.blocksRaycasts = true;
|
|
});
|
|
}
|
|
|
|
public void Hide()
|
|
{
|
|
if (!isVisible) return;
|
|
|
|
isVisible = false;
|
|
canvasGroup.interactable = false;
|
|
canvasGroup.blocksRaycasts = false;
|
|
|
|
scaleTween?.Kill();
|
|
fadeTween?.Kill();
|
|
|
|
scaleTween = panelRect.DOScale(0f, hideDuration).SetEase(hideEase).SetUpdate(true);
|
|
fadeTween = canvasGroup.DOFade(0f, hideDuration).SetUpdate(true).OnComplete(() =>
|
|
{
|
|
canvasGroup.gameObject.SetActive(false);
|
|
ClearButtons();
|
|
currentSlotIndex = -1;
|
|
});
|
|
}
|
|
|
|
private void PositionMenu(Vector2 screenPosition)
|
|
{
|
|
if (rootCanvas == null)
|
|
rootCanvas = GetComponentInParent<Canvas>();
|
|
|
|
RectTransform canvasRect = rootCanvas.transform as RectTransform;
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|
canvasRect, screenPosition, rootCanvas.worldCamera, out Vector2 anchoredPos);
|
|
|
|
anchoredPos += offset;
|
|
|
|
// Clamp to stay within canvas bounds
|
|
Vector2 menuSize = panelRect.sizeDelta;
|
|
Vector2 canvasSize = canvasRect.sizeDelta;
|
|
float halfW = canvasSize.x * 0.5f;
|
|
float halfH = canvasSize.y * 0.5f;
|
|
|
|
if (anchoredPos.x + menuSize.x > halfW)
|
|
anchoredPos.x = halfW - menuSize.x;
|
|
if (anchoredPos.y - menuSize.y < -halfH)
|
|
anchoredPos.y = -halfH + menuSize.y;
|
|
|
|
panelRect.anchoredPosition = anchoredPos;
|
|
}
|
|
|
|
private void AddButton(string label, Action onClick)
|
|
{
|
|
GameObject btnGO = Instantiate(buttonPrefab, buttonContainer);
|
|
btnGO.SetActive(true);
|
|
|
|
TextMeshProUGUI text = btnGO.GetComponentInChildren<TextMeshProUGUI>();
|
|
if (text != null)
|
|
text.text = label;
|
|
|
|
Button btn = btnGO.GetComponent<Button>();
|
|
if (btn != null)
|
|
btn.onClick.AddListener(() =>
|
|
{
|
|
onClick?.Invoke();
|
|
Hide();
|
|
});
|
|
|
|
activeButtons.Add(btnGO);
|
|
}
|
|
|
|
private void ClearButtons()
|
|
{
|
|
foreach (GameObject btn in activeButtons)
|
|
Destroy(btn);
|
|
activeButtons.Clear();
|
|
}
|
|
|
|
private bool IsPointerOverMenu()
|
|
{
|
|
Vector2 mousePos = Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero;
|
|
PointerEventData eventData = new PointerEventData(EventSystem.current)
|
|
{
|
|
position = mousePos
|
|
};
|
|
List<RaycastResult> results = new List<RaycastResult>();
|
|
EventSystem.current.RaycastAll(eventData, results);
|
|
|
|
foreach (RaycastResult result in results)
|
|
{
|
|
if (result.gameObject.transform.IsChildOf(transform))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- Actions ---
|
|
|
|
private void OnEquipToHotbar()
|
|
{
|
|
if (currentSlotIndex < 0) return;
|
|
PlayerInventory inv = PlayerInventory.Instance;
|
|
|
|
int targetSlot = inv.SelectedHotbarIndex;
|
|
for (int i = 0; i < inv.HotbarSize; i++)
|
|
{
|
|
if (inv.GetSlot(i).IsEmpty)
|
|
{
|
|
targetSlot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
InventoryUI.OnSwapRequested(currentSlotIndex, targetSlot);
|
|
}
|
|
|
|
private void OnMoveToInventory()
|
|
{
|
|
if (currentSlotIndex < 0) return;
|
|
PlayerInventory inv = PlayerInventory.Instance;
|
|
|
|
for (int i = inv.HotbarSize; i < inv.InventorySize; i++)
|
|
{
|
|
if (inv.GetSlot(i).IsEmpty)
|
|
{
|
|
InventoryUI.OnSwapRequested(currentSlotIndex, i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
InventorySlot sourceSlot = inv.GetSlot(currentSlotIndex);
|
|
for (int i = inv.HotbarSize; i < inv.InventorySize; i++)
|
|
{
|
|
InventorySlot slot = inv.GetSlot(i);
|
|
if (!slot.IsEmpty && slot.ItemData == sourceSlot.ItemData && slot.CanAccept(sourceSlot.ItemData))
|
|
{
|
|
InventoryUI.OnSwapRequested(currentSlotIndex, i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnUseItem()
|
|
{
|
|
if (currentSlotIndex < 0) return;
|
|
PlayerInventory.Instance.UseItem(currentSlotIndex);
|
|
}
|
|
|
|
private void OnDrop()
|
|
{
|
|
if (currentSlotIndex < 0) return;
|
|
PlayerInventory inv = PlayerInventory.Instance;
|
|
InventorySlot slot = inv.GetSlot(currentSlotIndex);
|
|
if (!slot.IsEmpty)
|
|
inv.DropItem(currentSlotIndex, slot.Quantity);
|
|
}
|
|
|
|
private void OnSplit()
|
|
{
|
|
if (currentSlotIndex < 0) return;
|
|
PlayerInventory inv = PlayerInventory.Instance;
|
|
InventorySlot slot = inv.GetSlot(currentSlotIndex);
|
|
if (slot.IsEmpty || slot.Quantity <= 1) return;
|
|
|
|
int halfQty = slot.Quantity / 2;
|
|
int remaining = slot.Quantity - halfQty;
|
|
|
|
for (int i = 0; i < inv.InventorySize; i++)
|
|
{
|
|
if (inv.GetSlot(i).IsEmpty)
|
|
{
|
|
slot.Set(slot.ItemData, remaining);
|
|
inv.GetSlot(i).Set(slot.ItemData, halfQty);
|
|
inv.onSlotChanged?.Invoke(currentSlotIndex);
|
|
inv.onSlotChanged?.Invoke(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|