Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Valheim ItemInfiniteStack v1.0.1
Valheim.ItemInfiniteStack.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.UI; using Valheim.ItemInfiniteStack.Configuration; using Valheim.ItemInfiniteStack.Core; using Valheim.ItemInfiniteStack.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Valheim.ItemInfiniteStack")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+77677a2a29189ae285a5e4b860f4687c514ee127")] [assembly: AssemblyProduct("Valheim.ItemInfiniteStack")] [assembly: AssemblyTitle("Valheim.ItemInfiniteStack")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] namespace Valheim.ItemInfiniteStack { [BepInPlugin("ckforgame.ItemInfiniteStack", "Valheim.ItemInfiniteStack", "1.0.1")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "ckforgame.ItemInfiniteStack"; public const string ModName = "Valheim.ItemInfiniteStack"; public const string ModVersion = "1.0.1"; private Harmony _harmony; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; PluginConfig.Initialize(((BaseUnityPlugin)this).Config); PluginConfig.OnConfigChanged += HandleConfigChanged; _harmony = new Harmony("ckforgame.ItemInfiniteStack"); _harmony.PatchAll(); Log.LogInfo((object)string.Format("{0} v{1} loaded successfully! InfiniteStackLimit: {2}", "Valheim.ItemInfiniteStack", "1.0.1", PluginConfig.InfiniteStackLimit.Value)); } private void OnDestroy() { PluginConfig.OnConfigChanged -= HandleConfigChanged; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } Instance = null; } private void Update() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (PluginConfig.ModEnabled.Value && InventoryGui.IsVisible() && !TextInput.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus())) { KeyboardShortcut value = PluginConfig.ConsolidateHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { InventoryConsolidator.ConsolidateActiveInventories(); } } } private static void HandleConfigChanged() { if (PluginConfig.LoggingEnabled.Value) { Log.LogInfo((object)"Configuration changed, refreshing ObjectDB and inventories..."); } StackManager.PatchObjectDB(); if ((Object)(object)Player.m_localPlayer != (Object)null) { StackManager.PatchPlayerInventory(Player.m_localPlayer); } } } } namespace Valheim.ItemInfiniteStack.UI { public static class ItemStackFormatter { private static readonly string[] SmallNumberCache; static ItemStackFormatter() { SmallNumberCache = new string[1001]; for (int i = 0; i <= 1000; i++) { SmallNumberCache[i] = i.ToString(); } } public static string FastIntToString(int value) { if (value >= 0 && value <= 1000) { return SmallNumberCache[value]; } return value.ToString(); } public static string FormatCompact(int value) { if (value < 0) { return "0"; } if (value < 1000) { return FastIntToString(value); } if (value < 1000000) { float num = (float)value / 1000f; if (value % 1000 != 0 && !(num >= 100f)) { return $"{num:0.#}k"; } return $"{Mathf_Floor(num)}k"; } if (value < 1000000000) { float num2 = (float)value / 1000000f; if (value % 1000000 != 0 && !(num2 >= 100f)) { return $"{num2:0.#}M"; } return $"{Mathf_Floor(num2)}M"; } float num3 = (float)value / 1E+09f; return $"{num3:0.#}B"; } private static int Mathf_Floor(float val) { return (int)val; } public static void FormatElementAmount(InventoryElement element, ItemData item) { if ((Object)(object)element?.m_amount == (Object)null || item?.m_shared == null) { return; } if (item.m_shared.m_maxStackSize <= 1) { ((Behaviour)element.m_amount).enabled = false; return; } if (item.m_stack <= 1 && PluginConfig.HideSingleItemCount.Value) { ((Behaviour)element.m_amount).enabled = false; return; } ((Behaviour)element.m_amount).enabled = true; switch (PluginConfig.DisplayFormat.Value) { default: if (item.m_stack >= PluginConfig.CompactThreshold.Value) { element.m_amount.text = FormatCompact(item.m_stack); } else { element.m_amount.text = FastIntToString(item.m_stack); } break; case StackDisplayMode.Compact: if (item.m_shared.m_maxStackSize >= 1000) { element.m_amount.text = FormatCompact(item.m_stack); } else { element.m_amount.text = FormatCompact(item.m_stack) + "/" + FormatCompact(item.m_shared.m_maxStackSize); } break; case StackDisplayMode.StandardRatio: if (item.m_shared.m_maxStackSize >= 1000) { element.m_amount.text = FastIntToString(item.m_stack) + "/" + FormatCompact(item.m_shared.m_maxStackSize); } else { element.m_amount.text = FastIntToString(item.m_stack) + "/" + FastIntToString(item.m_shared.m_maxStackSize); } break; } } } } namespace Valheim.ItemInfiniteStack.Patches { [HarmonyPatch(typeof(InventoryGrid))] public static class InventoryGridPatches { [HarmonyPatch("UpdateInventory")] [HarmonyPrefix] public static void UpdateInventory_Prefix(Inventory inventory) { if (PluginConfig.ModEnabled.Value && inventory != null) { StackManager.PatchInventory(inventory); } } [HarmonyPatch("UpdateGui")] [HarmonyPostfix] [HarmonyPriority(0)] public static void UpdateGui_Postfix(InventoryGrid __instance) { if (!PluginConfig.ModEnabled.Value || (Object)(object)__instance == (Object)null || __instance.m_inventory == null) { return; } int width = __instance.m_inventory.GetWidth(); List<ItemData> allItems = __instance.m_inventory.GetAllItems(); if (allItems == null) { return; } foreach (ItemData item in allItems) { if (item?.m_shared == null) { continue; } InventoryElement element = __instance.GetElement(item.m_gridPos.x, item.m_gridPos.y, width); if (!((Object)(object)element == (Object)null) && !((Object)(object)element.m_amount == (Object)null)) { try { ItemStackFormatter.FormatElementAmount(element, item); } catch { } } } } } [HarmonyPatch(typeof(Inventory))] public static class InventoryPatches { [HarmonyPatch("CanAddItem", new Type[] { typeof(ItemData), typeof(int) })] [HarmonyPrefix] public static bool CanAddItem_Prefix(Inventory __instance, ItemData item, int stack, ref bool __result) { if (!PluginConfig.ModEnabled.Value || item?.m_shared == null || __instance?.m_inventory == null) { return true; } string prefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); StackManager.ApplyStackModifications(item.m_shared, prefabName); if (!ItemClassifier.Classify(item.m_shared, prefabName).IsStackable) { return true; } if (stack <= 0) { stack = item.m_stack; } long num = 0L; foreach (ItemData item2 in __instance.m_inventory) { if (item2?.m_shared != null && item2.m_shared.m_name == item.m_shared.m_name && Mathf.Approximately((float)item2.m_worldLevel, (float)item.m_worldLevel)) { StackManager.ApplyStackModifications(item2.m_shared, ((Object)(object)item2.m_dropPrefab != (Object)null) ? ((Object)item2.m_dropPrefab).name : null); if (item2.m_stack < item2.m_shared.m_maxStackSize) { num += item2.m_shared.m_maxStackSize - item2.m_stack; } } } if (num >= stack) { __result = true; return false; } int num2 = __instance.m_width * __instance.m_height - __instance.m_inventory.Count; if (num2 > 0) { long num3 = (long)num2 * (long)item.m_shared.m_maxStackSize; long num4 = num + num3; __result = num4 >= stack; return false; } __result = false; return false; } [HarmonyPatch("FindFreeStackSpace", new Type[] { typeof(string), typeof(float) })] [HarmonyPrefix] public static bool FindFreeStackSpace_Prefix(Inventory __instance, string name, float worldLevel, ref int __result) { if (!PluginConfig.ModEnabled.Value || __instance == null || __instance.m_inventory == null) { return true; } long num = 0L; foreach (ItemData item in __instance.m_inventory) { if (item?.m_shared != null && item.m_shared.m_name == name && Mathf.Approximately((float)item.m_worldLevel, worldLevel)) { StackManager.ApplyStackModifications(item.m_shared, ((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); if (item.m_stack < item.m_shared.m_maxStackSize) { num += item.m_shared.m_maxStackSize - item.m_stack; } } } __result = (int)Math.Min(num, 999999999L); return false; } [HarmonyPatch("FindFreeStackItem", new Type[] { typeof(string), typeof(int), typeof(float) })] [HarmonyPrefix] public static bool FindFreeStackItem_Prefix(Inventory __instance, string name, int quality, float worldLevel, ref ItemData __result) { if (!PluginConfig.ModEnabled.Value || __instance == null || __instance.m_inventory == null) { return true; } foreach (ItemData item in __instance.m_inventory) { if (item?.m_shared != null && item.m_shared.m_name == name && item.m_quality == quality && Mathf.Approximately((float)item.m_worldLevel, worldLevel)) { StackManager.ApplyStackModifications(item.m_shared, ((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); if (item.m_stack < item.m_shared.m_maxStackSize) { __result = item; return false; } } } __result = null; return false; } [HarmonyPatch("AddItem", new Type[] { typeof(ItemData) })] [HarmonyPrefix] public static bool AddItem_Prefix(Inventory __instance, ItemData item, ref bool __result) { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) if (!PluginConfig.ModEnabled.Value || item?.m_shared == null || __instance?.m_inventory == null) { return true; } string prefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); StackManager.ApplyStackModifications(item.m_shared, prefabName); if (!ItemClassifier.Classify(item.m_shared, prefabName).IsStackable || item.m_shared.m_maxStackSize <= 1) { return true; } bool flag = true; bool flag2 = item.m_cheated && Achievements.IsCheatedAtAll(); int num = item.m_stack; while (num > 0) { ItemData val = __instance.FindFreeStackItem(item.m_shared.m_name, item.m_quality, (float)item.m_worldLevel); if (val != null) { int num2 = val.m_shared.m_maxStackSize - val.m_stack; if (num2 <= 0) { break; } int num3 = Math.Min(num2, num); val.m_stack += num3; num -= num3; if (flag2 && !PlayerProfile.s_bypassCheatChecks) { val.m_cheated = true; } } else { item.m_stack = num; Vector2i val2 = __instance.FindEmptySlot(__instance.TopFirst(item)); if (val2.x < 0) { flag = false; ZLog.LogWarning((object)$"Trying to add item to occupied slot {val2.x}, {val2.y}"); break; } item.m_gridPos = val2; __instance.m_inventory.Add(item); num = 0; } } if (num == 0 && !__instance.m_inventory.Contains(item)) { item.m_stack = 0; } else if (num > 0) { item.m_stack = num; } __instance.Changed(flag, flag2); __result = flag; return false; } [HarmonyPatch("AddItem", new Type[] { typeof(ItemData), typeof(Vector2i) })] [HarmonyPrefix] public static bool AddItemPos_Prefix(Inventory __instance, ItemData item, Vector2i pos, ref bool __result) { //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) if (!PluginConfig.ModEnabled.Value || item?.m_shared == null || __instance?.m_inventory == null) { return true; } string prefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); StackManager.ApplyStackModifications(item.m_shared, prefabName); if (!ItemClassifier.Classify(item.m_shared, prefabName).IsStackable || item.m_shared.m_maxStackSize <= 1) { return true; } bool flag = true; bool flag2 = item.m_cheated && Achievements.IsCheatedAtAll(); int num = item.m_stack; while (num > 0) { ItemData val = __instance.FindFreeStackItem(item.m_shared.m_name, item.m_quality, (float)item.m_worldLevel); if (val != null) { int num2 = val.m_shared.m_maxStackSize - val.m_stack; if (num2 <= 0) { break; } int num3 = Math.Min(num2, num); val.m_stack += num3; num -= num3; if (flag2 && !PlayerProfile.s_bypassCheatChecks) { val.m_cheated = true; } continue; } item.m_stack = num; if (__instance.GetItemAt(pos.x, pos.y) == null) { item.m_gridPos = pos; __instance.m_inventory.Add(item); num = 0; continue; } Vector2i val2 = __instance.FindEmptySlot(__instance.TopFirst(item)); if (val2.x >= 0) { item.m_gridPos = val2; __instance.m_inventory.Add(item); num = 0; continue; } flag = false; ZLog.LogWarning((object)$"Trying to add item to occupied slot {pos.x}, {pos.y}"); break; } if (num == 0 && !__instance.m_inventory.Contains(item)) { item.m_stack = 0; } else if (num > 0) { item.m_stack = num; } __instance.Changed(flag, flag2); __result = flag; return false; } [HarmonyPatch("AddItem", new Type[] { typeof(ItemData), typeof(int), typeof(int), typeof(int), typeof(bool) })] [HarmonyPrefix] public static void AddItemSlot_Prefix(Inventory __instance, ItemData item, int x, int y) { if (PluginConfig.ModEnabled.Value && item?.m_shared != null && __instance != null) { StackManager.ApplyStackModifications(item.m_shared, ((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); ItemData itemAt = __instance.GetItemAt(x, y); if (itemAt?.m_shared != null) { StackManager.ApplyStackModifications(itemAt.m_shared, ((Object)(object)itemAt.m_dropPrefab != (Object)null) ? ((Object)itemAt.m_dropPrefab).name : null); } } } [HarmonyPatch("Load", new Type[] { typeof(ZPackage) })] [HarmonyPostfix] [HarmonyPriority(0)] public static void Load_Postfix(Inventory __instance) { if (PluginConfig.ModEnabled.Value && __instance != null) { StackManager.PatchInventory(__instance); } } } [HarmonyPatch(typeof(Player))] public static class PlayerPatches { [HarmonyPatch("Load")] [HarmonyPostfix] [HarmonyPriority(0)] public static void Load_Postfix(Player __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null)) { StackManager.PatchPlayerInventory(__instance); } } [HarmonyPatch("OnSpawned", new Type[] { typeof(bool) })] [HarmonyPostfix] [HarmonyPriority(0)] public static void OnSpawned_Postfix(Player __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null)) { StackManager.PatchObjectDB(); StackManager.PatchPlayerInventory(__instance); } } } [HarmonyPatch(typeof(Container))] public static class ContainerPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] [HarmonyPriority(0)] public static void Awake_Postfix(Container __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null)) { StackManager.PatchInventory(__instance.GetInventory()); } } [HarmonyPatch("CheckForChanges")] [HarmonyPostfix] [HarmonyPriority(0)] public static void CheckForChanges_Postfix(Container __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null)) { StackManager.PatchInventory(__instance.GetInventory()); } } } [HarmonyPatch(typeof(InventoryGui))] public static class InventoryGuiPatches { [HarmonyPatch("Show")] [HarmonyPostfix] public static void Show_Postfix(Container container) { if (PluginConfig.ModEnabled.Value) { if ((Object)(object)Player.m_localPlayer != (Object)null) { StackManager.PatchPlayerInventory(Player.m_localPlayer); } if ((Object)(object)container != (Object)null) { StackManager.PatchInventory(container.GetInventory()); } else if ((Object)(object)InventoryGui.instance != (Object)null && (Object)(object)InventoryGui.instance.m_currentContainer != (Object)null) { StackManager.PatchInventory(InventoryGui.instance.m_currentContainer.GetInventory()); } if (PluginConfig.EnableAutoConsolidateOnOpen.Value) { InventoryConsolidator.ConsolidateActiveInventories(showMessage: false); } } } } [HarmonyPatch(typeof(ItemDrop))] public static class ItemDropPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] [HarmonyPriority(0)] public static void Awake_Postfix(ItemDrop __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null) && __instance.m_itemData?.m_shared != null) { string prefabName = (((Object)(object)((Component)__instance).gameObject != (Object)null) ? ((Object)((Component)__instance).gameObject).name : null); StackManager.ApplyStackModifications(__instance.m_itemData.m_shared, prefabName); } } } [HarmonyPatch] public static class ItemDropOnCreateNewPatch { [HarmonyTargetMethods] public static IEnumerable<MethodBase> TargetMethods() { MethodInfo methodInfo = AccessTools.Method(typeof(ItemDrop), "OnCreateNew", new Type[2] { typeof(ItemDrop), typeof(bool) }, (Type[])null); if (methodInfo != null) { yield return methodInfo; yield break; } MethodInfo methodInfo2 = AccessTools.Method(typeof(ItemDrop), "OnCreateNew", new Type[1] { typeof(ItemDrop) }, (Type[])null); if (methodInfo2 != null) { yield return methodInfo2; } } [HarmonyPostfix] [HarmonyPriority(0)] public static void Postfix(ItemDrop item) { if (PluginConfig.ModEnabled.Value && !((Object)(object)item == (Object)null) && item.m_itemData?.m_shared != null) { string prefabName = (((Object)(object)((Component)item).gameObject != (Object)null) ? ((Object)((Component)item).gameObject).name : null); StackManager.ApplyStackModifications(item.m_itemData.m_shared, prefabName); } } } [HarmonyPatch(typeof(ObjectDB))] public static class ObjectDBPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] [HarmonyPriority(0)] public static void Awake_Postfix(ObjectDB __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null)) { StackManager.PatchObjectDB(); } } [HarmonyPatch("CopyOtherDB")] [HarmonyPostfix] [HarmonyPriority(0)] public static void CopyOtherDB_Postfix(ObjectDB __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null)) { StackManager.PatchObjectDB(); } } [HarmonyPatch("UpdateRegisters")] [HarmonyPostfix] [HarmonyPriority(0)] public static void UpdateRegisters_Postfix(ObjectDB __instance) { if (PluginConfig.ModEnabled.Value && !((Object)(object)__instance == (Object)null)) { StackManager.PatchObjectDB(); } } } [HarmonyPatch(typeof(SplitDialog))] public static class SplitDialogPatches { [HarmonyPatch("SliderChanged")] [HarmonyPostfix] public static void SliderChanged_Postfix(SplitDialog __instance, float value) { if (!PluginConfig.ModEnabled.Value || (Object)(object)__instance == (Object)null || (Object)(object)__instance.m_splitAmount == (Object)null || (Object)(object)__instance.m_splitSlider == (Object)null) { return; } int num = (int)value; int num2 = (int)__instance.m_splitSlider.maxValue; if (num2 >= 10000) { if (num < 10000) { __instance.m_splitAmount.text = $"{num} / {ItemStackFormatter.FormatCompact(num2)}"; } else { __instance.m_splitAmount.text = ItemStackFormatter.FormatCompact(num) + " / " + ItemStackFormatter.FormatCompact(num2); } } else { __instance.m_splitAmount.text = $"{num} / {num2}"; } } } [HarmonyPatch(typeof(InventoryGui))] public static class InventoryGuiSplitPatches { [HarmonyPatch("UpdateSplitDialog")] [HarmonyPostfix] public static void UpdateSplitDialog_Postfix(InventoryGui __instance) { if (!PluginConfig.ModEnabled.Value || !PluginConfig.EnableSplitDialogEnhancements.Value || (Object)(object)__instance == (Object)null || (Object)(object)__instance.m_splitDialog == (Object)null || !__instance.m_splitDialog.IsActive) { return; } Slider splitSlider = __instance.m_splitDialog.m_splitSlider; if ((Object)(object)splitSlider == (Object)null) { return; } float axis = Input.GetAxis("Mouse ScrollWheel"); if (Mathf.Abs(axis) > 0.0001f) { int num = 1; if (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303)) { num *= 10; } if (Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) { num *= 100; } int num2 = PluginConfig.SplitWheelStep.Value * num; if (axis > 0f) { __instance.m_splitDialog.SliderValue = Mathf.Min(splitSlider.maxValue, splitSlider.value + (float)num2); } else { __instance.m_splitDialog.SliderValue = Mathf.Max(splitSlider.minValue, splitSlider.value - (float)num2); } } bool flag = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); bool flag2 = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); if (flag || flag2) { int num3 = ((flag && flag2) ? 1000 : (flag2 ? 100 : 10)); if (ZInput.GetKeyDown((KeyCode)276, true)) { __instance.m_splitDialog.SliderValue = Mathf.Max(splitSlider.minValue, splitSlider.value - (float)num3); } else if (ZInput.GetKeyDown((KeyCode)275, true)) { __instance.m_splitDialog.SliderValue = Mathf.Min(splitSlider.maxValue, splitSlider.value + (float)num3); } } if (ZInput.GetKeyDown((KeyCode)278, true)) { __instance.m_splitDialog.SliderValue = splitSlider.minValue; } else if (ZInput.GetKeyDown((KeyCode)279, true)) { __instance.m_splitDialog.SliderValue = splitSlider.maxValue; } else if (ZInput.GetKeyDown((KeyCode)104, true) || ZInput.GetKeyDown((KeyCode)32, true)) { __instance.m_splitDialog.SliderValue = Mathf.CeilToInt(splitSlider.maxValue / 2f); } } } } namespace Valheim.ItemInfiniteStack.Core { public static class DefaultRegistry { private static readonly Dictionary<string, int> _originalStackSizes = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, float> _originalWeights = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static readonly object _lock = new object(); public static string CleanPrefabName(string name) { if (string.IsNullOrEmpty(name)) { return name; } int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num >= 0) { name = name.Substring(0, num); } return name.Trim(); } public static void RegisterDefault(string prefabName, string sharedName, int maxStackSize, float weight) { string text = CleanPrefabName(prefabName); if ((string.IsNullOrEmpty(text) && string.IsNullOrEmpty(sharedName)) || maxStackSize >= 999999999 || maxStackSize >= 1000 || (PluginConfig.InfiniteStackLimit != null && maxStackSize >= PluginConfig.InfiniteStackLimit.Value && maxStackSize > 100)) { return; } lock (_lock) { if (!string.IsNullOrEmpty(text)) { if (!_originalStackSizes.ContainsKey(text)) { _originalStackSizes[text] = maxStackSize; } if (!_originalWeights.ContainsKey(text)) { _originalWeights[text] = weight; } } if (!string.IsNullOrEmpty(sharedName)) { if (!_originalStackSizes.ContainsKey(sharedName)) { _originalStackSizes[sharedName] = maxStackSize; } if (!_originalWeights.ContainsKey(sharedName)) { _originalWeights[sharedName] = weight; } } } } public static int GetOriginalStack(string identifier, int fallback) { if (string.IsNullOrEmpty(identifier)) { return fallback; } string text = CleanPrefabName(identifier); lock (_lock) { if (_originalStackSizes.TryGetValue(text, out var value)) { return value; } if (!string.Equals(text, identifier, StringComparison.OrdinalIgnoreCase) && _originalStackSizes.TryGetValue(identifier, out var value2)) { return value2; } return fallback; } } public static float GetOriginalWeight(string identifier, float fallback) { if (string.IsNullOrEmpty(identifier)) { return fallback; } string text = CleanPrefabName(identifier); lock (_lock) { if (_originalWeights.TryGetValue(text, out var value)) { return value; } if (!string.Equals(text, identifier, StringComparison.OrdinalIgnoreCase) && _originalWeights.TryGetValue(identifier, out var value2)) { return value2; } return fallback; } } public static bool HasDefault(string identifier) { if (string.IsNullOrEmpty(identifier)) { return false; } string key = CleanPrefabName(identifier); lock (_lock) { return _originalStackSizes.ContainsKey(key) || _originalStackSizes.ContainsKey(identifier); } } public static void Clear() { lock (_lock) { _originalStackSizes.Clear(); _originalWeights.Clear(); } } } public static class InventoryConsolidator { public static int Consolidate(Inventory inventory) { if (inventory == null || inventory.m_inventory == null || inventory.m_inventory.Count <= 1) { return 0; } StackManager.PatchInventory(inventory); int num = 0; List<ItemData> list = new List<ItemData>(); Dictionary<string, ItemData> dictionary = new Dictionary<string, ItemData>(StringComparer.Ordinal); for (int i = 0; i < inventory.m_inventory.Count; i++) { ItemData val = inventory.m_inventory[i]; if (val?.m_shared == null || val.m_equipped || val.m_shared.m_maxStackSize <= 1 || val.m_shared.m_useDurability || (val.m_customData != null && val.m_customData.Count > 0)) { continue; } string key = $"{val.m_shared.m_name}:{val.m_quality}:{val.m_variant}:{val.m_crafterID}:{val.m_worldLevel}"; if (!dictionary.TryGetValue(key, out var value)) { dictionary[key] = val; continue; } int num2 = value.m_shared.m_maxStackSize - value.m_stack; if (num2 > 0) { int num3 = Math.Min(num2, val.m_stack); ItemData obj = value; obj.m_stack += num3; val.m_stack -= num3; num++; if (val.m_stack <= 0) { list.Add(val); } } else { dictionary[key] = val; } } if (list.Count > 0) { foreach (ItemData item in list) { inventory.m_inventory.Remove(item); } inventory.Changed(false, false); } return num; } public static void ConsolidateActiveInventories(bool showMessage = true) { int num = 0; if ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.instance.IsContainerOpen() && (Object)(object)InventoryGui.instance.m_currentContainer != (Object)null) { Inventory inventory = InventoryGui.instance.m_currentContainer.GetInventory(); if (inventory != null) { num += Consolidate(inventory); } } if ((Object)(object)Player.m_localPlayer != (Object)null) { Inventory inventory2 = ((Humanoid)Player.m_localPlayer).GetInventory(); if (inventory2 != null) { num += Consolidate(inventory2); } } if (showMessage && num > 0 && (Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, $"Consolidated {num} duplicate item stack(s).", 0, (Sprite)null, false, true); } } } public enum ExclusionReason { None, Blacklisted, DurabilityItem, QualityUpgradable, EquipmentType, VanillaOneStack, Whitelisted } public readonly struct ClassificationDecision { public bool IsStackable { get; } public ExclusionReason Reason { get; } public string Details { get; } public ClassificationDecision(bool isStackable, ExclusionReason reason, string details = "") { IsStackable = isStackable; Reason = reason; Details = details; } } public static class ItemClassifier { private static readonly HashSet<ItemType> EquipmentItemTypes = new HashSet<ItemType> { (ItemType)3, (ItemType)14, (ItemType)22, (ItemType)4, (ItemType)5, (ItemType)6, (ItemType)7, (ItemType)11, (ItemType)17, (ItemType)18, (ItemType)19, (ItemType)15, (ItemType)10, (ItemType)24, (ItemType)20 }; public static ClassificationDecision Classify(SharedData shared, string prefabName) { //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Invalid comparison between Unknown and I4 //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Invalid comparison between Unknown and I4 //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Invalid comparison between Unknown and I4 //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Invalid comparison between Unknown and I4 //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Invalid comparison between Unknown and I4 //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Invalid comparison between Unknown and I4 //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Invalid comparison between Unknown and I4 if (shared == null) { return new ClassificationDecision(isStackable: false, ExclusionReason.None, "Null shared data"); } string text = ((!string.IsNullOrEmpty(prefabName)) ? prefabName : shared.m_name); if (PluginConfig.IsBlacklisted(prefabName, shared.m_name)) { return new ClassificationDecision(isStackable: false, ExclusionReason.Blacklisted, "User blacklisted: '" + text + "'"); } if (PluginConfig.IsWhitelisted(prefabName, shared.m_name)) { return new ClassificationDecision(isStackable: true, ExclusionReason.Whitelisted, "User whitelisted: '" + text + "'"); } if (PluginConfig.ProtectDurabilityItems.Value && shared.m_useDurability) { return new ClassificationDecision(isStackable: false, ExclusionReason.DurabilityItem, $"Durability item (max: {shared.m_maxDurability})"); } if (PluginConfig.ProtectQualityItems.Value && shared.m_maxQuality > 1 && (int)shared.m_itemType != 21) { return new ClassificationDecision(isStackable: false, ExclusionReason.QualityUpgradable, $"Quality upgradable (max: {shared.m_maxQuality})"); } if (PluginConfig.ProtectEquipment.Value && EquipmentItemTypes.Contains(shared.m_itemType)) { return new ClassificationDecision(isStackable: false, ExclusionReason.EquipmentType, $"Equipment type: {shared.m_itemType}"); } if ((int)shared.m_itemType != 1 && (int)shared.m_itemType != 2 && (int)shared.m_itemType != 9 && (int)shared.m_itemType != 23 && (int)shared.m_itemType != 13 && (int)shared.m_itemType != 21) { int originalStack = DefaultRegistry.GetOriginalStack(text, shared.m_maxStackSize); if (PluginConfig.PreserveVanillaOneStack.Value && originalStack <= 1) { return new ClassificationDecision(isStackable: false, ExclusionReason.VanillaOneStack, $"Vanilla default stack is {originalStack}"); } } return new ClassificationDecision(isStackable: true, ExclusionReason.None, "Stackable material/resource"); } public static bool IsEquipment(ItemType itemType) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return EquipmentItemTypes.Contains(itemType); } } public static class StackManager { public const int SafeMaxStackLimit = 999999999; public static bool ApplyStackModifications(SharedData shared, string prefabName) { if (shared == null) { return false; } string text = DefaultRegistry.CleanPrefabName(prefabName); string text2 = ((!string.IsNullOrEmpty(text)) ? text : shared.m_name); DefaultRegistry.RegisterDefault(text, shared.m_name, shared.m_maxStackSize, shared.m_weight); int originalStack = DefaultRegistry.GetOriginalStack(text2, shared.m_maxStackSize); float originalWeight = DefaultRegistry.GetOriginalWeight(text2, shared.m_weight); if (!PluginConfig.ModEnabled.Value) { bool num = shared.m_maxStackSize != originalStack; bool flag = Math.Abs(shared.m_weight - originalWeight) > 0.0001f; shared.m_maxStackSize = originalStack; shared.m_weight = originalWeight; return num || flag; } ClassificationDecision classificationDecision = ItemClassifier.Classify(shared, text); if (!classificationDecision.IsStackable) { bool flag2 = false; if (shared.m_maxStackSize != originalStack) { shared.m_maxStackSize = originalStack; flag2 = true; } if (Math.Abs(shared.m_weight - originalWeight) > 0.0001f) { shared.m_weight = originalWeight; flag2 = true; } if (PluginConfig.LoggingEnabled.Value && flag2) { Plugin.Log.LogDebug((object)$"[Preserved 1-Per-Slot] {text2} -> Stack: {shared.m_maxStackSize} (Reason: {classificationDecision.Reason})"); } return flag2; } int num2 = ((!(PluginConfig.GlobalStackMultiplier.Value > 0f)) ? PluginConfig.InfiniteStackLimit.Value : Mathf.CeilToInt((float)originalStack * PluginConfig.GlobalStackMultiplier.Value)); num2 = Mathf.Clamp(num2, 1, 999999999); float num3 = originalWeight; if (PluginConfig.EnableWeightScaling.Value) { float num4 = Mathf.Max(0f, PluginConfig.StackableWeightMultiplier.Value); num3 = originalWeight * num4; } bool flag3 = shared.m_maxStackSize != num2 || Math.Abs(shared.m_weight - num3) > 0.0001f; shared.m_maxStackSize = num2; shared.m_weight = num3; if (PluginConfig.LoggingEnabled.Value && flag3) { Plugin.Log.LogDebug((object)$"[Infinite Stack Applied] {text2} -> Stack: {num2}, Weight: {num3:F2}"); } return flag3; } public static int PatchObjectDB() { if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items == null) { return 0; } int num = 0; foreach (GameObject item in ObjectDB.instance.m_items) { if (!((Object)(object)item == (Object)null)) { ItemDrop component = item.GetComponent<ItemDrop>(); if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null && ApplyStackModifications(component.m_itemData.m_shared, ((Object)item).name)) { num++; } } } if (PluginConfig.LoggingEnabled.Value) { Plugin.Log.LogInfo((object)$"Patched {num} items in ObjectDB."); } return num; } public static bool PatchInventory(Inventory inventory) { if (inventory == null || inventory.m_inventory == null) { return false; } bool flag = false; foreach (ItemData item in inventory.m_inventory) { if (item?.m_shared != null && ApplyStackModifications(item.m_shared, ((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null)) { flag = true; } } if (flag) { inventory.Changed(false, false); } return flag; } public static bool PatchPlayerInventory(Player player) { if ((Object)(object)player != (Object)null) { return PatchInventory(((Humanoid)player).GetInventory()); } return false; } } } namespace Valheim.ItemInfiniteStack.Configuration { public enum StackDisplayMode { AmountOnly, Compact, StandardRatio } public static class PluginConfig { private static HashSet<string> _parsedBlacklist = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static HashSet<string> _parsedWhitelist = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public static ConfigEntry<bool> ModEnabled { get; private set; } public static ConfigEntry<bool> LoggingEnabled { get; private set; } public static ConfigEntry<int> InfiniteStackLimit { get; private set; } public static ConfigEntry<float> GlobalStackMultiplier { get; private set; } public static ConfigEntry<bool> ProtectDurabilityItems { get; private set; } public static ConfigEntry<bool> ProtectQualityItems { get; private set; } public static ConfigEntry<bool> ProtectEquipment { get; private set; } public static ConfigEntry<bool> PreserveVanillaOneStack { get; private set; } public static ConfigEntry<string> CustomBlacklist { get; private set; } public static ConfigEntry<string> CustomWhitelist { get; private set; } public static ConfigEntry<bool> EnableWeightScaling { get; private set; } public static ConfigEntry<float> StackableWeightMultiplier { get; private set; } public static ConfigEntry<StackDisplayMode> DisplayFormat { get; private set; } public static ConfigEntry<int> CompactThreshold { get; private set; } public static ConfigEntry<bool> HideSingleItemCount { get; private set; } public static ConfigEntry<bool> EnableSplitDialogEnhancements { get; private set; } public static ConfigEntry<int> SplitWheelStep { get; private set; } public static ConfigEntry<bool> EnableAutoConsolidateOnOpen { get; private set; } public static ConfigEntry<KeyboardShortcut> ConsolidateHotkey { get; private set; } public static event Action OnConfigChanged; public static void Initialize(ConfigFile config) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Expected O, but got Unknown //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Expected O, but got Unknown //IL_0280: Unknown result type (might be due to invalid IL or missing references) ModEnabled = config.Bind<bool>("1 - General", "Enabled", true, "Master switch to enable or disable the ItemInfiniteStack mod."); LoggingEnabled = config.Bind<bool>("1 - General", "LoggingEnabled", false, "Enable verbose debug logging in the BepInEx console."); InfiniteStackLimit = config.Bind<int>("2 - Stack Settings", "InfiniteStackLimit", 999999999, new ConfigDescription("Target maximum stack size for stackable items. Clamped to a safe max of 999,999,999 to prevent 32-bit integer overflow.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 999999999), Array.Empty<object>())); GlobalStackMultiplier = config.Bind<float>("2 - Stack Settings", "GlobalStackMultiplier", 0f, new ConfigDescription("Optional stack multiplier. If set above 0, an item's stack size will be originalStack * multiplier instead of InfiniteStackLimit. Set to 0 to use InfiniteStackLimit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>())); ProtectDurabilityItems = config.Bind<bool>("3 - Exceptions (1-Per-Slot)", "ProtectDurabilityItems", true, "Strictly preserve 1 item per slot for any item with durability (weapons, armor, tools). Prevents durability corruption."); ProtectQualityItems = config.Bind<bool>("3 - Exceptions (1-Per-Slot)", "ProtectQualityItems", true, "Strictly preserve 1 item per slot for upgradable items (maxQuality > 1). Prevents upgrade tier loss."); ProtectEquipment = config.Bind<bool>("3 - Exceptions (1-Per-Slot)", "ProtectEquipment", true, "Strictly preserve 1 item per slot for equipment types: Weapons, Shields, Armor, Tools, Torches, Trinkets, Utilities."); PreserveVanillaOneStack = config.Bind<bool>("3 - Exceptions (1-Per-Slot)", "PreserveVanillaOneStack", true, "Preserve 1 item per slot for any item whose original vanilla stack size is 1 (unless explicitly included in CustomWhitelist)."); CustomBlacklist = config.Bind<string>("3 - Exceptions (1-Per-Slot)", "CustomBlacklist", "", "Comma-separated list of item prefab names or localization tokens that MUST remain 1 item per slot (e.g. 'Wishbone, Tankard, Cart')."); CustomWhitelist = config.Bind<string>("3 - Exceptions (1-Per-Slot)", "CustomWhitelist", "", "Comma-separated list of item prefab names that are forced to have infinite stack even if they would otherwise be excluded."); EnableWeightScaling = config.Bind<bool>("4 - Weight Settings", "EnableWeightScaling", false, "If enabled, stackable items will have their weight multiplied by StackableWeightMultiplier."); StackableWeightMultiplier = config.Bind<float>("4 - Weight Settings", "StackableWeightMultiplier", 1f, new ConfigDescription("Weight multiplier applied to stackable items. 1.0 = vanilla weight, 0.5 = 50% weight, 0.0 = completely weightless.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); DisplayFormat = config.Bind<StackDisplayMode>("5 - UI & Display", "DisplayFormat", StackDisplayMode.AmountOnly, "How stack count is formatted on inventory slot icons. AmountOnly shows just current count (e.g. 1500). Compact abbreviates large numbers (1.5k, 25M). StandardRatio displays amount/max."); CompactThreshold = config.Bind<int>("5 - UI & Display", "CompactThreshold", 1000, new ConfigDescription("Threshold above which Compact format activates (e.g. 1000 turns into 1k).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 1000000), Array.Empty<object>())); HideSingleItemCount = config.Bind<bool>("5 - UI & Display", "HideSingleItemCount", true, "Hide stack count text when an item slot contains only 1 item (matches clean vanilla behavior)."); EnableSplitDialogEnhancements = config.Bind<bool>("6 - Quality of Life", "EnableSplitDialogEnhancements", true, "Enable mouse wheel scrolling and arrow key fine-tuning on the stack split dialog."); SplitWheelStep = config.Bind<int>("6 - Quality of Life", "SplitWheelStep", 1, new ConfigDescription("Base number of items adjusted per mouse wheel tick on split dialog. Shift scrolls 10x faster, Ctrl scrolls 100x faster.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); EnableAutoConsolidateOnOpen = config.Bind<bool>("6 - Quality of Life", "EnableAutoConsolidateOnOpen", true, "Automatically consolidate duplicate item stacks when opening containers or player inventory."); ConsolidateHotkey = config.Bind<KeyboardShortcut>("6 - Quality of Life", "ConsolidateHotkey", new KeyboardShortcut((KeyCode)127, Array.Empty<KeyCode>()), "Hotkey to immediately consolidate and merge duplicate stacks in the currently opened container or inventory."); RebuildLists(); config.SettingChanged += delegate { RebuildLists(); PluginConfig.OnConfigChanged?.Invoke(); }; } private static void RebuildLists() { _parsedBlacklist = ParseCommaSeparated(CustomBlacklist?.Value); _parsedWhitelist = ParseCommaSeparated(CustomWhitelist?.Value); } private static HashSet<string> ParseCommaSeparated(string input) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(input)) { return hashSet; } string[] array = input.Split(new char[3] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); } } return hashSet; } public static bool IsBlacklisted(string prefabName, string sharedName) { if (_parsedBlacklist.Count == 0) { return false; } if (!string.IsNullOrEmpty(prefabName) && _parsedBlacklist.Contains(prefabName)) { return true; } if (!string.IsNullOrEmpty(sharedName) && _parsedBlacklist.Contains(sharedName)) { return true; } return false; } public static bool IsWhitelisted(string prefabName, string sharedName) { if (_parsedWhitelist.Count == 0) { return false; } if (!string.IsNullOrEmpty(prefabName) && _parsedWhitelist.Contains(prefabName)) { return true; } if (!string.IsNullOrEmpty(sharedName) && _parsedWhitelist.Contains(sharedName)) { return true; } return false; } } }