411 lines
9.0 KiB
C#
411 lines
9.0 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using JetBrains.Annotations;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Events;
|
||
|
using UnityEngine.EventSystems;
|
||
|
using UnityEngine.Serialization;
|
||
|
using UnityEngine.UI;
|
||
|
|
||
|
namespace RoR2.UI;
|
||
|
|
||
|
[RequireComponent(typeof(RectTransform))]
|
||
|
public class ItemInventoryDisplay : UIBehaviour, ILayoutElement
|
||
|
{
|
||
|
private struct LayoutValues
|
||
|
{
|
||
|
public float width;
|
||
|
|
||
|
public float height;
|
||
|
|
||
|
public float iconSize;
|
||
|
|
||
|
public int iconsPerRow;
|
||
|
|
||
|
public float rowWidth;
|
||
|
|
||
|
public int rowCount;
|
||
|
|
||
|
public Vector3 iconLocalScale;
|
||
|
|
||
|
public Vector3 topLeftCorner;
|
||
|
}
|
||
|
|
||
|
private RectTransform rectTransform;
|
||
|
|
||
|
public GameObject itemIconPrefab;
|
||
|
|
||
|
[FormerlySerializedAs("iconWidth")]
|
||
|
public float itemIconPrefabWidth = 64f;
|
||
|
|
||
|
public float maxIconWidth = 64f;
|
||
|
|
||
|
public float maxHeight = 128f;
|
||
|
|
||
|
public float verticalMargin = 8f;
|
||
|
|
||
|
[SerializeField]
|
||
|
[HideInInspector]
|
||
|
private List<ItemIcon> itemIcons;
|
||
|
|
||
|
private ItemIndex[] itemOrder;
|
||
|
|
||
|
private int itemOrderCount;
|
||
|
|
||
|
private int[] itemStacks;
|
||
|
|
||
|
private float currentIconScale = 1f;
|
||
|
|
||
|
private float previousWidth;
|
||
|
|
||
|
private bool updateRequestPending;
|
||
|
|
||
|
[SerializeField]
|
||
|
private UnityEvent onItemClicked;
|
||
|
|
||
|
private Inventory inventory;
|
||
|
|
||
|
private bool inventoryWasValid;
|
||
|
|
||
|
private MPEventSystemLocator eventSystemLocator;
|
||
|
|
||
|
private bool isUninitialized => (object)rectTransform == null;
|
||
|
|
||
|
public MPEventSystem eventSystem => eventSystemLocator.eventSystem;
|
||
|
|
||
|
public float minWidth => preferredWidth;
|
||
|
|
||
|
public float preferredWidth => rectTransform.rect.width;
|
||
|
|
||
|
public float flexibleWidth => 0f;
|
||
|
|
||
|
public float minHeight => preferredHeight;
|
||
|
|
||
|
public float preferredHeight { get; set; }
|
||
|
|
||
|
public float flexibleHeight => 0f;
|
||
|
|
||
|
public int layoutPriority => 0;
|
||
|
|
||
|
public void SetSubscribedInventory([CanBeNull] Inventory newInventory)
|
||
|
{
|
||
|
if ((object)inventory != newInventory || (bool)inventory != inventoryWasValid)
|
||
|
{
|
||
|
if ((object)inventory != null)
|
||
|
{
|
||
|
inventory.onInventoryChanged -= OnInventoryChanged;
|
||
|
inventory = null;
|
||
|
}
|
||
|
inventory = newInventory;
|
||
|
inventoryWasValid = inventory;
|
||
|
if ((bool)inventory)
|
||
|
{
|
||
|
inventory.onInventoryChanged += OnInventoryChanged;
|
||
|
}
|
||
|
OnInventoryChanged();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnInventoryChanged()
|
||
|
{
|
||
|
if (base.isActiveAndEnabled)
|
||
|
{
|
||
|
if ((bool)inventory)
|
||
|
{
|
||
|
inventory.WriteItemStacks(itemStacks);
|
||
|
inventory.itemAcquisitionOrder.CopyTo(itemOrder);
|
||
|
itemOrderCount = inventory.itemAcquisitionOrder.Count;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Array.Clear(itemStacks, 0, itemStacks.Length);
|
||
|
itemOrderCount = 0;
|
||
|
}
|
||
|
RequestUpdateDisplay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int GetTotalVisibleItemCount()
|
||
|
{
|
||
|
int num = 0;
|
||
|
for (int i = 0; i < itemStacks.Length; i++)
|
||
|
{
|
||
|
if (ItemIsVisible((ItemIndex)i))
|
||
|
{
|
||
|
num += itemStacks[i];
|
||
|
}
|
||
|
}
|
||
|
return num;
|
||
|
}
|
||
|
|
||
|
private static bool ItemIsVisible(ItemIndex itemIndex)
|
||
|
{
|
||
|
ItemDef itemDef = ItemCatalog.GetItemDef(itemIndex);
|
||
|
if (itemDef != null)
|
||
|
{
|
||
|
return !itemDef.hidden;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private void AllocateIcons(int desiredItemCount)
|
||
|
{
|
||
|
if (desiredItemCount != itemIcons.Count)
|
||
|
{
|
||
|
while (itemIcons.Count > desiredItemCount)
|
||
|
{
|
||
|
UnityEngine.Object.Destroy(itemIcons[itemIcons.Count - 1].gameObject);
|
||
|
itemIcons.RemoveAt(itemIcons.Count - 1);
|
||
|
}
|
||
|
CalculateLayoutValues(out var v, desiredItemCount);
|
||
|
while (itemIcons.Count < desiredItemCount)
|
||
|
{
|
||
|
ItemIcon component = UnityEngine.Object.Instantiate(itemIconPrefab, rectTransform).GetComponent<ItemIcon>();
|
||
|
itemIcons.Add(component);
|
||
|
component.OnItemClicked = (Action)Delegate.Combine(component.OnItemClicked, (Action)delegate
|
||
|
{
|
||
|
onItemClicked?.Invoke();
|
||
|
});
|
||
|
LayoutIndividualIcon(in v, itemIcons.Count - 1);
|
||
|
}
|
||
|
}
|
||
|
OnIconCountChanged();
|
||
|
}
|
||
|
|
||
|
private float CalculateIconScale(int iconCount)
|
||
|
{
|
||
|
int num = (int)rectTransform.rect.width;
|
||
|
int num2 = (int)maxHeight;
|
||
|
int num3 = (int)itemIconPrefabWidth;
|
||
|
int num4 = num3;
|
||
|
int num5 = num3 / 8;
|
||
|
int num6 = Math.Max(num / num4, 1);
|
||
|
int num7 = HGMath.IntDivCeil(iconCount, num6);
|
||
|
while (num4 * num7 > num2)
|
||
|
{
|
||
|
num6++;
|
||
|
num4 = Math.Min(num / num6, num3);
|
||
|
num7 = HGMath.IntDivCeil(iconCount, num6);
|
||
|
if (num4 <= num5)
|
||
|
{
|
||
|
num4 = num5;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
num4 = Math.Min(num4, (int)maxIconWidth);
|
||
|
return (float)num4 / (float)num3;
|
||
|
}
|
||
|
|
||
|
private void OnIconCountChanged()
|
||
|
{
|
||
|
float num = CalculateIconScale(itemIcons.Count);
|
||
|
if (num != currentIconScale)
|
||
|
{
|
||
|
currentIconScale = num;
|
||
|
OnIconScaleChanged();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnIconScaleChanged()
|
||
|
{
|
||
|
LayoutAllIcons();
|
||
|
}
|
||
|
|
||
|
private void CalculateLayoutValues(out LayoutValues v, int iconCount)
|
||
|
{
|
||
|
float num = CalculateIconScale(itemIcons.Count);
|
||
|
Rect rect = rectTransform.rect;
|
||
|
v.width = rect.width;
|
||
|
v.iconSize = itemIconPrefabWidth * num;
|
||
|
v.iconsPerRow = Math.Max((int)v.width / (int)v.iconSize, 1);
|
||
|
v.rowWidth = (float)v.iconsPerRow * v.iconSize;
|
||
|
float num2 = (v.width - v.rowWidth) * 0.5f;
|
||
|
v.rowCount = HGMath.IntDivCeil(itemIcons.Count, v.iconsPerRow);
|
||
|
v.iconLocalScale = new Vector3(num, num, 1f);
|
||
|
v.topLeftCorner = new Vector3(rect.xMin + num2, rect.yMax - verticalMargin);
|
||
|
v.height = v.iconSize * (float)v.rowCount + verticalMargin * 2f;
|
||
|
}
|
||
|
|
||
|
private void LayoutAllIcons()
|
||
|
{
|
||
|
CalculateLayoutValues(out var v, itemIcons.Count);
|
||
|
int num = itemIcons.Count - (v.rowCount - 1) * v.iconsPerRow;
|
||
|
int i = 0;
|
||
|
int num2 = 0;
|
||
|
for (; i < v.rowCount; i++)
|
||
|
{
|
||
|
Vector3 topLeftCorner = v.topLeftCorner;
|
||
|
topLeftCorner.y += (float)i * (0f - v.iconSize);
|
||
|
int num3 = v.iconsPerRow;
|
||
|
if (i == v.rowCount - 1)
|
||
|
{
|
||
|
num3 = num;
|
||
|
}
|
||
|
int num4 = 0;
|
||
|
while (num4 < num3)
|
||
|
{
|
||
|
RectTransform obj = itemIcons[num2].rectTransform;
|
||
|
obj.localScale = v.iconLocalScale;
|
||
|
obj.localPosition = topLeftCorner;
|
||
|
topLeftCorner.x += v.iconSize;
|
||
|
num4++;
|
||
|
num2++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void LayoutIndividualIcon(in LayoutValues layoutValues, int i)
|
||
|
{
|
||
|
int num = i / layoutValues.iconsPerRow;
|
||
|
int num2 = i - num * layoutValues.iconsPerRow;
|
||
|
Vector3 topLeftCorner = layoutValues.topLeftCorner;
|
||
|
topLeftCorner.x += (float)num2 * layoutValues.iconSize;
|
||
|
topLeftCorner.y += (float)num * (0f - layoutValues.iconSize);
|
||
|
RectTransform obj = itemIcons[i].rectTransform;
|
||
|
obj.localPosition = topLeftCorner;
|
||
|
obj.localScale = layoutValues.iconLocalScale;
|
||
|
}
|
||
|
|
||
|
private void CacheComponents()
|
||
|
{
|
||
|
rectTransform = (RectTransform)base.transform;
|
||
|
}
|
||
|
|
||
|
protected override void Awake()
|
||
|
{
|
||
|
base.Awake();
|
||
|
CacheComponents();
|
||
|
itemStacks = ItemCatalog.RequestItemStackArray();
|
||
|
itemOrder = ItemCatalog.RequestItemOrderBuffer();
|
||
|
eventSystemLocator = GetComponent<MPEventSystemLocator>();
|
||
|
}
|
||
|
|
||
|
protected override void OnDestroy()
|
||
|
{
|
||
|
SetSubscribedInventory(null);
|
||
|
ItemCatalog.ReturnItemStackArray(itemStacks);
|
||
|
itemStacks = null;
|
||
|
ItemCatalog.ReturnItemOrderBuffer(itemOrder);
|
||
|
itemOrder = null;
|
||
|
base.OnDestroy();
|
||
|
}
|
||
|
|
||
|
protected override void OnEnable()
|
||
|
{
|
||
|
base.OnEnable();
|
||
|
if ((bool)inventory)
|
||
|
{
|
||
|
OnInventoryChanged();
|
||
|
}
|
||
|
RequestUpdateDisplay();
|
||
|
LayoutAllIcons();
|
||
|
}
|
||
|
|
||
|
private void RequestUpdateDisplay()
|
||
|
{
|
||
|
if (!updateRequestPending)
|
||
|
{
|
||
|
updateRequestPending = true;
|
||
|
RoR2Application.onNextUpdate += UpdateDisplay;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void UpdateDisplay()
|
||
|
{
|
||
|
updateRequestPending = false;
|
||
|
if (!this || !base.isActiveAndEnabled)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
ItemIndex[] array = ItemCatalog.RequestItemOrderBuffer();
|
||
|
int num = 0;
|
||
|
for (int i = 0; i < itemOrderCount; i++)
|
||
|
{
|
||
|
if (ItemIsVisible(itemOrder[i]))
|
||
|
{
|
||
|
array[num++] = itemOrder[i];
|
||
|
}
|
||
|
}
|
||
|
AllocateIcons(num);
|
||
|
for (int j = 0; j < num; j++)
|
||
|
{
|
||
|
ItemIndex itemIndex = array[j];
|
||
|
itemIcons[j].SetItemIndex(itemIndex, itemStacks[(int)itemIndex]);
|
||
|
}
|
||
|
ItemCatalog.ReturnItemOrderBuffer(array);
|
||
|
}
|
||
|
|
||
|
public void SetItems(List<ItemIndex> newItemOrder, int[] newItemStacks)
|
||
|
{
|
||
|
itemOrderCount = newItemOrder.Count;
|
||
|
for (int i = 0; i < itemOrderCount; i++)
|
||
|
{
|
||
|
itemOrder[i] = newItemOrder[i];
|
||
|
}
|
||
|
Array.Copy(newItemStacks, itemStacks, itemStacks.Length);
|
||
|
RequestUpdateDisplay();
|
||
|
}
|
||
|
|
||
|
public void SetItems(ItemIndex[] newItemOrder, int newItemOrderCount, int[] newItemStacks)
|
||
|
{
|
||
|
itemOrderCount = newItemOrderCount;
|
||
|
Array.Copy(newItemOrder, itemOrder, itemOrderCount);
|
||
|
Array.Copy(newItemStacks, itemStacks, itemStacks.Length);
|
||
|
RequestUpdateDisplay();
|
||
|
}
|
||
|
|
||
|
public void ResetItems()
|
||
|
{
|
||
|
itemOrderCount = 0;
|
||
|
Array.Clear(itemStacks, 0, itemStacks.Length);
|
||
|
RequestUpdateDisplay();
|
||
|
}
|
||
|
|
||
|
protected override void OnRectTransformDimensionsChange()
|
||
|
{
|
||
|
base.OnRectTransformDimensionsChange();
|
||
|
if ((bool)rectTransform)
|
||
|
{
|
||
|
float width = rectTransform.rect.width;
|
||
|
if (width != previousWidth)
|
||
|
{
|
||
|
previousWidth = width;
|
||
|
LayoutAllIcons();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void CalculateLayoutInputHorizontal()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public void CalculateLayoutInputVertical()
|
||
|
{
|
||
|
if (!isUninitialized)
|
||
|
{
|
||
|
CalculateLayoutValues(out var v, itemIcons.Count);
|
||
|
preferredHeight = v.height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected void OnValidate()
|
||
|
{
|
||
|
CacheComponents();
|
||
|
}
|
||
|
|
||
|
public bool SelectFirstItemIcon()
|
||
|
{
|
||
|
if (itemIcons.Count > 0)
|
||
|
{
|
||
|
eventSystem.SetSelectedObject(itemIcons[0].gameObject);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public int GetItemCount()
|
||
|
{
|
||
|
return itemOrderCount;
|
||
|
}
|
||
|
}
|