Automatically merge stacks (money, ammo, etc.) when quickly transferring items to containers not marked with a @loot tag

This commit is contained in:
CactusPie 2024-01-14 20:28:59 +01:00
parent 1bee34fc26
commit 545725d380
2 changed files with 102 additions and 30 deletions

View File

@ -8,16 +8,18 @@ namespace CactusPie.ContainerQuickLoot
public class ContainerQuickLootPlugin : BaseUnityPlugin public class ContainerQuickLootPlugin : BaseUnityPlugin
{ {
internal static ConfigEntry<bool> EnableForCtrlClick { get; set; } internal static ConfigEntry<bool> EnableForCtrlClick { get; set; }
internal static ConfigEntry<bool> EnableForLooseLoot { get; set; } internal static ConfigEntry<bool> EnableForLooseLoot { get; set; }
internal static ConfigEntry<bool> AutoMergeStacks { get; set; } internal static ConfigEntry<bool> AutoMergeStacks { get; set; }
internal static ConfigEntry<bool> AutoMergeStacksForNonLootContainers { get; set; }
[UsedImplicitly] [UsedImplicitly]
internal void Start() internal void Start()
{ {
const string sectionName = "Container quick loot setting"; const string sectionName = "Container quick loot setting";
EnableForCtrlClick = Config.Bind EnableForCtrlClick = Config.Bind
( (
sectionName, sectionName,
@ -28,7 +30,7 @@ namespace CactusPie.ContainerQuickLoot
"Automatically put the items in containers while transferring them with ctrl+click" "Automatically put the items in containers while transferring them with ctrl+click"
) )
); );
EnableForLooseLoot = Config.Bind EnableForLooseLoot = Config.Bind
( (
sectionName, sectionName,
@ -39,7 +41,7 @@ namespace CactusPie.ContainerQuickLoot
"Automatically put loose loot in containers" "Automatically put loose loot in containers"
) )
); );
AutoMergeStacks = Config.Bind AutoMergeStacks = Config.Bind
( (
sectionName, sectionName,
@ -51,6 +53,18 @@ namespace CactusPie.ContainerQuickLoot
) )
); );
AutoMergeStacksForNonLootContainers = Config.Bind
(
sectionName,
"Merge stacks for non-loot containers",
true,
new ConfigDescription
(
"Automatically merge stacks (money, ammo, etc.) when quickly transferring items to " +
"containers not marked with a @loot tag"
)
);
new QuickTransferPatch().Enable(); new QuickTransferPatch().Enable();
} }
} }

View File

@ -13,7 +13,7 @@ namespace CactusPie.ContainerQuickLoot
public class QuickTransferPatch : ModulePatch public class QuickTransferPatch : ModulePatch
{ {
private static readonly Regex LootTagRegex = new Regex("@loot[0-9]*", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100)); private static readonly Regex LootTagRegex = new Regex("@loot[0-9]*", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
protected override MethodBase GetTargetMethod() protected override MethodBase GetTargetMethod()
{ {
MethodInfo method = typeof(GClass2585).GetMethod("QuickFindAppropriatePlace", BindingFlags.Public | BindingFlags.Static); MethodInfo method = typeof(GClass2585).GetMethod("QuickFindAppropriatePlace", BindingFlags.Public | BindingFlags.Static);
@ -30,6 +30,8 @@ namespace CactusPie.ContainerQuickLoot
GClass2585.EMoveItemOrder order, GClass2585.EMoveItemOrder order,
bool simulate) bool simulate)
{ {
Inventory inventory;
// If is ctrl+click loot // If is ctrl+click loot
if (order == GClass2585.EMoveItemOrder.MoveToAnotherSide) if (order == GClass2585.EMoveItemOrder.MoveToAnotherSide)
{ {
@ -38,51 +40,44 @@ namespace CactusPie.ContainerQuickLoot
return true; return true;
} }
} }
// If is loose loot pick up // If is loose loot pick up
else if (order == GClass2585.EMoveItemOrder.PickUp && controller.OwnerType == EOwnerType.Profile) else if (order == GClass2585.EMoveItemOrder.PickUp && controller.OwnerType == EOwnerType.Profile)
{ {
if (!ContainerQuickLootPlugin.EnableForLooseLoot.Value) if (!ContainerQuickLootPlugin.EnableForLooseLoot.Value)
{ {
return true; if (!TryGetInventory(out inventory))
{
return true;
}
return !TryMergeItemIntoAnExistingStack(item, inventory, controller, simulate, ref __result);
} }
} }
else else
{ {
return true; return true;
} }
GameWorld gameWorld = Singleton<GameWorld>.Instance;
// If gameWorld is null that means the game is currently not in progress, for instance you're in your hideout
if (gameWorld == null)
{
return true;
}
// This check needs to be done only in game - otherwise we will not be able to receive quest rewards! // This check needs to be done only in game - otherwise we will not be able to receive quest rewards!
if (item.QuestItem) if (item.QuestItem)
{ {
return true; return true;
} }
Player player = GetLocalPlayerFromWorld(gameWorld); if (!TryGetInventory(out inventory))
var inventory = (Inventory)typeof(Player).GetProperty("Inventory", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(player);
if (inventory == null)
{ {
return true; return true;
} }
IEnumerable<IContainer> targetContainers = FindTargetContainers(item, inventory); IEnumerable<IContainer> targetContainers = FindTargetContainers(item, inventory);
foreach (IContainer collectionContainer in targetContainers) foreach (IContainer collectionContainer in targetContainers)
{ {
if (!(collectionContainer is GClass2318 container)) if (!(collectionContainer is GClass2318 container))
{ {
return true; return !TryMergeItemIntoAnExistingStack(item, inventory, controller, simulate, ref __result);
} }
// ReSharper disable once PossibleMultipleEnumeration // ReSharper disable once PossibleMultipleEnumeration
if (!(targets.SingleOrDefaultWithoutException() is EquipmentClass)) if (!(targets.SingleOrDefaultWithoutException() is EquipmentClass))
{ {
@ -97,18 +92,17 @@ namespace CactusPie.ContainerQuickLoot
{ {
continue; continue;
} }
if (containedItem.Key.StackObjectsCount + item.StackObjectsCount > item.StackMaxSize) if (containedItem.Key.StackObjectsCount + item.StackObjectsCount > item.StackMaxSize)
{ {
continue; continue;
} }
GStruct375<GClass2599> mergeResult = GClass2585.Merge(item, containedItem.Key, controller, simulate); GStruct375<GClass2599> mergeResult = GClass2585.Merge(item, containedItem.Key, controller, simulate);
__result = new GStruct375<GInterface275>(mergeResult.Value); __result = new GStruct375<GInterface275>(mergeResult.Value);
return false; return false;
} }
} }
GClass2580 location = container.FindLocationForItem(item); GClass2580 location = container.FindLocationForItem(item);
if (location == null) if (location == null)
@ -126,7 +120,32 @@ namespace CactusPie.ContainerQuickLoot
{ {
__result = moveResult.Cast<GClass2597, GInterface275>(); __result = moveResult.Cast<GClass2597, GInterface275>();
} }
return false;
}
return !TryMergeItemIntoAnExistingStack(item, inventory, controller, simulate, ref __result);
}
private static bool TryGetInventory(out Inventory inventory)
{
GameWorld gameWorld = Singleton<GameWorld>.Instance;
// If gameWorld is null that means the game is currently not in progress, for instance you're in your hideout
if (gameWorld == null)
{
inventory = null;
return false;
}
Player player = GetLocalPlayerFromWorld(gameWorld);
inventory = (Inventory)typeof(Player)
.GetProperty("Inventory", BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(player);
if (inventory == null)
{
return false; return false;
} }
@ -136,7 +155,7 @@ namespace CactusPie.ContainerQuickLoot
private static IEnumerable<IContainer> FindTargetContainers(Item item, Inventory inventory) private static IEnumerable<IContainer> FindTargetContainers(Item item, Inventory inventory)
{ {
var matchingContainerCollections = new List<(ContainerCollection containerCollection, int priority)>(); var matchingContainerCollections = new List<(ContainerCollection containerCollection, int priority)>();
foreach (Item inventoryItem in inventory.Equipment.GetAllItems()) foreach (Item inventoryItem in inventory.Equipment.GetAllItems())
{ {
// It has to be a container collection - an item that we can transfer the loot into // It has to be a container collection - an item that we can transfer the loot into
@ -173,7 +192,7 @@ namespace CactusPie.ContainerQuickLoot
string priorityString = regexMatch.Value.Substring(lootTagLength); string priorityString = regexMatch.Value.Substring(lootTagLength);
int priority = priorityString.Length == 0 ? 0 : int.Parse(priorityString); int priority = priorityString.Length == 0 ? 0 : int.Parse(priorityString);
matchingContainerCollections.Add((containerCollection, priority)); matchingContainerCollections.Add((containerCollection, priority));
} }
@ -184,6 +203,45 @@ namespace CactusPie.ContainerQuickLoot
return result; return result;
} }
// If there are not matching @loot containers found, we will try to merge the item into an existing stack
// anyway - but only if this behavior is enabled in the config
private static bool TryMergeItemIntoAnExistingStack(
Item item,
Inventory inventory,
TraderControllerClass controller,
bool simulate,
ref GStruct375<GInterface275> result)
{
if (!ContainerQuickLootPlugin.AutoMergeStacksForNonLootContainers.Value)
{
return false;
}
if (item.Template.StackMaxSize <= 1)
{
return false;
}
foreach (Item targetItem in inventory.Equipment.GetAllItems())
{
if (targetItem.Template._id != item.Template._id)
{
continue;
}
if (targetItem.StackObjectsCount + item.StackObjectsCount > item.Template.StackMaxSize)
{
continue;
}
GStruct375<GClass2599> mergeResult = GClass2585.Merge(item, targetItem, controller, simulate);
result = new GStruct375<GInterface275>(mergeResult.Value);
return true;
}
return false;
}
private static Player GetLocalPlayerFromWorld(GameWorld gameWorld) private static Player GetLocalPlayerFromWorld(GameWorld gameWorld)
{ {
if (gameWorld == null || gameWorld.MainPlayer == null) if (gameWorld == null || gameWorld.MainPlayer == null)