diff --git a/Multiselect/DrawMultiSelect.cs b/Multiselect/DrawMultiSelect.cs index 8e24cf5..bf3e469 100644 --- a/Multiselect/DrawMultiSelect.cs +++ b/Multiselect/DrawMultiSelect.cs @@ -25,6 +25,8 @@ public class DrawMultiSelect : MonoBehaviour private bool drawing; private bool secondary; + private static Vector2 Deadzone = new(5f, 5f); + public void Start() { selectTexture = new Texture2D(1, 1); @@ -70,6 +72,21 @@ public class DrawMultiSelect : MonoBehaviour selectOrigin = Input.mousePosition; drawing = true; secondary = shiftDown; + + if (!secondary) + { + MultiSelect.Clear(); + } + } + + if (drawing && !Settings.SelectionBoxKey.Value.IsPressedIgnoreOthers()) + { + drawing = false; + if (secondary) + { + MultiSelect.CombineSecondary(); + secondary = false; + } } if (drawing) @@ -77,6 +94,10 @@ public class DrawMultiSelect : MonoBehaviour selectEnd = Input.mousePosition; Rect selectRect = new(selectOrigin, selectEnd - selectOrigin); + if (Mathf.Abs(selectRect.size.x) < Deadzone.x && Mathf.Abs(selectRect.size.y) < Deadzone.y) + { + return; + } // If not secondary, then we can kick out any non-rendered items, plus they won't be covered by the foreach below if (!secondary) @@ -119,16 +140,6 @@ public class DrawMultiSelect : MonoBehaviour MultiSelect.Deselect(gridItemView, secondary); } } - - if (drawing && !Settings.SelectionBoxKey.Value.IsPressedIgnoreOthers()) - { - drawing = false; - if (secondary) - { - MultiSelect.CombineSecondary(); - secondary = false; - } - } } public void OnGUI() diff --git a/Multiselect/MultiSelect.cs b/Multiselect/MultiSelect.cs index e3d07fb..5859374 100644 --- a/Multiselect/MultiSelect.cs +++ b/Multiselect/MultiSelect.cs @@ -125,6 +125,8 @@ public class MultiSelect public static void OnKillItemView(GridItemView itemView) { + CombineSecondary(); + MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Value == itemView).Key; if (itemContext != null) { @@ -140,6 +142,8 @@ public class MultiSelect return; } + CombineSecondary(); + MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == itemView.Item).Key; if (itemContext != null) { @@ -158,6 +162,8 @@ public class MultiSelect return; } + CombineSecondary(); + MultiSelectItemContext oldItemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == eventArgs.Item).Key; if (oldItemContext != null) { @@ -221,7 +227,7 @@ public class MultiSelect public static bool Active { - get { return SelectedItems.Count > 0; } + get { return SelectedItems.Count > 0 || SecondaryItems.Count > 0; } } // Sort the items to prioritize the items that share a grid with the dragged item, prepend the dragContext as the first one diff --git a/Patches/ContextMenuShortcutPatches.cs b/Patches/ContextMenuShortcutPatches.cs index 5ed3c28..61e110c 100644 --- a/Patches/ContextMenuShortcutPatches.cs +++ b/Patches/ContextMenuShortcutPatches.cs @@ -1,11 +1,11 @@ -using EFT.InventoryLogic; +using Comfort.Common; +using EFT.InventoryLogic; using EFT.UI; using EFT.UI.DragAndDrop; using HarmonyLib; using SPT.Reflection.Patching; using System.Reflection; using TMPro; -using UnityEngine; using UnityEngine.EventSystems; namespace UIFixes; @@ -98,6 +98,11 @@ public static class ContextMenuShortcutPatches TryInteraction(__instance, itemContext, EItemInfoButton.LinkedSearch); } + if (Settings.SortingTableKeyBind.Value.IsDown()) + { + MoveToFromSortingTable(itemContext, __instance); + } + Interactions = null; } @@ -109,6 +114,36 @@ public static class ContextMenuShortcutPatches Interactions.ExecuteInteraction(fallbackInteraction.Value); } } + + private static void MoveToFromSortingTable(ItemContextAbstractClass itemContext, ItemUiContext itemUiContext) + { + Item item = itemContext.Item; + if (item.Owner is not InventoryControllerClass controller) + { + return; + } + + SortingTableClass sortingTable = controller.Inventory.SortingTable; + bool isInSortingTable = sortingTable != null && item.Parent.Container.ParentItem == sortingTable; + + var operation = isInSortingTable ? itemUiContext.QuickFindAppropriatePlace(itemContext, controller, false, true, true) : itemUiContext.QuickMoveToSortingTable(item, true); + if (operation.Succeeded && controller.CanExecute(operation.Value)) + { + if (operation.Value is IDestroyResult destroyResult && destroyResult.ItemsDestroyRequired) + { + NotificationManagerClass.DisplayWarningNotification(new DestroyError(item, destroyResult.ItemsToDestroy).GetLocalizedDescription()); + return; + } + + controller.RunNetworkTransaction(operation.Value, null); + if (itemUiContext.Tooltip != null) + { + itemUiContext.Tooltip.Close(); + } + + Singleton.Instance.PlayItemSound(item.ItemSound, EInventorySoundType.pickup, false); + } + } } // HideoutItemViews don't register themselves with ItemUiContext for some reason diff --git a/Patches/MultiSelectPatches.cs b/Patches/MultiSelectPatches.cs index fd5a8b7..174b699 100644 --- a/Patches/MultiSelectPatches.cs +++ b/Patches/MultiSelectPatches.cs @@ -148,7 +148,7 @@ public static class MultiSelectPatches } [PatchPostfix] - public static void Postfix(ItemView __instance, PointerEventData eventData) + public static void Postfix(ItemView __instance, PointerEventData eventData, TraderControllerClass ___ItemController) { if (!MultiSelect.Enabled || __instance is RagfairNewOfferItemView || __instance is InsuranceItemView) { @@ -159,14 +159,32 @@ public static class MultiSelectPatches bool shiftDown = Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift); bool altDown = Input.GetKey(KeyCode.LeftAlt) && !Input.GetKey(KeyCode.RightAlt); - if (Settings.EnableMultiClick.Value && __instance is GridItemView gridItemView && eventData.button == PointerEventData.InputButton.Left && shiftDown && !ctrlDown && !altDown) + // If sorting table is open and default shift-click behavior is enabled, don't multiselect + bool couldBeSortingTableMove = false; + if (Settings.DefaultSortingTableBind.Value && + shiftDown && + eventData.button == PointerEventData.InputButton.Left && + ___ItemController is InventoryControllerClass inventoryController) + { + SortingTableClass sortingTable = inventoryController.Inventory.SortingTable; + if (sortingTable != null && sortingTable.IsVisible) + { + couldBeSortingTableMove = true; + } + } + + if (Settings.EnableMultiClick.Value && + !couldBeSortingTableMove && + __instance is GridItemView gridItemView && + eventData.button == PointerEventData.InputButton.Left && + shiftDown && !ctrlDown && !altDown) { MultiSelect.Toggle(gridItemView); return; } // Mainly this tests for when selection box is rebound to another mouse button, to enable secondary selection - if (shiftDown && Settings.SelectionBoxKey.Value.IsDownIgnoreOthers()) + if (!couldBeSortingTableMove && shiftDown && Settings.SelectionBoxKey.Value.IsDownIgnoreOthers()) { return; } @@ -209,9 +227,14 @@ public static class MultiSelectPatches return false; } - if (shiftDown) + if (shiftDown && !ctrlDown && !altDown) { - // Nothing to do, mousedown handled it. + if (Settings.DefaultSortingTableBind.Value) + { + QuickMove(__instance, ___ItemUiContext, ___ItemController, true); + return false; + } + return true; } @@ -220,15 +243,17 @@ public static class MultiSelectPatches return true; } - private static void QuickMove(GridItemView gridItemView, ItemUiContext itemUiContext, TraderControllerClass itemController) + private static void QuickMove(GridItemView gridItemView, ItemUiContext itemUiContext, TraderControllerClass itemController, bool moveToSortingTable = false) { bool succeeded = true; DisableMerge = true; IgnoreItemParent = true; Stack operations = new(); - foreach (DragItemContext selectedItemContext in MultiSelect.SortedItemContexts()) + foreach (var selectedItemContext in MultiSelect.SortedItemContexts()) { - ItemOperation operation = itemUiContext.QuickFindAppropriatePlace(selectedItemContext, itemController, false /*forceStash*/, false /*showWarnings*/, false /*simulate*/); + ItemOperation operation = moveToSortingTable ? + itemUiContext.QuickMoveToSortingTable(selectedItemContext.Item, false /*simulate*/) : + itemUiContext.QuickFindAppropriatePlace(selectedItemContext, itemController, false /*forceStash*/, false /*showWarnings*/, false /*simulate*/); if (operation.Succeeded && itemController.CanExecute(operation.Value)) { operations.Push(operation); diff --git a/Patches/OpenSortingTablePatch.cs b/Patches/OpenSortingTablePatch.cs index d887cc1..58d07b7 100644 --- a/Patches/OpenSortingTablePatch.cs +++ b/Patches/OpenSortingTablePatch.cs @@ -1,59 +1,98 @@ using Comfort.Common; using EFT.UI; +using EFT.UI.DragAndDrop; using HarmonyLib; using SPT.Reflection.Patching; using System.Linq; using System.Reflection; using UnityEngine; +using UnityEngine.EventSystems; namespace UIFixes; -public class OpenSortingTablePatch : ModulePatch +public static class OpenSortingTablePatches { - private static readonly EItemUiContextType[] AllowedScreens = [EItemUiContextType.InventoryScreen, EItemUiContextType.ScavengerInventoryScreen]; - - - protected override MethodBase GetTargetMethod() + public static void Enable() { - return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.QuickMoveToSortingTable)); + new AutoOpenPatch().Enable(); + new DefaultBindPatch().Enable(); } - [PatchPrefix] - public static bool Prefix(ItemUiContext __instance, ref ItemOperation __result) + public class AutoOpenPatch : ModulePatch { - // BSG checks visibility, not in-raid. There's a bug where somehow that visibility can be true in raid - if (Plugin.InRaid()) + private static readonly EItemUiContextType[] AllowedScreens = [EItemUiContextType.InventoryScreen, EItemUiContextType.ScavengerInventoryScreen]; + + protected override MethodBase GetTargetMethod() { - __result = new GClass3370("SortingTable/VisibilityError"); - return false; + return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.QuickMoveToSortingTable)); } - if (!Settings.AutoOpenSortingTable.Value || !AllowedScreens.Contains(__instance.ContextType)) + [PatchPrefix] + public static bool Prefix(ItemUiContext __instance, ref ItemOperation __result) { + // BSG checks visibility, not in-raid. There's a bug where somehow that visibility can be true in raid + if (Plugin.InRaid()) + { + __result = new GClass3370("SortingTable/VisibilityError"); + return false; + } + + // Allowed screens only, and auto-open is enabled or the custom bind is active + if (!AllowedScreens.Contains(__instance.ContextType) || (!Settings.AutoOpenSortingTable.Value && !Settings.SortingTableKeyBind.Value.IsDown())) + { + return true; + } + + // Temporary work-around for LootValue bug - bail out if the ALT key is down + if (Input.GetKey(KeyCode.LeftAlt)) + { + return true; + } + + SortingTableClass sortingTable = __instance.R().InventoryController.Inventory.SortingTable; + if (sortingTable != null && !sortingTable.IsVisible) + { + if (__instance.ContextType == EItemUiContextType.InventoryScreen) + { + Singleton.Instance.InventoryScreen.method_6(); + Singleton.Instance.InventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true); + } + else if (__instance.ContextType == EItemUiContextType.ScavengerInventoryScreen) + { + Singleton.Instance.ScavengerInventoryScreen.method_7(); + Singleton.Instance.ScavengerInventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true); + } + } + return true; } + } - // Temporary work-around for LootValue bug - bail out if the ALT key is down - if (Input.GetKey(KeyCode.LeftAlt)) + public class DefaultBindPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() { + return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnClick)); + } + + [PatchPrefix] + public static bool Prefix(PointerEventData.InputButton button, bool doubleClick) + { + if (Settings.DefaultSortingTableBind.Value) + { + return true; + } + + bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + bool altDown = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); + bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + + if (button == PointerEventData.InputButton.Left && !doubleClick && !ctrlDown && !altDown && shiftDown) + { + return false; + } + return true; } - - SortingTableClass sortingTable = __instance.R().InventoryController.Inventory.SortingTable; - if (sortingTable != null && !sortingTable.IsVisible) - { - if (__instance.ContextType == EItemUiContextType.InventoryScreen) - { - Singleton.Instance.InventoryScreen.method_6(); - Singleton.Instance.InventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true); - } - else if (__instance.ContextType == EItemUiContextType.ScavengerInventoryScreen) - { - Singleton.Instance.ScavengerInventoryScreen.method_7(); - Singleton.Instance.ScavengerInventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true); - } - } - - return true; } } diff --git a/Plugin.cs b/Plugin.cs index b746601..5e0923c 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -47,7 +47,7 @@ public class Plugin : BaseUnityPlugin new LoadMagPresetsPatch().Enable(); KeepWindowsOnScreenPatches.Enable(); ContextMenuShortcutPatches.Enable(); - new OpenSortingTablePatch().Enable(); + OpenSortingTablePatches.Enable(); LoadAmmoInRaidPatches.Enable(); MultiSelectPatches.Enable(); new FixUnloadLastBulletPatch().Enable(); diff --git a/Settings.cs b/Settings.cs index a8815e5..56c5c8f 100644 --- a/Settings.cs +++ b/Settings.cs @@ -70,6 +70,7 @@ internal class Settings public static ConfigEntry UnpackKeyBind { get; set; } public static ConfigEntry FilterByKeyBind { get; set; } public static ConfigEntry LinkedSearchKeyBind { get; set; } + public static ConfigEntry SortingTableKeyBind { get; set; } public static ConfigEntry UseRaidMouseScrollMulti { get; set; } // Advanced public static ConfigEntry MouseScrollMultiInRaid { get; set; } // Advanced public static ConfigEntry ItemContextBlocksTextInputs { get; set; } // Advanced @@ -93,6 +94,7 @@ internal class Settings public static ConfigEntry MergeFIRAmmo { get; set; } public static ConfigEntry MergeFIROther { get; set; } public static ConfigEntry AutoOpenSortingTable { get; set; } + public static ConfigEntry DefaultSortingTableBind { get; set; } // Advanced public static ConfigEntry ContextMenuOnRight { get; set; } public static ConfigEntry ShowGPCurrency { get; set; } public static ConfigEntry ShowOutOfStockCheckbox { get; set; } @@ -330,6 +332,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(SortingTableKeyBind = config.Bind( + InputSection, + "Transfer to/from Sorting Table", + new KeyboardShortcut(KeyCode.None), + new ConfigDescription( + "Keybind to transfer items to and from the sorting table. Will auto-open sorting table if necessary.", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(ItemContextBlocksTextInputs = config.Bind( InputSection, "Block Text Inputs on Item Mouseover", @@ -502,6 +513,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(DefaultSortingTableBind = config.Bind( + InventorySection, + "Shift-Click to Sorting Table", + true, + new ConfigDescription( + "This setting lets you enable/disable the default Tarkov behavior of shift-clicking items to transfer them to the sorting table.", + null, + new ConfigurationManagerAttributes { IsAdvanced = true }))); + configEntries.Add(ContextMenuOnRight = config.Bind( InventorySection, "Context Menu Flyout on Right",