Decompiled source of WorkbenchCraftSearch v1.0.2

WorkbenchCraftSearch.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: AssemblyTitle("WorkbenchCraftSearch")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("1.0.2.0")]
namespace WorkbenchCraftSearch;

[BepInPlugin("local.valheim.workbenchcraftsearch", "WorkbenchCraftSearch", "1.0.2")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string Id = "local.valheim.workbenchcraftsearch";

	public const string Version = "1.0.2";

	internal static Plugin Instance;

	internal ConfigEntry<float> Width;

	internal ConfigEntry<float> Height;

	internal ConfigEntry<float> OffsetX;

	internal ConfigEntry<float> OffsetY;

	private Harmony harmony;

	private void Awake()
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Expected O, but got Unknown
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Expected O, but got Unknown
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e3: Expected O, but got Unknown
		Instance = this;
		Width = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "Width", 130f, new ConfigDescription("Search width in UI units.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(80f, 240f), new object[0]));
		Height = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "Height", 28f, new ConfigDescription("Search height in UI units.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 40f), new object[0]));
		OffsetX = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "OffsetX", 0f, "Horizontal adjustment in UI units.");
		OffsetY = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "OffsetY", 0f, "Vertical adjustment in UI units (positive = up).");
		try
		{
			SearchController.ValidateBindings();
			harmony = new Harmony("local.valheim.workbenchcraftsearch");
			harmony.PatchAll(typeof(Plugin).Assembly);
			if ((Object)(object)InventoryGui.instance != (Object)null)
			{
				SearchController.Attach(InventoryGui.instance);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"WorkbenchCraftSearch 1.0.2 loaded.");
		}
		catch (Exception ex)
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			((BaseUnityPlugin)this).Logger.LogError((object)("Could not enable craft search: " + ex));
		}
	}

	private void OnDestroy()
	{
		if ((Object)(object)SearchController.Current != (Object)null)
		{
			Object.Destroy((Object)(object)SearchController.Current);
		}
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		Instance = null;
	}

	internal static void Report(Exception error)
	{
		if ((Object)(object)Instance != (Object)null)
		{
			((BaseUnityPlugin)Instance).Logger.LogError((object)error);
		}
	}
}
[HarmonyPatch(typeof(InventoryGui), "Awake")]
internal static class InventoryAwakePatch
{
	private static void Postfix(InventoryGui __instance)
	{
		SearchController.Attach(__instance);
	}
}
[HarmonyPatch(typeof(InventoryGui), "Show", new Type[]
{
	typeof(Container),
	typeof(int)
})]
internal static class InventoryShowPatch
{
	private static void Prefix(InventoryGui __instance)
	{
		SearchController.Attach(__instance);
	}
}
[HarmonyPatch(typeof(InventoryGui), "UpdateCraftingPanel", new Type[] { typeof(bool) })]
internal static class CraftingPanelPatch
{
	private static void Prefix(InventoryGui __instance)
	{
		SearchController.Attach(__instance);
	}
}
[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })]
internal static class RecipeListPatch
{
	[HarmonyPriority(0)]
	private static void Prefix(InventoryGui __instance, ref List<Recipe> __0)
	{
		SearchController current = SearchController.Current;
		if ((Object)(object)current == (Object)null || !current.Owns(__instance) || current.Query.IsEmpty || __0 == null)
		{
			return;
		}
		SearchQuery query = current.Query;
		__0 = __0.FindAll(delegate(Recipe recipe)
		{
			if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null || recipe.m_item.m_itemData == null)
			{
				return false;
			}
			string name = recipe.m_item.m_itemData.m_shared.m_name;
			return query.Matches(Localization.instance.Localize(name));
		});
	}
}
[HarmonyPatch(typeof(InventoryGui), "Hide")]
internal static class InventoryHidePatch
{
	private static void Prefix(InventoryGui __instance)
	{
		SearchController current = SearchController.Current;
		if ((Object)(object)current != (Object)null && current.Owns(__instance))
		{
			current.Clear();
		}
	}
}
[HarmonyPatch(typeof(Chat), "HasFocus")]
internal static class ChatFocusPatch
{
	private static void Postfix(ref bool __result)
	{
		__result = __result || SearchController.BlockGameInput;
	}
}
public sealed class SearchController : MonoBehaviour
{
	private static readonly MethodInfo Refresh = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", new Type[1] { typeof(bool) }, (Type[])null);

	private static readonly FieldInfo CraftTimer = AccessTools.Field(typeof(InventoryGui), "m_craftTimer");

	internal static SearchController Current;

	internal SearchQuery Query = new SearchQuery("");

	private InventoryGui gui;

	private TMP_InputField input;

	private TMP_Text placeholder;

	private GameObject root;

	private RectTransform rect;

	private readonly Vector3[] corners = (Vector3[])(object)new Vector3[4];

	private int blockThroughFrame = -1;

	private bool dirty;

	private bool finishEditingRequested;

	private bool initializationFailed;

	private string language;

	internal static bool BlockGameInput
	{
		get
		{
			SearchController current = Current;
			if ((Object)(object)current != (Object)null && (Object)(object)current.input != (Object)null && ((Component)current.input).gameObject.activeInHierarchy)
			{
				if (!current.input.isFocused)
				{
					return Time.frameCount <= current.blockThroughFrame;
				}
				return true;
			}
			return false;
		}
	}

	internal static void ValidateBindings()
	{
		if (Refresh == null || CraftTimer == null)
		{
			throw new MissingMemberException("Unsupported InventoryGui: UpdateCraftingPanel or m_craftTimer is missing.");
		}
	}

	internal static void Attach(InventoryGui inventory)
	{
		if ((Object)(object)inventory == (Object)null || (Object)(object)inventory.m_crafting == (Object)null || (Object)(object)inventory.m_craftingStationName == (Object)null)
		{
			return;
		}
		SearchController searchController = ((Component)inventory).GetComponent<SearchController>();
		if ((Object)(object)searchController == (Object)null)
		{
			searchController = ((Component)inventory).gameObject.AddComponent<SearchController>();
		}
		if (searchController.initializationFailed)
		{
			return;
		}
		if ((Object)(object)searchController.root != (Object)null && (Object)(object)searchController.rect != (Object)null && (Object)(object)searchController.input != (Object)null && (Object)(object)((Transform)searchController.rect).parent == (Object)(object)inventory.m_crafting)
		{
			if (!searchController.root.activeSelf)
			{
				searchController.root.SetActive(true);
			}
			return;
		}
		string text = (((Object)(object)searchController.input != (Object)null) ? searchController.input.text : "");
		if ((Object)(object)searchController.root != (Object)null)
		{
			searchController.root.SetActive(false);
			Object.Destroy((Object)(object)searchController.root);
		}
		try
		{
			searchController.Initialize(inventory);
			searchController.input.SetTextWithoutNotify(text);
			searchController.Query = new SearchQuery(text);
			if (!searchController.Query.IsEmpty)
			{
				searchController.dirty = true;
			}
			((Behaviour)searchController).enabled = true;
		}
		catch (Exception error)
		{
			searchController.initializationFailed = true;
			((Behaviour)searchController).enabled = false;
			if ((Object)(object)Current == (Object)(object)searchController)
			{
				Current = null;
			}
			if ((Object)(object)searchController.root != (Object)null)
			{
				Object.Destroy((Object)(object)searchController.root);
			}
			Plugin.Report(error);
		}
	}

	internal bool Owns(InventoryGui inventory)
	{
		return (Object)(object)gui == (Object)(object)inventory;
	}

	private void Initialize(InventoryGui inventory)
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Expected O, but got Unknown
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Expected O, but got Unknown
		//IL_00b5: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_014c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_0291: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_02da: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		gui = inventory;
		if ((Object)(object)gui.m_crafting == (Object)null || (Object)(object)gui.m_craftingStationName == (Object)null)
		{
			throw new MissingMemberException("The crafting panel is unavailable.");
		}
		Current = this;
		root = new GameObject("WorkbenchCraftSearch", new Type[1] { typeof(RectTransform) });
		root.SetActive(false);
		rect = (RectTransform)root.transform;
		((Transform)rect).SetParent((Transform)(object)gui.m_crafting, false);
		RectTransform obj = rect;
		Vector2 anchorMin = (rect.anchorMax = new Vector2(0f, 1f));
		obj.anchorMin = anchorMin;
		rect.pivot = new Vector2(0f, 0.5f);
		Image val2 = root.AddComponent<Image>();
		Image val3 = (((Object)(object)gui.m_tabCraft != (Object)null) ? ((Component)gui.m_tabCraft).GetComponent<Image>() : null);
		if ((Object)(object)val3 != (Object)null)
		{
			val2.sprite = val3.sprite;
			val2.type = val3.type;
		}
		((Graphic)val2).color = new Color(0.18f, 0.15f, 0.14f, 0.95f);
		((Graphic)val2).raycastTarget = true;
		RectTransform val4 = NewRect("Viewport", (Transform)(object)rect);
		Stretch(val4, 7f, 5f);
		((Component)val4).gameObject.AddComponent<RectMask2D>();
		TMP_Text textComponent = NewText("Text", (Transform)(object)val4, gui.m_craftingStationName);
		placeholder = NewText("Placeholder", (Transform)(object)val4, gui.m_craftingStationName);
		((Graphic)placeholder).color = new Color(0.8f, 0.76f, 0.68f, 0.7f);
		placeholder.fontStyle = (FontStyles)2;
		input = root.AddComponent<TMP_InputField>();
		((Selectable)input).targetGraphic = (Graphic)(object)val2;
		input.textViewport = val4;
		input.textComponent = textComponent;
		input.placeholder = (Graphic)(object)placeholder;
		input.lineType = (LineType)0;
		input.contentType = (ContentType)0;
		input.characterLimit = 80;
		input.richText = false;
		input.restoreOriginalTextOnEscape = false;
		input.customCaretColor = true;
		input.caretColor = new Color(1f, 0.83f, 0.25f);
		input.selectionColor = new Color(1f, 0.76f, 0.15f, 0.3f);
		Navigation navigation = ((Selectable)input).navigation;
		((Navigation)(ref navigation)).mode = (Mode)0;
		((Selectable)input).navigation = navigation;
		((UnityEvent<string>)(object)input.onValueChanged).AddListener((UnityAction<string>)OnChanged);
		((UnityEvent<string>)(object)input.onSelect).AddListener((UnityAction<string>)delegate
		{
			blockThroughFrame = Time.frameCount + 1;
		});
		((UnityEvent<string>)(object)input.onDeselect).AddListener((UnityAction<string>)delegate
		{
			blockThroughFrame = Time.frameCount + 1;
		});
		((UnityEvent<string>)(object)input.onEndEdit).AddListener((UnityAction<string>)delegate
		{
			RequestFinishEditing();
		});
		((UnityEvent<string>)(object)input.onSubmit).AddListener((UnityAction<string>)delegate
		{
			RequestFinishEditing();
		});
		UpdateLanguage();
		PositionField();
		root.SetActive(true);
	}

	private static RectTransform NewRect(string name, Transform parent)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		RectTransform val = (RectTransform)new GameObject(name, new Type[1] { typeof(RectTransform) }).transform;
		((Transform)val).SetParent(parent, false);
		return val;
	}

	private static void Stretch(RectTransform target, float x, float y)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		target.anchorMin = Vector2.zero;
		target.anchorMax = Vector2.one;
		target.offsetMin = new Vector2(x, y);
		target.offsetMax = new Vector2(0f - x, 0f - y);
	}

	private static TMP_Text NewText(string name, Transform parent, TMP_Text style)
	{
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		RectTransform val = NewRect(name, parent);
		Stretch(val, 0f, 0f);
		TextMeshProUGUI val2 = ((Component)val).gameObject.AddComponent<TextMeshProUGUI>();
		((TMP_Text)val2).font = style.font;
		((TMP_Text)val2).fontSharedMaterial = style.fontSharedMaterial;
		((TMP_Text)val2).fontSize = 16f;
		((Graphic)val2).color = new Color(1f, 0.91f, 0.65f);
		((TMP_Text)val2).alignment = (TextAlignmentOptions)4097;
		((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
		((TMP_Text)val2).richText = false;
		((Graphic)val2).raycastTarget = false;
		((TMP_Text)val2).margin = Vector4.zero;
		return (TMP_Text)(object)val2;
	}

	private void UpdateLanguage()
	{
		string selectedLanguage = Localization.instance.GetSelectedLanguage();
		if (!(selectedLanguage == language))
		{
			language = selectedLanguage;
			placeholder.text = ((selectedLanguage == "Russian") ? "Поиск..." : "Search...");
			if (!Query.IsEmpty)
			{
				dirty = true;
			}
		}
	}

	private void PositionField()
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: 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_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: 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)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: 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_0178: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Unknown result type (might be due to invalid IL or missing references)
		//IL_018f: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		Plugin instance = Plugin.Instance;
		if (!((Object)(object)instance == (Object)null))
		{
			RectTransform crafting = gui.m_crafting;
			Rect val = crafting.rect;
			float num = ((Rect)(ref val)).xMin + 85f;
			if ((Object)(object)gui.m_craftingStationIcon != (Object)null && ((Component)gui.m_craftingStationIcon).gameObject.activeSelf)
			{
				((Graphic)gui.m_craftingStationIcon).rectTransform.GetWorldCorners(corners);
				num = Mathf.Max(num, ((Transform)crafting).InverseTransformPoint(corners[2]).x + 10f);
			}
			RectTransform rectTransform = gui.m_craftingStationName.rectTransform;
			Rect val2 = rectTransform.rect;
			float y = ((Transform)crafting).InverseTransformPoint(((Transform)rectTransform).TransformPoint(Vector2.op_Implicit(((Rect)(ref val2)).center))).y;
			float num2 = instance.Width.Value;
			if (gui.m_craftingStationName.havePropertiesChanged)
			{
				gui.m_craftingStationName.ForceMeshUpdate(false, false);
			}
			Bounds textBounds = gui.m_craftingStationName.textBounds;
			if (((Bounds)(ref textBounds)).size.x > 0f)
			{
				float x = ((Transform)crafting).InverseTransformPoint(((Transform)rectTransform).TransformPoint(((Bounds)(ref textBounds)).min)).x;
				num2 = Mathf.Min(num2, Mathf.Max(80f, x - num - 12f));
			}
			rect.sizeDelta = new Vector2(num2, instance.Height.Value);
			RectTransform obj = rect;
			float num3 = num;
			Rect val3 = crafting.rect;
			float num4 = num3 - ((Rect)(ref val3)).xMin + instance.OffsetX.Value;
			Rect val4 = crafting.rect;
			obj.anchoredPosition = new Vector2(num4, y - ((Rect)(ref val4)).yMax + instance.OffsetY.Value);
		}
	}

	private void OnChanged(string value)
	{
		Query = new SearchQuery(value);
		dirty = true;
	}

	private void RequestFinishEditing()
	{
		blockThroughFrame = Time.frameCount + 1;
		finishEditingRequested = true;
	}

	private void ApplyPendingFocusRelease()
	{
		if (!finishEditingRequested || (Object)(object)input == (Object)null)
		{
			return;
		}
		EventSystem current = EventSystem.current;
		if (!((Object)(object)current != (Object)null) || !current.alreadySelecting)
		{
			blockThroughFrame = Time.frameCount + 1;
			if ((Object)(object)input != (Object)null && input.isFocused)
			{
				input.DeactivateInputField(false);
			}
			if ((Object)(object)current != (Object)null && (Object)(object)current.currentSelectedGameObject == (Object)(object)((Component)input).gameObject)
			{
				current.SetSelectedGameObject((GameObject)null);
			}
			finishEditingRequested = false;
		}
	}

	internal void Clear()
	{
		if (!((Object)(object)input == (Object)null))
		{
			if (input.isFocused)
			{
				RequestFinishEditing();
			}
			input.SetTextWithoutNotify("");
			Query = new SearchQuery("");
			dirty = false;
		}
	}

	private void LateUpdate()
	{
		ApplyPendingFocusRelease();
		if ((Object)(object)gui == (Object)null || (Object)(object)input == (Object)null || (Object)(object)root == (Object)null || !InventoryGui.IsVisible() || !root.activeInHierarchy)
		{
			return;
		}
		UpdateLanguage();
		PositionField();
		bool flag = (float)CraftTimer.GetValue(gui) >= 0f;
		if (((Selectable)input).interactable == flag)
		{
			((Selectable)input).interactable = !flag;
		}
		if (!dirty || flag || (Object)(object)Player.m_localPlayer == (Object)null)
		{
			return;
		}
		dirty = false;
		try
		{
			Refresh.Invoke(gui, new object[1] { false });
			if ((Object)(object)gui.m_recipeListScroll != (Object)null)
			{
				gui.m_recipeListScroll.value = 1f;
			}
		}
		catch (Exception error)
		{
			Plugin.Report(error);
			initializationFailed = true;
			((Behaviour)this).enabled = false;
			Clear();
			root.SetActive(false);
			if ((Object)(object)Current == (Object)(object)this)
			{
				Current = null;
			}
		}
	}

	private void OnDestroy()
	{
		if ((Object)(object)Current == (Object)(object)this)
		{
			Current = null;
		}
		if ((Object)(object)root != (Object)null)
		{
			Object.Destroy((Object)(object)root);
		}
	}
}
internal sealed class SearchQuery
{
	private static readonly Regex Tags = new Regex("<[^>]*>", RegexOptions.Compiled);

	private readonly string[] words;

	internal bool IsEmpty => words.Length == 0;

	internal SearchQuery(string value)
	{
		words = Normalize(value).Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
	}

	internal bool Matches(string itemName)
	{
		if (IsEmpty)
		{
			return true;
		}
		string text = Normalize(Tags.Replace(itemName ?? string.Empty, string.Empty));
		string[] array = words;
		foreach (string value in array)
		{
			if (text.IndexOf(value, StringComparison.Ordinal) < 0)
			{
				return false;
			}
		}
		return true;
	}

	private static string Normalize(string value)
	{
		return (value ?? string.Empty).Normalize(NormalizationForm.FormKC).ToUpperInvariant().Replace('Ё', 'Е');
	}
}