Decompiled source of RecipePinner v1.3.0

plugins/RecipePinner.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("PinRecipe")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PinRecipe")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("b7fff297-caca-412c-8cb0-52556a76bd3f")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.3.0.0")]
namespace ValheimRecipePinner;

public class ContainerScanner
{
	public static List<Container> AllContainers = new List<Container>();

	private static readonly HashSet<Container> _containerSet = new HashSet<Container>();

	internal static readonly object ContainerLock = new object();

	public Dictionary<string, int> ContainerCache = new Dictionary<string, int>();

	private static readonly HashSet<int> _processedIDs = new HashSet<int>();

	private readonly List<Container> _snapshotBuffer = new List<Container>();

	private Vector3 _lastScanPos;

	private int _lastItemCount;

	private float _scanTimer;

	private float _moveScanCooldown;

	private const float MovementThresholdSqr = 4f;

	private const float MinMoveScanCooldown = 1f;

	private static volatile bool _isInitializing = false;

	public void InitializeContainers()
	{
		if (!RecipePinnerPlugin.EnableChestScanning.Value)
		{
			DebugLogger.Verbose("InitializeContainers skipped — chest scanning disabled");
			return;
		}
		if (_isInitializing)
		{
			DebugLogger.Verbose("InitializeContainers skipped — already initializing");
			return;
		}
		_isInitializing = true;
		try
		{
			DebugLogger.Verbose("Init containers");
			lock (ContainerLock)
			{
				if (AllContainers.Count > 0)
				{
					DebugLogger.Verbose($"InitializeContainers: list already populated ({AllContainers.Count}), skipping scan");
					return;
				}
				Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
				foreach (Container val in array)
				{
					if ((Object)(object)val != (Object)null && _containerSet.Add(val))
					{
						AllContainers.Add(val);
						if ((Object)(object)((Component)val).GetComponent<ContainerTracker>() == (Object)null)
						{
							((Component)val).gameObject.AddComponent<ContainerTracker>().MyContainer = val;
						}
					}
				}
				DebugLogger.Verbose($"Tracking {AllContainers.Count} containers");
			}
		}
		finally
		{
			_isInitializing = false;
		}
	}

	public static void ClearAll()
	{
		lock (ContainerLock)
		{
			AllContainers.Clear();
			_containerSet.Clear();
			DebugLogger.Verbose("ContainerScanner: all container references cleared");
		}
	}

	public void UpdateScanning()
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			return;
		}
		_scanTimer += Time.deltaTime;
		_moveScanCooldown += Time.deltaTime;
		bool flag = Vector3.SqrMagnitude(((Component)Player.m_localPlayer).transform.position - _lastScanPos) > 4f && _moveScanCooldown >= 1f;
		float num = (((Object)(object)InventoryGui.instance != (Object)null && (Object)(object)ReflectionHelper.GetCurrentContainer(InventoryGui.instance) != (Object)null) ? 0.5f : RecipePinnerPlugin.ChestScanInterval.Value);
		bool flag2 = _scanTimer >= num;
		if (!flag && !flag2)
		{
			return;
		}
		int num2 = 0;
		foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems())
		{
			num2 += allItem.m_stack;
		}
		bool flag3 = num2 != _lastItemCount;
		_lastItemCount = num2;
		DebugLogger.Verbose($"Scanning containers - Moved: {flag}, InvChanged: {flag3}, Interval: {flag2}");
		_scanTimer = 0f;
		if (flag)
		{
			_moveScanCooldown = 0f;
		}
		UpdateContainerCache();
	}

	private void UpdateContainerCache()
	{
		//IL_002d: 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)
		//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		ContainerCache.Clear();
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			DebugLogger.Verbose("Cannot scan - player is null");
			return;
		}
		Vector3 position = ((Component)Player.m_localPlayer).transform.position;
		float value = RecipePinnerPlugin.ChestScanRange.Value;
		float num = value * value;
		_snapshotBuffer.Clear();
		lock (ContainerLock)
		{
			_snapshotBuffer.AddRange(AllContainers);
		}
		_processedIDs.Clear();
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		foreach (Container item in _snapshotBuffer)
		{
			if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).transform == (Object)null)
			{
				num3++;
				continue;
			}
			int instanceID = ((Object)item).GetInstanceID();
			if (!_processedIDs.Add(instanceID))
			{
				num3++;
				continue;
			}
			if (Vector3.SqrMagnitude(((Component)item).transform.position - position) > num)
			{
				num3++;
				continue;
			}
			bool flag = true;
			if (ReflectionHelper.CheckContainerAccess != null)
			{
				flag = ReflectionHelper.CheckContainerAccess(item, Player.m_localPlayer.GetPlayerID());
			}
			if (!flag)
			{
				num4++;
				continue;
			}
			Inventory inventory = item.GetInventory();
			if (inventory == null)
			{
				continue;
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				string name = allItem.m_shared.m_name;
				if (ContainerCache.TryGetValue(name, out var value2))
				{
					ContainerCache[name] = value2 + allItem.m_stack;
				}
				else
				{
					ContainerCache[name] = allItem.m_stack;
				}
			}
			num2++;
		}
		_lastScanPos = position;
		DebugLogger.Verbose($"Container scan complete - Scanned: {num2}, Skipped: {num3}, AccessDenied: {num4}, UniqueItems: {ContainerCache.Count}");
	}

	[HarmonyPatch(typeof(Container), "Awake")]
	[HarmonyPostfix]
	public static void TrackContainerAwake(Container __instance)
	{
		if ((Object)(object)__instance == (Object)null || !RecipePinnerPlugin.EnableChestScanning.Value)
		{
			return;
		}
		lock (ContainerLock)
		{
			if (_containerSet.Add(__instance))
			{
				AllContainers.Add(__instance);
				(((Component)__instance).gameObject.GetComponent<ContainerTracker>() ?? ((Component)__instance).gameObject.AddComponent<ContainerTracker>()).MyContainer = __instance;
				DebugLogger.Verbose($"New container tracked: {((Object)__instance).name} (Total: {AllContainers.Count})");
			}
		}
	}

	public static void RemoveFromSet(Container c)
	{
		_containerSet.Remove(c);
	}
}
public class ContainerTracker : MonoBehaviour
{
	public Container MyContainer;

	private void OnDestroy()
	{
		if (ContainerScanner.AllContainers != null && (Object)(object)MyContainer != (Object)null)
		{
			lock (ContainerScanner.ContainerLock)
			{
				ContainerScanner.AllContainers.Remove(MyContainer);
				ContainerScanner.RemoveFromSet(MyContainer);
				DebugLogger.Verbose($"Container removed: {((Object)MyContainer).name} (Remaining: {ContainerScanner.AllContainers.Count})");
			}
		}
	}
}
public class DataPersistence
{
	public void SavePins()
	{
		try
		{
			string savePath = GetSavePath();
			if (string.IsNullOrEmpty(savePath))
			{
				DebugLogger.Warning("Cannot save - save path is invalid");
				return;
			}
			RecipeManager recipeManager = RecipePinnerPlugin.Instance?.RecipeMgr;
			if (recipeManager == null)
			{
				DebugLogger.Warning("Cannot save - RecipeMgr is null");
				return;
			}
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>();
			foreach (string item in recipeManager.PinnedRecipeOrder)
			{
				int value3;
				if (!hashSet.Add(item))
				{
					DebugLogger.Warning("Skipping duplicate pin order entry while saving: " + item);
				}
				else if (item.StartsWith("GROUP:"))
				{
					string text = item.Substring(6);
					if (!recipeManager.PinGroups.TryGetValue(text, out var value))
					{
						continue;
					}
					List<string> list2 = new List<string>();
					foreach (string memberRecipeKey in value.MemberRecipeKeys)
					{
						int value2;
						int num = ((!value.MemberCounts.TryGetValue(memberRecipeKey, out value2)) ? 1 : value2);
						list2.Add($"{EscapeSaveValue(memberRecipeKey)}:{num}");
					}
					string text2 = string.Join(",", list2);
					list.Add("GROUP:" + EscapeSaveValue(text) + "|" + text2);
					DebugLogger.Verbose($"Saved group: {text} with {value.MemberRecipeKeys.Count} members");
				}
				else if (recipeManager.PinnedRecipes.TryGetValue(item, out value3))
				{
					list.Add($"{EscapeSaveValue(item)}:{value3}");
				}
			}
			WriteAllLinesAtomically(savePath, list);
			int count = recipeManager.PinGroups.Count;
			DebugLogger.Log($"Saved {list.Count} entries ({list.Count - count} pins, {count} groups) to: {savePath}");
		}
		catch (Exception ex)
		{
			DebugLogger.Error("Failed to save pins", ex);
		}
	}

	public void LoadPins()
	{
		string savePath = GetSavePath();
		if (string.IsNullOrEmpty(savePath))
		{
			DebugLogger.Warning("Cannot load - save path is invalid");
			return;
		}
		RecipeManager recipeManager = RecipePinnerPlugin.Instance?.RecipeMgr;
		if (recipeManager == null)
		{
			DebugLogger.Warning("Cannot load - RecipeMgr is null");
			return;
		}
		if (!File.Exists(savePath))
		{
			DebugLogger.Log("No save file found at: " + savePath);
			return;
		}
		try
		{
			string[] array = File.ReadAllLines(savePath);
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			Dictionary<string, PinGroupData> dictionary2 = new Dictionary<string, PinGroupData>();
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				if (text.StartsWith("GROUP:"))
				{
					string text2 = text.Substring(6);
					int num4 = FindGroupSeparator(text2);
					if (num4 > 0 && num4 < text2.Length - 1)
					{
						string text3 = UnescapeSaveValue(text2.Substring(0, num4).Trim());
						string[] array3 = text2.Substring(num4 + 1).Trim().Split(new char[1] { ',' });
						if (!string.IsNullOrEmpty(text3) && array3.Length >= 2)
						{
							PinGroupData pinGroupData = new PinGroupData
							{
								GroupName = text3
							};
							string[] array4 = array3;
							for (int j = 0; j < array4.Length; j++)
							{
								string text4 = array4[j].Trim();
								if (string.IsNullOrEmpty(text4))
								{
									continue;
								}
								int num5 = text4.LastIndexOf(':');
								if (num5 > 0 && num5 < text4.Length - 1)
								{
									string text5 = UnescapeSaveValue(text4.Substring(0, num5));
									int result = 1;
									int.TryParse(text4.Substring(num5 + 1), out result);
									if (result < 1)
									{
										result = 1;
									}
									pinGroupData.MemberRecipeKeys.Add(text5);
									pinGroupData.MemberCounts[text5] = result;
								}
								else
								{
									string text6 = UnescapeSaveValue(text4);
									pinGroupData.MemberRecipeKeys.Add(text6);
									pinGroupData.MemberCounts[text6] = 1;
								}
							}
							if (pinGroupData.MemberRecipeKeys.Count >= 2)
							{
								string item = "GROUP:" + text3;
								if (!hashSet.Add(item))
								{
									DebugLogger.Warning("Duplicate group entry in save file, keeping first order position and latest data: " + text3);
									num3++;
								}
								else
								{
									list.Add(item);
								}
								dictionary2[text3] = pinGroupData;
								num2++;
								DebugLogger.Verbose($"Loaded group: {text3} with {pinGroupData.MemberRecipeKeys.Count} members");
							}
							else
							{
								DebugLogger.Warning("Group '" + text3 + "' has less than 2 members, skipping");
								num3++;
							}
						}
						else
						{
							DebugLogger.Warning("Invalid group format: " + text);
							num3++;
						}
					}
					else
					{
						DebugLogger.Warning("Invalid group line (missing pipe): " + text);
						num3++;
					}
					continue;
				}
				int num6 = text.LastIndexOf(':');
				if (num6 > 0 && num6 < text.Length - 1)
				{
					string text7 = UnescapeSaveValue(text.Substring(0, num6).Trim());
					if (int.TryParse(text.Substring(num6 + 1).Trim(), out var result2))
					{
						if (!hashSet.Add(text7))
						{
							DebugLogger.Warning("Duplicate pin entry in save file, keeping first order position and latest count: " + text7);
							num3++;
						}
						else
						{
							list.Add(text7);
						}
						dictionary[text7] = result2;
						num++;
					}
					else
					{
						DebugLogger.Warning("Invalid count value in save file: " + text);
						num3++;
					}
				}
				else
				{
					string text8 = UnescapeSaveValue(text.Trim());
					if (!hashSet.Add(text8))
					{
						DebugLogger.Warning("Duplicate legacy pin entry in save file, keeping first order position and latest count: " + text8);
						num3++;
					}
					else
					{
						list.Add(text8);
					}
					dictionary[text8] = 1;
					num++;
				}
			}
			recipeManager.PinnedRecipes.Clear();
			recipeManager.PinGroups.Clear();
			recipeManager.PinnedRecipeOrder.Clear();
			foreach (KeyValuePair<string, int> item2 in dictionary)
			{
				recipeManager.PinnedRecipes[item2.Key] = item2.Value;
			}
			foreach (KeyValuePair<string, PinGroupData> item3 in dictionary2)
			{
				recipeManager.PinGroups[item3.Key] = item3.Value;
			}
			recipeManager.PinnedRecipeOrder.AddRange(list);
			int effectivePinCount = recipeManager.GetEffectivePinCount();
			if (effectivePinCount > RecipePinnerPlugin.MaximumPins.Value)
			{
				int num7 = recipeManager.TrimToMaximumPins(RecipePinnerPlugin.MaximumPins.Value);
				DebugLogger.Warning($"Loaded save exceeded max effective pins ({effectivePinCount} > {RecipePinnerPlugin.MaximumPins.Value}) - trimmed {num7} effective pin(s)");
			}
			DebugLogger.Log($"Loaded {num} pins and {num2} groups from: {savePath} (Errors: {num3})");
		}
		catch (Exception ex)
		{
			DebugLogger.Error("Failed to load pins", ex);
		}
	}

	private void WriteAllLinesAtomically(string savePath, List<string> lines)
	{
		string? directoryName = Path.GetDirectoryName(savePath);
		if (string.IsNullOrEmpty(directoryName))
		{
			throw new IOException("Invalid save directory for path: " + savePath);
		}
		string fileName = Path.GetFileName(savePath);
		string text = Path.Combine(directoryName, $"{fileName}.{Guid.NewGuid():N}.tmp");
		string text2 = savePath + ".bak";
		try
		{
			File.WriteAllLines(text, lines);
			if (File.Exists(savePath))
			{
				if (File.Exists(text2))
				{
					File.Delete(text2);
				}
				File.Replace(text, savePath, text2, ignoreMetadataErrors: true);
			}
			else
			{
				File.Move(text, savePath);
			}
		}
		catch
		{
			try
			{
				if (File.Exists(text))
				{
					File.Delete(text);
				}
			}
			catch (Exception ex)
			{
				DebugLogger.Warning("Failed to delete temp save file '" + text + "': " + ex.Message);
			}
			throw;
		}
	}

	private static int FindGroupSeparator(string groupContent)
	{
		int num = groupContent.LastIndexOf('|');
		if (num >= 0)
		{
			return num;
		}
		return -1;
	}

	private static string EscapeSaveValue(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return string.Empty;
		}
		return value.Replace("%", "%25").Replace("|", "%7C").Replace(",", "%2C")
			.Replace("\r", "%0D")
			.Replace("\n", "%0A");
	}

	private static string UnescapeSaveValue(string value)
	{
		if (string.IsNullOrEmpty(value) || value.IndexOf('%') < 0)
		{
			return value;
		}
		StringBuilder stringBuilder = new StringBuilder(value.Length);
		for (int i = 0; i < value.Length; i++)
		{
			if (value[i] == '%' && i + 2 < value.Length && IsHexDigit(value[i + 1]) && IsHexDigit(value[i + 2]))
			{
				string value2 = value.Substring(i + 1, 2);
				stringBuilder.Append((char)Convert.ToInt32(value2, 16));
				i += 2;
			}
			else
			{
				stringBuilder.Append(value[i]);
			}
		}
		return stringBuilder.ToString();
	}

	private static bool IsHexDigit(char c)
	{
		if ((c < '0' || c > '9') && (c < 'a' || c > 'f'))
		{
			if (c >= 'A')
			{
				return c <= 'F';
			}
			return false;
		}
		return true;
	}

	private string GetSavePath()
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null)
		{
			DebugLogger.Verbose("Cannot get save path - local player is null");
			return null;
		}
		string playerName = Player.m_localPlayer.GetPlayerName();
		if (string.IsNullOrWhiteSpace(playerName))
		{
			DebugLogger.Warning("Cannot get save path - player name is empty");
			return null;
		}
		string text = Path.Combine(Paths.ConfigPath, "RecipePinner_Data");
		if (!Directory.Exists(text))
		{
			try
			{
				Directory.CreateDirectory(text);
				DebugLogger.Log("Created save directory: " + text);
			}
			catch (Exception ex)
			{
				DebugLogger.Error("Failed to create save directory: " + text, ex);
				return null;
			}
		}
		string text2 = playerName;
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		foreach (char oldChar in invalidFileNameChars)
		{
			text2 = text2.Replace(oldChar, '_');
		}
		string text3 = Path.Combine(text, text2 + ".txt");
		DebugLogger.Verbose("Save path: " + text3);
		return text3;
	}
}
public static class DebugLogger
{
	private const string Prefix = "[RecipePinner]";

	public static void Log(string message)
	{
		if (IsDebugEnabled())
		{
			Debug.Log((object)("[RecipePinner] " + message));
		}
	}

	public static void Warning(string message)
	{
		Debug.LogWarning((object)("[RecipePinner] " + message));
	}

	public static void Error(string message)
	{
		Debug.LogError((object)("[RecipePinner] " + message));
	}

	public static void Error(string message, Exception ex)
	{
		Debug.LogError((object)("[RecipePinner] " + message + "\nException: " + ex.Message + "\nStackTrace: " + ex.StackTrace));
	}

	public static void Verbose(string message)
	{
		if (IsDebugEnabled())
		{
			Debug.Log((object)("[RecipePinner] [VERBOSE] " + message));
		}
	}

	private static bool IsDebugEnabled()
	{
		if ((Object)(object)RecipePinnerPlugin.Instance != (Object)null && RecipePinnerPlugin.EnableDebugLogging != null)
		{
			return RecipePinnerPlugin.EnableDebugLogging.Value;
		}
		return false;
	}
}
public class LocalizationManager
{
	private readonly RecipePinnerPlugin _plugin;

	private readonly Dictionary<string, string> _localizedText = new Dictionary<string, string>();

	private static readonly Dictionary<string, string> _defaultEnglish = new Dictionary<string, string>
	{
		{ "pinned", "Recipe Pinned!" },
		{ "unpinned", "Pin Removed" },
		{ "list_full", "List Full!" },
		{ "added_more", "Added More: {0}x" },
		{ "decreased", "Decreased: {0}x" },
		{ "cleared", "Pinned Recipes Cleared" },
		{ "clear_confirm_hotkey", "Press again to clear all pins" },
		{ "max_level", "Max Level Reached" },
		{ "no_upgrade_cost", "No upgrade cost found" },
		{ "gathering_title", "GATHERING LIST" },
		{ "gathering_opened", "Gathering List Opened" },
		{ "gathering_closed", "Gathering List Closed" },
		{ "gathering_empty", "No Recipes Pinned" },
		{ "gathering_hint", "Open/Close: {0}" },
		{ "mypins_title", "MY PINS" },
		{ "mypins_button", "Pins" },
		{ "mypins_empty", "No Recipes Pinned" },
		{ "group_button", "Group" },
		{ "group_confirm", "Confirm" },
		{ "group_cancel", "Cancel" },
		{ "group_name_prompt", "Enter group name:" },
		{ "group_created", "Group Created: {0}" },
		{ "group_disbanded", "Group Disbanded: {0}" },
		{ "group_select_hint", "Select pins to group" },
		{ "group_min_select", "Select at least 2 pins" },
		{ "group_need_more", "At least 2 pins needed to create a group" },
		{ "group_create_failed", "Group could not be created" },
		{ "group_name_exists", "Group '{0}' already exists" },
		{ "disband_button", "Disband" },
		{ "confirm_delete_group", "Delete group \"{0}\" and all member pins?" },
		{ "confirm_delete_pin", "Delete \"{0}\"?" },
		{ "confirm_remove_member", "Remove \"{0}\" from group \"{1}\"?" },
		{ "confirm_button", "Confirm" },
		{ "cancel_button", "Cancel" },
		{ "confirm_disband_group", "Disband group \"{0}\"? Members will become individual pins." },
		{ "clear_button", "Clear" },
		{ "clear_confirm_msg", "Remove all pins?" },
		{ "clear_no_pins", "No pins to clear" },
		{ "group_no_pins", "Not enough pins to group" },
		{ "close_button", "Close" },
		{ "controls_title", "CONTROLS" },
		{ "controls_config_note", "Controls can be changed in\nthe config file." },
		{ "controls_config_note_single", "Controls can be changed in the config file." },
		{ "howto_header", "HOW TO USE" },
		{ "howto_pin", "Hover over a recipe in the crafting menu and press [{0}] to pin it." },
		{ "howto_unpin", "Hold [{0}] and press [{1}] to unpin a recipe." },
		{ "howto_toggle_hud", "Press [{0}] to show or hide the pinned recipe overlay." },
		{ "howto_gathering", "Press [{0}] to open or close the gathering list." },
		{ "howto_next_page", "Press [{0}] to cycle through HUD pages." },
		{ "howto_clear_all", "Press [{0}] to remove all pinned recipes." },
		{ "keybindings_header", "KEY BINDINGS" },
		{ "ctrl_pin", "Pin Recipe" },
		{ "ctrl_unpin", "Unpin  (hold + Pin Recipe key)" },
		{ "ctrl_toggle_hud", "Toggle HUD Visibility" },
		{ "ctrl_gathering", "Toggle Gathering List" },
		{ "ctrl_next_page", "Next HUD Page" },
		{ "ctrl_clear_all", "Clear All Pins" }
	};

	public LocalizationManager(RecipePinnerPlugin plugin)
	{
		_plugin = plugin;
		DebugLogger.Log("LocalizationManager init");
	}

	public void LoadTranslations()
	{
		_localizedText.Clear();
		string text = RecipePinnerPlugin.LanguageOverride?.Value?.Trim();
		if (string.IsNullOrEmpty(text) || text.ToLower() == "auto")
		{
			text = ((Localization.instance == null) ? "English" : Localization.instance.GetSelectedLanguage());
			DebugLogger.Log("Auto-detected language: " + text);
		}
		else
		{
			DebugLogger.Log("Using forced language: " + text);
		}
		string text2 = text;
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		foreach (char oldChar in invalidFileNameChars)
		{
			text2 = text2.Replace(oldChar, '_');
		}
		string text3 = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)_plugin).Info.Location), "RecipePinner_languages", text2 + ".json");
		if (!File.Exists(text3))
		{
			DebugLogger.Log("Language file not found: " + text3 + " - Using default English");
			return;
		}
		try
		{
			string text4 = File.ReadAllText(text3);
			int num = 0;
			string[] array = text4.Split(new string[3] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text5 = array[i].Trim();
				if (string.IsNullOrEmpty(text5) || text5 == "{" || text5 == "}" || !text5.Contains(":"))
				{
					continue;
				}
				string[] array2 = text5.Split(new char[1] { ':' }, 2);
				if (array2.Length == 2)
				{
					string text6 = array2[0].Trim(',', '"', ' ', '\t', '\r');
					string text7 = array2[1].Trim(',', '"', ' ', '\t', '\r');
					text7 = text7.Replace("\\\"", "\"").Replace("\\n", "\n").Replace("\\t", "\t")
						.Replace("\\\\", "\\");
					if (!string.IsNullOrEmpty(text6) && !string.IsNullOrEmpty(text7))
					{
						_localizedText[text6] = text7;
						num++;
					}
				}
			}
			DebugLogger.Log($"Loaded {num} translations from: {text}.json");
		}
		catch (Exception ex)
		{
			DebugLogger.Error("Failed to load language file: " + text3, ex);
		}
	}

	public string GetText(string key)
	{
		if (_localizedText.TryGetValue(key, out var value))
		{
			DebugLogger.Verbose("Translation found for '" + key + "': " + value);
			return value;
		}
		if (_defaultEnglish.TryGetValue(key, out var value2))
		{
			DebugLogger.Verbose("Using default English for '" + key + "': " + value2);
			return value2;
		}
		DebugLogger.Warning("No translation found for key: " + key);
		return key;
	}
}
public class PinnedRecipeData
{
	public Recipe RecipeRef;

	public string RawName;

	public string CachedHeader;

	public Sprite Icon;

	public int StackCount;

	public List<PinnedResData> Resources = new List<PinnedResData>();

	public bool IsDirty = true;

	public bool IsGroup;

	public PinGroupData GroupRef;
}
public class PinnedResData
{
	public string ItemName;

	public string CachedName;

	public Sprite Icon;

	public int RequiredAmount;

	public int LastKnownAmount;

	public int LastKnownInvAmount;

	public string CachedAmountString;
}
public class PinGroupData
{
	public string GroupName;

	public List<string> MemberRecipeKeys = new List<string>();

	public Dictionary<string, int> MemberCounts = new Dictionary<string, int>();

	public List<PinnedRecipeData> MemberPins = new List<PinnedRecipeData>();

	public List<PinnedResData> MergedResources = new List<PinnedResData>();

	public List<Sprite> MemberIcons = new List<Sprite>();

	public bool IsDirty = true;
}
public class RecipeManager
{
	public Dictionary<string, int> PinnedRecipes = new Dictionary<string, int>();

	public List<string> PinnedRecipeOrder = new List<string>();

	public List<PinnedRecipeData> CachedPins = new List<PinnedRecipeData>();

	public Dictionary<string, PinGroupData> PinGroups = new Dictionary<string, PinGroupData>();

	private readonly Dictionary<string, Recipe> _fakeRecipeCache = new Dictionary<string, Recipe>();

	private static readonly Regex CleanNameRegex = new Regex("<.*?>", RegexOptions.Compiled);

	private static readonly Regex AmountSuffixRegex = new Regex("\\s*[xX]?\\s*\\d+$", RegexOptions.Compiled);

	private static readonly Regex UpgradeStarRegex = new Regex("\\s*★(\\d+)$", RegexOptions.Compiled);

	private static readonly Dictionary<Type, FieldInfo> _cachedRecipeFields = new Dictionary<Type, FieldInfo>();

	private static readonly Dictionary<Type, PropertyInfo> _cachedRecipeProps = new Dictionary<Type, PropertyInfo>();

	private static readonly Dictionary<Type, FieldInfo> _cachedItemFields = new Dictionary<Type, FieldInfo>();

	private static readonly Dictionary<Type, PropertyInfo> _cachedItemProps = new Dictionary<Type, PropertyInfo>();

	private static readonly Dictionary<Type, PropertyInfo> _cachedElementProps = new Dictionary<Type, PropertyInfo>();

	private static readonly Dictionary<Type, FieldInfo> _cachedElementFields = new Dictionary<Type, FieldInfo>();

	private static readonly HashSet<Type> _elementLookupFailed = new HashSet<Type>();

	public void Cleanup()
	{
		DebugLogger.Log("RecipeManager cleanup");
		int count = _fakeRecipeCache.Count;
		foreach (Recipe value in _fakeRecipeCache.Values)
		{
			if ((Object)(object)value != (Object)null)
			{
				Object.Destroy((Object)(object)value);
			}
		}
		_fakeRecipeCache.Clear();
		DebugLogger.Log($"Cleaned {count} fake recipes");
		PinGroups.Clear();
		_cachedRecipeFields.Clear();
		_cachedRecipeProps.Clear();
		_cachedItemFields.Clear();
		_cachedItemProps.Clear();
		_cachedElementProps.Clear();
		_cachedElementFields.Clear();
		_elementLookupFailed.Clear();
	}

	public void RefreshRecipeCache()
	{
		DebugLogger.Verbose("Refreshing cache");
		CachedPins.Clear();
		if ((Object)(object)ObjectDB.instance == (Object)null)
		{
			DebugLogger.Warning("ObjectDB null, can't refresh");
			return;
		}
		int num = 0;
		int num2 = 0;
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		foreach (PinGroupData value9 in PinGroups.Values)
		{
			foreach (string memberRecipeKey in value9.MemberRecipeKeys)
			{
				int value;
				int num3 = ((!value9.MemberCounts.TryGetValue(memberRecipeKey, out value)) ? 1 : value);
				if (dictionary.ContainsKey(memberRecipeKey))
				{
					dictionary[memberRecipeKey] += num3;
				}
				else
				{
					dictionary[memberRecipeKey] = num3;
				}
			}
		}
		Dictionary<string, PinnedRecipeData> dictionary2 = new Dictionary<string, PinnedRecipeData>();
		int num4 = 0;
		foreach (KeyValuePair<string, PinGroupData> pinGroup in PinGroups)
		{
			PinGroupData value2 = pinGroup.Value;
			value2.MemberPins.Clear();
			value2.MergedResources.Clear();
			value2.MemberIcons.Clear();
			value2.IsDirty = true;
			Dictionary<string, PinnedResData> dictionary3 = new Dictionary<string, PinnedResData>();
			foreach (string memberRecipeKey2 in value2.MemberRecipeKeys)
			{
				int value3;
				int count = ((!value2.MemberCounts.TryGetValue(memberRecipeKey2, out value3)) ? 1 : value3);
				Recipe recipeByName = GetRecipeByName(memberRecipeKey2);
				if ((Object)(object)recipeByName == (Object)null)
				{
					DebugLogger.Warning("Group '" + value2.GroupName + "' member not found: " + memberRecipeKey2);
					continue;
				}
				PinnedRecipeData pinnedRecipeData = BuildPinnedRecipeData(recipeByName, memberRecipeKey2, count);
				if (pinnedRecipeData == null)
				{
					continue;
				}
				value2.MemberPins.Add(pinnedRecipeData);
				if ((Object)(object)pinnedRecipeData.Icon != (Object)null && value2.MemberIcons.Count < 4)
				{
					value2.MemberIcons.Add(pinnedRecipeData.Icon);
				}
				foreach (PinnedResData resource in pinnedRecipeData.Resources)
				{
					if (dictionary3.TryGetValue(resource.ItemName, out var value4))
					{
						value4.RequiredAmount += resource.RequiredAmount;
						continue;
					}
					dictionary3[resource.ItemName] = new PinnedResData
					{
						ItemName = resource.ItemName,
						CachedName = resource.CachedName,
						Icon = resource.Icon,
						RequiredAmount = resource.RequiredAmount,
						LastKnownAmount = -1,
						LastKnownInvAmount = -1
					};
				}
			}
			foreach (PinnedResData value10 in dictionary3.Values)
			{
				value2.MergedResources.Add(value10);
			}
			PinnedRecipeData value5 = new PinnedRecipeData
			{
				IsDirty = true,
				RecipeRef = null,
				RawName = value2.GroupName,
				CachedHeader = value2.GroupName,
				Icon = ((value2.MemberIcons.Count > 0) ? value2.MemberIcons[0] : null),
				StackCount = 1,
				Resources = value2.MergedResources,
				IsGroup = true,
				GroupRef = value2
			};
			dictionary2[pinGroup.Key] = value5;
			num4++;
			DebugLogger.Verbose($"Group pin built: {value2.GroupName} ({value2.MemberPins.Count} members, {value2.MergedResources.Count} resources)");
		}
		foreach (string item in GetDisplayPinOrder())
		{
			if (item.StartsWith("GROUP:"))
			{
				string key = item.Substring(6);
				if (dictionary2.TryGetValue(key, out var value6))
				{
					CachedPins.Add(value6);
				}
			}
			else
			{
				if (!PinnedRecipes.TryGetValue(item, out var value7))
				{
					continue;
				}
				if (dictionary.TryGetValue(item, out var value8))
				{
					int num5 = value7 - value8;
					if (num5 <= 0)
					{
						DebugLogger.Verbose($"Skipping grouped recipe (no excess): {item} (claims={value8})");
						continue;
					}
					value7 = num5;
					DebugLogger.Verbose($"Grouped recipe excess for overlay: {item} x{num5} (claims={value8})");
				}
				Recipe recipeByName2 = GetRecipeByName(item);
				if ((Object)(object)recipeByName2 != (Object)null)
				{
					PinnedRecipeData pinnedRecipeData2 = BuildPinnedRecipeData(recipeByName2, item, value7);
					if (pinnedRecipeData2 != null)
					{
						CachedPins.Add(pinnedRecipeData2);
						num++;
					}
					else
					{
						num2++;
					}
				}
				else
				{
					DebugLogger.Warning("Recipe not found: " + item);
					num2++;
				}
			}
		}
		DebugLogger.Log($"Cache refreshed: {num} pins, {num4} groups, {num2} failed");
		if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)RecipePinnerPlugin.Instance != (Object)null)
		{
			RecipePinnerPlugin.Instance.UIMgr.UpdateUI(RecipePinnerPlugin.IsUiVisible);
			RecipePinnerPlugin.Instance.UIMgr.RefreshMyPinsList();
		}
	}

	public List<string> GetDisplayPinOrder()
	{
		List<string> list = new List<string>();
		List<string> list2 = new List<string>();
		HashSet<string> hashSet = new HashSet<string>();
		Dictionary<string, string> dictionary = new Dictionary<string, string>();
		foreach (string item in PinnedRecipeOrder)
		{
			if (!item.StartsWith("GROUP:"))
			{
				continue;
			}
			string text = item.Substring(6);
			if (!PinGroups.TryGetValue(text, out var value))
			{
				continue;
			}
			foreach (string memberRecipeKey in value.MemberRecipeKeys)
			{
				dictionary[memberRecipeKey] = text;
			}
		}
		foreach (string item2 in PinnedRecipeOrder)
		{
			if (item2.StartsWith("GROUP:"))
			{
				string text2 = item2.Substring(6);
				if (PinGroups.ContainsKey(text2))
				{
					list.Add(item2);
					AppendDeferredExcessForGroup(list, list2, hashSet, dictionary, text2);
				}
			}
			else
			{
				if (!PinnedRecipes.TryGetValue(item2, out var value2))
				{
					continue;
				}
				int groupClaimCount = GetGroupClaimCount(item2);
				if (groupClaimCount <= 0)
				{
					list.Add(item2);
				}
				else
				{
					if (value2 <= groupClaimCount)
					{
						continue;
					}
					if (dictionary.ContainsKey(item2))
					{
						if (hashSet.Add(item2))
						{
							list2.Add(item2);
						}
					}
					else
					{
						list.Add(item2);
					}
				}
			}
		}
		foreach (string item3 in list2)
		{
			list.Add(item3);
		}
		return list;
	}

	private static void AppendDeferredExcessForGroup(List<string> displayOrder, List<string> deferredExcess, HashSet<string> deferredSet, Dictionary<string, string> lastClaimingGroup, string groupName)
	{
		int num = 0;
		while (num < deferredExcess.Count)
		{
			string text = deferredExcess[num];
			if (lastClaimingGroup.TryGetValue(text, out var value) && value == groupName)
			{
				displayOrder.Add(text);
				deferredSet.Remove(text);
				deferredExcess.RemoveAt(num);
			}
			else
			{
				num++;
			}
		}
	}

	public Recipe GetRecipeByName(string name)
	{
		if ((Object)(object)ObjectDB.instance == (Object)null)
		{
			return null;
		}
		if (_fakeRecipeCache.TryGetValue(name, out var value))
		{
			DebugLogger.Verbose("Found cached fake recipe: " + name);
			return value;
		}
		Match match = UpgradeStarRegex.Match(name);
		if (match.Success)
		{
			string name2 = name.Substring(0, match.Index).Trim();
			if (!int.TryParse(match.Groups[1].Value, out var result))
			{
				DebugLogger.Warning("Invalid upgrade level in recipe key: " + name);
				return null;
			}
			Recipe recipeByName = GetRecipeByName(name2);
			if ((Object)(object)recipeByName != (Object)null)
			{
				if (!IsValidUpgradeTarget(recipeByName, result, name))
				{
					return null;
				}
				Recipe val = CreateFakeUpgradeRecipe(recipeByName, result, name);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
		}
		GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name);
		ItemDrop val2 = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null);
		if ((Object)(object)val2 != (Object)null)
		{
			Recipe recipe = ObjectDB.instance.GetRecipe(val2.m_itemData);
			if ((Object)(object)recipe != (Object)null)
			{
				DebugLogger.Verbose("Found standard recipe: " + name);
				return recipe;
			}
		}
		Recipe val3 = null;
		foreach (Recipe recipe2 in ObjectDB.instance.m_recipes)
		{
			if (((Object)recipe2).name == name)
			{
				val3 = recipe2;
				break;
			}
		}
		if ((Object)(object)val3 != (Object)null)
		{
			DebugLogger.Verbose("Found recipe in ObjectDB: " + name);
			return val3;
		}
		ZNetScene instance = ZNetScene.instance;
		GameObject val4 = ((instance != null) ? instance.GetPrefab(name) : null);
		if ((Object)(object)val4 != (Object)null)
		{
			Piece component = val4.GetComponent<Piece>();
			if ((Object)(object)component != (Object)null && component.m_resources != null && component.m_resources.Length != 0)
			{
				Recipe val5 = ScriptableObject.CreateInstance<Recipe>();
				((Object)val5).hideFlags = (HideFlags)61;
				((Object)val5).name = name;
				val5.m_item = val4.GetComponent<ItemDrop>();
				val5.m_resources = (Requirement[])component.m_resources.Clone();
				_fakeRecipeCache[name] = val5;
				DebugLogger.Verbose("Created fake recipe for piece: " + name);
				return val5;
			}
		}
		DebugLogger.Warning("Recipe not found anywhere: " + name);
		return null;
	}

	private Recipe CreateFakeUpgradeRecipe(Recipe baseRecipe, int targetLevel, string customName)
	{
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Expected O, but got Unknown
		if ((Object)(object)baseRecipe == (Object)null)
		{
			return null;
		}
		if (!IsValidUpgradeTarget(baseRecipe, targetLevel, customName))
		{
			return null;
		}
		Recipe val = ScriptableObject.CreateInstance<Recipe>();
		((Object)val).hideFlags = (HideFlags)61;
		((Object)val).name = customName;
		val.m_item = baseRecipe.m_item;
		val.m_amount = 1;
		int num = Mathf.Max(1, targetLevel - 1);
		List<Requirement> list = new List<Requirement>();
		Requirement[] resources = baseRecipe.m_resources;
		foreach (Requirement val2 in resources)
		{
			if (val2.m_amountPerLevel > 0)
			{
				Requirement item = new Requirement
				{
					m_resItem = val2.m_resItem,
					m_amount = val2.m_amountPerLevel * num,
					m_amountPerLevel = 0,
					m_recover = val2.m_recover
				};
				list.Add(item);
			}
		}
		if (list.Count == 0)
		{
			Object.Destroy((Object)(object)val);
			return null;
		}
		val.m_resources = list.ToArray();
		_fakeRecipeCache[customName] = val;
		DebugLogger.Verbose("Created fake upgrade recipe: " + customName);
		return val;
	}

	private bool IsValidUpgradeTarget(Recipe baseRecipe, int targetLevel, string customName)
	{
		if (targetLevel < 2)
		{
			DebugLogger.Warning("Invalid upgrade level for '" + customName + "': target level must be at least 2");
			return false;
		}
		SharedData val = (baseRecipe.m_item?.m_itemData)?.m_shared;
		if (val == null)
		{
			DebugLogger.Warning("Cannot validate upgrade level for '" + customName + "' - item data is missing");
			return false;
		}
		int maxQuality = val.m_maxQuality;
		if (maxQuality < 2 || targetLevel > maxQuality)
		{
			DebugLogger.Warning($"Invalid upgrade level for '{customName}': target={targetLevel}, max={maxQuality}");
			return false;
		}
		return true;
	}

	public void ValidateAndCleanPins()
	{
		if ((Object)(object)ObjectDB.instance == (Object)null)
		{
			DebugLogger.Warning("Cannot validate pins - ObjectDB.instance is null");
			return;
		}
		DebugLogger.Log("Validating pins");
		List<string> list = new List<string>();
		foreach (string key in PinnedRecipes.Keys)
		{
			if ((Object)(object)GetRecipeByName(key) == (Object)null)
			{
				list.Add(key);
			}
		}
		if (list.Count > 0)
		{
			foreach (string item in list)
			{
				PinnedRecipes.Remove(item);
				PinnedRecipeOrder.Remove(item);
				DebugLogger.Warning("Removed invalid recipe: " + item);
			}
			DebugLogger.Log($"Removed {list.Count} invalid pins");
		}
		else
		{
			DebugLogger.Log("All individual pins valid");
		}
		int num = CleanInvalidGroupMembers();
		if (list.Count > 0 || num > 0)
		{
			RecipePinnerPlugin.Instance?.DataMgr.SavePins();
		}
	}

	private int CleanInvalidGroupMembers()
	{
		int num = 0;
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, PinGroupData> pinGroup in PinGroups)
		{
			string key = pinGroup.Key;
			PinGroupData value = pinGroup.Value;
			List<string> list2 = new List<string>();
			foreach (string memberRecipeKey in value.MemberRecipeKeys)
			{
				if ((Object)(object)GetRecipeByName(memberRecipeKey) == (Object)null)
				{
					value.MemberCounts.Remove(memberRecipeKey);
					num++;
					DebugLogger.Warning("Removed invalid group member: " + memberRecipeKey + " from group '" + key + "'");
				}
				else
				{
					list2.Add(memberRecipeKey);
				}
			}
			if (list2.Count != value.MemberRecipeKeys.Count)
			{
				value.MemberRecipeKeys.Clear();
				value.MemberRecipeKeys.AddRange(list2);
			}
			List<string> list3 = new List<string>();
			foreach (string key2 in value.MemberCounts.Keys)
			{
				if (!value.MemberRecipeKeys.Contains(key2))
				{
					list3.Add(key2);
				}
			}
			foreach (string item in list3)
			{
				value.MemberCounts.Remove(item);
			}
			if (value.MemberRecipeKeys.Count < 2)
			{
				list.Add(key);
			}
		}
		foreach (string item2 in list)
		{
			PinGroups.Remove(item2);
			PinnedRecipeOrder.Remove("GROUP:" + item2);
			DebugLogger.Warning("Removed group '" + item2 + "' because it has less than 2 valid members");
		}
		if (num > 0 || list.Count > 0)
		{
			DebugLogger.Log($"Removed {num} invalid group member(s) and {list.Count} invalid group(s)");
		}
		return num + list.Count;
	}

	public void TryPinHoveredRecipe(InventoryGui gui)
	{
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Expected O, but got Unknown
		Transform recipeListRoot = ReflectionHelper.GetRecipeListRoot(gui);
		if (!(ReflectionHelper.GetAvailableRecipes(gui) is IList list) || (Object)(object)recipeListRoot == (Object)null)
		{
			DebugLogger.Verbose("Cannot pin - listRoot or availableRecipes is null");
			return;
		}
		ScrollRect componentInParent = ((Component)recipeListRoot).GetComponentInParent<ScrollRect>();
		bool flag = !((Selectable)gui.m_tabUpgrade).interactable;
		foreach (Transform item in recipeListRoot)
		{
			Transform val = item;
			if (!((Component)val).gameObject.activeInHierarchy)
			{
				continue;
			}
			RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null);
			if ((Object)(object)val2 == (Object)null || !IsVisibleInScroll(val2, componentInParent) || !InputHelper.IsMouseOverRect(val2, logHit: false))
			{
				continue;
			}
			string text = ExtractTextFromUI(val);
			if (string.IsNullOrEmpty(text))
			{
				continue;
			}
			string text2 = CleanNameRegex.Replace(text, string.Empty).Trim();
			text2 = text2.Replace("\r", "").Replace("\n", "");
			string text3 = AmountSuffixRegex.Replace(text2, "").Trim();
			int num = -1;
			for (int i = 0; i < list.Count; i++)
			{
				GameObject interfaceElementFromObject = GetInterfaceElementFromObject(list[i]);
				if (!((Object)(object)interfaceElementFromObject == (Object)null) && ((Object)(object)interfaceElementFromObject == (Object)(object)((Component)val).gameObject || interfaceElementFromObject.transform.IsChildOf(val)))
				{
					num = i;
					break;
				}
			}
			int num2 = -1;
			foreach (object item2 in list)
			{
				num2++;
				if (num >= 0 && num2 != num)
				{
					continue;
				}
				Recipe recipeFromObject = GetRecipeFromObject(item2);
				if (!((Object)(object)recipeFromObject != (Object)null))
				{
					continue;
				}
				bool flag2 = num >= 0;
				if (!flag2)
				{
					string rawRecipeName = GetRawRecipeName(recipeFromObject);
					if (string.IsNullOrEmpty(rawRecipeName))
					{
						continue;
					}
					string text4 = rawRecipeName;
					if (Localization.instance != null)
					{
						text4 = Localization.instance.Localize(rawRecipeName);
					}
					text4 = text4.Replace("\r", "").Replace("\n", "");
					flag2 = text4.Equals(text3, StringComparison.OrdinalIgnoreCase) || text4.Equals(text2, StringComparison.OrdinalIgnoreCase);
				}
				if (!flag2)
				{
					continue;
				}
				if (flag)
				{
					ItemData val3 = GetItemDataFromObject(item2) ?? ReflectionHelper.GetCraftUpgradeItem(gui);
					if (val3 != null)
					{
						int quality = val3.m_quality;
						int num3 = quality + 1;
						int maxQuality = val3.m_shared.m_maxQuality;
						if (quality >= maxQuality)
						{
							string text5 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("max_level");
							Player localPlayer = Player.m_localPlayer;
							if (localPlayer != null)
							{
								((Character)localPlayer).Message((MessageType)2, text5, 0, (Sprite)null);
							}
							return;
						}
						string name = ((Object)recipeFromObject.m_item).name;
						string text6 = $"{name} ★{num3}";
						if (IsUnpinHotkeyHeld() && !PinnedRecipes.ContainsKey(text6))
						{
							return;
						}
						DebugLogger.Verbose("Attempting to pin hovered recipe...");
						DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})");
						DebugLogger.Log("Attempting to pin upgrade: " + text6 + " (Base: " + name + ")");
						if ((Object)(object)GetRecipeByName(text6) != (Object)null)
						{
							TogglePin(text6);
							return;
						}
						string text7 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("no_upgrade_cost");
						Player localPlayer2 = Player.m_localPlayer;
						if (localPlayer2 != null)
						{
							((Character)localPlayer2).Message((MessageType)2, text7, 0, (Sprite)null);
						}
					}
					else
					{
						DebugLogger.Warning("Matched name but could not get ItemData for upgrade.");
					}
				}
				else if (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(((Object)recipeFromObject).name))
				{
					DebugLogger.Verbose("Attempting to pin hovered recipe...");
					DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})");
					DebugLogger.Log("Matched recipe: " + ((Object)recipeFromObject).name);
					TogglePin(((Object)recipeFromObject).name);
				}
				return;
			}
		}
	}

	public void TryPinHoveredPiece()
	{
		if (!((Object)(object)Hud.instance == (Object)null))
		{
			Piece hoveredPiece = ReflectionHelper.GetHoveredPiece(Hud.instance);
			if ((Object)(object)hoveredPiece != (Object)null && hoveredPiece.m_resources != null && hoveredPiece.m_resources.Length != 0 && (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(((Object)hoveredPiece).name)))
			{
				DebugLogger.Verbose("Attempting to pin hovered piece...");
				DebugLogger.Log("Pinning piece: " + ((Object)hoveredPiece).name);
				TogglePin(((Object)hoveredPiece).name);
			}
		}
	}

	private bool IsUnpinHotkeyHeld()
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: 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)
		ConfigEntry<KeyCode> hotkeyUnpin = RecipePinnerPlugin.HotkeyUnpin;
		KeyCode val = (KeyCode)((hotkeyUnpin == null) ? 304 : ((int)hotkeyUnpin.Value));
		if ((int)val != 0)
		{
			return Input.GetKey(val);
		}
		return false;
	}

	private void TogglePin(string recipeName)
	{
		bool flag = IsUnpinHotkeyHeld();
		LocalizationManager localizationMgr = RecipePinnerPlugin.Instance.LocalizationMgr;
		if (PinnedRecipes.TryGetValue(recipeName, out var value))
		{
			if (flag)
			{
				int groupClaimCount = GetGroupClaimCount(recipeName);
				int num = groupClaimCount;
				value--;
				if (value < num)
				{
					if (groupClaimCount > 0)
					{
						value = groupClaimCount;
						PinnedRecipes[recipeName] = value;
						string groupContainingRecipe = GetGroupContainingRecipe(recipeName);
						Player localPlayer = Player.m_localPlayer;
						if (localPlayer != null)
						{
							((Character)localPlayer).Message((MessageType)2, "Cannot remove: in group \"" + groupContainingRecipe + "\"", 0, (Sprite)null);
						}
						DebugLogger.Log($"Hotkey unpin blocked: {recipeName} min={groupClaimCount}");
					}
					else
					{
						PinnedRecipes.Remove(recipeName);
						PinnedRecipeOrder.Remove(recipeName);
						Player localPlayer2 = Player.m_localPlayer;
						if (localPlayer2 != null)
						{
							((Character)localPlayer2).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null);
						}
						DebugLogger.Log("Unpinned: " + recipeName);
					}
				}
				else if (value == 0)
				{
					PinnedRecipes.Remove(recipeName);
					PinnedRecipeOrder.Remove(recipeName);
					Player localPlayer3 = Player.m_localPlayer;
					if (localPlayer3 != null)
					{
						((Character)localPlayer3).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null);
					}
					DebugLogger.Log("Unpinned: " + recipeName);
				}
				else
				{
					PinnedRecipes[recipeName] = value;
					int num2 = value - groupClaimCount;
					if (num2 > 0)
					{
						string text = string.Format(localizationMgr.GetText("decreased"), num2);
						Player localPlayer4 = Player.m_localPlayer;
						if (localPlayer4 != null)
						{
							((Character)localPlayer4).Message((MessageType)2, text, 0, (Sprite)null);
						}
					}
					else
					{
						Player localPlayer5 = Player.m_localPlayer;
						if (localPlayer5 != null)
						{
							((Character)localPlayer5).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null);
						}
					}
					DebugLogger.Log($"Decreased pin count: {recipeName} = {value}");
				}
			}
			else
			{
				value++;
				PinnedRecipes[recipeName] = value;
				int groupClaimCount2 = GetGroupClaimCount(recipeName);
				if (groupClaimCount2 > 0)
				{
					int num3 = value - groupClaimCount2;
					if (num3 == 1)
					{
						if (!PinnedRecipeOrder.Contains(recipeName))
						{
							PinnedRecipeOrder.Add(recipeName);
						}
						Player localPlayer6 = Player.m_localPlayer;
						if (localPlayer6 != null)
						{
							((Character)localPlayer6).Message((MessageType)2, localizationMgr.GetText("pinned"), 0, (Sprite)null);
						}
					}
					else
					{
						string text2 = string.Format(localizationMgr.GetText("added_more"), num3);
						Player localPlayer7 = Player.m_localPlayer;
						if (localPlayer7 != null)
						{
							((Character)localPlayer7).Message((MessageType)2, text2, 0, (Sprite)null);
						}
					}
				}
				else
				{
					string text3 = string.Format(localizationMgr.GetText("added_more"), value);
					Player localPlayer8 = Player.m_localPlayer;
					if (localPlayer8 != null)
					{
						((Character)localPlayer8).Message((MessageType)2, text3, 0, (Sprite)null);
					}
				}
				DebugLogger.Log($"Increased pin count: {recipeName} = {value}");
			}
		}
		else
		{
			if (flag)
			{
				return;
			}
			if (GetEffectivePinCount() < RecipePinnerPlugin.MaximumPins.Value)
			{
				PinnedRecipes.Add(recipeName, 1);
				if (!PinnedRecipeOrder.Contains(recipeName))
				{
					PinnedRecipeOrder.Add(recipeName);
				}
				Player localPlayer9 = Player.m_localPlayer;
				if (localPlayer9 != null)
				{
					((Character)localPlayer9).Message((MessageType)2, localizationMgr.GetText("pinned"), 0, (Sprite)null);
				}
				DebugLogger.Log("Pinned new recipe: " + recipeName);
			}
			else
			{
				Player localPlayer10 = Player.m_localPlayer;
				if (localPlayer10 != null)
				{
					((Character)localPlayer10).Message((MessageType)2, localizationMgr.GetText("list_full"), 0, (Sprite)null);
				}
				DebugLogger.Warning($"Cannot pin {recipeName} - max pins reached ({RecipePinnerPlugin.MaximumPins.Value})");
			}
		}
		RefreshRecipeCache();
		RecipePinnerPlugin.Instance?.DataMgr.SavePins();
	}

	private Recipe GetRecipeFromObject(object data)
	{
		if (data == null)
		{
			return null;
		}
		Recipe val = (Recipe)((data is Recipe) ? data : null);
		if (val != null)
		{
			return val;
		}
		Type type = data.GetType();
		if (_cachedRecipeFields.TryGetValue(type, out var value))
		{
			object? value2 = value.GetValue(data);
			return (Recipe)((value2 is Recipe) ? value2 : null);
		}
		if (_cachedRecipeProps.TryGetValue(type, out var value3))
		{
			object? value4 = value3.GetValue(data, null);
			return (Recipe)((value4 is Recipe) ? value4 : null);
		}
		PropertyInfo property = type.GetProperty("Key");
		if (property != null)
		{
			object? value5 = property.GetValue(data, null);
			Recipe val2 = (Recipe)((value5 is Recipe) ? value5 : null);
			if (val2 != null)
			{
				_cachedRecipeProps[type] = property;
				return val2;
			}
		}
		FieldInfo field = type.GetField("m_recipe", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (field != null)
		{
			object? value6 = field.GetValue(data);
			Recipe val3 = (Recipe)((value6 is Recipe) ? value6 : null);
			if (val3 != null)
			{
				_cachedRecipeFields[type] = field;
				return val3;
			}
		}
		FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (fieldInfo.FieldType == typeof(Recipe))
			{
				_cachedRecipeFields[type] = fieldInfo;
				object? value7 = fieldInfo.GetValue(data);
				return (Recipe)((value7 is Recipe) ? value7 : null);
			}
		}
		PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (PropertyInfo propertyInfo in properties)
		{
			if (propertyInfo.PropertyType == typeof(Recipe) && propertyInfo.CanRead)
			{
				_cachedRecipeProps[type] = propertyInfo;
				object? value8 = propertyInfo.GetValue(data, null);
				return (Recipe)((value8 is Recipe) ? value8 : null);
			}
		}
		return null;
	}

	private ItemData GetItemDataFromObject(object data)
	{
		if (data == null)
		{
			return null;
		}
		Type type = data.GetType();
		if (_cachedItemFields.TryGetValue(type, out var value))
		{
			object? value2 = value.GetValue(data);
			return (ItemData)((value2 is ItemData) ? value2 : null);
		}
		if (_cachedItemProps.TryGetValue(type, out var value3))
		{
			object? value4 = value3.GetValue(data, null);
			return (ItemData)((value4 is ItemData) ? value4 : null);
		}
		PropertyInfo property = type.GetProperty("Value");
		if (property != null)
		{
			object? value5 = property.GetValue(data, null);
			ItemData val = (ItemData)((value5 is ItemData) ? value5 : null);
			if (val != null)
			{
				_cachedItemProps[type] = property;
				return val;
			}
		}
		PropertyInfo property2 = type.GetProperty("Item2");
		if (property2 != null)
		{
			object? value6 = property2.GetValue(data, null);
			ItemData val2 = (ItemData)((value6 is ItemData) ? value6 : null);
			if (val2 != null)
			{
				_cachedItemProps[type] = property2;
				return val2;
			}
		}
		FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (fieldInfo.FieldType == typeof(ItemData))
			{
				_cachedItemFields[type] = fieldInfo;
				object? value7 = fieldInfo.GetValue(data);
				return (ItemData)((value7 is ItemData) ? value7 : null);
			}
		}
		PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (PropertyInfo propertyInfo in properties)
		{
			if (propertyInfo.PropertyType == typeof(ItemData) && propertyInfo.CanRead)
			{
				_cachedItemProps[type] = propertyInfo;
				object? value8 = propertyInfo.GetValue(data, null);
				return (ItemData)((value8 is ItemData) ? value8 : null);
			}
		}
		return null;
	}

	private GameObject GetInterfaceElementFromObject(object data)
	{
		if (data == null)
		{
			return null;
		}
		Type type = data.GetType();
		if (_elementLookupFailed.Contains(type))
		{
			return null;
		}
		if (_cachedElementProps.TryGetValue(type, out var value))
		{
			object? value2 = value.GetValue(data, null);
			return (GameObject)((value2 is GameObject) ? value2 : null);
		}
		if (_cachedElementFields.TryGetValue(type, out var value3))
		{
			object? value4 = value3.GetValue(data);
			return (GameObject)((value4 is GameObject) ? value4 : null);
		}
		PropertyInfo property = type.GetProperty("InterfaceElement", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (property != null && property.PropertyType == typeof(GameObject) && property.CanRead)
		{
			_cachedElementProps[type] = property;
			object? value5 = property.GetValue(data, null);
			return (GameObject)((value5 is GameObject) ? value5 : null);
		}
		PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (PropertyInfo propertyInfo in properties)
		{
			if (propertyInfo.PropertyType == typeof(GameObject) && propertyInfo.CanRead)
			{
				_cachedElementProps[type] = propertyInfo;
				object? value6 = propertyInfo.GetValue(data, null);
				return (GameObject)((value6 is GameObject) ? value6 : null);
			}
		}
		FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (fieldInfo.FieldType == typeof(GameObject))
			{
				_cachedElementFields[type] = fieldInfo;
				object? value7 = fieldInfo.GetValue(data);
				return (GameObject)((value7 is GameObject) ? value7 : null);
			}
		}
		_elementLookupFailed.Add(type);
		DebugLogger.Warning("GetInterfaceElementFromObject: no GameObject member on '" + type.Name + "' - falling back to name matching");
		return null;
	}

	private string ExtractTextFromUI(Transform child)
	{
		Text componentInChildren = ((Component)child).GetComponentInChildren<Text>();
		if ((Object)(object)componentInChildren != (Object)null)
		{
			return componentInChildren.text;
		}
		Component[] componentsInChildren = ((Component)child).GetComponentsInChildren<Component>(true);
		foreach (Component val in componentsInChildren)
		{
			if (!((object)val).GetType().Name.Contains("TextMeshPro") && !((object)val).GetType().Name.Contains("TMP_Text"))
			{
				continue;
			}
			PropertyInfo property = ((object)val).GetType().GetProperty("text");
			if (property != null)
			{
				string text = property.GetValue(val, null) as string;
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
		}
		return null;
	}

	private string GetRawRecipeName(Recipe r)
	{
		if ((Object)(object)r.m_item != (Object)null && r.m_item.m_itemData != null)
		{
			return r.m_item.m_itemData.m_shared.m_name;
		}
		ZNetScene instance = ZNetScene.instance;
		GameObject val = ((instance != null) ? instance.GetPrefab(((Object)r).name) : null);
		if ((Object)(object)val != (Object)null)
		{
			ItemDrop component = val.GetComponent<ItemDrop>();
			if ((Object)(object)component != (Object)null)
			{
				return component.m_itemData.m_shared.m_name;
			}
			Piece component2 = val.GetComponent<Piece>();
			if ((Object)(object)component2 != (Object)null)
			{
				return component2.m_name;
			}
		}
		return null;
	}

	private bool IsVisibleInScroll(RectTransform item, ScrollRect scrollRect)
	{
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: 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_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)item == (Object)null || !((Component)item).gameObject.activeInHierarchy)
		{
			return false;
		}
		if ((Object)(object)scrollRect == (Object)null || (Object)(object)scrollRect.viewport == (Object)null)
		{
			return true;
		}
		Vector3[] array = (Vector3[])(object)new Vector3[4];
		scrollRect.viewport.GetWorldCorners(array);
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(array[0].x, array[0].y, array[2].x - array[0].x, array[2].y - array[0].y);
		Vector3[] array2 = (Vector3[])(object)new Vector3[4];
		item.GetWorldCorners(array2);
		Vector3 val2 = (array2[0] + array2[2]) / 2f;
		return ((Rect)(ref val)).Contains(val2);
	}

	private PinnedRecipeData BuildPinnedRecipeData(Recipe r, string recipeName, int count)
	{
		if ((Object)(object)r == (Object)null)
		{
			return null;
		}
		PinnedRecipeData pinnedRecipeData = new PinnedRecipeData
		{
			IsDirty = true,
			RecipeRef = r,
			StackCount = count
		};
		if ((Object)(object)r.m_item != (Object)null && r.m_item.m_itemData != null)
		{
			pinnedRecipeData.Icon = r.m_item.m_itemData.GetIcon();
			pinnedRecipeData.RawName = r.m_item.m_itemData.m_shared?.m_name;
		}
		else if ((Object)(object)r.m_item != (Object)null)
		{
			DebugLogger.Warning("BuildPinnedRecipeData: recipe '" + recipeName + "' has item without itemData, using fallback name");
		}
		else
		{
			ZNetScene instance = ZNetScene.instance;
			GameObject val = ((instance != null) ? instance.GetPrefab(((Object)r).name) : null);
			if ((Object)(object)val != (Object)null)
			{
				Piece component = val.GetComponent<Piece>();
				if ((Object)(object)component != (Object)null)
				{
					pinnedRecipeData.Icon = component.m_icon;
					pinnedRecipeData.RawName = component.m_name;
				}
			}
		}
		if (string.IsNullOrEmpty(pinnedRecipeData.RawName))
		{
			pinnedRecipeData.RawName = ((Object)r).name;
		}
		string text = pinnedRecipeData.RawName;
		if (Localization.instance != null)
		{
			Match match = UpgradeStarRegex.Match(recipeName);
			text = ((!match.Success) ? Localization.instance.Localize(pinnedRecipeData.RawName) : (Localization.instance.Localize(pinnedRecipeData.RawName) + match.Value));
		}
		text = text.Replace("\r", "").Replace("\n", "");
		if (r.m_amount > 1)
		{
			text += $" (x{r.m_amount})";
		}
		if (count > 1)
		{
			text = $"{count}x {text}";
		}
		pinnedRecipeData.CachedHeader = text;
		if (r.m_resources == null)
		{
			DebugLogger.Warning("BuildPinnedRecipeData: recipe '" + recipeName + "' has null resources, skipping");
			return null;
		}
		Requirement[] resources = r.m_resources;
		foreach (Requirement val2 in resources)
		{
			if (val2 == null || val2.m_amount <= 0)
			{
				continue;
			}
			if ((Object)(object)val2.m_resItem == (Object)null || val2.m_resItem.m_itemData == null)
			{
				DebugLogger.Warning("BuildPinnedRecipeData: skipping invalid resource in '" + recipeName + "'");
				continue;
			}
			PinnedResData pinnedResData = new PinnedResData
			{
				ItemName = val2.m_resItem.m_itemData.m_shared?.m_name,
				Icon = val2.m_resItem.m_itemData.GetIcon(),
				RequiredAmount = val2.m_amount * count,
				LastKnownAmount = -1,
				LastKnownInvAmount = -1
			};
			if (string.IsNullOrEmpty(pinnedResData.ItemName))
			{
				DebugLogger.Warning("BuildPinnedRecipeData: skipping resource with empty item name in '" + recipeName + "'");
				continue;
			}
			string text2 = pinnedResData.ItemName;
			if (Localization.instance != null)
			{
				text2 = Localization.instance.Localize(pinnedResData.ItemName);
			}
			text2 = text2.Replace("\r", "").Replace("\n", "");
			pinnedResData.CachedName = text2;
			pinnedRecipeData.Resources.Add(pinnedResData);
		}
		return pinnedRecipeData;
	}

	public bool CreateGroup(string groupName, List<string> selectedKeys)
	{
		if (string.IsNullOrWhiteSpace(groupName))
		{
			DebugLogger.Warning("CreateGroup: group name is empty");
			return false;
		}
		if (selectedKeys == null || selectedKeys.Count < 2)
		{
			DebugLogger.Warning($"CreateGroup: need at least 2 pins, got {selectedKeys?.Count ?? 0}");
			return false;
		}
		if (PinGroups.ContainsKey(groupName))
		{
			DebugLogger.Warning("CreateGroup: group '" + groupName + "' already exists");
			return false;
		}
		PinGroupData pinGroupData = new PinGroupData
		{
			GroupName = groupName
		};
		foreach (string selectedKey in selectedKeys)
		{
			if (PinnedRecipes.TryGetValue(selectedKey, out var value))
			{
				int groupClaimCount = GetGroupClaimCount(selectedKey);
				int num = value - groupClaimCount;
				if (num <= 0)
				{
					DebugLogger.Warning($"CreateGroup: recipe key '{selectedKey}' has no ungrouped excess to claim (total={value}, claims={groupClaimCount}), skipping");
					continue;
				}
				pinGroupData.MemberRecipeKeys.Add(selectedKey);
				pinGroupData.MemberCounts[selectedKey] = num;
				DebugLogger.Verbose($"CreateGroup: added member '{selectedKey}' to group '{groupName}' (claim={num}, total={value}, previousClaims={groupClaimCount})");
			}
			else
			{
				DebugLogger.Warning("CreateGroup: recipe key '" + selectedKey + "' not found in PinnedRecipes, skipping");
			}
		}
		if (pinGroupData.MemberRecipeKeys.Count < 2)
		{
			DebugLogger.Warning($"CreateGroup: only {pinGroupData.MemberRecipeKeys.Count} valid members, need at least 2");
			return false;
		}
		PinGroups[groupName] = pinGroupData;
		string item = "GROUP:" + groupName;
		PinnedRecipeOrder.Add(item);
		DebugLogger.Log($"Group created: '{groupName}' with {pinGroupData.MemberRecipeKeys.Count} members (added to end)");
		RefreshRecipeCache();
		RecipePinnerPlugin.Instance?.DataMgr.SavePins();
		return true;
	}

	public bool DisbandGroup(string groupName)
	{
		if (!PinGroups.TryGetValue(groupName, out var value))
		{
			DebugLogger.Warning("DisbandGroup: group '" + groupName + "' not found");
			return false;
		}
		PinGroups.Remove(groupName);
		PinnedRecipeOrder.Remove("GROUP:" + groupName);
		DebugLogger.Log($"Group disbanded: '{groupName}' ({value.MemberRecipeKeys.Count} members restored)");
		RefreshRecipeCache();
		RecipePinnerPlugin.Instance?.DataMgr.SavePins();
		return true;
	}

	public void DecrementGroupMemberCounts(string recipeKey)
	{
		string text = null;
		foreach (string item in PinnedRecipeOrder)
		{
			if (item.StartsWith("GROUP:"))
			{
				string text2 = item.Substring(6);
				if (PinGroups.TryGetValue(text2, out var value) && value.MemberCounts.ContainsKey(recipeKey))
				{
					text = text2;
				}
			}
		}
		if (text == null)
		{
			return;
		}
		string text3 = text;
		if (!PinGroups.TryGetValue(text3, out var value2))
		{
			return;
		}
		int num = value2.MemberCounts[recipeKey];
		int num2 = num - 1;
		if (num2 <= 0)
		{
			value2.MemberCounts.Remove(recipeKey);
			value2.MemberRecipeKeys.Remove(recipeKey);
			DebugLogger.Log($"AutoUnpin: '{recipeKey}' claim reached 0, removed from group '{text3}' ({value2.MemberRecipeKeys.Count} remaining)");
			if (value2.MemberRecipeKeys.Count < 2)
			{
				DebugLogger.Log($"AutoUnpin: Group '{text3}' auto-disbanded ({value2.MemberRecipeKeys.Count} members)");
				PinGroups.Remove(text3);
				PinnedRecipeOrder.Remove("GROUP:" + text3);
			}
		}
		else
		{
			value2.MemberCounts[recipeKey] = num2;
			DebugLogger.Log($"AutoUnpin: '{recipeKey}' claim decremented in group '{text3}': {num}->{num2}");
		}
	}

	public void RemoveMemberFromGroup(string groupName, string recipeKey)
	{
		if (!PinGroups.TryGetValue(groupName, out var value))
		{
			DebugLogger.Warning("RemoveMemberFromGroup: group '" + groupName + "' not found");
			return;
		}
		if (!value.MemberRecipeKeys.Remove(recipeKey))
		{
			DebugLogger.Warning("RemoveMemberFromGroup: '" + recipeKey + "' not in group '" + groupName + "'");
			return;
		}
		int value2;
		int num = ((!value.MemberCounts.TryGetValue(recipeKey, out value2)) ? 1 : value2);
		value.MemberCounts.Remove(recipeKey);
		DebugLogger.Log($"Removed '{recipeKey}' from group '{groupName}' (claim={num}, {value.MemberRecipeKeys.Count} remaining)");
		if (PinnedRecipes.TryGetValue(recipeKey, out var value3))
		{
			int num2 = value3 - num;
			if (num2 <= 0)
			{
				PinnedRecipes.Remove(recipeKey);
				PinnedRecipeOrder.Remove(recipeKey);
			}
			else
			{
				PinnedRecipes[recipeKey] = num2;
			}
		}
		if (value.MemberRecipeKeys.Count < 2)
		{
			DebugLogger.Log($"Group '{groupName}' has {value.MemberRecipeKeys.Count} member(s), auto-disbanding");
			PinGroups.Remove(groupName);
			PinnedRecipeOrder.Remove("GROUP:" + groupName);
		}
		RefreshRecipeCache();
		RecipePinnerPlugin.Instance?.DataMgr.SavePins();
		if (GetEffectivePinCount() < 2)
		{
			RecipePinnerPlugin.Instance?.UIMgr.CloseGatheringList();
		}
	}

	public void RemovePinFromMyPinsPanel(string key)
	{
		if (PinGroups.TryGetValue(key, out var value))
		{
			foreach (string memberRecipeKey in value.MemberRecipeKeys)
			{
				if (PinnedRecipes.TryGetValue(memberRecipeKey, out var value2))
				{
					int value3;
					int num = ((!value.MemberCounts.TryGetValue(memberRecipeKey, out value3)) ? 1 : value3);
					int num2 = value2 - num;
					if (num2 <= 0)
					{
						PinnedRecipes.Remove(memberRecipeKey);
						PinnedRecipeOrder.Remove(memberRecipeKey);
						DebugLogger.Verbose("Removed group member pin entirely: " + memberRecipeKey);
					}
					else
					{
						PinnedRecipes[memberRecipeKey] = num2;
						DebugLogger.Verbose($"Group member pin kept as individual: {memberRecipeKey} x{num2}");
					}
				}
			}
			PinGroups.Remove(key);
			PinnedRecipeOrder.Remove("GROUP:" + key);
			DebugLogger.Log("Removed group: " + key + " (member excess pins preserved)");
		}
		else
		{
			int groupClaimCount = GetGroupClaimCount(key);
			if (groupClaimCount > 0)
			{
				if (PinnedRecipes.TryGetValue(key, out var value4) && value4 > groupClaimCount)
				{
					PinnedRecipes[key] = groupClaimCount;
					DebugLogger.Log($"Removed individual excess for grouped pin: {key} (kept {groupClaimCount} for groups)");
				}
				else
				{
					DebugLogger.Log($"No individual excess to remove for: {key} (claims={groupClaimCount})");
				}
			}
			else
			{
				PinnedRecipes.Remove(key);
				PinnedRecipeOrder.Remove(key);
				DebugLogger.Log("Removed pin: " + key);
			}
		}
		RefreshRecipeCache();
		RecipePinnerPlugin.Instance?.DataMgr.SavePins();
		if (GetEffectivePinCount() < 2)
		{
			RecipePinnerPlugin.Instance?.UIMgr.CloseGatheringList();
		}
	}

	public void AdjustPinCount(string key, int delta, bool showMessage = true)
	{
		if (!PinnedRecipes.TryGetValue(key, out var value))
		{
			DebugLogger.Warning("AdjustPinCount: recipe '" + key + "' not found");
			return;
		}
		int groupClaimCount = GetGroupClaimCount(key);
		int num = ((groupClaimCount <= 0) ? 1 : (groupClaimCount + 1));
		value += delta;
		DebugLogger.Log($"AdjustPinCount: {key} -> {value} (min={num}, claims={groupClaimCount})");
		if (value < num)
		{
			value = num;
			DebugLogger.Log($"AdjustPinCount: clamped to minimum {num} for '{key}'");
			return;
		}
		PinnedRecipes[key] = value;
		if (showMessage)
		{
			LocalizationManager localizationManager = RecipePinnerPlugin.Instance?.LocalizationMgr;
			if (localizationManager != null)
			{
				int num2 = value - groupClaimCount;
				if (delta > 0)
				{
					string text = string.Format(localizationManager.GetText("added_more"), num2);
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null);
					}
				}
				else
				{
					string text2 = string.Format(localizationManager.GetText("decreased"), num2);
					Player localPlayer2 = Player.m_localPlayer;
					if (localPlayer2 != null)
					{
						((Character)localPlayer2).Message((MessageType)2, text2, 0, (Sprite)null);
					}
				}
			}
		}
		RefreshRecipeCache();
		RecipePinnerPlugin.Instance?.DataMgr.SavePins();
	}

	public string GetGroupContainingRecipe(string recipeKey)
	{
		foreach (KeyValuePair<string, PinGroupData> pinGroup in PinGroups)
		{
			if (pinGroup.Value.MemberRecipeKeys.Contains(recipeKey))
			{
				return pinGroup.Key;
			}
		}
		return null;
	}

	public int GetGroupClaimCount(string recipeKey)
	{
		int num = 0;
		foreach (PinGroupData value2 in PinGroups.Values)
		{
			if (value2.MemberCounts.TryGetValue(recipeKey, out var value))
			{
				num += value;
			}
			else if (value2.MemberRecipeKeys.Contains(recipeKey))
			{
				num++;
			}
		}
		return num;
	}

	public int TrimToMaximumPins(int maxEffectivePins)
	{
		int num = 0;
		while (GetEffectivePinCount() > maxEffectivePins)
		{
			bool flag = false;
			for (int num2 = PinnedRecipeOrder.Count - 1; num2 >= 0; num2--)
			{
				string text = PinnedRecipeOrder[num2];
				if (text.StartsWith("GROUP:"))
				{
					string key = text.Substring(6);
					PinnedRecipeOrder.RemoveAt(num2);
					if (PinGroups.TryGetValue(key, out var value))
					{
						foreach (string memberRecipeKey in value.MemberRecipeKeys)
						{
							if (PinnedRecipes.TryGetValue(memberRecipeKey, out var value2))
							{
								int value3;
								int num3 = ((!value.MemberCounts.TryGetValue(memberRecipeKey, out value3)) ? 1 : value3);
								int num4 = value2 - num3;
								if (num4 > 0)
								{
									PinnedRecipes[memberRecipeKey] = num4;
									continue;
								}
								PinnedRecipes.Remove(memberRecipeKey);
								PinnedRecipeOrder.Remove(memberRecipeKey);
							}
						}
						PinGroups.Remove(key);
					}
					num++;
					flag = true;
					break;
				}
				if (PinnedRecipes.TryGetValue(text, out var value4))
				{
					int groupClaimCount = GetGroupClaimCount(text);
					if (value4 > groupClaimCount)
					{
						if (groupClaimCount > 0)
						{
							PinnedRecipes[text] = groupClaimCount;
						}
						else
						{
							PinnedRecipes.Remove(text);
							PinnedRecipeOrder.RemoveAt(num2);
						}
						num++;
						flag = true;
						break;
					}
				}
			}
			if (!flag)
			{
				break;
			}
		}
		return num;
	}

	public int GetEffectivePinCount()
	{
		int num = PinGroups.Count;
		foreach (KeyValuePair<string, int> pinnedRecipe in PinnedRecipes)
		{
			int groupClaimCount = GetGroupClaimCount(pinnedRecipe.Key);
			if (pinnedRecipe.Value > groupClaimCount)
			{
				num++;
			}
		}
		return num;
	}
}
[BepInPlugin("com.Kadrio.RecipePinner", "Recipe Pinner", "1.3.0")]
public class RecipePinnerPlugin : BaseUnityPlugin
{
	public enum PinLayoutMode
	{
		AutoDetect,
		ForceVertical,
		ForceHorizontal,
		ForceBottomRightHorizontal
	}

	public class ConfigurationManagerAttributes
	{
		public bool? ShowRangeAsPercent;

		public Action<ConfigEntryBase> CustomDrawer;

		public bool? Browsable;

		public string Category;

		public object DefaultValue;

		public bool? HideDefaultButton;

		public bool? HideSettingName;

		public string Description;

		public string DispName;

		public int? Order;

		public bool? ReadOnly;

		public bool? IsAdvanced;

		public Func<object, string> ObjToStr;

		public Func<string, object> StrToObj;
	}

	public static RecipePinnerPlugin Instance;

	public LocalizationManager LocalizationMgr;

	public RecipeManager RecipeMgr;

	public ContainerScanner ContainerMgr;

	public UIManager UIMgr;

	public DataPersistence DataMgr;

	internal bool _mluiMapListEnabled;

	internal bool _mluiNoMapListEnabled;

	internal bool _mluiInstalled;

	private bool _startupInitialized;

	private string _lastLanguage = "";

	private string _currentSessionPlayer;

	private const float ClearAllConfirmWindow = 2f;

	private float _clearAllArmedUntil;

	private static bool _isUiVisible = true;

	public static ConfigEntry<bool> EnableMod;

	public static ConfigEntry<string> LanguageOverride;

	public static ConfigEntry<PinLayoutMode> LayoutModeConfig;

	public static ConfigEntry<int> MaximumPins;

	public static ConfigEntry<int> PinsPerPage;

	public static ConfigEntry<bool> AutoUnpinAfterCrafting;

	public static ConfigEntry<bool> AutoUnpinAfterBuilding;

	public static ConfigEntry<KeyCode> HotkeyPin;

	public static ConfigEntry<KeyCode> HotkeyUnpin;

	public static ConfigEntry<KeyCode> HotkeyClearAll;

	public static ConfigEntry<KeyCode> HotkeyToggleVisibility;

	public static ConfigEntry<KeyCode> HotkeyPageSwitch;

	public static ConfigEntry<KeyCode> HotkeyGatheringList;

	public static ConfigEntry<bool> EnableChestScanning;

	public static ConfigEntry<float> ChestScanRange;

	public static ConfigEntry<float> ChestScanInterval;

	public static ConfigEntry<float> UIScale;

	public static ConfigEntry<float> BackgroundOpacity;

	public static ConfigEntry<int> FontSizeRecipeName;

	public static ConfigEntry<int> FontSizeMaterials;

	public static ConfigEntry<int> HudRecipeIconSize;

	public static ConfigEntry<int> HudMaterialIconSize;

	public static ConfigEntry<int> HudGroupIconSize;

	public static ConfigEntry<bool> EnableCraftReadiness;

	public static ConfigEntry<Color> ColorHeader;

	public static ConfigEntry<Color> ColorEnoughInInventory;

	public static ConfigEntry<Color> ColorEnoughWithChests;

	public static ConfigEntry<Color> ColorMissing;

	public static ConfigEntry<Color> ColorCraftReady;

	public static ConfigEntry<Color> ColorCraftNotReady;

	public static ConfigEntry<Color> ColorPaginationActive;

	public static ConfigEntry<float> PaginationInactiveOpacity;

	public static ConfigEntry<int> PaginationDotSize;

	public static ConfigEntry<int> PaginationDotSpacing;

	public static ConfigEntry<bool> EnableGatheringList;

	public static ConfigEntry<bool> AutoOpenGatheringList;

	public static ConfigEntry<int> GatheringListColumns;

	public static ConfigEntry<int> GatheringListFontSizeTitle;

	public static ConfigEntry<int> GatheringListFontSizeMaterials;

	public static ConfigEntry<Vector2> ContainerGatheringListPosition;

	public static ConfigEntry<int> GroupCompactThreshold;

	public static ConfigEntry<int> GroupCompactMaxRows;

	public static ConfigEntry<int> GroupIconFontSize;

	public static ConfigEntry<float> MyPinsPanelWidth;

	public static ConfigEntry<float> MyPinsPanelHeight;

	public static ConfigEntry<Vector2> MyPinsPanelPosition;

	public static ConfigEntry<Color> ButtonTextColor;

	public static ConfigEntry<Vector2> MyPinsButtonPosition;

	public static ConfigEntry<int> MyPinsButtonSize;

	public static ConfigEntry<float> VerticalListWidth;

	public static ConfigEntry<float> VerticalPinSpacing;

	public static ConfigEntry<Vector2> VerticalPosition;

	public static ConfigEntry<float> HorizontalColumnWidth;

	public static ConfigEntry<float> HorizontalPinSpacing;

	public static ConfigEntry<Vector2> HorizontalPosition;

	public static ConfigEntry<float> BottomRightColumnWidth;

	public static ConfigEntry<float> BottomRightPinSpacing;

	public static ConfigEntry<Vector2> BottomRightPosition;

	public static ConfigEntry<bool> EnableDebugLogging;

	public static bool IsUiVisible => _isUiVisible;

	internal bool IsPinDataLoaded => _startupInitialized;

	public bool IsHorizontalMode
	{
		get
		{
			if (LayoutModeConfig.Value == PinLayoutMode.ForceBottomRightHorizontal)
			{
				return true;
			}
			if (LayoutModeConfig.Value == PinLayoutMode.ForceHorizontal)
			{
				return true;
			}
			if (LayoutModeConfig.Value == PinLayoutMode.ForceVertical)
			{
				return false;
			}
			if (!_mluiInstalled)
			{
				return false;
			}
			if (Game.m_noMap)
			{
				return _mluiNoMapListEnabled;
			}
			return _mluiMapListEnabled;
		}
	}

	private void Awake()
	{
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		BindConfigs();
		DebugLogger.Log("Plugin init");
		LocalizationMgr = new LocalizationManager(this);
		RecipeMgr = new RecipeManager();
		ContainerMgr = new ContainerScanner();
		UIMgr = new UIManager();
		DataMgr = new DataPersistence();
		DebugLogger.Log("Managers ready");
		Harmony val = new Harmony("com.Kadrio.RecipePinner");
		val.PatchAll(typeof(RecipePinnerPlugin));
		val.PatchAll(typeof(ContainerScanner));
		DebugLogger.Log("Patches applied");
	}

	private void Start()
	{
		DebugLogger.Log("Start()");
		LocalizationMgr.LoadTranslations();
		ReadMyLittleUIConfig();
		ContainerMgr.InitializeContainers();
		DebugLogger.Log("Start done");
	}

	private void OnDestroy()
	{
		DebugLogger.Log("OnDestroy");
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer != (Object)null && !string.IsNullOrEmpty(localPlayer.GetPlayerName()))
		{
			if (EnableMod == null || !EnableMod.Value)
			{
				DebugLogger.Verbose("OnDestroy save skipped - mod is disabled");
			}
			else if (!_startupInitialized)
			{
				DebugLogger.Verbose("OnDestroy save skipped - pin data not loaded yet");
			}
			else
			{
				DataMgr.SavePins();
			}
		}
		RecipeMgr.Cleanup();
	}

	private void OnApplicationFocus(bool hasFocus)
	{
		if (hasFocus)
		{
			UIMgr?.ResetMyPinsInputState("application focus restored");
		}
	}

	private void Update()
	{
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_0110: Unknown result type (might be due to invalid IL or missing references)
		//IL_0121: Unknown result type (might be due to invalid IL or missing references)
		//IL_0150: 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_01b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0203: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_041d: Unknown result type (might be due to invalid IL or missing references)
		if (!EnableMod.Value)
		{
			return;
		}
		ReflectionHelper.UpdateGuiScale();
		if (!_startupInitialized && (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_recipes.Count > 0)
		{
			DebugLogger.Log("First init");
			_lastLanguage = Localization.instance.GetSelectedLanguage();
			DataMgr.LoadPins();
			RecipeMgr.ValidateAndCleanPins();
			RecipeMgr.RefreshRecipeCache();
			_startupInitialized = true;
			DebugLogger.Log($"Init done - {RecipeMgr.PinnedRecipes.Count} pins loaded");
		}
		if (EnableChestScanning.Value && (Object)(object)Player.m_localPlayer != (Object)null && RecipeMgr.CachedPins.Count > 0)
		{
			ContainerMgr.UpdateScanning();
		}
		bool flag = (Input.GetKeyDown(HotkeyToggleVisibility.Value) || Input.GetKeyDown(HotkeyPin.Value) || Input.GetKeyDown(HotkeyClearAll.Value) || Input.GetKeyDown(HotkeyPageSwitch.Value) || Input.GetKeyDown(HotkeyGatheringList.Value)) && AreRecipePinnerHotkeysBlocked();
		if (Input.GetKeyDown(HotkeyToggleVisibility.Value) && !flag)
		{
			_isUiVisible = !_isUiVisible;
			DebugLogger.Log($"UI visibility toggled: {_isUiVisible}");
		}
		if ((Object)(object)Player.m_localPlayer != (Object)null)
		{
			UpdatePlayerSession();
		}
		bool flag2 = (Object)(object)Player.m_localPlayer != (Object)null && ((Character)Player.m_localPlayer).InPlaceMode();
		if (Input.GetKeyDown(HotkeyPin.Value) && !flag)
		{
			if ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible())
			{
				RecipeMgr.TryPinHoveredRecipe(InventoryGui.instance);
			}
			else if (flag2)
			{
				RecipeMgr.TryPinHoveredPiece();
			}
		}
		if (Input.GetKeyDown(HotkeyClearAll.Value) && !flag && (RecipeMgr.PinnedRecipes.Count > 0 || RecipeMgr.PinGroups.Count > 0))
		{
			if (Time.unscaledTime <= _clearAllArmedUntil)
			{
				_clearAllArmedUntil = 0f;
				int count = RecipeMgr.PinnedRecipes.Count;
				int count2 = RecipeMgr.PinGroups.Count;
				RecipeMgr.PinnedRecipes.Clear();
				RecipeMgr.PinnedRecipeOrder.Clear();
				RecipeMgr.PinGroups.Clear();
				_isUiVisible = true;
				RecipeMgr.RefreshRecipeCache();
				UIMgr.CloseGatheringList();
				UIMgr.RefreshMyPinsList();
				DataMgr.SavePins();
				Player localPlayer = Player.m_localPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, LocalizationMgr.GetText("cleared"), 0, (Sprite)null);
				}
				DebugLogger.Log($"Cleared {count} pinned recipes and {count2} groups");
			}
			else
			{
				_clearAllArmedUntil = Time.unscaledTime + 2f;
				Player localPlayer2 = Player.m_localPlayer;
				if (localPlayer2 != null)
				{
					((Character)localPlayer2).Message((MessageType)2, LocalizationMgr.GetText("clear_confirm_hotkey"), 0, (Sprite)null);
				}
				DebugLogger.Log("Clear-all armed - press again to confirm");
			}
		}
		if (Localization.instance != null)
		{
			string selectedLanguage = Localization.instance.GetSelectedLanguage();
			if (_lastLanguage != selectedLanguage)
			{
				DebugLogger.Log("Language changed from " + _lastLanguage + " to " + selectedLanguage);
				_lastLanguage = selectedLanguage;
				LocalizationMgr.LoadTranslations();
				if ((Object)(object)ObjectDB.instance != (Object)null)
				{
					RecipeMgr.RefreshRecipeCache();
				}
				UIMgr?.DestroyUI();
				UIMgr?.DestroyMyPinsUI();
			}
		}
		if (Input.GetKeyDown(HotkeyPageSwitch.Value) && _isUiVisible && !flag)
		{
			UIMgr?.CyclePage();
		}
		if (Input.GetKeyDown(HotkeyGatheringList.Value) && !flag && EnableGatheringList.Value)
		{
			UIMgr?.ToggleGatheringList();
		}
		if ((Object)(object)Player.m_localPlayer != (Object)null)
		{
			UIMgr?.UpdateMyPinsInventoryState();
		}
	}

	private bool AreRecipePinnerHotkeysBlocked()
	{
		if (InputHelper.IsInputBlocked())
		{
			return true;
		}
		if (UIMgr != null && UIMgr.IsMyPinsPanelOpen)
		{
			return true;
		}
		if (ControlsInfoPanel.IsOpen)
		{
			return true;
		}
		InventoryGui instance = InventoryGui.instance;
		if ((Object)(object)instance != (Object)null && InventoryGui.IsVisible() && ReflectionHelper.IsBlockingInventoryPanelOpen(instance))
		{
			return true;
		}
		return false;
	}

	private void UpdatePlayerSession()
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsDead())
		{
			UIMgr?.UpdateUI(isVisible: false);
			return;
		}
		string playerName = Player.m_localPlayer.GetPlayerName();
		if (string.IsNullOrEmpty(playerName))
		{
			return;
		}
		if (_currentSessionPlayer != playerName)
		{
			DebugLogger.Log("Player session changed from '" + _currentSessionPlayer + "' to '" + playerName + "'");
			RecipeMgr.PinnedRecipes.Clear();
			RecipeMgr.PinnedRecipeOrder.Clear();
			RecipeMgr.CachedPins.Clear();
			RecipeMgr.PinGroups.Clear();
			UIMgr.DestroyUI();
			UIMgr.DestroyMyPinsUI();
			_currentSessionPlayer = playerName;
			DataMgr.LoadPins();
			if ((Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_recipes.Count > 0)
			{
				RecipeMgr.ValidateAndCleanPins();
			}
			RecipeMgr.RefreshRecipeCache();
		}
		UIMgr.UpdateUI(_isUiVisible);
	}

	private void ReadMyLittleUIConfig()
	{
		if (!Chainloader.PluginInfos.ContainsKey("shudnal.MyLittleUI"))
		{
			_mluiInstalled = false;
			DebugLogger.Log("MyLittleUI not detected");
			return;
		}
		_mluiInstalled = true;
		_mluiMapListEnabled = true;
		_mluiNoMapListEnabled = true;
		string path = Path.Combine(Paths.ConfigPath, "shudnal.MyLittleUI.cfg");
		if (!File.Exists(path))
		{
			DebugLogger.Log("MyLittleUI installed but config not found");
			return;
		}
		try
		{
			string[] array = File.ReadAllLines(path);
			string text = "";
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text2 = array2[i].Trim();
				bool value;
				if (text2.StartsWith("[") && text2.EndsWith("]"))
				{
					text = text2;
				}
				else if (TryReadBoolConfigValue(text2, "Enable", out value))
				{
					if (text == "[Status effects - Map - List]")
					{
						_mluiMapListEnabled = value;
					}
					else if (text == "[Status effects - Nomap - List]")
					{
						_mluiNoMapListEnabled = value;
					}
				}
			}
			DebugLogger.Log($"MyLittleUI Config: MapList={_mluiMapListEnabled}, NoMapList={_mluiNoMapListEnabled}");
		}
		catch (Exception ex)
		{
			DebugLogger.Error("Error reading MyLittleUI config", ex);
		}
	}

	private static bool TryReadBoolConfigValue(string line, string key, out bool value)
	{
		value = false;
		int num = line.IndexOf('=');
		if (num <= 0)
		{
			return false;
		}
		if (!string.Equals(line.Substring(0, num).Trim(), key, StringComparison.OrdinalIgnoreCase))
		{
			return false;
		}
		string text = line.Substring(num + 1);
		int num2 = text.IndexOf('#');
		if (num2 >= 0)
		{
			text = text.Substring(0, num2);
		}
		return bool.TryParse(text.Trim(), out value);
	}

	[HarmonyPatch(typeof(Game), "SavePlayerProfile")]
	[HarmonyPostfix]
	public static void AutoSavePinsHook()
	{
		if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Instance == (Object)null))
		{
			if (EnableMod == null || !EnableMod.Value)
			{
				DebugLogger.Verbose("Auto-save skipped - mod is disabled");
				return;
			}
			if (!Instance.IsPinDataLoaded)
			{
				DebugLogger.Verbose("Auto-save skipped - pin data not loaded yet");
				return;
			}
			DebugLogger.Log("Auto-saving pins");
			Instance.DataMgr.SavePins();
		}
	}

	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	[HarmonyPostfix]
	public static void AutoUnpinHook(InventoryGui __instance)
	{
		if (!EnableMod.Value || !AutoUnpinAfterCrafting.Value || (Object)(object)Instance == (Object)null)
		{
			return;
		}
		Recipe craftRecipe = ReflectionHelper.GetCraftRecipe(__instance);
		if (!((Object)(object)craftRecipe != (Object)null))
		{
			return;
		}
		string text = null;
		if (!((Selectable)__instance.m_tabUpgrade).interactable)
		{
			ItemData craftUpgradeItem = ReflectionHelper.GetCraftUpgradeItem(__instance);
			if (craftUpgradeItem != null)
			{
				string name = ((Object)craftRecipe.m_item).name;
				int quality = craftUpgradeItem.m_quality;
				int num = quality + 1;
				text = $"{name} ★{num}";
				DebugLogger.Log($"Upgrade crafted: Unpinning target {text} (Base Level: {quality})");
			}
		}
		else
		{
			text = ((Object)craftRecipe).name;
		}
		if (text != null && Instance.RecipeMgr.PinnedRecipes.TryGetValue(text, out var value))
		{
			bool num2 = value > Instance.RecipeMgr.GetGroupClaimCount(text);
			value--;
			DebugLogger.Log($"Auto-unpin: {text}, remaining count: {value}");
			if (num2)
			{
				DebugLogger.Verbose("Auto-unpin: consumed ungrouped copy of '" + text + "', group claims untouched");
			}
			else
			{
				Instance.RecipeMgr.DecrementGroupMemberCounts(text);
			}
			if (value <= 0)
			{
				Instance.RecipeMgr.PinnedRecipes.Remove(text);
				Instance.RecipeMgr.PinnedRecipeOrder.Remove(text);
				DebugLogger.Log("Recipe " + text + " fully unpinned");
			}
			else
			{
				Instance.RecipeMgr.PinnedRecipes[text] = value;
			}
			Instance.RecipeMgr.RefreshRecipeCache();
			Instance.DataMgr.SavePins();
			if (Instance.RecipeMgr.GetEffectivePinCount() < 2)
			{
				Instance.UIMgr.CloseGatheringList();
			}
		}
		else if (text != null)
		{
			string text2 = string.Join(", ", Instance.RecipeMgr.PinnedRecipes.Keys);
			DebugLogger.Verbose("Auto-unpin: '" + text + "' not found in PinnedRecipes. Current keys: [" + text2 + "]");
		}
	}

	[HarmonyPatch(typeof(Player), "PlacePiece")]
	[HarmonyPostfix]
	public static void AutoUnpinBuildHook(Piece piece)
	{
		DebugLogger.Verbose("AutoUnpinBuildHook fired (PlacePiece postfix)");
		if ((Object)(object)Instance == (Object)null || !EnableMod.Value || !AutoUnpinAfterBuilding.Value)
		{
			DebugLogger.Verbose($"AutoUnpinBuildHook early exit: Instance={(Object)(object)Instance != (Object)null}, EnableMod={EnableMod?.Value}, AutoUnpin={AutoUnpinAfterBuilding?.Value}");
			return;
		}
		if ((Object)(object)piece == (Object)null)
		{
			DebugLogger.Verbose("AutoUnpinBuildHook: piece is null");
			return;
		}
		string text = ((Object)piece).name.Replace("(Clone)", "").Trim();
		if (!Instance.RecipeMgr.PinnedRecipes.TryGetValue(text, out var value))
		{
			DebugLogger.Verbose("AutoUnpinBuildHook: '" + text + "' not pinned");
			return;
		}
		bool num = value > Instance.RecipeMgr.GetGroupClaimCount(text);
		value--;
		DebugLogger.Log($"Auto-unpin (Build): {text}, remaining count: {value}");
		if (num)
		{
			DebugLogger.Verbose("Auto-unpin (Build): consumed ungrouped copy of '" + text + "', group claims untouched");
		}
		else
		{
			Instance.RecipeMgr.DecrementGroupMemberCounts(text);
		}
		if (value <= 0)
		{
			Instance.RecipeMgr.PinnedRecipes.Remove(text);
			Instance.RecipeMgr.PinnedRecipeOrder.Remove(text);
			DebugLogger.Log("Build recipe " + text + " fully unpinned");
		}
		else
		{
			Instance.RecipeMgr.PinnedRecipes[text] = value;
		}
		Instance.RecipeMgr.RefreshRecipeCache();
		Instance.DataMgr.SavePins();
		if (Instance.RecipeMgr.GetEffectivePinCount() < 2)
		{
			Instance.UIMgr.CloseGatheringList();
		}
	}

	[HarmonyPatch(typeof(Player), "TakeInput")]
	[HarmonyPrefix]
	public static bool Player_TakeInput_BlockDuringDialog()
	{
		if (GroupNameDialog.IsDialogOpen || ConfirmDialog.IsDialogOpen)
		{
			return false;
		}
		if (Instance?.UIMgr != null && Instance.UIMgr.IsMyPinsPanelOpen)
		{
			return false;
		}
		return true;
	}

	[HarmonyPatch(typeof(InventoryGui), "Hide")]
	[HarmonyPrefix]
	public static bool InventoryGui_Hide_BlockDuringDialog()
	{
		if (GroupNameDialog.IsDialogOpen)
		{
			DebugLogger.Verbose("InventoryGui.Hide blocked - GroupNameDialog is open");
			return false;
		}
		if (ConfirmDialog.IsDialogOpen)
		{
			DebugLogger.Verbose("InventoryGui.Hide blocked - ConfirmDialog is open");
			return false;
		}
		if (ControlsInfoPanel.IsOpen)
		{
			ControlsInfoPanel.Instance?.Hide();
			DebugLogger.Log("InventoryGui.Hide intercepted (ESC) - closing ControlsInfoPanel only");
			return false;
		}
		if (Instance?.UIMgr != null && Instance.UIMgr.IsMyPinsPanelOpen)
		{
			bool keyDown = Input.GetKeyDown((KeyCode)27);
			Instance.UIMgr.ToggleMyPinsPanel();
			if (keyDown)
			{
				DebugLogger.Log("InventoryGui.Hide intercepted (ESC) - closing My Pins panel only");
				return false;
			}
			DebugLogger.Log("InventoryGui.Hide intercepted (Tab) - closing My Pins panel + inventory");
			return true;
		}
		return true;
	}

	private void BindConfigs()
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Expected O, but got Unknown
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Expected O, but got Unknown
		//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Expected O, but got Unknown
		//IL_0143: Unknown result type (might be due to invalid IL or missing references)
		//IL_014d: Expected O, but got Unknown
		//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ab: Expected O, but got Unknown
		//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0202: Expected O, but got Unknown
		//IL_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Expected O, but got Unknown
		//IL_027e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0288: Expected O, but got Unknown
		//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cd: Expected O, but got Unknown
		//IL_0308: Unknown result type (might be due to invalid IL or missing references)
		//IL_0312: Expected O, but got Unknown
		//IL_034d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0357: Expected O, but got Unknown
		//IL_0392: Unknown result type (might be due to invalid IL or missing references)
		//IL_039c: Expected O, but got Unknown
		//IL_03d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_03de: Expected O, but got Unknown
		//IL_0415: Unknown result type (might be due to invalid IL or missing references)
		//IL_041f: Expected O, but got Unknown
		//IL_047e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0488: Expected O, but got Unknown
		//IL_04d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_04db: Expected O, but got Unknown
		//IL_0524: Unknown result type (might be due to invalid IL or missing references)
		//IL_052e: Expected O, but got Unknown
		//IL_058d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0597: Expected O, but got Unknown
		//IL_05d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_05e0: Expected O, but got Unknown
		//IL_0635: Unknown result type (might be due to invalid IL or missing references)
		//IL_063f: Expected O, but got Unknown
		//IL_0695: Unknown result type (might be due to invalid IL or missing references)
		//IL_069f: Expected O, but got Unknown
		//IL_06f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_06ff: Expected O, but got Unknown
		//IL_0755: Unknown result type (might be due to invalid IL or missing references)
		//IL_075f: Expected O, but got Unknown
		//IL_07ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_07b6: Expected O, but got Unknown
		//IL_07f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_081b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0825: Expected O, but got Unknown
		//IL_0864: Unknown result type (might be due to invalid IL or missing references)
		//IL_088a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0894: Expected O, but got Unknown
		//IL_08d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_08f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0903: Expected O, but got Unknown
		//IL_0942: Unknown result type (might be due to invalid IL or missing references)
		//IL_0968: Unknown result type (might be due to invalid IL or missing references)
		//IL_0972: Expected O, but got Unknown
		//IL_09b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_09d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_09e1: Expected O, but got Unknown
		//IL_0a20: Unknown result type (might be due to invalid IL or missing references)
		//IL_0a46: Unknown result type (might be due to invalid IL or missing references)
		//IL_0a50: Expected O, but got Unknown
		//IL_0a8f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0ab5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0abf: Expected O, but got Unknown
		//IL_0b1e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0b28: Expected O, but got Unknown
		//IL_0b7d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0b87: Expected O, but got Unknown
		//IL_0bdb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0be5: Expected O, but got Unknown
		//IL_0c32: Unknown result type (might be due to invalid IL or missing references)
		//IL_0c3c: Expected O, but got Unknown
		//IL_0c89: Unknown result type (might be due to invalid IL or missing references)
		//IL_0c93: Expected O, but got Unknown
		//IL_0cd1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0cdb: Expected O, but got Unknown
		//IL_0d30: Unknown result type (might be due to invalid IL or missing references)
		//IL_0d3a: Expected O, but got Unknown
		//IL_0d8f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0d99: Expected O, but got Unknown
		//IL_0dce: Unknown result type (might be due to invalid IL or missing references)
		//IL_0df4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0dfe: Expected O, but got Unknown
		//IL_0e3c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0e46: Expected O, but got Unknown
		//IL_0e9a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0ea4: Expected O, but got Unknown
		//IL_0ef9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0f03: Expected O, but got Unknown
		//IL_0f62: Unknown result type (might be due to invalid IL or missing references)
		//IL_0f6c: Expected O, but got Unknown
		//IL_0fcb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0fd5: Expected O, but got Unknown
		//IL_1000: Unknown result type (might be due to invalid IL or missing references)
		//IL_1026: Unknown result type (might be due to invalid IL or missing references)
		//IL_1030: Expected O, but got Unknown
		//IL_106f: Unknown result type (might be due to invalid IL or missing references)
		//IL_1095: Unknown result type (might be due to invalid IL or missing references)
		//IL_109f: Expected O, but got Unknown
		//IL_10d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_10fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_1104: Expected O, but got Unknown
		//IL_115d: Unknown result type (might be due to invalid IL or missing references)
		//IL_1167: Expected O, but got Unknown
		//IL_11b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_11c2: Expected O, but got Unknown
		//IL_11fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_1207: Expected O, but got Unknown
		//IL_1226: Unknown result type (might be due to invalid IL or missing references)
		//IL_124c: Unknown result type (might be due to invalid IL or missing references)
		//IL_1256: Expected O, but got Unknown
		//IL_1291: Unknown result type (might be due to invalid IL or missing references)
		//IL_129b: Expected O, but got Unknown
		//IL_12d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_12e0: Expected O, but got Unknown
		//IL_12ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_1325: Unknown result type (might be due to invalid IL or missing references)
		//IL_132f: Expected O, but got Unknown
		//IL_136a: Unknown result type (might be due to invalid IL or missing references)
		//IL_1374: Expected O, but got Unknown
		//IL_13af: Unknown result type (might be due to invalid IL or missing references)
		//IL_13b9: Expected O, but got Unknown
		//IL_13d8: Unkn