greedy stack transfers

This commit is contained in:
Tyfon
2024-06-22 14:03:24 -07:00
parent f4b7fae31d
commit dff100db30
7 changed files with 245 additions and 54 deletions

View File

@@ -238,7 +238,7 @@ namespace UIFixes
{ {
if (!allOrNothing || InteractionCount(EItemInfoButton.Equip, itemUiContext) == Count) if (!allOrNothing || InteractionCount(EItemInfoButton.Equip, itemUiContext) == Count)
{ {
var taskSerializer = itemUiContext.GetOrAddComponent<ItemContextTaskSerializer>(); var taskSerializer = itemUiContext.gameObject.AddComponent<ItemContextTaskSerializer>();
taskSerializer.Initialize(SortedItemContexts(), itemContext => itemUiContext.QuickEquip(itemContext.Item)); taskSerializer.Initialize(SortedItemContexts(), itemContext => itemUiContext.QuickEquip(itemContext.Item));
itemUiContext.Tooltip?.Close(); itemUiContext.Tooltip?.Close();
} }
@@ -248,7 +248,7 @@ namespace UIFixes
{ {
if (!allOrNothing || InteractionCount(EItemInfoButton.Unequip, itemUiContext) == Count) if (!allOrNothing || InteractionCount(EItemInfoButton.Unequip, itemUiContext) == Count)
{ {
var taskSerializer = itemUiContext.GetOrAddComponent<ItemContextTaskSerializer>(); var taskSerializer = itemUiContext.gameObject.AddComponent<ItemContextTaskSerializer>();
taskSerializer.Initialize(SortedItemContexts(), itemContext => itemUiContext.Uninstall(itemContext.GClass2813_0)); taskSerializer.Initialize(SortedItemContexts(), itemContext => itemUiContext.Uninstall(itemContext.GClass2813_0));
itemUiContext.Tooltip?.Close(); itemUiContext.Tooltip?.Close();
} }
@@ -260,7 +260,7 @@ namespace UIFixes
if (!allOrNothing || InteractionCount(EItemInfoButton.UnloadAmmo, itemUiContext) == Count) if (!allOrNothing || InteractionCount(EItemInfoButton.UnloadAmmo, itemUiContext) == Count)
{ {
// Call Initialize() before setting UnloadSerializer so that the initial synchronous call to StopProcesses()->StopUnloading() doesn't immediately cancel this // Call Initialize() before setting UnloadSerializer so that the initial synchronous call to StopProcesses()->StopUnloading() doesn't immediately cancel this
var taskSerializer = itemUiContext.GetOrAddComponent<ItemContextTaskSerializer>(); var taskSerializer = itemUiContext.gameObject.AddComponent<ItemContextTaskSerializer>();
taskSerializer.Initialize(SortedItemContexts(), itemContext => itemUiContext.UnloadAmmo(itemContext.Item)); taskSerializer.Initialize(SortedItemContexts(), itemContext => itemUiContext.UnloadAmmo(itemContext.Item));
UnloadSerializer = taskSerializer; UnloadSerializer = taskSerializer;
@@ -391,6 +391,15 @@ namespace UIFixes
return true; return true;
} }
public static IEnumerable<ItemContextClass> RepeatUntilEmpty(this ItemContextClass itemContext)
{
while (itemContext.Item.StackObjectsCount > 0)
{
yield return itemContext;
}
}
} }
} }

View File

@@ -11,6 +11,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
@@ -103,7 +104,7 @@ namespace UIFixes
__instance.TransferItemsScreen.GetOrAddComponent<DrawMultiSelect>(); __instance.TransferItemsScreen.GetOrAddComponent<DrawMultiSelect>();
__instance.ScavengerInventoryScreen.GetOrAddComponent<DrawMultiSelect>(); __instance.ScavengerInventoryScreen.GetOrAddComponent<DrawMultiSelect>();
void ToggleDebug() static void ToggleDebug()
{ {
if (Settings.ShowMultiSelectDebug.Value) if (Settings.ShowMultiSelectDebug.Value)
{ {
@@ -475,13 +476,54 @@ namespace UIFixes
Item targetItem = __instance.method_8(targetItemContext); Item targetItem = __instance.method_8(targetItemContext);
DisableMerge = targetItem == null; DisableMerge = targetItem == null;
bool showHighlights = targetItem == null; bool isGridPlacement = targetItem == null;
Stack<GStruct413> operations = new(); Stack<GStruct413> operations = new();
foreach (ItemContextClass selectedItemContext in MultiSelect.SortedItemContexts(itemContext)) foreach (ItemContextClass selectedItemContext in MultiSelect.SortedItemContexts(itemContext))
{
if (Settings.GreedyStackMove.Value && !isGridPlacement && selectedItemContext.Item.StackObjectsCount > 1)
{
int stackCount = int.MaxValue;
bool failed = false;
while (selectedItemContext.Item.StackObjectsCount > 0)
{
if (selectedItemContext.Item.StackObjectsCount >= stackCount)
{
break;
}
stackCount = selectedItemContext.Item.StackObjectsCount;
operation = wrappedInstance.TraderController.ExecutePossibleAction(selectedItemContext, targetItem, false /* splitting */, false /* simulate */);
if (__result = operation.Succeeded)
{
operations.Push(operation);
}
else
{
if (operation.Error is GClass3292 noRoomError)
{
// Wrap this error to display it
operation = new(new DisplayableErrorWrapper(noRoomError));
}
// Need to double-break
failed = true;
break;
}
}
if (failed)
{
break;
}
}
else
{
if (isGridPlacement)
{ {
FindOrigin = GetTargetGridAddress(itemContext, selectedItemContext, hoveredAddress); FindOrigin = GetTargetGridAddress(itemContext, selectedItemContext, hoveredAddress);
FindVerticalFirst = selectedItemContext.ItemRotation == ItemRotation.Vertical; FindVerticalFirst = selectedItemContext.ItemRotation == ItemRotation.Vertical;
}
if (targetItem is SortingTableClass) if (targetItem is SortingTableClass)
{ {
@@ -500,7 +542,7 @@ namespace UIFixes
if (__result = operation.Succeeded) if (__result = operation.Succeeded)
{ {
operations.Push(operation); operations.Push(operation);
if (targetItem != null && showHighlights) // targetItem was originally null so this is the rest of the items if (targetItem != null && isGridPlacement) // targetItem was originally null so this is the rest of the items
{ {
ShowPreview(__instance, selectedItemContext, operation); ShowPreview(__instance, selectedItemContext, operation);
} }
@@ -510,7 +552,7 @@ namespace UIFixes
// Moving item to the same place, cool, not a problem // Moving item to the same place, cool, not a problem
__result = true; __result = true;
operation = default; operation = default;
if (showHighlights && selectedItemContext.Item.Parent is GClass2769 gridAddress) if (isGridPlacement && selectedItemContext.Item.Parent is GClass2769 gridAddress)
{ {
ShowPreview(__instance, selectedItemContext, gridAddress, R.GridView.ValidMoveColor); ShowPreview(__instance, selectedItemContext, gridAddress, R.GridView.ValidMoveColor);
} }
@@ -525,6 +567,7 @@ namespace UIFixes
break; break;
} }
}
// Set this after the first one // Set this after the first one
targetItem ??= __instance.Grid.ParentItem; targetItem ??= __instance.Grid.ParentItem;
@@ -603,7 +646,7 @@ namespace UIFixes
targetItemContext = new GClass2817(__instance.Grid.ParentItem, EItemViewType.Empty); targetItemContext = new GClass2817(__instance.Grid.ParentItem, EItemViewType.Empty);
} }
var serializer = __instance.GetOrAddComponent<ItemContextTaskSerializer>(); var serializer = __instance.gameObject.AddComponent<ItemContextTaskSerializer>();
__result = serializer.Initialize(MultiSelect.SortedItemContexts(itemContext), ic => __result = serializer.Initialize(MultiSelect.SortedItemContexts(itemContext), ic =>
{ {
FindOrigin = GetTargetGridAddress(itemContext, ic, hoveredAddress); FindOrigin = GetTargetGridAddress(itemContext, ic, hoveredAddress);
@@ -665,7 +708,7 @@ namespace UIFixes
// Multiselect always disables "Transfer", which is a partial merge // Multiselect always disables "Transfer", which is a partial merge
// It leaves things behind and that's not intuitive when multi-selecting // It leaves things behind and that's not intuitive when multi-selecting
order &= ~PartialMerge; // order &= ~PartialMerge;
if (DisableMerge) if (DisableMerge)
{ {
@@ -773,6 +816,8 @@ namespace UIFixes
Stack<GStruct413> operations = new(); Stack<GStruct413> operations = new();
foreach (ItemContextClass itemContext in MultiSelect.SortedItemContexts()) foreach (ItemContextClass itemContext in MultiSelect.SortedItemContexts())
{
if (!Settings.GreedyStackMove.Value || itemContext.Item.StackObjectsCount <= 1)
{ {
__result = itemContext.CanAccept(__instance.Slot, __instance.ParentItemContext, ___InventoryController, out operation, false /* simulate */); __result = itemContext.CanAccept(__instance.Slot, __instance.ParentItemContext, ___InventoryController, out operation, false /* simulate */);
if (operation.Succeeded) if (operation.Succeeded)
@@ -789,6 +834,38 @@ namespace UIFixes
break; break;
} }
} }
else
{
int stackCount = int.MaxValue;
bool failed = false;
while (itemContext.Item.StackObjectsCount > 0)
{
if (itemContext.Item.StackObjectsCount >= stackCount)
{
// The whole stack moved or nothing happened, it's done
break;
}
stackCount = itemContext.Item.StackObjectsCount;
__result = itemContext.CanAccept(__instance.Slot, __instance.ParentItemContext, ___InventoryController, out operation, false /* simulate */);
if (operation.Succeeded)
{
operations.Push(operation);
}
else
{
// Need to double-break
failed = true;
break;
}
}
if (failed)
{
break;
}
}
}
// Didn't simulate so now undo // Didn't simulate so now undo
while (operations.Any()) while (operations.Any())
@@ -818,7 +895,7 @@ namespace UIFixes
InPatch = true; InPatch = true;
var serializer = __instance.GetOrAddComponent<ItemContextTaskSerializer>(); var serializer = __instance.gameObject.AddComponent<ItemContextTaskSerializer>();
__result = serializer.Initialize(MultiSelect.SortedItemContexts(), itemContext => __instance.AcceptItem(itemContext, targetItemContext)); __result = serializer.Initialize(MultiSelect.SortedItemContexts(), itemContext => __instance.AcceptItem(itemContext, targetItemContext));
__result.ContinueWith(_ => { InPatch = false; }); __result.ContinueWith(_ => { InPatch = false; });

View File

@@ -0,0 +1,87 @@
using Aki.Reflection.Patching;
using EFT.UI.DragAndDrop;
using HarmonyLib;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace UIFixes
{
public static class StackMoveGreedyPatches
{
private static bool InPatch = false;
public static void Enable()
{
new GridViewPatch().Enable();
new SlotViewPatch().Enable();
}
public class GridViewPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(GridView), nameof(GridView.AcceptItem));
}
[PatchPrefix]
[HarmonyPriority(Priority.LowerThanNormal)]
public static bool Prefix(GridView __instance, ItemContextClass itemContext, ItemContextAbstractClass targetItemContext, ref Task __result)
{
return AcceptStackable(__instance, itemContext, targetItemContext, ref __result);
}
}
public class SlotViewPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(SlotView), nameof(SlotView.AcceptItem));
}
[PatchPrefix]
[HarmonyPriority(Priority.LowerThanNormal)]
public static bool Prefix(SlotView __instance, ItemContextClass itemContext, ItemContextAbstractClass targetItemContext, ref Task __result)
{
return AcceptStackable(__instance, itemContext, targetItemContext, ref __result);
}
}
private static bool AcceptStackable<T>(T __instance, ItemContextClass itemContext, ItemContextAbstractClass targetItemContext, ref Task __result) where T : MonoBehaviour, IContainer
{
if (!Settings.GreedyStackMove.Value || InPatch || itemContext.Item.StackObjectsCount <= 1 || targetItemContext == null)
{
return true;
}
InPatch = true;
int stackCount = int.MaxValue;
var serializer = __instance.gameObject.AddComponent<ItemContextTaskSerializer>();
__result = serializer.Initialize(itemContext.RepeatUntilEmpty(), ic =>
{
if (ic.Item.StackObjectsCount >= stackCount)
{
// Nothing happened, bail out
return Task.FromCanceled(new CancellationToken(true));
}
stackCount = ic.Item.StackObjectsCount;
return __instance.AcceptItem(ic, targetItemContext);
});
// This won't block the first action from swapping, but will prevent follow up swaps
SwapPatches.BlockSwaps = true;
__result.ContinueWith(_ =>
{
InPatch = false;
SwapPatches.BlockSwaps = false;
});
return false;
}
}
}

View File

@@ -30,6 +30,8 @@ namespace UIFixes
private static readonly EOwnerType[] BannedOwnerTypes = [EOwnerType.Mail, EOwnerType.Trader]; private static readonly EOwnerType[] BannedOwnerTypes = [EOwnerType.Mail, EOwnerType.Trader];
public static bool BlockSwaps = false;
public static void Enable() public static void Enable()
{ {
new DetectSwapSourceContainerPatch().Enable(); new DetectSwapSourceContainerPatch().Enable();
@@ -57,6 +59,11 @@ namespace UIFixes
return false; return false;
} }
if (BlockSwaps)
{
return false;
}
var wrappedOperation = new R.GridViewCanAcceptOperation(operation); var wrappedOperation = new R.GridViewCanAcceptOperation(operation);
if (InHighlight || itemContext == null || targetItemContext == null || wrappedOperation.Succeeded) if (InHighlight || itemContext == null || targetItemContext == null || wrappedOperation.Succeeded)

View File

@@ -51,6 +51,7 @@ namespace UIFixes
LoadAmmoInRaidPatches.Enable(); LoadAmmoInRaidPatches.Enable();
MultiSelectPatches.Enable(); MultiSelectPatches.Enable();
new FixUnloadLastBulletPatch().Enable(); new FixUnloadLastBulletPatch().Enable();
StackMoveGreedyPatches.Enable();
} }
public static bool InRaid() public static bool InRaid()

View File

@@ -72,6 +72,7 @@ namespace UIFixes
public static ConfigEntry<bool> SwapItems { get; set; } public static ConfigEntry<bool> SwapItems { get; set; }
public static ConfigEntry<bool> SwapImpossibleContainers { get; set; } public static ConfigEntry<bool> SwapImpossibleContainers { get; set; }
public static ConfigEntry<bool> SynchronizeStashScrolling { get; set; } public static ConfigEntry<bool> SynchronizeStashScrolling { get; set; }
public static ConfigEntry<bool> GreedyStackMove { get; set; }
public static ConfigEntry<bool> MergeFIRMoney { get; set; } public static ConfigEntry<bool> MergeFIRMoney { get; set; }
public static ConfigEntry<bool> MergeFIRAmmo { get; set; } public static ConfigEntry<bool> MergeFIRAmmo { get; set; }
public static ConfigEntry<bool> MergeFIROther { get; set; } public static ConfigEntry<bool> MergeFIROther { get; set; }
@@ -359,6 +360,15 @@ namespace UIFixes
null, null,
new ConfigurationManagerAttributes { }))); new ConfigurationManagerAttributes { })));
configEntries.Add(GreedyStackMove = config.Bind(
InventorySection,
"Always Move Entire Stacks",
false,
new ConfigDescription(
"When moving into a container that contains a partial stack, this will top up that stack and try to move the remainder into an open spot (or another stack), instead of leaving it behind.",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(MergeFIRMoney = config.Bind( configEntries.Add(MergeFIRMoney = config.Bind(
InventorySection, InventorySection,
"Autostack Money with FiR Money", "Autostack Money with FiR Money",

View File

@@ -9,13 +9,13 @@ namespace UIFixes
public class TaskSerializer<T> : MonoBehaviour public class TaskSerializer<T> : MonoBehaviour
{ {
private Func<T, Task> func; private Func<T, Task> func;
private Queue<T> items; private IEnumerator<T> enumerator;
private Task currentTask; private Task currentTask;
private TaskCompletionSource totalTask; private TaskCompletionSource totalTask;
public Task Initialize(IEnumerable<T> items, Func<T, Task> func) public Task Initialize(IEnumerable<T> items, Func<T, Task> func)
{ {
this.items = new(items); this.enumerator = items.GetEnumerator();
this.func = func; this.func = func;
currentTask = Task.CompletedTask; currentTask = Task.CompletedTask;
@@ -44,9 +44,9 @@ namespace UIFixes
return; return;
} }
if (items.Any()) if (enumerator.MoveNext())
{ {
currentTask = func(items.Dequeue()); currentTask = func(enumerator.Current);
} }
else else
{ {