public void Refresh(bool wantClobber = true) { LogDebug(string.Format("[LimitItems] Refresh: {0} {1} {2} {3}", index, filteredInventory.Count, itemLimit, new Traverse(inventoryWidget).Field("scrollbarArea").GetValue <UnityEngine.UI.ScrollRect>().verticalNormalizedPosition)); if (index > filteredInventory.Count - itemsOnScreen) { index = filteredInventory.Count - itemsOnScreen; } if (filteredInventory.Count < itemsOnScreen) { index = 0; } if (index < 0) { index = 0; } if (Spam) { LogSpam(string.Format("[LimitItems] Refresh(F): {0} {1} {2} {3}", index, filteredInventory.Count, itemLimit, new Traverse(inventoryWidget).Field("scrollbarArea").GetValue <UnityEngine.UI.ScrollRect>().verticalNormalizedPosition)); } Func <ListElementController_BASE_NotListView, string> pp = lec => { return(string.Format("[id:{0},damage:{1},quantity:{2},id:{3}]" , GetRef(lec).ComponentDefID , GetRef(lec).DamageLevel , lec.quantity , lec.GetId())); }; var iw_corrupted_add = inventoryWidget.localInventory.Where(x => !ielCache.Contains(x)).ToList(); if (iw_corrupted_add.Count > 0) { LogError("inventoryWidget has been corrupted, items were added directly: " + string.Join(", ", iw_corrupted_add.Select(c => c.controller).Select(pp).ToArray())); } var iw_corrupted_remove = ielCache.Where(x => !inventoryWidget.localInventory.Contains(x)).ToList(); if (iw_corrupted_remove.Count > 0) { LogError("inventoryWidget has been corrupted, iel elements were removed."); } if (iw_corrupted_add.Any() || iw_corrupted_remove.Any()) { LogWarning("Restoring to last good state. Duplication or item loss may occur."); inventoryWidget.localInventory = ielCache.ToArray().ToList(); } var toShow = filteredInventory.Skip(index).Take(itemLimit).ToList(); var icc = ielCache.ToList(); if (Spam) { LogSpam("[LimitItems] Showing: " + string.Join(", ", toShow.Select(pp).ToArray())); } var details = new List <string>(); toShow.ForEach(lec => { var iw = icc[0]; icc.RemoveAt(0); var cref = GetRef(lec); iw.ClearEverything(); iw.ComponentRef = cref; lec.ItemWidget = iw; iw.SetData(lec, inventoryWidget, lec.quantity, false, null); lec.SetupLook(iw); iw.gameObject.SetActive(true); details.Insert(0, string.Format("enabled {0} {1}", iw.ComponentRef.ComponentDefID, iw.GetComponent <UnityEngine.RectTransform>().anchoredPosition)); }); icc.ForEach(unused => unused.gameObject.SetActive(false)); var listElemSize = 64.0f; var spacerTotal = 16.0f; // IEL elements are 64 tall, but have a total of 80 pixels between each when considering spacing. var spacerHalf = spacerTotal * .5f; var tsize = listElemSize + spacerTotal; var virtualStartSize = tsize * index - spacerHalf; DummyStart.gameObject.SetActive(index > 0); //If nothing prefixing, must disable to prevent halfspacer offset. DummyStart.sizeDelta = new UnityEngine.Vector2(100, virtualStartSize); DummyStart.SetAsFirstSibling(); var itemsHanging = filteredInventory.Count - (index + ielCache.Count(ii => ii.gameObject.activeSelf)); var ap1 = ielCache[0].GetComponent <UnityEngine.RectTransform>().anchoredPosition; var ap2 = ielCache[1].GetComponent <UnityEngine.RectTransform>().anchoredPosition; LogDebug(string.Format("[LimitItems] Items prefixing {0} hanging {1} total {2} {3}/{4}", index, itemsHanging, filteredInventory.Count, ap1, ap2)); var virtualEndSize = tsize * itemsHanging - spacerHalf; DummyEnd.gameObject.SetActive(itemsHanging > 0); //If nothing postfixing, must disable to prevent halfspacer offset. DummyEnd.sizeDelta = new UnityEngine.Vector2(100, virtualEndSize); DummyEnd.SetAsLastSibling(); new Traverse(instance).Method("RefreshInventorySelectability").GetValue(); if (Spam) { var sr = new Traverse(inventoryWidget).Field("scrollbarArea").GetValue <UnityEngine.UI.ScrollRect>(); LogSpam(string.Format("[LimitItems] RefreshDone dummystart {0} dummyend {1} vnp {2} lli {3}" , DummyStart.anchoredPosition.y , DummyEnd.anchoredPosition.y , sr.verticalNormalizedPosition , "(" + string.Join(", ", details.ToArray()) + ")" )); } }
public PatchMechlabLimitItems(MechLabPanel instance) { try { var sw = new Stopwatch(); sw.Start(); this.instance = instance; this.inventoryWidget = new Traverse(instance).Field("inventoryWidget") .GetValue <MechLabInventoryWidget>() .LogIfNull("inventoryWidget is null"); LogDebug($"StorageInventory contains {instance.storageInventory.Count}"); if (instance.IsSimGame) { new Traverse(instance).Field("originalStorageInventory").SetValue(instance.storageInventory.LogIfNull("storageInventory is null")); } LogDebug($"Mechbay Patch initialized :simGame? {instance.IsSimGame}"); List <ListElementController_BASE_NotListView> BuildRawInventory() => instance.storageInventory.Select <MechComponentRef, ListElementController_BASE_NotListView>(componentRef => { componentRef.LogIfNull("componentRef is null"); componentRef.DataManager = instance.dataManager.LogIfNull("(MechLabPanel instance).dataManager is null"); componentRef.RefreshComponentDef(); componentRef.Def.LogIfNull("componentRef.Def is null"); var count = (!instance.IsSimGame ? int.MinValue : instance.sim.GetItemCount(componentRef.Def.Description, componentRef.Def.GetType(), instance.sim.GetItemCountDamageType(componentRef))); if (componentRef.ComponentDefType == ComponentType.Weapon) { ListElementController_InventoryWeapon_NotListView controller = new ListElementController_InventoryWeapon_NotListView(); controller.InitAndFillInSpecificWidget(componentRef, null, instance.dataManager, null, count, false); return(controller); } else { ListElementController_InventoryGear_NotListView controller = new ListElementController_InventoryGear_NotListView(); controller.InitAndFillInSpecificWidget(componentRef, null, instance.dataManager, null, count, false); return(controller); } }).ToList(); /* Build a list of data only for all components. */ rawInventory = Sort(BuildRawInventory()); InventoryItemElement_NotListView mkiie(bool nonexistant) { var nlv = instance.dataManager.PooledInstantiate(ListElementController_BASE_NotListView.INVENTORY_ELEMENT_PREFAB_NotListView , BattleTechResourceType.UIModulePrefabs, null, null, null) .LogIfNull("Unable to instantiate INVENTORY_ELEMENT_PREFAB_NotListView") .GetComponent <InventoryItemElement_NotListView>() .LogIfNull("Inventory_Element_prefab does not contain a NLV"); nlv.gameObject.IsDestroyedError("NLV gameObject has been destroyed"); nlv.gameObject.LogIfNull("NLV gameObject has been destroyed"); if (!nonexistant) { nlv.SetRadioParent(new Traverse(inventoryWidget).Field("inventoryRadioSet").GetValue <HBSRadioSet>().LogIfNull("inventoryRadioSet is null")); nlv.gameObject.transform.SetParent(new Traverse(inventoryWidget).Field("listParent").GetValue <UnityEngine.Transform>().LogIfNull("listParent is null"), false); nlv.gameObject.transform.localScale = UnityEngine.Vector3.one; } return(nlv); }; iieTmp = mkiie(true); /* Allocate very few visual elements, as this is extremely slow for both allocation and deallocation. * It's the difference between a couple of milliseconds and several seconds for many unique items in inventory * This is the core of the fix, the rest is just to make it work within HBS's existing code. */ List <InventoryItemElement_NotListView> make_ielCache() => Enumerable.Repeat <Func <InventoryItemElement_NotListView> >(() => mkiie(false), itemLimit) .Select(thunk => thunk()) .ToList(); ielCache = make_ielCache(); var li = new Traverse(inventoryWidget).Field("localInventory").GetValue <List <InventoryItemElement_NotListView> >(); ielCache.ForEach(iw => li.Add(iw)); // End var lp = new Traverse(inventoryWidget).Field("listParent").GetValue <UnityEngine.Transform>(); // DummyStart&End are blank rects stored at the beginning and end of the list so that unity knows how big the scrollrect should be // "placeholders" if (DummyStart == null) { DummyStart = new UnityEngine.GameObject().AddComponent <UnityEngine.RectTransform>(); } if (DummyEnd == null) { DummyEnd = new UnityEngine.GameObject().AddComponent <UnityEngine.RectTransform>(); } DummyStart.SetParent(lp, false); DummyEnd.SetParent(lp, false); LogDebug(string.Format("[LimitItems] inventory cached in {0} ms", sw.Elapsed.TotalMilliseconds)); FilterChanged(); } catch (Exception e) { LogException(e); } }