using EFT.InventoryLogic; using EFT.UI; using EFT.UI.DragAndDrop; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; namespace UIFixes { public class MultiSelect { private static GameObject SelectedMarkTemplate; private static GameObject SelectedBackgroundTemplate; private static readonly Dictionary SelectedItems = []; private static readonly Dictionary SecondaryItems = []; public static void Initialize() { // Grab the selection objects from ragfair as templates RagfairNewOfferItemView ragfairNewOfferItemView = ItemViewFactory.CreateFromPool("ragfair_layout"); SelectedMarkTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedMark, null, false); UnityEngine.Object.DontDestroyOnLoad(SelectedMarkTemplate); SelectedBackgroundTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedBackground, null, false); UnityEngine.Object.DontDestroyOnLoad(SelectedBackgroundTemplate); ragfairNewOfferItemView.ReturnToPool(); } public static void Toggle(GridItemView itemView, bool secondary = false) { var dictionary = secondary ? SecondaryItems : SelectedItems; ItemContextClass itemContext = dictionary.FirstOrDefault(x => x.Value == itemView).Key; if (itemContext != null) { Deselect(itemContext, secondary); } else { Select(itemView, secondary); } } public static void Clear() { // ToList() because modifying the collection foreach (ItemContextClass itemContext in SelectedItems.Keys.ToList()) { Deselect(itemContext); } } public static void Select(GridItemView itemView, bool secondary = false) { var dictionary = secondary ? SecondaryItems : SelectedItems; if (itemView.IsSelectable() && !SelectedItems.Any(x => x.Key.Item == itemView.Item) && !SecondaryItems.Any(x => x.Key.Item == itemView.Item)) { ItemContextClass itemContext = new MultiSelectItemContext(itemView.ItemContext, itemView.ItemRotation); // Remove event handlers that no one cares about and cause stack overflows itemContext.method_1(); // Subscribe to window closures to deselect GClass3085 windowContext = itemView.GetComponentInParent()?.WindowContext ?? itemView.GetComponentInParent()?.WindowContext; if (windowContext != null) { windowContext.OnClose += () => Deselect(itemContext); } dictionary.Add(itemContext, itemView); ShowSelection(itemView); } } public static void Deselect(ItemContextClass itemContext, bool secondary = false) { var dictionary = secondary ? SecondaryItems : SelectedItems; if (dictionary.TryGetValue(itemContext, out GridItemView itemView)) { HideSelection(itemView); } dictionary.Remove(itemContext); itemContext.Dispose(); } public static void Deselect(GridItemView itemView, bool secondary = false) { var dictionary = secondary ? SecondaryItems : SelectedItems; ItemContextClass itemContext = dictionary.FirstOrDefault(x => x.Value == itemView).Key; if (itemContext != null) { dictionary.Remove(itemContext); itemContext.Dispose(); HideSelection(itemView); } } public static void OnKillItemView(GridItemView itemView) { ItemContextClass itemContext = SelectedItems.FirstOrDefault(x => x.Value == itemView).Key; if (itemContext != null) { SelectedItems[itemContext] = null; HideSelection(itemView); } } public static void OnNewItemView(GridItemView itemView) { ItemContextClass itemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == itemView.Item).Key; if (itemContext != null) { // We need to refresh the context because if the item moved, it has a new address Deselect(itemContext); Select(itemView); } } public static bool IsSelected(GridItemView itemView, bool secondary = false) { var dictionary = secondary ? SecondaryItems : SelectedItems; return dictionary.Any(x => x.Key.Item == itemView.Item); } public static void Prune() { foreach (var entry in SelectedItems.ToList()) { if (entry.Value == null) { Deselect(entry.Key); } } } public static void CombineSecondary() { foreach (var entry in SecondaryItems) { SelectedItems.Add(entry.Key, entry.Value); } SecondaryItems.Clear(); } public static IEnumerable ItemContexts { get { return SelectedItems.Keys; } } public static IEnumerable SecondaryContexts { get { return SecondaryItems.Keys; } } public static int Count { get { return SelectedItems.Count; } } public static int SecondaryCount { get { return SecondaryItems.Count; } } public static bool Active { get { return SelectedItems.Count > 0; } } public static void ShowDragCount(DraggedItemView draggedItemView) { if (Count > 1) { GameObject textOverlay = new("MultiSelectText", [typeof(RectTransform), typeof(TextMeshProUGUI)]); textOverlay.transform.parent = draggedItemView.transform; textOverlay.transform.SetAsLastSibling(); textOverlay.SetActive(true); RectTransform overlayRect = textOverlay.GetComponent(); overlayRect.anchorMin = Vector2.zero; overlayRect.anchorMax = Vector2.one; overlayRect.anchoredPosition = new Vector2(0.5f, 0.5f); TextMeshProUGUI text = textOverlay.GetComponent(); text.text = MultiSelect.Count.ToString(); text.fontSize = 36; text.alignment = TextAlignmentOptions.Baseline; } } private static void ShowSelection(GridItemView itemView) { GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject; if (selectedMark == null) { selectedMark = UnityEngine.Object.Instantiate(SelectedMarkTemplate, itemView.transform, false); selectedMark.name = "SelectedMark"; } selectedMark.SetActive(true); GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject; if (selectedBackground == null) { selectedBackground = UnityEngine.Object.Instantiate(SelectedBackgroundTemplate, itemView.transform, false); selectedBackground.transform.SetAsFirstSibling(); selectedBackground.name = "SelectedBackground"; } selectedBackground.SetActive(true); } private static void HideSelection(GridItemView itemView) { if (itemView == null) { return; } GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject; GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject; selectedMark?.SetActive(false); selectedBackground?.SetActive(false); } } public class MultiSelectItemContext(ItemContextAbstractClass itemContext, ItemRotation rotation) : ItemContextClass(itemContext, rotation) { public override bool SplitAvailable => false; // used by ItemUiContext.QuickFindAppropriatePlace, the one that picks a container, i.e. ctrl-click // ItemContextClass (drag) defaults to None, but we want what the underlying item allows public override bool CanQuickMoveTo(ETargetContainer targetContainer) { if (this.GClass2813_0 != null) { return this.GClass2813_0.CanQuickMoveTo(targetContainer); } return base.CanQuickMoveTo(targetContainer); } } public static class MultiSelectExtensions { public static bool IsSelectable(this ItemView itemView) { // Common non-interactable stuff if (!itemView.IsInteractable || !itemView.IsSearched || itemView.RemoveError.Value != null) { return false; } // You can't multi-select trader's items or items being sold if (itemView is TradingItemView tradingItemView) { if (itemView is not TradingPlayerItemView || tradingItemView.R().IsBeingSold) { return false; } } return true; } } }