added inventory and stamina system
This commit is contained in:
632
Assets/Scripts/Inventory.cs
Normal file
632
Assets/Scripts/Inventory.cs
Normal file
@@ -0,0 +1,632 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Inventory system with a toggleable HUD (press I) and right-click context menu.
|
||||
/// Attach to the Player GameObject alongside WeaponManager.
|
||||
/// </summary>
|
||||
public class Inventory : MonoBehaviour
|
||||
{
|
||||
// ─── Item data ────────────────────────────────────────────────────
|
||||
[System.Serializable]
|
||||
public class InventoryEntry
|
||||
{
|
||||
public ItemDefinition definition;
|
||||
public string itemName;
|
||||
public Sprite icon;
|
||||
public int count;
|
||||
public bool isEquipped;
|
||||
|
||||
public InventoryEntry(string name, Sprite icon)
|
||||
{
|
||||
this.itemName = name;
|
||||
this.icon = icon;
|
||||
this.count = 1;
|
||||
this.isEquipped = false;
|
||||
}
|
||||
|
||||
public InventoryEntry(ItemDefinition def)
|
||||
{
|
||||
this.definition = def;
|
||||
this.itemName = def.itemName;
|
||||
this.icon = def.icon;
|
||||
this.count = 1;
|
||||
this.isEquipped = false;
|
||||
}
|
||||
|
||||
public bool _isWeaponSlot;
|
||||
|
||||
public bool IsWeapon =>
|
||||
(definition != null && definition.type == ItemDefinition.ItemType.Weapon)
|
||||
|| _isWeaponSlot;
|
||||
|
||||
// True for weapons AND equippable misc items
|
||||
public bool IsEquippable =>
|
||||
IsWeapon || (definition != null && definition.isEquippable);
|
||||
|
||||
public string DisplayName => definition != null ? definition.itemName : itemName;
|
||||
}
|
||||
|
||||
public List<InventoryEntry> items = new List<InventoryEntry>();
|
||||
|
||||
// ─── HUD config ───────────────────────────────────────────────────
|
||||
[Header("HUD")]
|
||||
public KeyCode toggleKey = KeyCode.I;
|
||||
public KeyCode equipKey = KeyCode.F;
|
||||
public int columns = 4;
|
||||
public int maxSlots = 20;
|
||||
|
||||
[Header("HUD Colours")]
|
||||
public Color colBackground = new Color(0.05f, 0.05f, 0.05f, 0.92f);
|
||||
public Color colPanel = new Color(0.10f, 0.10f, 0.10f, 0.95f);
|
||||
public Color colSlotEmpty = new Color(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
public Color colSlotFilled = new Color(0.22f, 0.28f, 0.22f, 1.00f);
|
||||
public Color colSlotHover = new Color(0.35f, 0.45f, 0.35f, 1.00f);
|
||||
public Color colSlotSelected = new Color(0.45f, 0.75f, 0.45f, 1.00f);
|
||||
public Color colSlotEquipped = new Color(0.60f, 0.40f, 0.10f, 1.00f);
|
||||
public Color colBorder = new Color(0.40f, 0.65f, 0.40f, 1.00f);
|
||||
public Color colText = new Color(0.85f, 0.95f, 0.85f, 1.00f);
|
||||
public Color colDim = new Color(0.50f, 0.60f, 0.50f, 1.00f);
|
||||
public Color colAccent = new Color(0.50f, 0.90f, 0.50f, 1.00f);
|
||||
public Color colWeaponAccent = new Color(0.95f, 0.75f, 0.20f, 1.00f);
|
||||
|
||||
[Header("Context Menu Colours")]
|
||||
public Color colCtxBg = new Color(0.08f, 0.10f, 0.08f, 0.98f);
|
||||
public Color colCtxBorder = new Color(0.40f, 0.65f, 0.40f, 1.00f);
|
||||
public Color colCtxHover = new Color(0.20f, 0.35f, 0.20f, 1.00f);
|
||||
public Color colCtxText = new Color(0.85f, 0.95f, 0.85f, 1.00f);
|
||||
public Color colCtxDestructive = new Color(0.85f, 0.25f, 0.20f, 1.00f);
|
||||
|
||||
// ─── Private state ────────────────────────────────────────────────
|
||||
private bool _open = false;
|
||||
private int _selectedIndex = 0;
|
||||
private int _hoveredIndex = -1;
|
||||
private WeaponManager _weaponManager;
|
||||
|
||||
// ─── Context menu state ───────────────────────────────────────────
|
||||
private bool _ctxOpen = false;
|
||||
private int _ctxItemIndex = -1;
|
||||
private Vector2 _ctxPos;
|
||||
|
||||
private const float kCtxItemH = 28f;
|
||||
private const float kCtxWidth = 140f;
|
||||
private const float kCtxPadX = 10f;
|
||||
|
||||
// Context menu action labels — built per item
|
||||
private struct CtxAction
|
||||
{
|
||||
public string label;
|
||||
public bool isDestructive;
|
||||
public System.Action callback;
|
||||
}
|
||||
private List<CtxAction> _ctxActions = new List<CtxAction>();
|
||||
|
||||
// ─── Layout constants ─────────────────────────────────────────────
|
||||
private const float kSlotSize = 80f;
|
||||
private const float kSlotPad = 8f;
|
||||
private const float kPanelPad = 20f;
|
||||
private const float kHeaderH = 40f;
|
||||
private const float kDetailPanelH = 120f;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
void Start()
|
||||
{
|
||||
_weaponManager = GetComponent<WeaponManager>();
|
||||
if (_weaponManager != null)
|
||||
Invoke(nameof(SyncStartingWeapons), 0.1f);
|
||||
}
|
||||
|
||||
void SyncStartingWeapons()
|
||||
{
|
||||
if (_weaponManager == null) return;
|
||||
foreach (var slot in _weaponManager.slots)
|
||||
{
|
||||
if (slot.definition != null)
|
||||
AddItem(slot.definition, autoEquip: false);
|
||||
else
|
||||
AddItemRaw(slot.itemName, null, isWeaponSlot: true);
|
||||
}
|
||||
SyncEquippedState();
|
||||
}
|
||||
|
||||
// ─── Public API ───────────────────────────────────────────────────
|
||||
public void AddItem(ItemDefinition def, bool autoEquip = false)
|
||||
{
|
||||
if (def == null) return;
|
||||
InventoryEntry existing = items.Find(e => e.definition == def);
|
||||
if (existing != null)
|
||||
{
|
||||
if (def.type != ItemDefinition.ItemType.Weapon) existing.count++;
|
||||
Debug.Log($"[Inventory] Already have: {def.itemName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var entry = new InventoryEntry(def);
|
||||
items.Add(entry);
|
||||
Debug.Log($"[Inventory] Picked up: {def.itemName}");
|
||||
if (def.type == ItemDefinition.ItemType.Weapon)
|
||||
{
|
||||
_weaponManager?.AddWeapon(def);
|
||||
if (autoEquip) EquipItem(items.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddItem(string itemName, Sprite icon = null) => AddItemRaw(itemName, icon, false);
|
||||
|
||||
private void AddItemRaw(string itemName, Sprite icon, bool isWeaponSlot)
|
||||
{
|
||||
InventoryEntry existing = items.Find(e => e.itemName == itemName && e.definition == null);
|
||||
if (existing != null) { existing.count++; return; }
|
||||
var entry = new InventoryEntry(itemName, icon);
|
||||
entry._isWeaponSlot = isWeaponSlot;
|
||||
items.Add(entry);
|
||||
}
|
||||
|
||||
public bool RemoveItem(string itemName, int amount = 1)
|
||||
{
|
||||
InventoryEntry entry = items.Find(e => e.DisplayName == itemName);
|
||||
if (entry == null) return false;
|
||||
entry.count -= amount;
|
||||
if (entry.count <= 0) items.Remove(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasItem(string itemName) => items.Exists(e => e.DisplayName == itemName);
|
||||
public int CountOf(string itemName)
|
||||
{
|
||||
var e = items.Find(x => x.DisplayName == itemName);
|
||||
return e != null ? e.count : 0;
|
||||
}
|
||||
|
||||
public bool IsOpen => _open;
|
||||
|
||||
// ─── Equip / Unequip ──────────────────────────────────────────────
|
||||
void EquipItem(int index)
|
||||
{
|
||||
if (index < 0 || index >= items.Count) return;
|
||||
InventoryEntry entry = items[index];
|
||||
if (!entry.IsEquippable) return;
|
||||
|
||||
if (entry.IsWeapon)
|
||||
{
|
||||
// Unequip other weapons
|
||||
foreach (var e in items) { if (e.IsWeapon) e.isEquipped = false; }
|
||||
entry.isEquipped = true;
|
||||
_weaponManager?.EquipByName(entry.DisplayName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Toggle equip for misc equippables (boots, etc.)
|
||||
entry.isEquipped = true;
|
||||
}
|
||||
Debug.Log($"[Inventory] Equipped: {entry.DisplayName}");
|
||||
}
|
||||
|
||||
void UnequipItem(int index)
|
||||
{
|
||||
if (index < 0 || index >= items.Count) return;
|
||||
InventoryEntry entry = items[index];
|
||||
if (!entry.IsEquippable || !entry.isEquipped) return;
|
||||
entry.isEquipped = false;
|
||||
|
||||
if (entry.IsWeapon && _weaponManager != null)
|
||||
{
|
||||
var slot = _weaponManager.slots.Find(s => s.itemName == entry.DisplayName);
|
||||
if (slot != null) slot.instance.SetActive(false);
|
||||
_weaponManager.Unequip();
|
||||
}
|
||||
Debug.Log($"[Inventory] Unequipped: {entry.DisplayName}");
|
||||
}
|
||||
|
||||
void DropItem(int index)
|
||||
{
|
||||
if (index < 0 || index >= items.Count) return;
|
||||
InventoryEntry entry = items[index];
|
||||
if (entry.isEquipped) UnequipItem(index);
|
||||
items.RemoveAt(index);
|
||||
if (_selectedIndex >= items.Count) _selectedIndex = items.Count - 1;
|
||||
Debug.Log($"[Inventory] Dropped: {entry.DisplayName}");
|
||||
// TODO: optionally spawn the pickup back in the world here
|
||||
}
|
||||
|
||||
void SyncEquippedState()
|
||||
{
|
||||
if (_weaponManager == null) return;
|
||||
string active = _weaponManager.ActiveWeaponName;
|
||||
foreach (var e in items)
|
||||
{
|
||||
// Only sync weapon equipped state — leave misc equippables alone
|
||||
if (e.IsWeapon)
|
||||
e.isEquipped = (e.DisplayName == active);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Context menu builder ─────────────────────────────────────────
|
||||
void OpenContextMenu(int itemIndex, Vector2 screenPos)
|
||||
{
|
||||
if (itemIndex < 0 || itemIndex >= items.Count) return;
|
||||
|
||||
_ctxItemIndex = itemIndex;
|
||||
_ctxActions.Clear();
|
||||
|
||||
InventoryEntry entry = items[itemIndex];
|
||||
|
||||
if (entry.IsEquippable)
|
||||
{
|
||||
if (entry.isEquipped)
|
||||
{
|
||||
int captured = itemIndex;
|
||||
_ctxActions.Add(new CtxAction
|
||||
{
|
||||
label = "Unequip",
|
||||
isDestructive = false,
|
||||
callback = () => UnequipItem(captured)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
int captured = itemIndex;
|
||||
_ctxActions.Add(new CtxAction
|
||||
{
|
||||
label = "Equip",
|
||||
isDestructive = false,
|
||||
callback = () => EquipItem(captured)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Consumables / misc could have a "Use" action here in future
|
||||
// For now everyone gets Inspect (just selects and focuses the detail panel)
|
||||
{
|
||||
int captured = itemIndex;
|
||||
_ctxActions.Add(new CtxAction
|
||||
{
|
||||
label = "Inspect",
|
||||
isDestructive = false,
|
||||
callback = () => _selectedIndex = captured
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
int captured = itemIndex;
|
||||
_ctxActions.Add(new CtxAction
|
||||
{
|
||||
label = "Drop",
|
||||
isDestructive = true,
|
||||
callback = () => DropItem(captured)
|
||||
});
|
||||
}
|
||||
|
||||
// Clamp so the menu doesn't go off screen
|
||||
float menuH = _ctxActions.Count * kCtxItemH + 8f;
|
||||
float clampedX = Mathf.Min(screenPos.x, Screen.width - kCtxWidth - 4f);
|
||||
float clampedY = Mathf.Min(screenPos.y, Screen.height - menuH - 4f);
|
||||
_ctxPos = new Vector2(clampedX, clampedY);
|
||||
_ctxOpen = true;
|
||||
}
|
||||
|
||||
void CloseContextMenu() { _ctxOpen = false; _ctxItemIndex = -1; }
|
||||
|
||||
// ─── Toggle ───────────────────────────────────────────────────────
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(toggleKey))
|
||||
{
|
||||
CloseContextMenu();
|
||||
SetOpen(!_open);
|
||||
}
|
||||
|
||||
if (_open && Input.GetKeyDown(equipKey))
|
||||
{
|
||||
if (_selectedIndex < items.Count && items[_selectedIndex].IsWeapon)
|
||||
EquipItem(_selectedIndex);
|
||||
CloseContextMenu();
|
||||
}
|
||||
|
||||
if (!_open)
|
||||
SyncEquippedState();
|
||||
}
|
||||
|
||||
void SetOpen(bool open)
|
||||
{
|
||||
_open = open;
|
||||
SyncEquippedState();
|
||||
Cursor.lockState = open ? CursorLockMode.None : CursorLockMode.Locked;
|
||||
Cursor.visible = open;
|
||||
}
|
||||
|
||||
// ─── GUI ──────────────────────────────────────────────────────────
|
||||
void OnGUI()
|
||||
{
|
||||
if (!_open) return;
|
||||
|
||||
int rows = Mathf.CeilToInt((float)maxSlots / columns);
|
||||
float gridW = columns * (kSlotSize + kSlotPad) - kSlotPad;
|
||||
float gridH = rows * (kSlotSize + kSlotPad) - kSlotPad;
|
||||
float panelW = gridW + kPanelPad * 2f;
|
||||
float panelH = kHeaderH + kPanelPad + gridH + kPanelPad + kDetailPanelH + kPanelPad;
|
||||
float panelX = (Screen.width - panelW) * 0.5f;
|
||||
float panelY = (Screen.height - panelH) * 0.5f;
|
||||
|
||||
Vector2 mouse = Event.current.mousePosition;
|
||||
|
||||
// ── Click outside context menu to close it ────────────────────
|
||||
if (_ctxOpen && Event.current.type == EventType.MouseDown)
|
||||
{
|
||||
Rect ctxRect = GetCtxRect();
|
||||
if (!ctxRect.Contains(mouse))
|
||||
CloseContextMenu();
|
||||
}
|
||||
|
||||
// ── Backdrop ──────────────────────────────────────────────────
|
||||
DrawRect(new Rect(0, 0, Screen.width, Screen.height), new Color(0, 0, 0, 0.55f));
|
||||
|
||||
// ── Main panel ────────────────────────────────────────────────
|
||||
DrawBorderedBox(new Rect(panelX, panelY, panelW, panelH), colPanel, colBorder, 2f);
|
||||
|
||||
GUIStyle headerStyle = MakeStyle(18, FontStyle.Bold, colAccent, TextAnchor.MiddleCenter);
|
||||
GUI.Label(new Rect(panelX, panelY + 8f, panelW, 28f), "[ INVENTORY ]", headerStyle);
|
||||
DrawRect(new Rect(panelX + 10f, panelY + kHeaderH - 2f, panelW - 20f, 1f), colBorder);
|
||||
|
||||
if (_weaponManager != null && _weaponManager.ActiveWeaponName != "")
|
||||
{
|
||||
GUIStyle activeStyle = MakeStyle(11, FontStyle.Normal, colWeaponAccent, TextAnchor.MiddleRight);
|
||||
GUI.Label(new Rect(panelX, panelY + 10f, panelW - 10f, 20f),
|
||||
$"EQUIPPED: {_weaponManager.ActiveWeaponName}", activeStyle);
|
||||
}
|
||||
|
||||
float gx = panelX + kPanelPad;
|
||||
float gy = panelY + kHeaderH + kPanelPad;
|
||||
|
||||
_hoveredIndex = -1;
|
||||
|
||||
// ── Slot grid ─────────────────────────────────────────────────
|
||||
for (int i = 0; i < maxSlots; i++)
|
||||
{
|
||||
int col = i % columns;
|
||||
int row = i / columns;
|
||||
float sx = gx + col * (kSlotSize + kSlotPad);
|
||||
float sy = gy + row * (kSlotSize + kSlotPad);
|
||||
Rect slotRect = new Rect(sx, sy, kSlotSize, kSlotSize);
|
||||
|
||||
bool hasItem = i < items.Count;
|
||||
bool isHover = slotRect.Contains(mouse) && !_ctxOpen;
|
||||
bool isSel = (i == _selectedIndex);
|
||||
bool isEquipped = hasItem && items[i].isEquipped;
|
||||
bool isWeapon = hasItem && items[i].IsWeapon;
|
||||
|
||||
if (isHover) _hoveredIndex = i;
|
||||
|
||||
// Left-click: select
|
||||
if (isHover && Event.current.type == EventType.MouseDown
|
||||
&& Event.current.button == 0 && hasItem)
|
||||
{
|
||||
_selectedIndex = i;
|
||||
CloseContextMenu();
|
||||
}
|
||||
|
||||
// Left double-click: equip weapon
|
||||
if (isHover && Event.current.type == EventType.MouseDown
|
||||
&& Event.current.button == 0 && Event.current.clickCount == 2 && isWeapon)
|
||||
EquipItem(i);
|
||||
|
||||
// Right-click: open context menu
|
||||
if (isHover && Event.current.type == EventType.MouseDown
|
||||
&& Event.current.button == 1 && hasItem)
|
||||
{
|
||||
_selectedIndex = i;
|
||||
OpenContextMenu(i, mouse);
|
||||
}
|
||||
|
||||
// Slot colour
|
||||
Color slotCol;
|
||||
if (isEquipped) slotCol = colSlotEquipped;
|
||||
else if (isSel) slotCol = colSlotSelected;
|
||||
else if (isHover) slotCol = colSlotHover;
|
||||
else if (hasItem) slotCol = colSlotFilled;
|
||||
else slotCol = colSlotEmpty;
|
||||
|
||||
Color borderCol = isEquipped ? colWeaponAccent : (isSel ? colAccent : colBorder);
|
||||
DrawBorderedBox(slotRect, slotCol, borderCol, (isSel || isEquipped) ? 2f : 1f);
|
||||
|
||||
if (!hasItem) continue;
|
||||
|
||||
InventoryEntry entry = items[i];
|
||||
|
||||
// Icon or letter placeholder
|
||||
if (entry.icon != null)
|
||||
{
|
||||
float ip = 10f;
|
||||
GUI.DrawTexture(new Rect(sx + ip, sy + ip, kSlotSize - ip * 2f, kSlotSize - 28f),
|
||||
entry.icon.texture, ScaleMode.ScaleToFit, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Color lc = isEquipped ? colWeaponAccent : (isSel ? Color.white : colAccent);
|
||||
GUI.Label(new Rect(sx, sy + 8f, kSlotSize, kSlotSize - 24f),
|
||||
entry.DisplayName.Substring(0, 1).ToUpper(), MakeStyle(26, FontStyle.Bold, lc, TextAnchor.MiddleCenter));
|
||||
}
|
||||
|
||||
// Weapon tag
|
||||
if (isWeapon)
|
||||
{
|
||||
Color tc = isEquipped ? colWeaponAccent : new Color(0.6f, 0.5f, 0.2f, 1f);
|
||||
GUI.Label(new Rect(sx + 3f, sy + 2f, 40f, 12f),
|
||||
isEquipped ? "EQUIP" : "WPN", MakeStyle(8, FontStyle.Bold, tc, TextAnchor.UpperLeft));
|
||||
}
|
||||
|
||||
// Right-click hint on hovered slot
|
||||
if (isHover)
|
||||
{
|
||||
GUIStyle hint = MakeStyle(8, FontStyle.Normal, new Color(0.6f, 0.6f, 0.6f, 0.9f), TextAnchor.LowerRight);
|
||||
GUI.Label(new Rect(sx + 2f, sy + 2f, kSlotSize - 4f, kSlotSize - 4f), "RMB ▾", hint);
|
||||
}
|
||||
|
||||
// Item name
|
||||
string dn = entry.DisplayName.Length > 10 ? entry.DisplayName.Substring(0, 9) + "…" : entry.DisplayName;
|
||||
Color nc = isEquipped ? colWeaponAccent : (isSel ? Color.white : colText);
|
||||
GUI.Label(new Rect(sx + 2f, sy + kSlotSize - 28f, kSlotSize - 4f, 16f),
|
||||
dn, MakeStyle(9, FontStyle.Bold, nc, TextAnchor.LowerCenter));
|
||||
|
||||
// Count badge
|
||||
if (entry.count > 1)
|
||||
{
|
||||
DrawRect(new Rect(sx + kSlotSize - 22f, sy + kSlotSize - 18f, 20f, 16f), new Color(0, 0, 0, 0.7f));
|
||||
GUI.Label(new Rect(sx + kSlotSize - 22f, sy + kSlotSize - 18f, 19f, 16f),
|
||||
$"x{entry.count}", MakeStyle(10, FontStyle.Bold, Color.white, TextAnchor.LowerRight));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Detail panel ──────────────────────────────────────────────
|
||||
float detailY = gy + gridH + kPanelPad;
|
||||
Rect detailRect = new Rect(panelX + kPanelPad, detailY, panelW - kPanelPad * 2f, kDetailPanelH - kPanelPad);
|
||||
DrawBorderedBox(detailRect, new Color(0.08f, 0.08f, 0.08f, 1f), colBorder, 1f);
|
||||
|
||||
if (_selectedIndex < items.Count)
|
||||
{
|
||||
InventoryEntry sel = items[_selectedIndex];
|
||||
float dy = detailRect.y + 8f;
|
||||
float dx = detailRect.x + 10f;
|
||||
float dw = detailRect.width - 20f;
|
||||
|
||||
GUI.Label(new Rect(dx, dy, dw, 16f), "SELECTED", MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.UpperLeft));
|
||||
GUI.Label(new Rect(dx, dy + 16f, dw, 22f), sel.DisplayName, MakeStyle(14, FontStyle.Bold, colText, TextAnchor.UpperLeft));
|
||||
|
||||
string desc = (sel.definition != null && sel.definition.description != "")
|
||||
? sel.definition.description : (sel.IsWeapon ? "Weapon" : "Item");
|
||||
GUI.Label(new Rect(dx, dy + 38f, dw * 0.65f, 30f), desc, MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.UpperLeft));
|
||||
|
||||
if (sel.IsEquippable)
|
||||
{
|
||||
Color wc = sel.isEquipped ? colWeaponAccent : colDim;
|
||||
string tag = sel.isEquipped ? "▶ EQUIPPED" : "right-click or [F] to equip";
|
||||
GUI.Label(new Rect(dx, dy + 68f, dw, 18f), tag, MakeStyle(11, FontStyle.Bold, wc, TextAnchor.UpperLeft));
|
||||
}
|
||||
else if (sel.count > 1)
|
||||
{
|
||||
GUI.Label(new Rect(dx, dy + 68f, dw, 18f), $"Quantity: {sel.count}",
|
||||
MakeStyle(11, FontStyle.Bold, colAccent, TextAnchor.UpperLeft));
|
||||
}
|
||||
|
||||
// Equip / Unequip button bottom-right of detail panel
|
||||
if (sel.IsEquippable)
|
||||
{
|
||||
Rect btnRect = new Rect(detailRect.x + detailRect.width - 110f,
|
||||
detailRect.y + detailRect.height - 34f, 100f, 26f);
|
||||
if (sel.isEquipped)
|
||||
{
|
||||
DrawBorderedBox(btnRect, new Color(0.4f, 0.15f, 0.05f, 1f), colWeaponAccent, 1f);
|
||||
GUI.Label(btnRect, "[ F ] UNEQUIP", MakeStyle(11, FontStyle.Bold, Color.white, TextAnchor.MiddleCenter));
|
||||
if (GUI.Button(btnRect, "", GUIStyle.none)) UnequipItem(_selectedIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBorderedBox(btnRect, colSlotEquipped, colWeaponAccent, 1f);
|
||||
GUI.Label(btnRect, "[ F ] EQUIP", MakeStyle(11, FontStyle.Bold, Color.white, TextAnchor.MiddleCenter));
|
||||
if (GUI.Button(btnRect, "", GUIStyle.none)) EquipItem(_selectedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Label(detailRect, "No item selected", MakeStyle(11, FontStyle.Normal, colDim, TextAnchor.MiddleCenter));
|
||||
}
|
||||
|
||||
// Hints
|
||||
GUIStyle hintStyle = MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.LowerRight);
|
||||
GUI.Label(new Rect(panelX, panelY + panelH - 18f, panelW - 8f, 16f), "[ I ] close | RMB slot for options", hintStyle);
|
||||
|
||||
// ── Context menu (drawn last so it's on top) ──────────────────
|
||||
if (_ctxOpen)
|
||||
DrawContextMenu(mouse);
|
||||
}
|
||||
|
||||
// ─── Context menu draw ────────────────────────────────────────────
|
||||
Rect GetCtxRect()
|
||||
{
|
||||
float menuH = _ctxActions.Count * kCtxItemH + 8f;
|
||||
return new Rect(_ctxPos.x, _ctxPos.y, kCtxWidth, menuH);
|
||||
}
|
||||
|
||||
void DrawContextMenu(Vector2 mouse)
|
||||
{
|
||||
float menuH = _ctxActions.Count * kCtxItemH + 8f;
|
||||
Rect bgRect = new Rect(_ctxPos.x, _ctxPos.y, kCtxWidth, menuH);
|
||||
|
||||
// Shadow
|
||||
DrawRect(new Rect(bgRect.x + 3f, bgRect.y + 3f, bgRect.width, bgRect.height),
|
||||
new Color(0f, 0f, 0f, 0.45f));
|
||||
|
||||
// Background + border
|
||||
DrawBorderedBox(bgRect, colCtxBg, colCtxBorder, 1.5f);
|
||||
|
||||
// Header strip — item name
|
||||
if (_ctxItemIndex >= 0 && _ctxItemIndex < items.Count)
|
||||
{
|
||||
string title = items[_ctxItemIndex].DisplayName;
|
||||
DrawRect(new Rect(bgRect.x + 1.5f, bgRect.y + 1.5f, bgRect.width - 3f, 20f),
|
||||
new Color(0.15f, 0.22f, 0.15f, 1f));
|
||||
GUI.Label(new Rect(bgRect.x + kCtxPadX, bgRect.y + 3f, kCtxWidth - kCtxPadX * 2f, 16f),
|
||||
title.ToUpper(), MakeStyle(9, FontStyle.Bold, colWeaponAccent, TextAnchor.MiddleLeft));
|
||||
}
|
||||
|
||||
// Action rows
|
||||
for (int i = 0; i < _ctxActions.Count; i++)
|
||||
{
|
||||
float rowY = bgRect.y + 22f + i * kCtxItemH;
|
||||
Rect rowRect = new Rect(bgRect.x + 1.5f, rowY, bgRect.width - 3f, kCtxItemH);
|
||||
bool hover = rowRect.Contains(mouse);
|
||||
|
||||
if (hover)
|
||||
DrawRect(rowRect, colCtxHover);
|
||||
|
||||
Color textColor = _ctxActions[i].isDestructive
|
||||
? (hover ? Color.white : colCtxDestructive)
|
||||
: (hover ? Color.white : colCtxText);
|
||||
|
||||
GUI.Label(new Rect(bgRect.x + kCtxPadX, rowY + 2f, kCtxWidth - kCtxPadX * 2f, kCtxItemH - 4f),
|
||||
_ctxActions[i].label, MakeStyle(12, FontStyle.Normal, textColor, TextAnchor.MiddleLeft));
|
||||
|
||||
// Separator line between items
|
||||
if (i < _ctxActions.Count - 1)
|
||||
DrawRect(new Rect(bgRect.x + 6f, rowY + kCtxItemH - 1f, bgRect.width - 12f, 1f),
|
||||
new Color(1f, 1f, 1f, 0.06f));
|
||||
|
||||
// Click to fire action
|
||||
if (hover && Event.current.type == EventType.MouseDown && Event.current.button == 0)
|
||||
{
|
||||
_ctxActions[i].callback?.Invoke();
|
||||
CloseContextMenu();
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Style / Draw helpers ─────────────────────────────────────────
|
||||
static GUIStyle MakeStyle(int size, FontStyle style, Color color, TextAnchor align)
|
||||
{
|
||||
var s = new GUIStyle();
|
||||
s.fontSize = size;
|
||||
s.fontStyle = style;
|
||||
s.normal.textColor = color;
|
||||
s.alignment = align;
|
||||
return s;
|
||||
}
|
||||
|
||||
static void DrawRect(Rect r, Color c)
|
||||
{
|
||||
Color prev = GUI.color;
|
||||
GUI.color = c;
|
||||
GUI.DrawTexture(r, Texture2D.whiteTexture);
|
||||
GUI.color = prev;
|
||||
}
|
||||
|
||||
static void DrawBorderedBox(Rect r, Color fill, Color border, float thickness)
|
||||
{
|
||||
DrawRect(r, border);
|
||||
DrawRect(new Rect(r.x + thickness, r.y + thickness,
|
||||
r.width - thickness * 2f,
|
||||
r.height - thickness * 2f), fill);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user