diff --git a/MultiSelect.cs b/MultiSelect.cs index 02e4e38..7ddf0c1 100644 --- a/MultiSelect.cs +++ b/MultiSelect.cs @@ -1,4 +1,5 @@ -using EFT.UI; +using EFT.InventoryLogic; +using EFT.UI; using EFT.UI.DragAndDrop; using System.Collections.Generic; using System.Linq; @@ -235,6 +236,18 @@ namespace UIFixes 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 diff --git a/MultiSelectDebug.cs b/MultiSelectDebug.cs index 96595a6..aed68ea 100644 --- a/MultiSelectDebug.cs +++ b/MultiSelectDebug.cs @@ -36,7 +36,7 @@ namespace UIFixes foreach (ItemContextClass itemContext in MultiSelect.ItemContexts) { - builder.Append(itemContext.Item.ToString()); + builder.AppendFormat("x{0} {1}", itemContext.Item.StackObjectsCount, itemContext.Item.ToString()); builder.AppendLine(); } diff --git a/Patches/ContextMenuPatches.cs b/Patches/ContextMenuPatches.cs index 246eaa3..98cda70 100644 --- a/Patches/ContextMenuPatches.cs +++ b/Patches/ContextMenuPatches.cs @@ -95,7 +95,10 @@ namespace UIFixes .Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i)) .Count(); - ____text.text += " (x" + count + ")"; + if (count > 0) + { + ____text.text += " (x" + count + ")"; + } } } } diff --git a/Patches/MultiSelectPatches.cs b/Patches/MultiSelectPatches.cs index 13786c6..f3eda44 100644 --- a/Patches/MultiSelectPatches.cs +++ b/Patches/MultiSelectPatches.cs @@ -1,5 +1,6 @@ using Aki.Reflection.Patching; using Comfort.Common; +using EFT.Communications; using EFT.InventoryLogic; using EFT.UI; using EFT.UI.DragAndDrop; @@ -30,6 +31,7 @@ namespace UIFixes // Prevents QuickFind from attempting a merge private static bool DisableMerge = false; + private static bool IgnoreItemParent = false; // Specific type of TaskSerializer because Unity can't understand generics public class ItemContextTaskSerializer : TaskSerializer { } @@ -39,7 +41,7 @@ namespace UIFixes new InitializeCommonUIPatch().Enable(); new InitializeMenuUIPatch().Enable(); new SelectOnMouseDownPatch().Enable(); - new DeselectOnGridItemViewClickPatch().Enable(); + new ItemViewClickPatch().Enable(); new DeselectOnTradingItemViewClickPatch().Enable(); new HandleItemViewInitPatch().Enable(); new HandleItemViewKillPatch().Enable(); @@ -144,26 +146,101 @@ namespace UIFixes } } - public class DeselectOnGridItemViewClickPatch : ModulePatch + public class ItemViewClickPatch : ModulePatch { protected override MethodBase GetTargetMethod() { return AccessTools.Method(typeof(GridItemView), nameof(GridItemView.OnClick)); } - [PatchPostfix] - public static void Postfix(PointerEventData.InputButton button) + [PatchPrefix] + public static bool Prefix(GridItemView __instance, PointerEventData.InputButton button, ItemUiContext ___ItemUiContext, TraderControllerClass ___ItemController) { - if (!MultiSelect.Active) + if (!MultiSelect.Active || button != PointerEventData.InputButton.Left || ___ItemUiContext == null || !__instance.IsSearched) { - return; + return true; } - // Mousedown handles most things, just need to handle the non-shift click of a selected item - if (button == PointerEventData.InputButton.Left && !Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift)) + bool ctrlDown = Input.GetKey(KeyCode.LeftControl) && !Input.GetKey(KeyCode.RightControl); + bool shiftDown = Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift); + bool altDown = Input.GetKey(KeyCode.LeftAlt) && !Input.GetKey(KeyCode.RightAlt); + + if (ctrlDown && !shiftDown && !altDown) { - MultiSelect.Clear(); + bool succeeded = true; + DisableMerge = true; + IgnoreItemParent = true; + Stack operations = new(); + foreach (ItemContextClass selectedItemContext in SortSelectedContexts(MultiSelect.ItemContexts, null, false)) + { + GStruct413 operation = ___ItemUiContext.QuickFindAppropriatePlace(selectedItemContext, ___ItemController, false /*forceStash*/, false /*showWarnings*/, false /*simulate*/); + if (operation.Succeeded && ___ItemController.CanExecute(operation.Value)) + { + operations.Push(operation); + } + else + { + succeeded = false; + break; + } + + IDestroyResult destroyResult; + if ((destroyResult = operation.Value as IDestroyResult) != null && destroyResult.ItemsDestroyRequired) + { + NotificationManagerClass.DisplayWarningNotification(new GClass3320(__instance.Item, destroyResult.ItemsToDestroy).GetLocalizedDescription(), ENotificationDurationType.Default); + succeeded = false; + break; + } + } + + DisableMerge = false; + IgnoreItemParent = true; + + if (succeeded) + { + string itemSound = __instance.Item.ItemSound; + + // We didn't simulate because we needed each result to depend on the last, but we have to undo before we actually do :S + Stack networkOps = new(); + while (operations.Any()) + { + GStruct413 operation = operations.Pop(); + operation.Value.RollBack(); + networkOps.Push(operation); + } + + while (networkOps.Any()) + { + ___ItemController.RunNetworkTransaction(networkOps.Pop().Value, null); + } + + if (___ItemUiContext.Tooltip != null) + { + ___ItemUiContext.Tooltip.Close(); + } + + Singleton.Instance.PlayItemSound(itemSound, EInventorySoundType.pickup, false); + } + else + { + while (operations.Any()) + { + operations.Pop().Value?.RollBack(); + } + } + + return false; } + + if (shiftDown) + { + // Nothing to do, mousedown handled it. + return true; + } + + // if neither ctrl or shift is down, this is a click to clear + MultiSelect.Clear(); + return true; } } @@ -300,6 +377,13 @@ namespace UIFixes return false; } + // BSG BUG - if the targetItem is a magazine, and the multiselect is bullets, it will NOT be able to rollback correctly!! + // To prevent inventory corruption, reject magazines outright. Sorry, no multiple bullet stacks into one magazine + if (targetItemContext != null && targetItemContext.Item is MagazineClass) + { + return false; + } + Item item = itemContext.Item; ItemAddress itemAddress = itemContext.ItemAddress; if (itemAddress == null) @@ -421,23 +505,8 @@ namespace UIFixes // Need to fully implement AcceptItem for the sorting table - normally that just uses null targetItemContext if (InPatch && targetItemContext?.Item is SortingTableClass) { + MoveToSortingTable(__instance, itemContext, ___itemUiContext_0); __result = Task.CompletedTask; - var itemController = __instance.R().TraderController; - - GStruct413 operation = ___itemUiContext_0.QuickMoveToSortingTable(itemContext.Item, true); - if (operation.Failed || !itemController.CanExecute(operation.Value)) - { - return false; - } - - itemController.RunNetworkTransaction(operation.Value, null); - - if (___itemUiContext_0.Tooltip != null) - { - ___itemUiContext_0.Tooltip.Close(); - } - - Singleton.Instance.PlayItemSound(itemContext.Item.ItemSound, EInventorySoundType.pickup, false); return false; } @@ -480,6 +549,26 @@ namespace UIFixes return false; } + + private static void MoveToSortingTable(GridView gridView, ItemContextClass itemContext, ItemUiContext itemUiContext) + { + var itemController = gridView.R().TraderController; + + GStruct413 operation = itemUiContext.QuickMoveToSortingTable(itemContext.Item, true); + if (operation.Failed || !itemController.CanExecute(operation.Value)) + { + return; + } + + itemController.RunNetworkTransaction(operation.Value, null); + + if (itemUiContext.Tooltip != null) + { + itemUiContext.Tooltip.Close(); + } + + Singleton.Instance.PlayItemSound(itemContext.Item.ItemSound, EInventorySoundType.pickup, false); + } } public class AdjustQuickFindFlagsPatch : ModulePatch @@ -508,6 +597,11 @@ namespace UIFixes { order &= ~InteractionsHandlerClass.EMoveItemOrder.TryMerge; } + + if (IgnoreItemParent) + { + order |= InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent; + } } } @@ -548,19 +642,20 @@ namespace UIFixes } // Sort the items to prioritize the items that share a grid with the dragged item, prepend the dragContext as the first one + // Can pass no itemContext, and it just sorts items by their grid order private static IEnumerable SortSelectedContexts( IEnumerable selectedContexts, ItemContextClass itemContext, bool prepend = true) { static int gridOrder(LocationInGrid loc, StashGridClass grid) => grid.GridWidth.Value * loc.y + loc.x; var result = selectedContexts - .Where(ic => ic.Item != itemContext.Item) + .Where(ic => itemContext == null || ic.Item != itemContext.Item) .OrderByDescending(ic => ic.ItemAddress is GClass2769) - .ThenByDescending(ic => itemContext.ItemAddress is GClass2769 originalDraggedAddress && ic.ItemAddress is GClass2769 selectedGridAddress && selectedGridAddress.Grid == originalDraggedAddress.Grid) + .ThenByDescending(ic => itemContext != null && itemContext.ItemAddress is GClass2769 originalDraggedAddress && ic.ItemAddress is GClass2769 selectedGridAddress && selectedGridAddress.Grid == originalDraggedAddress.Grid) .ThenByDescending(ic => ic.ItemAddress is GClass2769 selectedGridAddress ? selectedGridAddress.Grid.Id : null) .ThenBy(ic => ic.ItemAddress is GClass2769 selectedGridAddress ? gridOrder(selectedGridAddress.LocationInGrid, selectedGridAddress.Grid) : 0); - return prepend ? result.Prepend(itemContext) : result; + return itemContext != null && prepend ? result.Prepend(itemContext) : result; } public class GridViewDisableHighlightPatch : ModulePatch @@ -620,7 +715,7 @@ namespace UIFixes } Stack operations = new(); - foreach (ItemContextClass itemContext in MultiSelect.ItemContexts) + foreach (ItemContextClass itemContext in SortSelectedContexts(MultiSelect.ItemContexts, null, false)) { __result = itemContext.CanAccept(__instance.Slot, __instance.ParentItemContext, ___InventoryController, out operation, false /* simulate */); if (operation.Succeeded) @@ -667,7 +762,7 @@ namespace UIFixes InPatch = true; var serializer = __instance.GetOrAddComponent(); - __result = serializer.Initialize(MultiSelect.ItemContexts, itemContext => __instance.AcceptItem(itemContext, targetItemContext)); + __result = serializer.Initialize(SortSelectedContexts(MultiSelect.ItemContexts, null, false), itemContext => __instance.AcceptItem(itemContext, targetItemContext)); __result.ContinueWith(_ => { InPatch = false; });