Decompiled source of ValFilter v0.2.1

ValFilter.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GUIFramework;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ValFilter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.1.0")]
[assembly: AssemblyInformationalVersion("0.2.1+f92aa4861139c99435cfc25862270acadd42fdba")]
[assembly: AssemblyProduct("ValFilter")]
[assembly: AssemblyTitle("ValFilter")]
[assembly: AssemblyVersion("0.2.1.0")]
[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 ValFilter
{
	public static class FuzzyMatcher
	{
		public sealed class Query
		{
			public readonly string Compact;

			public readonly string[] Words;

			public bool IsEmpty => Compact.Length == 0;

			public Query(string raw)
			{
				Words = (raw ?? "").ToLowerInvariant().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
				Compact = string.Concat(Words);
			}
		}

		public sealed class Candidate
		{
			public readonly string Text;

			public readonly string[] Words;

			public Candidate(string text)
			{
				Text = (text ?? "").ToLowerInvariant();
				Words = Text.Split(WordSeparators, StringSplitOptions.RemoveEmptyEntries);
			}
		}

		public const int CleanTier = 100000;

		private const int ScoreMatch = 16;

		private const int BonusBoundary = 8;

		private const int BonusFirstCharMultiplier = 2;

		private const int BonusConsecutive = 6;

		private const int BonusExactSubstring = 24;

		private const int PenaltyGapStart = -3;

		private const int PenaltyGapExtension = -1;

		private const int MinTypoWordLength = 4;

		private const int MaxStackChars = 256;

		private static readonly char[] WordSeparators = new char[9] { ' ', '-', '_', '(', ')', '\'', ',', '.', '/' };

		public static int Score(Query query, Candidate candidate, bool allowTypos = true)
		{
			if (query.IsEmpty)
			{
				return 100000;
			}
			int num = SubsequenceScore(query.Compact, candidate.Text);
			if (num > 0)
			{
				return 100000 + num;
			}
			if (!allowTypos)
			{
				return 0;
			}
			return TypoScore(query, candidate);
		}

		internal static int SubsequenceScore(string pattern, string text)
		{
			int length = pattern.Length;
			int length2 = text.Length;
			if (length == 0 || length > length2 || length2 > 256)
			{
				return 0;
			}
			Span<int> span = stackalloc int[length2];
			Span<int> span2 = stackalloc int[length2];
			for (int i = 0; i < length2; i++)
			{
				span[i] = ((text[i] == pattern[0]) ? (16 + BoundaryBonus(text, i) * 2) : (-1073741824));
			}
			for (int j = 1; j < length; j++)
			{
				char c = pattern[j];
				int num = -1073741824;
				for (int k = 0; k < length2; k++)
				{
					int num2 = -1073741824;
					if (k >= 1 && text[k] == c)
					{
						if (span[k - 1] > -1073741824)
						{
							num2 = span[k - 1] + 6;
						}
						if (num > -1073741824 && num > num2)
						{
							num2 = num;
						}
						if (num2 > -1073741824)
						{
							num2 += 16 + BoundaryBonus(text, k);
						}
					}
					span2[k] = num2;
					if (num > -1073741824)
					{
						num += -1;
					}
					if (k >= 1 && span[k - 1] > -1073741824 && span[k - 1] + -3 > num)
					{
						num = span[k - 1] + -3;
					}
				}
				Span<int> span3 = span;
				span = span2;
				span2 = span3;
			}
			int num3 = -1073741824;
			for (int l = 0; l < length2; l++)
			{
				if (span[l] > num3)
				{
					num3 = span[l];
				}
			}
			if (num3 <= -1073741824)
			{
				return 0;
			}
			if (text.IndexOf(pattern, StringComparison.Ordinal) >= 0)
			{
				num3 += 24;
			}
			return Math.Max(1, num3);
		}

		private static int TypoScore(Query query, Candidate candidate)
		{
			int num = 0;
			int num2 = 0;
			string[] words = query.Words;
			foreach (string text in words)
			{
				int num3 = SubsequenceScore(text, candidate.Text);
				if (num3 > 0)
				{
					num += num3;
					continue;
				}
				if (text.Length < 4)
				{
					return 0;
				}
				int num4 = ((text.Length < 8) ? 1 : 2);
				int num5 = int.MaxValue;
				string[] words2 = candidate.Words;
				foreach (string text2 in words2)
				{
					if (text2[0] == text[0])
					{
						int num6 = PrefixEditDistance(text, text2, num4);
						if (num6 < num5)
						{
							num5 = num6;
						}
					}
				}
				if (num5 > num4)
				{
					return 0;
				}
				num += text.Length * 16 / 2;
				num2 += num5;
			}
			return Math.Max(1, Math.Min(99999, num - num2 * 16));
		}

		internal static int PrefixEditDistance(string a, string b, int limit)
		{
			int length = a.Length;
			int length2 = b.Length;
			if (length2 > 256 || length > 256)
			{
				return limit + 1;
			}
			Span<int> span = stackalloc int[length2 + 1];
			Span<int> span2 = stackalloc int[length2 + 1];
			Span<int> span3 = stackalloc int[length2 + 1];
			for (int i = 0; i <= length2; i++)
			{
				span2[i] = i;
			}
			for (int j = 1; j <= length; j++)
			{
				span3[0] = j;
				int num = span3[0];
				for (int k = 1; k <= length2; k++)
				{
					int num2 = ((a[j - 1] != b[k - 1]) ? 1 : 0);
					int num3 = Math.Min(Math.Min(span2[k] + 1, span3[k - 1] + 1), span2[k - 1] + num2);
					if (j > 1 && k > 1 && a[j - 1] == b[k - 2] && a[j - 2] == b[k - 1])
					{
						num3 = Math.Min(num3, span[k - 2] + 1);
					}
					span3[k] = num3;
					if (num3 < num)
					{
						num = num3;
					}
				}
				if (num > limit)
				{
					return limit + 1;
				}
				Span<int> span4 = span;
				span = span2;
				span2 = span3;
				span3 = span4;
			}
			int num4 = int.MaxValue;
			for (int l = 0; l <= length2; l++)
			{
				if (span2[l] < num4)
				{
					num4 = span2[l];
				}
			}
			return num4;
		}

		private static int BoundaryBonus(string text, int j)
		{
			if (j == 0)
			{
				return 8;
			}
			if (Array.IndexOf(WordSeparators, text[j - 1]) < 0)
			{
				return 0;
			}
			return 8;
		}
	}
	[BepInPlugin("com.placidfireball.valfilter", "ValFilter", "0.2.1")]
	public class Main : BaseUnityPlugin
	{
		private const string pluginGUID = "com.placidfireball.valfilter";

		private const string pluginName = "ValFilter";

		private const string pluginVersion = "0.2.1";

		private readonly Harmony HarmonyInstance = new Harmony("com.placidfireball.valfilter");

		public static ManualLogSource logger = Logger.CreateLogSource("ValFilter");

		public static ConfigEntry<bool> ClearOnClose;

		public static ConfigEntry<float> SearchWidth;

		public static ConfigEntry<Vector2> SearchOffset;

		public static ConfigEntry<KeyCode> FocusKey;

		public static ConfigEntry<SearchMode> SearchMode;

		public static ConfigEntry<bool> TypoTolerance;

		public void Awake()
		{
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			SearchMode = ((BaseUnityPlugin)this).Config.Bind<SearchMode>("Search", "Mode", ValFilter.SearchMode.Substring, "Substring: the name must contain exactly what you typed, in the game's usual order. Fuzzy (opt-in): letters in order, not necessarily adjacent (\"bzarw\" finds Bronzehead arrow), best matches first.");
			TypoTolerance = ((BaseUnityPlugin)this).Config.Bind<bool>("Search", "TypoTolerance", true, "Fuzzy mode only: also match words of 4+ letters with a typo or two (\"baer\" finds Bear). Typo matches are listed after exact ones.");
			ClearOnClose = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ClearOnClose", true, "Clear the search text whenever the inventory/crafting panel closes.");
			FocusKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "FocusKey", (KeyCode)102, "Key that focuses the search box while the inventory is open. Set to None to disable.");
			SearchWidth = ((BaseUnityPlugin)this).Config.Bind<float>("Layout", "Width", 220f, "Width of the search box in UI units.");
			SearchOffset = ((BaseUnityPlugin)this).Config.Bind<Vector2>("Layout", "Offset", new Vector2(40f, 0f), "Offset of the search box from the right edge of the Upgrade tab. The cloned field's key hint badge hangs off its left edge, so leave room for it.");
			HarmonyInstance.PatchAll(typeof(Main).Assembly);
			logger.LogInfo((object)"ValFilter 0.2.1 loaded");
		}

		public void OnDestroy()
		{
			HarmonyInstance.UnpatchSelf();
		}
	}
	public enum SearchMode
	{
		Fuzzy,
		Substring
	}
	internal static class RecipeSearch
	{
		private struct Entry
		{
			public object Pair;

			public GameObject Element;

			public Recipe Recipe;

			public bool CanCraft;

			public int Index;
		}

		private struct Match
		{
			public Entry Entry;

			public int Score;

			public int NameLength;
		}

		private static GuiInputField searchField;

		private static string term = "";

		private static FuzzyMatcher.Query query = new FuzzyMatcher.Query("");

		private static readonly FieldInfo availableRecipesField = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes");

		private static readonly FieldInfo recipeListBaseSizeField = AccessTools.Field(typeof(InventoryGui), "m_recipeListBaseSize");

		private static readonly Type recipeDataPairType = AccessTools.Inner(typeof(InventoryGui), "RecipeDataPair");

		private static readonly PropertyInfo pairRecipeProp = AccessTools.Property(recipeDataPairType, "Recipe");

		private static readonly PropertyInfo pairElementProp = AccessTools.Property(recipeDataPairType, "InterfaceElement");

		private static readonly PropertyInfo pairCanCraftProp = AccessTools.Property(recipeDataPairType, "CanCraft");

		private static readonly MethodInfo getSelectedRecipeIndexMethod = AccessTools.Method(typeof(InventoryGui), "GetSelectedRecipeIndex", (Type[])null, (Type[])null);

		private static readonly MethodInfo setRecipeMethod = AccessTools.Method(typeof(InventoryGui), "SetRecipe", (Type[])null, (Type[])null);

		private static readonly List<Entry> allEntries = new List<Entry>();

		private static readonly List<Match> matches = new List<Match>();

		private static readonly Dictionary<string, FuzzyMatcher.Candidate> candidateCache = new Dictionary<string, FuzzyMatcher.Candidate>();

		private static readonly Comparison<Match> byRank = delegate(Match a, Match b)
		{
			int num = b.Entry.CanCraft.CompareTo(a.Entry.CanCraft);
			if (num == 0)
			{
				num = b.Score.CompareTo(a.Score);
			}
			if (num == 0)
			{
				num = a.NameLength.CompareTo(b.NameLength);
			}
			if (num == 0)
			{
				num = a.Entry.Index.CompareTo(b.Entry.Index);
			}
			return num;
		};

		public static bool HasFocus
		{
			get
			{
				if ((Object)(object)searchField != (Object)null)
				{
					return ((TMP_InputField)searchField).isFocused;
				}
				return false;
			}
		}

		public static void EnsureCreated(InventoryGui gui)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: 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_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Expected O, but got Unknown
			if (!((Object)(object)searchField != (Object)null))
			{
				Hud instance = Hud.instance;
				GuiInputField val = (((Object)(object)instance != (Object)null && (Object)(object)instance.m_buildUi != (Object)null) ? Traverse.Create((object)instance.m_buildUi).Field<GuiInputField>("m_searchField").Value : null);
				if ((Object)(object)val == (Object)null)
				{
					Main.logger.LogWarning((object)"Could not find the build menu search field to clone; search box not created.");
					return;
				}
				RectTransform val2 = (RectTransform)((Component)gui.m_tabUpgrade).transform;
				GameObject obj = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Transform)val2).parent, false);
				((Object)obj).name = "ValFilterSearch";
				obj.SetActive(true);
				RectTransform val3 = (RectTransform)obj.transform;
				val3.anchorMin = val2.anchorMin;
				val3.anchorMax = val2.anchorMin;
				val3.pivot = new Vector2(0f, val2.pivot.y);
				float value = Main.SearchWidth.Value;
				Rect rect = val2.rect;
				val3.sizeDelta = new Vector2(value, ((Rect)(ref rect)).height);
				float x = val2.anchoredPosition.x;
				rect = val2.rect;
				float num = x + ((Rect)(ref rect)).width * (1f - val2.pivot.x);
				val3.anchoredPosition = new Vector2(num, val2.anchoredPosition.y) + Main.SearchOffset.Value;
				searchField = obj.GetComponent<GuiInputField>();
				((TMP_InputField)searchField).onValueChanged = new OnChangeEvent();
				((TMP_InputField)searchField).text = term;
				((UnityEvent<string>)(object)((TMP_InputField)searchField).onValueChanged).AddListener((UnityAction<string>)OnSearchChanged);
			}
		}

		public static void HandleFocusKey()
		{
			//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)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			KeyCode value = Main.FocusKey.Value;
			if ((int)value != 0 && !((Object)(object)searchField == (Object)null) && !HasFocus && ((Component)searchField).gameObject.activeInHierarchy && InventoryGui.IsVisible() && !Console.IsVisible() && !TextInput.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && ZInput.GetKeyDown(value, true))
			{
				searchField.ActivateInputField();
			}
		}

		public static void Clear()
		{
			term = "";
			query = new FuzzyMatcher.Query("");
			if ((Object)(object)searchField != (Object)null)
			{
				((TMP_InputField)searchField).SetTextWithoutNotify("");
				((TMP_InputField)searchField).DeactivateInputField(false);
			}
		}

		private static void OnSearchChanged(string value)
		{
			term = value.Trim();
			query = new FuzzyMatcher.Query(term);
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance != (Object)null && InventoryGui.IsVisible())
			{
				ApplyFilter(instance, reselect: true);
			}
		}

		public static void RestoreFullList(InventoryGui gui)
		{
			if (allEntries.Count == 0)
			{
				return;
			}
			IList list = (IList)availableRecipesField.GetValue(gui);
			list.Clear();
			foreach (Entry allEntry in allEntries)
			{
				list.Add(allEntry.Pair);
			}
			allEntries.Clear();
		}

		public static void CaptureAndFilter(InventoryGui gui)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			allEntries.Clear();
			foreach (object item in (IList)availableRecipesField.GetValue(gui))
			{
				allEntries.Add(new Entry
				{
					Pair = item,
					Element = (GameObject)pairElementProp.GetValue(item),
					Recipe = (Recipe)pairRecipeProp.GetValue(item),
					CanCraft = (bool)pairCanCraftProp.GetValue(item),
					Index = allEntries.Count
				});
			}
			if (!string.IsNullOrEmpty(term))
			{
				ApplyFilter(gui, reselect: false);
			}
		}

		private static void ApplyFilter(InventoryGui gui, bool reselect)
		{
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			bool flag = Main.SearchMode.Value == SearchMode.Fuzzy;
			matches.Clear();
			foreach (Entry allEntry in allEntries)
			{
				int nameLength;
				int num = Score(allEntry.Recipe, flag, out nameLength);
				allEntry.Element.SetActive(num > 0);
				if (num > 0)
				{
					matches.Add(new Match
					{
						Entry = allEntry,
						Score = num,
						NameLength = nameLength
					});
				}
			}
			if (flag && !query.IsEmpty)
			{
				matches.Sort(byRank);
			}
			IList list = (IList)availableRecipesField.GetValue(gui);
			list.Clear();
			foreach (Match match in matches)
			{
				((RectTransform)match.Entry.Element.transform).anchoredPosition = new Vector2(0f, (float)list.Count * (0f - gui.m_recipeListSpace));
				list.Add(match.Entry.Pair);
			}
			float num2 = Mathf.Max((float)recipeListBaseSizeField.GetValue(gui), (float)list.Count * gui.m_recipeListSpace);
			gui.m_recipeListRoot.SetSizeWithCurrentAnchors((Axis)1, num2);
			if (reselect)
			{
				gui.m_recipeListScroll.value = 1f;
				int num3 = ((list.Count == 0) ? (-1) : ((!flag || query.IsEmpty) ? ((int)getSelectedRecipeIndexMethod.Invoke(gui, new object[1] { true })) : 0));
				setRecipeMethod.Invoke(gui, new object[2] { num3, false });
			}
		}

		private static int Score(Recipe recipe, bool fuzzy, out int nameLength)
		{
			nameLength = 0;
			if (query.IsEmpty)
			{
				return 1;
			}
			SharedData val = recipe?.m_item?.m_itemData?.m_shared;
			if (val == null)
			{
				return 0;
			}
			if (!candidateCache.TryGetValue(val.m_name, out var value))
			{
				value = new FuzzyMatcher.Candidate(Localization.instance.Localize(val.m_name));
				candidateCache[val.m_name] = value;
			}
			nameLength = value.Text.Length;
			if (fuzzy)
			{
				int num = FuzzyMatcher.Score(query, value, Main.TypoTolerance.Value);
				if (num > 0)
				{
					return num;
				}
			}
			else if (value.Text.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return 1;
			}
			if (term.Length < 3 || val.m_name.IndexOf(query.Compact, StringComparison.OrdinalIgnoreCase) < 0)
			{
				return 0;
			}
			return 1;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Show")]
	internal static class InventoryGui_Show_Patch
	{
		private static void Prefix(InventoryGui __instance)
		{
			RecipeSearch.EnsureCreated(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Update")]
	internal static class InventoryGui_Update_Patch
	{
		private static void Postfix()
		{
			RecipeSearch.HandleFocusKey();
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Hide")]
	internal static class InventoryGui_Hide_Patch
	{
		private static void Postfix()
		{
			if (Main.ClearOnClose.Value)
			{
				RecipeSearch.Clear();
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList")]
	internal static class InventoryGui_UpdateRecipeList_Patch
	{
		private static void Prefix(InventoryGui __instance)
		{
			RecipeSearch.RestoreFullList(__instance);
		}

		private static void Postfix(InventoryGui __instance)
		{
			RecipeSearch.CaptureAndFilter(__instance);
		}
	}
	[HarmonyPatch(typeof(Chat), "HasFocus")]
	internal static class Chat_HasFocus_Patch
	{
		private static void Postfix(ref bool __result)
		{
			if (RecipeSearch.HasFocus)
			{
				__result = true;
			}
		}
	}
}