Decompiled source of InventorySort v1.2.0

InventorySort.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
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 Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("InventorySort")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("InventorySort")]
[assembly: AssemblyTitle("InventorySort")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace InventorySort
{
	internal static class InventorySorter
	{
		private const int HotbarRow = 0;

		public static bool CanSortNow()
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				return false;
			}
			if ((Object)(object)InventoryGui.instance == (Object)null || !InventoryGui.IsVisible())
			{
				return false;
			}
			if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return false;
			}
			if (Console.IsVisible() || TextInput.IsVisible() || Menu.IsVisible())
			{
				return false;
			}
			if ((Object)(object)InventoryGui.instance.m_dragGo != (Object)null)
			{
				return false;
			}
			return true;
		}

		public static void SortContainer(Container container)
		{
			if ((Object)(object)container == (Object)null)
			{
				return;
			}
			if ((Object)(object)container.m_nview == (Object)null || !container.m_nview.IsValid() || !container.m_nview.IsOwner())
			{
				InventorySortPlugin.Log.LogDebug((object)"Container not owned by local player, skipping sort");
				return;
			}
			Sort(container.GetInventory(), InventorySortPlugin.ContainerFill.Value, (Vector2i _) => false);
		}

		public static void SortPlayer()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			bool skipHotbar = InventorySortPlugin.SkipHotbar.Value;
			HashSet<(int, int)> locked = (InventorySortPlugin.RespectQuickStackLocks.Value ? QuickStackLocks.LoadFor(localPlayer) : new HashSet<(int, int)>());
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			if (inventory != null && InventorySortPlugin.IgnoreEquipped.Value)
			{
				foreach (ItemData item in inventory.m_inventory)
				{
					if (item.m_equipped)
					{
						locked.Add((item.m_gridPos.x, item.m_gridPos.y));
					}
				}
			}
			Sort(inventory, InventorySortPlugin.PlayerFill.Value, (Vector2i pos) => (skipHotbar && pos.y == 0) || locked.Contains((pos.x, pos.y)));
		}

		private static void Sort(Inventory inventory, FillStart fill, Func<Vector2i, bool> isReserved)
		{
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			if (inventory == null)
			{
				return;
			}
			List<ItemData> inventory2 = inventory.m_inventory;
			List<ItemData> list = inventory2.Where((ItemData i) => !isReserved(i.m_gridPos)).ToList();
			if (list.Count == 0)
			{
				return;
			}
			if (InventorySortPlugin.MergeStacks.Value)
			{
				Merge(list, inventory2);
			}
			List<Vector2i> list2 = BuildSlotOrder(inventory.GetWidth(), inventory.GetHeight(), fill, isReserved);
			List<ItemData> list3 = Order(list);
			if (list3.Count > list2.Count)
			{
				InventorySortPlugin.Log.LogWarning((object)$"Not enough slots to sort ({list3.Count} items, {list2.Count} slots); leaving layout as is");
				inventory.Changed(false, false);
				return;
			}
			for (int num = 0; num < list3.Count; num++)
			{
				list3[num].m_gridPos = list2[num];
			}
			inventory.Changed(false, false);
		}

		private static List<Vector2i> BuildSlotOrder(int width, int height, FillStart fill, Func<Vector2i, bool> isReserved)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			List<Vector2i> list = new List<Vector2i>(width * height);
			Vector2i val = default(Vector2i);
			for (int i = 0; i < height; i++)
			{
				int num = ((fill == FillStart.Top) ? i : (height - 1 - i));
				for (int j = 0; j < width; j++)
				{
					((Vector2i)(ref val))..ctor(j, num);
					if (!isReserved(val))
					{
						list.Add(val);
					}
				}
			}
			return list;
		}

		private static void Merge(List<ItemData> movable, List<ItemData> all)
		{
			foreach (IGrouping<string, ItemData> item in movable.Where(IsMergeable).GroupBy(StackKey).ToList())
			{
				List<ItemData> list = item.OrderByDescending((ItemData i) => i.m_stack).ToList();
				if (list.Count < 2)
				{
					continue;
				}
				int num = list.Sum((ItemData i) => i.m_stack);
				int maxStackSize = list[0].m_shared.m_maxStackSize;
				foreach (ItemData item2 in list)
				{
					int num2 = Mathf.Min(maxStackSize, num);
					num -= num2;
					if (num2 > 0)
					{
						item2.m_stack = num2;
						continue;
					}
					movable.Remove(item2);
					all.Remove(item2);
				}
			}
		}

		private static bool IsMergeable(ItemData item)
		{
			if (item.m_shared.m_maxStackSize > 1)
			{
				if (item.m_customData != null)
				{
					return item.m_customData.Count == 0;
				}
				return true;
			}
			return false;
		}

		private static string StackKey(ItemData item)
		{
			return $"{item.m_shared.m_name}|{item.m_quality}|{item.m_variant}|{item.m_worldLevel}";
		}

		private static List<ItemData> Order(List<ItemData> items)
		{
			StringComparer currentCultureIgnoreCase = StringComparer.CurrentCultureIgnoreCase;
			var source = from i in items
				group i by i.m_shared.m_name into g
				select new
				{
					Name = LocalizedName(g.Key),
					Total = g.Sum((ItemData i) => i.m_stack),
					Items = (from i in g
						orderby i.m_quality descending, i.m_stack descending, i.m_durability descending
						select i).ToList()
				};
			var list = ((InventorySortPlugin.Mode.Value == SortMode.Quantity) ? source.OrderByDescending(t => t.Total).ThenBy(t => t.Name, currentCultureIgnoreCase).ToList() : source.OrderBy(t => t.Name, currentCultureIgnoreCase).ToList());
			if (InventorySortPlugin.Reverse.Value)
			{
				list.Reverse();
			}
			return list.SelectMany(t => t.Items).ToList();
		}

		private static string LocalizedName(string token)
		{
			if (Localization.instance == null)
			{
				return token;
			}
			return Localization.instance.Localize(token);
		}
	}
	public enum FillStart
	{
		Bottom,
		Top
	}
	public enum SortMode
	{
		Alphabetical,
		Quantity
	}
	[BepInPlugin("dreamscapist.valheim.inventorysort", "InventorySort", "1.2.0")]
	public class InventorySortPlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "dreamscapist.valheim.inventorysort";

		public const string PluginName = "InventorySort";

		public const string PluginVersion = "1.2.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<KeyboardShortcut> SortKey;

		internal static ConfigEntry<bool> KeySortsPlayerWhenNoContainer;

		internal static ConfigEntry<SortMode> Mode;

		internal static ConfigEntry<bool> Reverse;

		internal static ConfigEntry<bool> MergeStacks;

		internal static ConfigEntry<FillStart> ContainerFill;

		internal static ConfigEntry<FillStart> PlayerFill;

		internal static ConfigEntry<bool> SkipHotbar;

		internal static ConfigEntry<bool> RespectQuickStackLocks;

		internal static ConfigEntry<bool> IgnoreEquipped;

		internal static ConfigEntry<bool> ShowButtons;

		internal static ConfigEntry<float> ContainerButtonX;

		internal static ConfigEntry<float> ContainerButtonY;

		internal static ConfigEntry<float> PlayerButtonX;

		internal static ConfigEntry<float> PlayerButtonY;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			SortKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("1 - General", "SortKey", new KeyboardShortcut((KeyCode)116, Array.Empty<KeyCode>()), "Sorts the open storage while the inventory is open.");
			KeySortsPlayerWhenNoContainer = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "KeySortsPlayerWhenNoContainer", true, "If no storage is open, the sort key sorts your own inventory instead.");
			Mode = ((BaseUnityPlugin)this).Config.Bind<SortMode>("2 - Sorting", "SortMode", SortMode.Alphabetical, "Alphabetical = by item name. Quantity = item types you have the most of come first.");
			Reverse = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Sorting", "ReverseOrder", false, "Reverses the order (Z→A for alphabetical, fewest first for quantity).");
			MergeStacks = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Sorting", "MergeStacks", true, "Combine partial stacks of the same item while sorting.");
			ContainerFill = ((BaseUnityPlugin)this).Config.Bind<FillStart>("2 - Sorting", "StorageFillStart", FillStart.Bottom, "Where sorted items start filling in storages. Bottom = bottom row first; Top = top row first.");
			PlayerFill = ((BaseUnityPlugin)this).Config.Bind<FillStart>("2 - Sorting", "PlayerFillStart", FillStart.Top, "Where sorted items start filling in your own inventory.");
			SkipHotbar = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Sorting", "SkipHotbar", true, "Leave the hotbar row (top row, slots 1–8) of your own inventory untouched.");
			RespectQuickStackLocks = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Sorting", "RespectQuickStackDepositLocks", true, "Leave inventory slots locked by QuickStackDeposit untouched. Does nothing if QuickStackDeposit isn't installed.");
			IgnoreEquipped = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Sorting", "IgnoreEquippedItems", true, "Leave equipped items (weapons, armor, etc.) in their current slots when sorting your inventory.");
			ShowButtons = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - UI", "ShowSortButtons", true, "Show the Sort buttons.");
			ContainerButtonX = ((BaseUnityPlugin)this).Config.Bind<float>("3 - UI", "StorageButtonOffsetX", 8f, "Horizontal offset of the storage Sort button from the storage panel's top-right corner.");
			ContainerButtonY = ((BaseUnityPlugin)this).Config.Bind<float>("3 - UI", "StorageButtonOffsetY", 0f, "Vertical offset of the storage Sort button from the storage panel's top-right corner.");
			PlayerButtonX = ((BaseUnityPlugin)this).Config.Bind<float>("3 - UI", "InventoryButtonOffsetX", 8f, "Horizontal offset of the inventory Sort button from the inventory panel's top-right corner.");
			PlayerButtonY = ((BaseUnityPlugin)this).Config.Bind<float>("3 - UI", "InventoryButtonOffsetY", 0f, "Vertical offset of the inventory Sort button from the inventory panel's top-right corner.");
			ShowButtons.SettingChanged += delegate
			{
				SortButtons.ApplyLayout();
			};
			ContainerButtonX.SettingChanged += delegate
			{
				SortButtons.ApplyLayout();
			};
			ContainerButtonY.SettingChanged += delegate
			{
				SortButtons.ApplyLayout();
			};
			PlayerButtonX.SettingChanged += delegate
			{
				SortButtons.ApplyLayout();
			};
			PlayerButtonY.SettingChanged += delegate
			{
				SortButtons.ApplyLayout();
			};
			_harmony = new Harmony("dreamscapist.valheim.inventorysort");
			_harmony.PatchAll();
			Log.LogInfo((object)"InventorySort 1.2.0 loaded");
		}

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = SortKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown() && InventorySorter.CanSortNow())
			{
				Container currentContainer = InventoryGui.instance.m_currentContainer;
				if ((Object)(object)currentContainer != (Object)null)
				{
					InventorySorter.SortContainer(currentContainer);
				}
				else if (KeySortsPlayerWhenNoContainer.Value)
				{
					InventorySorter.SortPlayer();
				}
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class QuickStackLocks
	{
		private const string FileName = "dreamscapist.valheim.quickstackdeposit.locks.txt";

		public static HashSet<(int, int)> LoadFor(Player player)
		{
			HashSet<(int, int)> hashSet = new HashSet<(int, int)>();
			string path = Path.Combine(Paths.ConfigPath, "dreamscapist.valheim.quickstackdeposit.locks.txt");
			if (!File.Exists(path))
			{
				return hashSet;
			}
			string text = player.GetPlayerID().ToString();
			try
			{
				string[] array = File.ReadAllLines(path);
				for (int i = 0; i < array.Length; i++)
				{
					string text2 = array[i].Trim();
					int num = text2.IndexOf('|');
					if (num <= 0 || text2.Substring(0, num).Trim() != text)
					{
						continue;
					}
					string[] array2 = text2.Substring(num + 1).Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
					for (int j = 0; j < array2.Length; j++)
					{
						string[] array3 = array2[j].Split(',');
						if (array3.Length == 2 && int.TryParse(array3[0].Trim(), out var result) && int.TryParse(array3[1].Trim(), out var result2))
						{
							hashSet.Add((result, result2));
						}
					}
				}
				return hashSet;
			}
			catch (Exception ex)
			{
				InventorySortPlugin.Log.LogWarning((object)("Couldn't read QuickStackDeposit locks: " + ex.Message));
				return hashSet;
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Awake")]
	internal static class InventoryGui_Awake_Patch
	{
		private static void Postfix(InventoryGui __instance)
		{
			SortButtons.Create(__instance);
		}
	}
	internal static class SortButtons
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__SortPlayer;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static UnityAction <>9__2_0;

			internal void <Create>b__2_0()
			{
				Container val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_currentContainer : null);
				if ((Object)(object)val != (Object)null)
				{
					InventorySorter.SortContainer(val);
				}
			}
		}

		private static Button _containerButton;

		private static Button _playerButton;

		public static void Create(InventoryGui gui)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Expected O, but got Unknown
			Button takeAllButton = gui.m_takeAllButton;
			if ((Object)(object)takeAllButton == (Object)null)
			{
				InventorySortPlugin.Log.LogWarning((object)"Take All button not found; Sort buttons not created");
				return;
			}
			Rect rect = ((RectTransform)((Component)takeAllButton).transform).rect;
			Vector2 size = ((Rect)(ref rect)).size;
			_containerButton = Clone(takeAllButton, (Transform)(object)gui.m_container, "InventorySort_StorageSortButton", size);
			ButtonClickedEvent onClick = _containerButton.onClick;
			object obj = <>c.<>9__2_0;
			if (obj == null)
			{
				UnityAction val = delegate
				{
					Container val3 = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_currentContainer : null);
					if ((Object)(object)val3 != (Object)null)
					{
						InventorySorter.SortContainer(val3);
					}
				};
				<>c.<>9__2_0 = val;
				obj = (object)val;
			}
			((UnityEvent)onClick).AddListener((UnityAction)obj);
			_playerButton = Clone(takeAllButton, (Transform)(object)gui.m_player, "InventorySort_InventorySortButton", size);
			ButtonClickedEvent onClick2 = _playerButton.onClick;
			object obj2 = <>O.<0>__SortPlayer;
			if (obj2 == null)
			{
				UnityAction val2 = InventorySorter.SortPlayer;
				<>O.<0>__SortPlayer = val2;
				obj2 = (object)val2;
			}
			((UnityEvent)onClick2).AddListener((UnityAction)obj2);
			ApplyLayout();
		}

		public static void ApplyLayout()
		{
			Place(_containerButton, InventorySortPlugin.ContainerButtonX.Value, InventorySortPlugin.ContainerButtonY.Value);
			Place(_playerButton, InventorySortPlugin.PlayerButtonX.Value, InventorySortPlugin.PlayerButtonY.Value);
		}

		private static Button Clone(Button template, Transform parent, string name, Vector2 size)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, parent, false);
			((Object)val).name = name;
			Button component = val.GetComponent<Button>();
			component.onClick = new ButtonClickedEvent();
			UIGamePad[] componentsInChildren = val.GetComponentsInChildren<UIGamePad>(true);
			foreach (UIGamePad val2 in componentsInChildren)
			{
				if ((Object)(object)val2.m_hint != (Object)null)
				{
					Object.Destroy((Object)(object)val2.m_hint);
				}
				Object.Destroy((Object)(object)val2);
			}
			TMP_Text componentInChildren = val.GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				componentInChildren.text = "Sort";
			}
			RectTransform val3 = (RectTransform)val.transform;
			val3.anchorMin = new Vector2(1f, 1f);
			val3.anchorMax = new Vector2(1f, 1f);
			val3.pivot = new Vector2(0f, 1f);
			val3.sizeDelta = size;
			return component;
		}

		private static void Place(Button button, float x, float y)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)button == (Object)null))
			{
				((RectTransform)((Component)button).transform).anchoredPosition = new Vector2(x, y);
				((Component)button).gameObject.SetActive(InventorySortPlugin.ShowButtons.Value);
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}