From 3c76f3c58860c3af8dea157001a3a401442394d1 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 15 Jun 2024 13:14:22 -0700 Subject: [PATCH] gridview containers can accept --- DrawMultiSelect.cs | 129 ++++++++++++++++--- MultiSelect.cs | 13 +- Patches/MultiSelectPatches.cs | 185 ++++++++++++++++++++-------- Patches/TradingAutoSwitchPatches.cs | 4 +- R.cs | 4 +- Settings.cs | 10 ++ UIFixes.csproj | 3 + 7 files changed, 269 insertions(+), 79 deletions(-) diff --git a/DrawMultiSelect.cs b/DrawMultiSelect.cs index 5ebe8d5..2459e0a 100644 --- a/DrawMultiSelect.cs +++ b/DrawMultiSelect.cs @@ -1,34 +1,68 @@ -using EFT.UI; +using Comfort.Common; +using EFT.UI; using EFT.UI.DragAndDrop; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; namespace UIFixes { public class DrawMultiSelect : MonoBehaviour { - Texture2D selectTexture; + private Texture2D selectTexture; - Vector3 selectOrigin; - Vector3 selectEnd; + private Vector3 selectOrigin; + private Vector3 selectEnd; - bool drawing; + private GraphicRaycaster preloaderRaycaster; + + private bool drawing; public void Start() { selectTexture = new Texture2D(1, 1); - selectTexture.SetPixel(0, 0, new Color(1f, 1f, 1f, 0.8f)); + selectTexture.SetPixel(0, 0, new Color(1f, 1f, 1f, 0.6f)); selectTexture.Apply(); + + preloaderRaycaster = Singleton.Instance.transform.GetChild(0).GetComponent(); + if (preloaderRaycaster == null) + { + throw new InvalidOperationException("DrawMultiSelect couldn't find the PreloaderUI GraphicRayCaster"); + } } public void Update() { + if (!Settings.EnableMultiSelect.Value) + { + return; + } + if (Input.GetKeyDown(KeyCode.Mouse0) && ItemUiContext.Instance.R().ItemContext == null) { + PointerEventData eventData = new(EventSystem.current); + eventData.position = Input.mousePosition; + + List results = new(); + var preloaderRaycaster = Singleton.Instance.transform.GetChild(0).GetComponent(); + preloaderRaycaster.Raycast(eventData, results); + + foreach (GameObject gameObject in results.Select(r => r.gameObject)) + { + var dragInterfaces = gameObject.GetComponents() + .Where(c => c is IDragHandler || c is IBeginDragHandler) + .Where(c => c is not ScrollRectNoDrag) // this disables scrolling, it doesn't add it + .Where(c => c.name != "Inner"); // there's a random DragTrigger sitting in ItemInfoWindows + + if (dragInterfaces.Any()) + { + return; + } + } + selectOrigin = Input.mousePosition; drawing = true; } @@ -37,20 +71,62 @@ namespace UIFixes { selectEnd = Input.mousePosition; - Rect selectRect = new(selectOrigin.x, selectOrigin.y, selectEnd.x - selectOrigin.x, selectEnd.y - selectOrigin.y); - foreach (GridItemView gridItemView in GetComponentsInChildren()) + Rect selectRect = new(selectOrigin, selectEnd - selectOrigin); + foreach (GridItemView gridItemView in GetComponentsInChildren().Concat(Singleton.Instance.GetComponentsInChildren())) { RectTransform itemTransform = gridItemView.GetComponent(); - Rect screenRect = new((Vector2)itemTransform.position + itemTransform.rect.position, itemTransform.rect.size); + Rect itemRect = new((Vector2)itemTransform.position + itemTransform.rect.position * itemTransform.lossyScale, itemTransform.rect.size * itemTransform.lossyScale); - if (selectRect.Overlaps(screenRect, true)) + if (selectRect.Overlaps(itemRect, true)) { + // Otherwise, ensure it's not overlapped by window UI + PointerEventData eventData = new(EventSystem.current); + + // Non-absolute width/height + float width = itemRect.xMax - itemRect.xMin; + float height = itemRect.yMax - itemRect.yMin; + + List raycastResults = new(); + eventData.position = new Vector2(itemRect.xMin + 0.1f * width, itemRect.yMin + 0.1f * height); + preloaderRaycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + MultiSelect.Deselect(gridItemView); + continue; + } + + raycastResults.Clear(); + eventData.position = new Vector2(itemRect.xMin + 0.1f * width, itemRect.yMax - 0.1f * height); + preloaderRaycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + MultiSelect.Deselect(gridItemView); + continue; + } + + raycastResults.Clear(); + eventData.position = new Vector2(itemRect.xMax - 0.1f * width, itemRect.yMax - 0.1f * height); + preloaderRaycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + MultiSelect.Deselect(gridItemView); + continue; + } + + raycastResults.Clear(); + eventData.position = new Vector2(itemRect.xMax - 0.1f * width, itemRect.yMin + 0.1f * height); + preloaderRaycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + MultiSelect.Deselect(gridItemView); + continue; + } + MultiSelect.Select(gridItemView); + continue; } - else - { - MultiSelect.Deselect(gridItemView); - } + + MultiSelect.Deselect(gridItemView); } } @@ -82,6 +158,27 @@ namespace UIFixes GUI.DrawTexture(lineArea, selectTexture); } } + } + public static class TransformExtensions + { + public static bool IsDescendantOf(this Transform transform, Transform target) + { + if (transform == target) + { + return true; + } + + while (transform.parent != null) + { + transform = transform.parent; + if (transform == target) + { + return true; + } + } + + return false; + } } } diff --git a/MultiSelect.cs b/MultiSelect.cs index b85b490..f64c3b6 100644 --- a/MultiSelect.cs +++ b/MultiSelect.cs @@ -1,10 +1,6 @@ -using EFT.InventoryLogic; -using EFT.UI.DragAndDrop; -using System; +using EFT.UI.DragAndDrop; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using TMPro; using UnityEngine; @@ -22,8 +18,13 @@ namespace UIFixes { // 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(); } @@ -91,7 +92,7 @@ namespace UIFixes public static bool Active { - get { return SelectedItemViews.Any(); } + get { return SelectedItemViews.Count > 1; } } public static bool IsSelected(GridItemView itemView) diff --git a/Patches/MultiSelectPatches.cs b/Patches/MultiSelectPatches.cs index b230d82..1fddc45 100644 --- a/Patches/MultiSelectPatches.cs +++ b/Patches/MultiSelectPatches.cs @@ -1,5 +1,5 @@ using Aki.Reflection.Patching; -using Comfort.Common; +using EFT.InventoryLogic; using EFT.UI; using EFT.UI.DragAndDrop; using HarmonyLib; @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using TMPro; using UnityEngine; using UnityEngine.EventSystems; @@ -29,6 +28,7 @@ namespace UIFixes new EndDragPatch().Enable(); new GridViewCanAcceptPatch().Enable(); + new GridViewAcceptItemPatch().Enable(); new SlotViewCanAcceptPatch().Enable(); new SlotViewAcceptItemPatch().Enable(); } @@ -43,11 +43,16 @@ namespace UIFixes [PatchPostfix] public static void Postfix(CommonUI __instance) { + if (!Settings.EnableMultiSelect.Value) + { + return; + } + MultiSelect.Initialize(); __instance.InventoryScreen.GetOrAddComponent(); - //__instance.TransferItemsInRaidScreen.GetOrAddComponent(); - //__instance.TransferItemsScreen.GetOrAddComponent(); + __instance.TransferItemsInRaidScreen.GetOrAddComponent(); + __instance.TransferItemsScreen.GetOrAddComponent(); } } @@ -61,13 +66,18 @@ namespace UIFixes [PatchPostfix] public static void Postfix(GridItemView __instance, PointerEventData.InputButton button) { - if (button == PointerEventData.InputButton.Left && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))) + if (!Settings.EnableMultiSelect.Value) + { + return; + } + + if (__instance.Item != null && button == PointerEventData.InputButton.Left && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))) { MultiSelect.Toggle(__instance); return; } - if (button == PointerEventData.InputButton.Left)// && !MultiSelect.IsSelected(__instance)) + if (button == PointerEventData.InputButton.Left) { MultiSelect.Clear(); } @@ -84,6 +94,11 @@ namespace UIFixes [PatchPostfix] public static void Postfix(ItemView __instance, PointerEventData eventData) { + if (!Settings.EnableMultiSelect.Value) + { + return; + } + if (eventData.button == PointerEventData.InputButton.Left && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))) { // This will be shift-click, let it cook @@ -107,6 +122,11 @@ namespace UIFixes [PatchPostfix] public static void Postfix(ItemView __instance) { + if (!Settings.EnableMultiSelect.Value) + { + return; + } + if (__instance is GridItemView gridItemView) { MultiSelect.Deselect(gridItemView); @@ -124,12 +144,22 @@ namespace UIFixes [PatchPrefix] public static void Prefix() { + if (!Settings.EnableMultiSelect.Value) + { + return; + } + MultiSelect.BeginDrag(); } [PatchPostfix] public static void Postfix(ItemView __instance) { + if (!Settings.EnableMultiSelect.Value) + { + return; + } + MultiSelect.ShowDragCount(__instance.DraggedItemView); } } @@ -144,6 +174,11 @@ namespace UIFixes [PatchPostfix] public static void Postfix() { + if (!Settings.EnableMultiSelect.Value) + { + return; + } + MultiSelect.EndDrag(); } } @@ -158,27 +193,84 @@ namespace UIFixes [PatchPrefix] public static bool Prefix(GridView __instance, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result) { - if (InPatch || !MultiSelect.Active) + if (!Settings.EnableMultiSelect.Value || InPatch || !MultiSelect.Active) { return true; } + // Reimplementing this in order to control the simulate param. Need to *not* simulate, then rollback myself in order to test + // multiple items going in + var wrappedInstance = __instance.R(); operation = default; __result = false; - return false; - /* InPatch = true; + if (__instance.Grid == null || wrappedInstance.NonInteractable) + { + return false; + } + + if (targetItemContext != null && !targetItemContext.ModificationAvailable) + { + operation = new StashGridClass.GClass3291(__instance.Grid); + return false; + } + + Item targetItem = __instance.method_8(targetItemContext); + + // baby steps: bail if no targetItem for now + if (targetItem == null) + { + return false; + } + + Stack operations = new(); foreach (ItemContextClass itemContext in MultiSelect.ItemContexts) { - __result = __instance.CanAccept(itemContext, targetItemContext, out operation); - if (!__result) + operation = wrappedInstance.TraderController.ExecutePossibleAction(itemContext, targetItem, false /* splitting */, false /* simulate */); + if (__result = operation.Succeeded) + { + operations.Push(operation); + } + else { break; } } - InPatch = false; - return false;*/ + // We didn't simulate so now we undo + while (operations.Any()) + { + operations.Pop().Value?.RollBack(); + } + + // result and operation are set to the last one that completed - so success if they all passed, or the first failure + return false; + } + } + + public class GridViewAcceptItemPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(GridView), nameof(GridView.AcceptItem)); + } + + [PatchPrefix] + public static bool Prefix(GridView __instance, ItemContextAbstractClass targetItemContext, ref Task __result) + { + if (!Settings.EnableMultiSelect.Value || InPatch || !MultiSelect.Active) + { + return true; + } + + InPatch = true; + + var serializer = __instance.GetOrAddComponent(); + __result = serializer.Initialize(MultiSelect.ItemContexts, itemContext => __instance.AcceptItem(itemContext, targetItemContext)); + + __result.ContinueWith(_ => { InPatch = false; }); + + return false; } } @@ -190,27 +282,43 @@ namespace UIFixes } [PatchPrefix] - public static bool Prefix(SlotView __instance, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result) + public static bool Prefix(SlotView __instance, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result, InventoryControllerClass ___InventoryController) { - if (InPatch || !MultiSelect.Active) + if (!Settings.EnableMultiSelect.Value || InPatch || !MultiSelect.Active) { return true; } - operation = default; - __result = false; + // Reimplementing this in order to control the simulate param. Need to *not* simulate, then rollback myself in order to test + // multiple items going in + if (targetItemContext != null && !targetItemContext.ModificationAvailable || + __instance.ParentItemContext != null && !__instance.ParentItemContext.ModificationAvailable) + { + operation = new StashGridClass.GClass3291(__instance.Slot); + return false; + } - InPatch = true; + Stack operations = new(); foreach (ItemContextClass itemContext in MultiSelect.ItemContexts) { - __result = __instance.CanAccept(itemContext, targetItemContext, out operation); - if (!__result) + __result = itemContext.CanAccept(__instance.Slot, __instance.ParentItemContext, ___InventoryController, out operation, false /* simulate */); + if (operation.Succeeded) + { + operations.Push(operation); + } + else { break; } } - InPatch = false; + // We didn't simulate so now we undo + while(operations.Any()) + { + operations.Pop().Value?.RollBack(); + } + + // result and operation are set to the last one that completed - so success if they all passed, or the first failure return false; } } @@ -225,22 +333,18 @@ namespace UIFixes [PatchPrefix] public static bool Prefix(SlotView __instance, ItemContextAbstractClass targetItemContext, ref Task __result) { - if (InPatch || !MultiSelect.Active) + if (!Settings.EnableMultiSelect.Value || InPatch || !MultiSelect.Active) { return true; } InPatch = true; - /* __result = Task.CompletedTask; - foreach (ItemContextClass itemContext in MultiSelect.ItemContexts.ToList()) - { - __result = __result.ContinueWith(_ => __instance.AcceptItem(itemContext, targetItemContext)); - }*/ var serializer = __instance.GetOrAddComponent(); __result = serializer.Initialize(MultiSelect.ItemContexts, itemContext => __instance.AcceptItem(itemContext, targetItemContext)); __result.ContinueWith(_ => { InPatch = false; }); + return false; } } @@ -254,6 +358,7 @@ namespace UIFixes public Task Initialize(IEnumerable itemContexts, Func func) { + // Create new contexts because the underlying ones will be disposed when drag ends this.itemContexts = new(itemContexts); this.func = func; @@ -283,31 +388,5 @@ namespace UIFixes } } } - - /*public class GridViewAcceptItemPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(GridView), nameof(GridView.AcceptItem)); - } - - [PatchPrefix] - public static async bool Prefix(GridView __instance, ItemContextAbstractClass targetItemContext, ref Task __result) - { - if (InPatch || !MultiSelectContext.Instance.Any()) - { - return true; - } - - InPatch = true; - foreach (ItemContextClass itemContext in MultiSelectContext.Instance.SelectedDragContexts) - { - await __instance.AcceptItem(itemContext, targetItemContext); - } - - InPatch = false; - return false; - } - }*/ } } diff --git a/Patches/TradingAutoSwitchPatches.cs b/Patches/TradingAutoSwitchPatches.cs index 5fb848f..ba6e537 100644 --- a/Patches/TradingAutoSwitchPatches.cs +++ b/Patches/TradingAutoSwitchPatches.cs @@ -78,7 +78,7 @@ namespace UIFixes return true; } - if (!___bool_8 && ctrlPressed && tradingItemView.TraderAssortmentControler.QuickFindTradingAppropriatePlace(__instance.Item, null)) + if (!___bool_8 && ctrlPressed && tradingItemView.TraderAssortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null)) { __instance.ItemContext.CloseDependentWindows(); __instance.HideTooltip(); @@ -91,7 +91,7 @@ namespace UIFixes if (___bool_8) { - tradingItemView.TraderAssortmentControler.SelectItem(__instance.Item); + tradingItemView.TraderAssortmentController.SelectItem(__instance.Item); BuyTab.OnPointerClick(null); diff --git a/R.cs b/R.cs index 2ea179a..15764fb 100644 --- a/R.cs +++ b/R.cs @@ -295,7 +295,7 @@ namespace UIFixes public static void InitTypes() { Type = typeof(EFT.UI.DragAndDrop.GridView); - TraderControllerField = AccessTools.GetDeclaredFields(Type).Single(f => f.FieldType == typeof(TraderControllerClass)); + TraderControllerField = AccessTools.GetDeclaredFields(Type).Single(f => f.FieldType == typeof(TraderControllerClass)); // field gclass2758_0 NonInteractableField = AccessTools.Field(Type, "_nonInteractable"); } @@ -562,7 +562,7 @@ namespace UIFixes TraderAssortmentControllerField = AccessTools.GetDeclaredFields(Type).Single(t => t.FieldType == typeof(TraderAssortmentControllerClass)); } - public TraderAssortmentControllerClass TraderAssortmentControler { get { return (TraderAssortmentControllerClass)TraderAssortmentControllerField.GetValue(Value); } } + public TraderAssortmentControllerClass TraderAssortmentController { get { return (TraderAssortmentControllerClass)TraderAssortmentControllerField.GetValue(Value); } } } public class GridWindow(object value) : UIInputNode(value) diff --git a/Settings.cs b/Settings.cs index abe8aab..4720853 100644 --- a/Settings.cs +++ b/Settings.cs @@ -54,6 +54,7 @@ namespace UIFixes public static ConfigEntry MouseScrollMultiInRaid { get; set; } // Advanced // Inventory + public static ConfigEntry EnableMultiSelect { get; set; } public static ConfigEntry SwapItems { get; set; } public static ConfigEntry SwapImpossibleContainers { get; set; } public static ConfigEntry SynchronizeStashScrolling { get; set; } @@ -272,6 +273,15 @@ namespace UIFixes new ConfigurationManagerAttributes { IsAdvanced = true }))); // Inventory + configEntries.Add(EnableMultiSelect = config.Bind( + InventorySection, + "Enable Multiselect", + true, + new ConfigDescription( + "Enable multiselect via Shift-click and drag-to-select", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(SwapItems = config.Bind( InventorySection, "Enable In-Place Item Swapping", diff --git a/UIFixes.csproj b/UIFixes.csproj index cdc2bc0..85ad6ae 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -66,6 +66,9 @@ $(PathToSPT)\EscapeFromTarkov_Data\Managed\UnityEngine.UI.dll + + $(PathToSPT)\EscapeFromTarkov_Data\Managed\UnityEngine.UIModule.dll +