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 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(); 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(); } 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 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; } }