diff --git a/ValheimPlus/GameClasses/InventoryGUI.cs b/ValheimPlus/GameClasses/InventoryGUI.cs
index 649b367c..076f72c8 100644
--- a/ValheimPlus/GameClasses/InventoryGUI.cs
+++ b/ValheimPlus/GameClasses/InventoryGUI.cs
@@ -135,7 +135,7 @@ public static void Prefix(InventoryGui __instance)
}
}
}
-
+
///
/// Inventory HUD setup
@@ -215,4 +215,96 @@ private static bool Prefix(Transform elementRoot, Piece.Requirement req, Player
return false;
}
}
+
+ [HarmonyPatch(typeof(InventoryGui), nameof(InventoryGui.DoCrafting))]
+ public static class InventoryGui_DoCrafting_Transpiler
+ {
+ private static MethodInfo method_Player_Inventory_RemoveItem = AccessTools.Method(typeof(Inventory), nameof(Inventory.RemoveItem), new Type[] { typeof(string), typeof(int), typeof(int) });
+ private static MethodInfo method_Player_GetFirstRequiredItem = AccessTools.Method(typeof(Player), nameof(Player.GetFirstRequiredItem));
+ private static MethodInfo method_UseItemFromIventoryOrChest = AccessTools.Method(typeof(InventoryGui_DoCrafting_Transpiler), nameof(InventoryGui_DoCrafting_Transpiler.UseItemFromIventoryOrChest));
+ private static MethodInfo method_GetFirstRequiredItemFromInventoryOrChest = AccessTools.Method(typeof(InventoryGui_DoCrafting_Transpiler), nameof(InventoryGui_DoCrafting_Transpiler.GetFirstRequiredItemFromInventoryOrChest));
+
+ ///
+ /// Patches out the code that's called when crafting.
+ /// This changes the call `player.GetInventory().RemoveItem(itemData.m_shared.m_name, amount2, itemData.m_quality);`
+ /// to allow crafting recipes with materials comming from containers when they have m_requireOnlyOneIngredient set to True.
+ ///
+ [HarmonyTranspiler]
+ public static IEnumerable Transpile(IEnumerable instructions)
+ {
+ if (!Configuration.Current.CraftFromChest.IsEnabled) return instructions;
+
+ List il = instructions.ToList();
+
+ for (int i = 0; i < il.Count; i++)
+ {
+ if (il[i].Calls(method_Player_GetFirstRequiredItem))
+ {
+ il[i] = new CodeInstruction(OpCodes.Call, method_GetFirstRequiredItemFromInventoryOrChest);
+ il.RemoveRange(i - 6, 2);
+ break;
+ }
+ }
+ for (int i = 0; i < il.Count; i++)
+ {
+ if (il[i].Calls(method_Player_Inventory_RemoveItem))
+ {
+ il[i] = new CodeInstruction(OpCodes.Call, method_UseItemFromIventoryOrChest);
+ il.RemoveAt(i - 7); // removes calls to Player::GetInventory
+
+ return il.AsEnumerable();
+ }
+ }
+
+ return instructions;
+ }
+
+ private static ItemDrop.ItemData GetFirstRequiredItemFromInventoryOrChest(Player player, Recipe recipe, int quality, out int quantity)
+ {
+ ItemDrop.ItemData found = player.GetFirstRequiredItem(player.GetInventory(), recipe, quality, out quantity);
+ if (found != null) return found;
+
+ GameObject pos = player.GetCurrentCraftingStation()?.gameObject;
+ if (!pos || !Configuration.Current.CraftFromChest.checkFromWorkbench) pos = player.gameObject;
+
+ List nearbyChests = InventoryAssistant.GetNearbyChests(pos, Helper.Clamp(Configuration.Current.CraftFromChest.range, 1, 50), !Configuration.Current.CraftFromChest.ignorePrivateAreaCheck);
+
+ foreach (Container chest in nearbyChests)
+ {
+ found = player.GetFirstRequiredItem(chest.GetInventory(), recipe, quality, out quantity);
+ if (found != null)
+ {
+ return found;
+ }
+ }
+
+ return null;
+ }
+
+ private static void UseItemFromIventoryOrChest(Player player, string itemName, int quantity, int quality)
+ {
+ Inventory playerInventory = player.GetInventory();
+ if (playerInventory.CountItems(itemName, quality) >= quantity)
+ {
+ playerInventory.RemoveItem(itemName, quantity, quality);
+ return;
+ }
+
+ GameObject pos = player.GetCurrentCraftingStation()?.gameObject;
+ if (!pos || !Configuration.Current.CraftFromChest.checkFromWorkbench) pos = player.gameObject;
+
+ List nearbyChests = InventoryAssistant.GetNearbyChests(pos, Helper.Clamp(Configuration.Current.CraftFromChest.range, 1, 50), !Configuration.Current.CraftFromChest.ignorePrivateAreaCheck);
+
+ int toRemove = quantity;
+ foreach (Container chest in nearbyChests)
+ {
+ Inventory chestInventory = chest.GetInventory();
+ if (chestInventory.CountItems(itemName, quality) > 0)
+ {
+ toRemove -= InventoryAssistant.RemoveItemFromChest(chest, itemName, toRemove);
+ if (toRemove == 0) return;
+ }
+ }
+ }
+ }
}
diff --git a/ValheimPlus/GameClasses/Player.cs b/ValheimPlus/GameClasses/Player.cs
index 4067b64c..a9a51141 100644
--- a/ValheimPlus/GameClasses/Player.cs
+++ b/ValheimPlus/GameClasses/Player.cs
@@ -117,7 +117,7 @@ private static void Postfix(ref Player __instance, ref Vector3 ___m_moveDir, ref
EnvMan env = EnvMan.instance;
// only update the time at most once per minute
- if (savedEnvMinutes != env.m_totalSeconds/60)
+ if (savedEnvMinutes != env.m_totalSeconds / 60)
{
int day = env.GetCurrentDay();
@@ -145,7 +145,7 @@ private static void Postfix(ref Player __instance, ref Vector3 ___m_moveDir, ref
timeObj.GetComponent().position = new Vector2(staminaBarRect.position.x, statusEffectBarRect.position.y);
timeObj.SetActive(true);
- savedEnvMinutes = env.m_totalSeconds/60;
+ savedEnvMinutes = env.m_totalSeconds / 60;
}
}
}
@@ -271,7 +271,7 @@ public static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable
+ /// Patches out the function Player::GetFirstRequiredItem
+ /// As the original code is calling Inventory::CountItems using `this` instead of using the inventory parameter
+ /// we can't use this function to check get the first required item from a Container (chest) inventory.
+ /// Instead of calling `this.m_inventory.CountItems` we're now calling `inventory.CountItems`.
+ ///
+ /// As the original function passes `this.GetInventory()` where `this` is the Player instance as the inventory parameter,
+ /// there is no change to the way the function would usually work.
+ ///
+ [HarmonyTranspiler]
+ public static IEnumerable Transpile(IEnumerable instructions)
+ {
+ List il = instructions.ToList();
+
+ for (int i = 0; i < il.Count; i++)
+ {
+ if (il[i].opcode == OpCodes.Ldarg_0)
+ {
+ il[i].opcode = OpCodes.Ldarg_1;
+ il.RemoveAt(i + 1);
+
+ return il.AsEnumerable();
+ }
+ }
+ return instructions;
+ }
+ }
}
diff --git a/ValheimPlus/Utility/InventoryAssistant.cs b/ValheimPlus/Utility/InventoryAssistant.cs
index ce246d69..e01d3b92 100644
--- a/ValheimPlus/Utility/InventoryAssistant.cs
+++ b/ValheimPlus/Utility/InventoryAssistant.cs
@@ -18,11 +18,11 @@ public static List GetNearbyChests(GameObject target, float range, bo
string[] layerMask = { "piece" };
- if(Configuration.Current.CraftFromChest.allowCraftingFromCarts || Configuration.Current.CraftFromChest.allowCraftingFromShips)
+ if (Configuration.Current.CraftFromChest.allowCraftingFromCarts || Configuration.Current.CraftFromChest.allowCraftingFromShips)
layerMask = new string[] { "piece", "item", "vehicle" };
Collider[] hitColliders = Physics.OverlapSphere(target.transform.position, range, LayerMask.GetMask(layerMask));
-
+
// Order the found objects to select the nearest first instead of the farthest inventory.
IOrderedEnumerable orderedColliders = hitColliders.OrderBy(x => Vector3.Distance(x.gameObject.transform.position, target.transform.position));
@@ -49,7 +49,7 @@ public static List GetNearbyChests(GameObject target, float range, bo
if (isShip && !Configuration.Current.CraftFromChest.allowCraftingFromShips)
continue;
- if(piece.IsPlacedByPlayer() || (isShip && Configuration.Current.CraftFromChest.allowCraftingFromShips))
+ if (piece.IsPlacedByPlayer() || (isShip && Configuration.Current.CraftFromChest.allowCraftingFromShips))
validContainers.Add(foundContainer);
}
}
@@ -94,6 +94,19 @@ public static bool ChestContainsItem(Container chest, ItemDrop.ItemData needle)
return false;
}
+ public static bool ChestContainsItem(Container chest, string needle)
+ {
+ List items = chest.GetInventory().GetAllItems();
+
+ foreach (ItemDrop.ItemData item in items)
+ {
+ if (item.m_shared.m_name == needle)
+ return true;
+ }
+
+ return false;
+ }
+
///
/// function to get all items in nearby chests by range
///
@@ -144,6 +157,17 @@ public static int GetItemAmountInItemList(List itemList, Item
return amount;
}
+ public static int GetItemAmountInItemList(List itemList, string needle)
+ {
+ int amount = 0;
+ foreach (ItemDrop.ItemData item in itemList)
+ {
+ if (item.m_shared.m_name == needle) amount += item.m_stack;
+ }
+
+ return amount;
+ }
+
// function to remove items in the amount from all nearby chests
public static int RemoveItemInAmountFromAllNearbyChests(GameObject target, float range, ItemDrop.ItemData needle, int amount, bool checkWard = true)
{
@@ -174,6 +198,35 @@ public static int RemoveItemInAmountFromAllNearbyChests(GameObject target, float
return itemsRemovedTotal;
}
+ public static int RemoveItemInAmountFromAllNearbyChests(GameObject target, float range, string needle, int amount, bool checkWard = true)
+ {
+ List nearbyChests = GetNearbyChests(target, range, checkWard);
+
+ // check if there are enough items nearby
+ List allItems = GetNearbyChestItemsByContainerList(nearbyChests);
+
+ // get amount of item
+ int availableAmount = GetItemAmountInItemList(allItems, needle);
+
+ // check if there are enough items
+ if (amount == 0)
+ return 0;
+
+ // iterate all chests and remove as many items as possible for the respective chest
+ int itemsRemovedTotal = 0;
+ foreach (Container chest in nearbyChests)
+ {
+ if (itemsRemovedTotal != amount)
+ {
+ int removedItems = RemoveItemFromChest(chest, needle, amount);
+ itemsRemovedTotal += removedItems;
+ amount -= removedItems;
+ }
+ }
+
+ return itemsRemovedTotal;
+ }
+
// function to add a item by name/ItemDrop.ItemData to a specified chest
///
@@ -215,6 +268,42 @@ public static int RemoveItemFromChest(Container chest, ItemDrop.ItemData needle,
return totalRemoved;
}
+ public static int RemoveItemFromChest(Container chest, string needle, int amount = 1)
+ {
+ if (!ChestContainsItem(chest, needle))
+ {
+ return 0;
+ }
+
+ int totalRemoved = 0;
+ // find item
+ List allItems = chest.GetInventory().GetAllItems();
+ foreach (ItemDrop.ItemData itemData in allItems)
+ {
+ if (itemData.m_shared.m_name == needle)
+ {
+ int num = Mathf.Min(itemData.m_stack, amount);
+ itemData.m_stack -= num;
+ amount -= num;
+ totalRemoved += num;
+ if (amount <= 0)
+ {
+ break;
+ }
+ }
+ }
+
+ // We don't want to send chest content through network
+ if (totalRemoved == 0) return 0;
+
+ allItems.RemoveAll((ItemDrop.ItemData x) => x.m_stack <= 0);
+ chest.m_inventory.m_inventory = allItems;
+
+ ConveyContainerToNetwork(chest);
+
+ return totalRemoved;
+ }
+
///
/// Function to convey the changes of a container to the network
///