Decompiled source of RecipePinner v1.5.1

plugins/RecipePinner.dll

Decompiled 5 days 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.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Kadrio")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Pin crafting recipes and building pieces to your Valheim HUD.")]
[assembly: AssemblyFileVersion("1.5.1.0")]
[assembly: AssemblyInformationalVersion("1.5.1+1ce90562bb720c66ecba93bd0b9768897b010ef5")]
[assembly: AssemblyProduct("Recipe Pinner")]
[assembly: AssemblyTitle("RecipePinner")]
[assembly: AssemblyVersion("1.5.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ValheimRecipePinner
{
	public class ConfirmDialog : MonoBehaviour
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__PlayButtonSFX;
		}

		public RectTransform DialogRect;

		public Image BgImage;

		public Image OverlayBg;

		public Text MessageText;

		public Button ConfirmButton;

		public Button CancelButton;

		public Action OnConfirm;

		public Action OnCancel;

		public static bool IsDialogOpen;

		private bool _listenersWired;

		public void SetActive(bool active)
		{
			((Component)this).gameObject.SetActive(active);
			IsDialogOpen = active;
		}

		public void Show(string message, Action onConfirm, Action onCancel = null)
		{
			OnConfirm = onConfirm;
			OnCancel = onCancel;
			if ((Object)(object)MessageText != (Object)null)
			{
				MessageText.text = message;
			}
			WireButtonListeners();
			((Component)this).transform.SetAsLastSibling();
			SetActive(active: true);
			DebugLogger.Log("ConfirmDialog shown: " + message);
		}

		private void WireButtonListeners()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			if (_listenersWired)
			{
				return;
			}
			if ((Object)(object)ConfirmButton != (Object)null)
			{
				((UnityEventBase)ConfirmButton.onClick).RemoveAllListeners();
				((UnityEvent)ConfirmButton.onClick).AddListener(new UnityAction(OnConfirmClicked));
				ButtonClickedEvent onClick = ConfirmButton.onClick;
				object obj = <>O.<0>__PlayButtonSFX;
				if (obj == null)
				{
					UnityAction val = UIBuilder.PlayButtonSFX;
					<>O.<0>__PlayButtonSFX = val;
					obj = (object)val;
				}
				((UnityEvent)onClick).AddListener((UnityAction)obj);
			}
			if ((Object)(object)CancelButton != (Object)null)
			{
				((UnityEventBase)CancelButton.onClick).RemoveAllListeners();
				((UnityEvent)CancelButton.onClick).AddListener(new UnityAction(OnCancelClicked));
				ButtonClickedEvent onClick2 = CancelButton.onClick;
				object obj2 = <>O.<0>__PlayButtonSFX;
				if (obj2 == null)
				{
					UnityAction val2 = UIBuilder.PlayButtonSFX;
					<>O.<0>__PlayButtonSFX = val2;
					obj2 = (object)val2;
				}
				((UnityEvent)onClick2).AddListener((UnityAction)obj2);
			}
			_listenersWired = true;
		}

		private void OnConfirmClicked()
		{
			if (IsDialogOpen && ((Component)this).gameObject.activeSelf)
			{
				DebugLogger.Log("ConfirmDialog: Confirm clicked");
				SetActive(active: false);
				OnConfirm?.Invoke();
			}
		}

		private void OnCancelClicked()
		{
			if (IsDialogOpen && ((Component)this).gameObject.activeSelf)
			{
				DebugLogger.Log("ConfirmDialog: Cancel clicked");
				SetActive(active: false);
				OnCancel?.Invoke();
			}
		}

		private void Update()
		{
			if (!((Component)this).gameObject.activeSelf)
			{
				return;
			}
			bool keyDown = Input.GetKeyDown((KeyCode)27);
			bool flag = Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271);
			try
			{
				if (ZInput.instance != null)
				{
					ZInput.ResetButtonStatus("Use");
					ZInput.ResetButtonStatus("Attack");
					ZInput.ResetButtonStatus("SecondAttack");
					ZInput.ResetButtonStatus("Block");
					ZInput.ResetButtonStatus("Inventory");
					ZInput.ResetButtonStatus("Hide");
				}
			}
			catch (Exception)
			{
			}
			if (keyDown)
			{
				OnCancelClicked();
			}
			else if (flag)
			{
				OnConfirmClicked();
			}
		}

		private void OnDestroy()
		{
			if (IsDialogOpen)
			{
				IsDialogOpen = false;
				DebugLogger.Warning("ConfirmDialog: Force-closed on destroy");
			}
		}
	}
	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 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)
			{
				_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 ControlsInfoPanel : MonoBehaviour
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__PlayButtonSFX;
		}

		public Button CloseButton;

		public Transform RowsParent;

		public Button InfoButton;

		private Font _font;

		private Sprite _badgeSprite;

		private bool _listenersWired;

		public static ControlsInfoPanel Instance { get; private set; }

		public static bool IsOpen { get; private set; }

		private void Awake()
		{
			Instance = this;
		}

		private void OnDisable()
		{
			IsOpen = false;
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
			IsOpen = false;
		}

		public void Initialize(Font font, Sprite badgeSprite)
		{
			_font = font;
			_badgeSprite = badgeSprite;
			WireListeners();
		}

		public void Show()
		{
			((Component)this).gameObject.SetActive(true);
			RefreshContent();
			if ((Object)(object)RowsParent != (Object)null)
			{
				LayoutRebuilder.ForceRebuildLayoutImmediate(((Component)this).GetComponent<RectTransform>());
			}
			IsOpen = true;
			if ((Object)(object)InfoButton != (Object)null)
			{
				((Component)InfoButton).gameObject.SetActive(false);
			}
			DebugLogger.Log("ControlsInfoPanel: opened");
		}

		public void Hide()
		{
			((Component)this).gameObject.SetActive(false);
			IsOpen = false;
			if ((Object)(object)InfoButton != (Object)null)
			{
				((Component)InfoButton).gameObject.SetActive(true);
			}
			DebugLogger.Log("ControlsInfoPanel: closed");
		}

		private void WireListeners()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			if (_listenersWired)
			{
				return;
			}
			if ((Object)(object)CloseButton != (Object)null)
			{
				((UnityEventBase)CloseButton.onClick).RemoveAllListeners();
				((UnityEvent)CloseButton.onClick).AddListener(new UnityAction(Hide));
				ButtonClickedEvent onClick = CloseButton.onClick;
				object obj = <>O.<0>__PlayButtonSFX;
				if (obj == null)
				{
					UnityAction val = UIBuilder.PlayButtonSFX;
					<>O.<0>__PlayButtonSFX = val;
					obj = (object)val;
				}
				((UnityEvent)onClick).AddListener((UnityAction)obj);
			}
			_listenersWired = true;
		}

		private void RefreshContent()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: 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_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)RowsParent == (Object)null)
			{
				return;
			}
			foreach (Transform item in RowsParent)
			{
				Object.Destroy((Object)(object)((Component)item).gameObject);
			}
			LocalizationManager localizationManager = RecipePinnerPlugin.Instance?.LocalizationMgr;
			ConfigEntry<KeyCode> hotkeyPin = RecipePinnerPlugin.HotkeyPin;
			string text = FormatKey((KeyCode)((hotkeyPin == null) ? 325 : ((int)hotkeyPin.Value)));
			ConfigEntry<KeyCode> hotkeyUnpin = RecipePinnerPlugin.HotkeyUnpin;
			string text2 = FormatKey((KeyCode)((hotkeyUnpin == null) ? 304 : ((int)hotkeyUnpin.Value)));
			ConfigEntry<KeyCode> hotkeyToggleVisibility = RecipePinnerPlugin.HotkeyToggleVisibility;
			string text3 = FormatKey((KeyCode)((hotkeyToggleVisibility == null) ? 288 : ((int)hotkeyToggleVisibility.Value)));
			ConfigEntry<KeyCode> hotkeyGatheringList = RecipePinnerPlugin.HotkeyGatheringList;
			string text4 = FormatKey((KeyCode)((hotkeyGatheringList == null) ? 289 : ((int)hotkeyGatheringList.Value)));
			ConfigEntry<KeyCode> hotkeyPageSwitch = RecipePinnerPlugin.HotkeyPageSwitch;
			string text5 = FormatKey((KeyCode)((hotkeyPageSwitch == null) ? 308 : ((int)hotkeyPageSwitch.Value)));
			ConfigEntry<KeyCode> hotkeyClearAll = RecipePinnerPlugin.HotkeyClearAll;
			string text6 = FormatKey((KeyCode)((hotkeyClearAll == null) ? 112 : ((int)hotkeyClearAll.Value)));
			CreateSectionHeader(localizationManager?.GetText("howto_header") ?? "HOW TO USE");
			CreateInstructionRow(string.Format(localizationManager?.GetText("howto_pin") ?? "Hover over a recipe in the crafting menu and press [{0}] to pin it.", text));
			CreateInstructionRow(string.Format(localizationManager?.GetText("howto_unpin") ?? "Hold [{0}] and press [{1}] to unpin a recipe.", text2, text));
			CreateInstructionRow(string.Format(localizationManager?.GetText("howto_toggle_hud") ?? "Press [{0}] to show or hide the pinned recipe overlay.", text3));
			CreateInstructionRow(string.Format(localizationManager?.GetText("howto_gathering") ?? "Press [{0}] to open or close the gathering list.", text4));
			CreateInstructionRow(string.Format(localizationManager?.GetText("howto_next_page") ?? "Press [{0}] to cycle through HUD pages.", text5));
			CreateInstructionRow(string.Format(localizationManager?.GetText("howto_clear_all") ?? "Press [{0}] to remove all pinned recipes.", text6));
			CreateSectionHeader(localizationManager?.GetText("keybindings_header") ?? "KEY BINDINGS");
			CreateRow(localizationManager?.GetText("ctrl_pin") ?? "Pin Recipe", text);
			CreateRow(localizationManager?.GetText("ctrl_unpin") ?? "Unpin  (hold + Pin key)", text2);
			CreateRow(localizationManager?.GetText("ctrl_toggle_hud") ?? "Toggle HUD Visibility", text3);
			CreateRow(localizationManager?.GetText("ctrl_gathering") ?? "Toggle Gathering List", text4);
			CreateRow(localizationManager?.GetText("ctrl_next_page") ?? "Next HUD Page", text5);
			CreateRow(localizationManager?.GetText("ctrl_clear_all") ?? "Clear All Pins", text6);
		}

		private void CreateSectionHeader(string text)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_font == (Object)null))
			{
				GameObject val = new GameObject("SectionHeader", new Type[1] { typeof(RectTransform) })
				{
					layer = 5
				};
				val.transform.SetParent(RowsParent, false);
				Text obj = val.AddComponent<Text>();
				obj.text = text;
				obj.font = _font;
				obj.fontSize = 15;
				obj.fontStyle = (FontStyle)1;
				obj.alignment = (TextAnchor)3;
				((Graphic)obj).color = new Color(1f, 0.718f, 0.357f, 1f);
				((Graphic)obj).raycastTarget = false;
				LayoutElement obj2 = val.AddComponent<LayoutElement>();
				obj2.flexibleWidth = 1f;
				obj2.minHeight = 22f;
				obj2.preferredHeight = 22f;
			}
		}

		private void CreateInstructionRow(string text)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_font == (Object)null))
			{
				GameObject val = new GameObject("InstructionRow", new Type[1] { typeof(RectTransform) })
				{
					layer = 5
				};
				val.transform.SetParent(RowsParent, false);
				Text obj = val.AddComponent<Text>();
				obj.text = "• " + text;
				obj.font = _font;
				obj.fontSize = 14;
				obj.fontStyle = (FontStyle)0;
				obj.alignment = (TextAnchor)0;
				((Graphic)obj).color = new Color(0.85f, 0.82f, 0.7f, 1f);
				((Graphic)obj).raycastTarget = false;
				obj.horizontalOverflow = (HorizontalWrapMode)0;
				obj.verticalOverflow = (VerticalWrapMode)1;
				LayoutElement obj2 = val.AddComponent<LayoutElement>();
				obj2.flexibleWidth = 1f;
				obj2.minHeight = 18f;
				obj2.preferredHeight = 42f;
			}
		}

		private void CreateRow(string label, string keyText)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Expected O, but got Unknown
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: 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)
			if (!((Object)(object)_font == (Object)null))
			{
				GameObject val = new GameObject("ControlRow", new Type[1] { typeof(RectTransform) })
				{
					layer = 5
				};
				val.transform.SetParent(RowsParent, false);
				HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
				((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
				((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
				((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
				((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
				((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f;
				((LayoutGroup)obj).padding = new RectOffset(0, 0, 2, 2);
				LayoutElement obj2 = val.AddComponent<LayoutElement>();
				obj2.flexibleWidth = 1f;
				obj2.minHeight = 26f;
				GameObject val2 = new GameObject("Label", new Type[1] { typeof(RectTransform) })
				{
					layer = 5
				};
				val2.transform.SetParent(val.transform, false);
				Text obj3 = val2.AddComponent<Text>();
				obj3.text = label;
				obj3.font = _font;
				obj3.fontSize = 15;
				((Graphic)obj3).color = new Color(0.88f, 0.84f, 0.72f, 1f);
				obj3.alignment = (TextAnchor)3;
				((Graphic)obj3).raycastTarget = false;
				val2.AddComponent<LayoutElement>().flexibleWidth = 1f;
				GameObject val3 = new GameObject("KeyBadge", new Type[1] { typeof(RectTransform) })
				{
					layer = 5
				};
				val3.transform.SetParent(val.transform, false);
				Image val4 = val3.AddComponent<Image>();
				((Graphic)val4).color = new Color(0.1f, 0.08f, 0.04f, 0.9f);
				((Graphic)val4).raycastTarget = false;
				if ((Object)(object)_badgeSprite != (Object)null)
				{
					val4.sprite = _badgeSprite;
					val4.type = (Type)((_badgeSprite.border != Vector4.zero) ? 1 : 0);
				}
				LayoutElement obj4 = val3.AddComponent<LayoutElement>();
				obj4.preferredWidth = 130f;
				obj4.flexibleWidth = 0f;
				obj4.minHeight = 22f;
				GameObject val5 = new GameObject("KeyText", new Type[1] { typeof(RectTransform) })
				{
					layer = 5
				};
				val5.transform.SetParent(val3.transform, false);
				RectTransform component = val5.GetComponent<RectTransform>();
				component.anchorMin = Vector2.zero;
				component.anchorMax = Vector2.one;
				component.offsetMin = new Vector2(4f, 2f);
				component.offsetMax = new Vector2(-4f, -2f);
				Text obj5 = val5.AddComponent<Text>();
				obj5.text = keyText;
				obj5.font = _font;
				obj5.fontSize = 13;
				obj5.fontStyle = (FontStyle)1;
				((Graphic)obj5).color = new Color(1f, 0.78f, 0.38f, 1f);
				obj5.alignment = (TextAnchor)4;
				((Graphic)obj5).raycastTarget = false;
			}
		}

		public unsafe static string FormatKey(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected I4, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Invalid comparison between Unknown and I4
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected I4, but got Unknown
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Invalid comparison between Unknown and I4
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Invalid comparison between Unknown and I4
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Expected I4, but got Unknown
			if ((int)key <= 27)
			{
				if ((int)key <= 9)
				{
					if ((int)key == 8)
					{
						return "Backspace";
					}
					if ((int)key == 9)
					{
						return "Tab";
					}
				}
				else
				{
					if ((int)key == 13)
					{
						return "Enter";
					}
					if ((int)key == 27)
					{
						return "Escape";
					}
				}
			}
			else if ((int)key <= 127)
			{
				if ((int)key == 32)
				{
					return "Space";
				}
				if ((int)key == 127)
				{
					return "Delete";
				}
			}
			else
			{
				switch (key - 256)
				{
				default:
					switch (key - 303)
					{
					case 1:
						return "Left Shift";
					case 0:
						return "Right Shift";
					case 5:
						return "Left Alt";
					case 4:
						return "Right Alt";
					case 3:
						return "Left Ctrl";
					case 2:
						return "Right Ctrl";
					}
					switch (key - 323)
					{
					case 0:
						return "Left Mouse";
					case 1:
						return "Right Mouse";
					case 2:
						return "Middle Mouse";
					case 3:
						return "Mouse 4";
					case 4:
						return "Mouse 5";
					}
					break;
				case 15:
					return "Numpad Enter";
				case 17:
					return "Up Arrow";
				case 18:
					return "Down Arrow";
				case 20:
					return "Left Arrow";
				case 19:
					return "Right Arrow";
				case 0:
					return "Numpad 0";
				case 1:
					return "Numpad 1";
				case 2:
					return "Numpad 2";
				case 3:
					return "Numpad 3";
				case 4:
					return "Numpad 4";
				case 5:
					return "Numpad 5";
				case 6:
					return "Numpad 6";
				case 7:
					return "Numpad 7";
				case 8:
					return "Numpad 8";
				case 9:
					return "Numpad 9";
				case 24:
					return "Page Up";
				case 25:
					return "Page Down";
				case 22:
					return "Home";
				case 23:
					return "End";
				case 21:
					return "Insert";
				case 10:
				case 11:
				case 12:
				case 13:
				case 14:
				case 16:
					break;
				}
			}
			return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString();
		}
	}
	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 GatheringItemUI : MonoBehaviour
	{
		public Image Icon;

		public Text AmountText;

		public int LastHave = int.MinValue;

		public int LastRequired = int.MinValue;

		public bool LastComplete;

		public void SetActive(bool active)
		{
			((Component)this).gameObject.SetActive(active);
		}
	}
	public class GatheringListUI : MonoBehaviour
	{
		public RectTransform PanelRect;

		public Image BgImage;

		public Transform ItemListRoot;

		public Text HintText;

		public List<GatheringItemUI> ItemSlots = new List<GatheringItemUI>();

		private Coroutine _layoutCoroutine;

		public void SetActive(bool active)
		{
			((Component)this).gameObject.SetActive(active);
		}

		public void RefreshLayout()
		{
			if (((Component)this).gameObject.activeInHierarchy)
			{
				if (_layoutCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_layoutCoroutine);
				}
				_layoutCoroutine = ((MonoBehaviour)this).StartCoroutine(FixLayout());
			}
		}

		private void OnDisable()
		{
			_layoutCoroutine = null;
		}

		private IEnumerator FixLayout()
		{
			yield return null;
			if ((Object)(object)ItemListRoot != (Object)null)
			{
				Transform itemListRoot = ItemListRoot;
				LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((itemListRoot is RectTransform) ? itemListRoot : null));
			}
			if ((Object)(object)PanelRect != (Object)null)
			{
				LayoutRebuilder.ForceRebuildLayoutImmediate(PanelRect);
			}
		}

		public void ApplyColumns(int configCols)
		{
			//IL_0056: 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)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ItemListRoot == (Object)null)
			{
				return;
			}
			GridLayoutGroup component = ((Component)ItemListRoot).GetComponent<GridLayoutGroup>();
			if (!((Object)(object)component == (Object)null))
			{
				VerticalLayoutGroup component2 = ((Component)this).GetComponent<VerticalLayoutGroup>();
				float num = (((Object)(object)component2 != (Object)null) ? ((float)(((LayoutGroup)component2).padding.left + ((LayoutGroup)component2).padding.right)) : 22f);
				float x = component.cellSize.x;
				float x2 = component.spacing.x;
				if (configCols > 0)
				{
					component.constraintCount = configCols;
					float num2 = (float)configCols * (x + x2) - x2 + num;
					PanelRect.sizeDelta = new Vector2(num2, PanelRect.sizeDelta.y);
				}
				else
				{
					float num3 = PanelRect.sizeDelta.x - num;
					int constraintCount = Mathf.Max(1, Mathf.FloorToInt((num3 + x2) / (x + x2)));
					component.constraintCount = constraintCount;
				}
			}
		}
	}
	public class GatheringItemData
	{
		public string ItemName;

		public Sprite Icon;

		public int TotalRequired;

		public int TotalHave;

		public bool IsComplete;

		public int Stamp;
	}
	public class GroupNameDialog : MonoBehaviour
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__PlayButtonSFX;
		}

		public RectTransform DialogRect;

		public Image BgImage;

		public Image OverlayBg;

		public InputField NameInput;

		public Button ConfirmButton;

		public Button CancelButton;

		public Func<string, bool> OnConfirm;

		public Action OnCancel;

		public static bool IsDialogOpen;

		private bool _inputLocked;

		private bool _listenersWired;

		public void SetActive(bool active)
		{
			if (active && (Object)(object)NameInput == (Object)null)
			{
				((Component)this).gameObject.SetActive(false);
				UnlockGameInput();
				IsDialogOpen = false;
				DebugLogger.Error("GroupNameDialog: NameInput is null, dialog closed to avoid input lock");
				return;
			}
			((Component)this).gameObject.SetActive(active);
			if (active)
			{
				WireButtonListeners();
				LockGameInput();
				ClearInput();
			}
			else
			{
				UnlockGameInput();
			}
		}

		private void WireButtonListeners()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			if (_listenersWired)
			{
				return;
			}
			if ((Object)(object)ConfirmButton != (Object)null)
			{
				((UnityEventBase)ConfirmButton.onClick).RemoveAllListeners();
				((UnityEvent)ConfirmButton.onClick).AddListener(new UnityAction(OnConfirmClicked));
				ButtonClickedEvent onClick = ConfirmButton.onClick;
				object obj = <>O.<0>__PlayButtonSFX;
				if (obj == null)
				{
					UnityAction val = UIBuilder.PlayButtonSFX;
					<>O.<0>__PlayButtonSFX = val;
					obj = (object)val;
				}
				((UnityEvent)onClick).AddListener((UnityAction)obj);
			}
			if ((Object)(object)CancelButton != (Object)null)
			{
				((UnityEventBase)CancelButton.onClick).RemoveAllListeners();
				((UnityEvent)CancelButton.onClick).AddListener(new UnityAction(OnCancelClicked));
				ButtonClickedEvent onClick2 = CancelButton.onClick;
				object obj2 = <>O.<0>__PlayButtonSFX;
				if (obj2 == null)
				{
					UnityAction val2 = UIBuilder.PlayButtonSFX;
					<>O.<0>__PlayButtonSFX = val2;
					obj2 = (object)val2;
				}
				((UnityEvent)onClick2).AddListener((UnityAction)obj2);
			}
			if ((Object)(object)NameInput != (Object)null)
			{
				((UnityEventBase)NameInput.onEndEdit).RemoveAllListeners();
				((UnityEvent<string>)(object)NameInput.onEndEdit).AddListener((UnityAction<string>)OnInputEndEdit);
			}
			_listenersWired = true;
		}

		private void OnInputEndEdit(string text)
		{
			if (IsDialogOpen && ((Component)this).gameObject.activeSelf && (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271)))
			{
				DebugLogger.Log("GroupNameDialog: Enter via onEndEdit");
				OnConfirmClicked();
			}
		}

		private void OnConfirmClicked()
		{
			if (!IsDialogOpen || !((Component)this).gameObject.activeSelf)
			{
				return;
			}
			InputField nameInput = NameInput;
			string text = ((nameInput == null) ? null : nameInput.text?.Trim());
			if (!string.IsNullOrEmpty(text))
			{
				DebugLogger.Log("GroupNameDialog: Confirm clicked with name '" + text + "'");
				Func<string, bool> onConfirm = OnConfirm;
				if (onConfirm == null || onConfirm(text))
				{
					SetActive(active: false);
				}
				else
				{
					ClearInput();
				}
			}
			else
			{
				string text2 = (RecipePinnerPlugin.Instance?.LocalizationMgr)?.GetText("group_name_empty") ?? "Group name cannot be empty";
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					((Character)Player.m_localPlayer).Message((MessageType)2, text2, 0, (Sprite)null, false);
				}
			}
		}

		private void OnCancelClicked()
		{
			if (IsDialogOpen && ((Component)this).gameObject.activeSelf)
			{
				DebugLogger.Log("GroupNameDialog: Cancel clicked");
				SetActive(active: false);
				OnCancel?.Invoke();
			}
		}

		private void ClearInput()
		{
			if ((Object)(object)NameInput != (Object)null)
			{
				NameInput.text = "";
				NameInput.ActivateInputField();
				((Selectable)NameInput).Select();
			}
		}

		private void LockGameInput()
		{
			if (!_inputLocked)
			{
				IsDialogOpen = true;
				_inputLocked = true;
				DebugLogger.Log("GroupNameDialog: Game input locked (Harmony patches active)");
			}
		}

		private void UnlockGameInput()
		{
			if (_inputLocked)
			{
				IsDialogOpen = false;
				_inputLocked = false;
				DebugLogger.Log("GroupNameDialog: Game input unlocked");
			}
		}

		private void OnDestroy()
		{
			if (_inputLocked)
			{
				UnlockGameInput();
				DebugLogger.Warning("GroupNameDialog: Force-unlocked input on destroy");
			}
		}

		private void Update()
		{
			if (!((Component)this).gameObject.activeSelf)
			{
				return;
			}
			bool keyDown = Input.GetKeyDown((KeyCode)27);
			bool flag = Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271);
			if (IsDialogOpen)
			{
				Input.ResetInputAxes();
				try
				{
					if (ZInput.instance != null)
					{
						ZInput.ResetButtonStatus("Forward");
						ZInput.ResetButtonStatus("Backward");
						ZInput.ResetButtonStatus("Left");
						ZInput.ResetButtonStatus("Right");
						ZInput.ResetButtonStatus("Jump");
						ZInput.ResetButtonStatus("Crouch");
						ZInput.ResetButtonStatus("Run");
						ZInput.ResetButtonStatus("Use");
						ZInput.ResetButtonStatus("Attack");
						ZInput.ResetButtonStatus("SecondAttack");
						ZInput.ResetButtonStatus("Block");
						ZInput.ResetButtonStatus("Inventory");
						ZInput.ResetButtonStatus("Hide");
						ZInput.ResetButtonStatus("Sit");
						ZInput.ResetButtonStatus("GPower");
						ZInput.ResetButtonStatus("Emote1");
						ZInput.ResetButtonStatus("Emote2");
					}
				}
				catch (Exception)
				{
				}
			}
			if (IsDialogOpen)
			{
				if (keyDown)
				{
					DebugLogger.Log("GroupNameDialog: Escape pressed, cancelling");
					OnCancelClicked();
				}
				else if (flag)
				{
					DebugLogger.Log("GroupNameDialog: Enter pressed, confirming");
					OnConfirmClicked();
				}
			}
		}
	}
	public static class KeyHintInjector
	{
		private const string PinEntryName = "RecipePinnerPin";

		private const string UnpinEntryName = "RecipePinnerUnpin";

		private static KeyHints _injectedInto;

		public static void EnsureInjected()
		{
			KeyHints instance = KeyHints.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)_injectedInto == (Object)(object)instance))
			{
				_injectedInto = instance;
				InjectInto(instance.m_inventoryHints);
				InjectInto(instance.m_inventoryWithContainerHints);
				InjectInto(instance.m_buildHints);
			}
		}

		public static void RefreshKeys()
		{
			KeyHints instance = KeyHints.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				RefreshBar(instance.m_inventoryHints);
				RefreshBar(instance.m_inventoryWithContainerHints);
				RefreshBar(instance.m_buildHints);
			}
		}

		public static void UpdateHintVisibility()
		{
			KeyHints instance = KeyHints.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				bool flag = HasHotkeyRemovablePin();
				bool flag2 = IsBuildMenuOpen();
				SetEntriesActive(instance.m_inventoryHints, pinVisible: true, flag);
				SetEntriesActive(instance.m_inventoryWithContainerHints, pinVisible: true, flag);
				SetEntriesActive(instance.m_buildHints, flag2, flag2 && flag);
			}
		}

		private static bool HasHotkeyRemovablePin()
		{
			RecipePinnerPlugin instance = RecipePinnerPlugin.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			RecipeManager recipeMgr = instance.RecipeMgr;
			if (recipeMgr == null)
			{
				return false;
			}
			foreach (PinnedRecipeData cachedPin in recipeMgr.CachedPins)
			{
				if (cachedPin != null && !cachedPin.IsGroup)
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsBuildMenuOpen()
		{
			return Hud.InBuildUi();
		}

		private static void SetEntriesActive(GameObject bar, bool pinVisible, bool unpinVisible)
		{
			if (!((Object)(object)bar == (Object)null))
			{
				Transform val = bar.transform.Find("Keyboard");
				if (!((Object)(object)val == (Object)null))
				{
					SetEntryActive(val.Find("RecipePinnerPin"), pinVisible);
					SetEntryActive(val.Find("RecipePinnerUnpin"), unpinVisible);
				}
			}
		}

		private static void SetEntryActive(Transform entry, bool visible)
		{
			if (!((Object)(object)entry == (Object)null) && ((Component)entry).gameObject.activeSelf != visible)
			{
				((Component)entry).gameObject.SetActive(visible);
			}
		}

		private static void InjectInto(GameObject bar)
		{
			//IL_0056: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)bar == (Object)null)
			{
				return;
			}
			Transform val = bar.transform.Find("Keyboard");
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Find("RecipePinnerPin") != (Object)null))
			{
				Transform val2 = FindTemplate(val, 1);
				if ((Object)(object)val2 != (Object)null)
				{
					AddEntry(val, val2, "RecipePinnerPin", "hint_pin", GetPinKey(), (KeyCode)0);
				}
				Transform val3 = FindTemplate(val, 2);
				if ((Object)(object)val3 != (Object)null)
				{
					AddEntry(val, val3, "RecipePinnerUnpin", "hint_unpin", GetUnpinKey(), GetPinKey());
				}
			}
		}

		private static void RefreshBar(GameObject bar)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)bar == (Object)null)
			{
				return;
			}
			Transform val = bar.transform.Find("Keyboard");
			if (!((Object)(object)val == (Object)null))
			{
				Transform val2 = val.Find("RecipePinnerPin");
				if ((Object)(object)val2 != (Object)null)
				{
					ApplyTexts(((Component)val2).gameObject, "hint_pin", GetPinKey(), (KeyCode)0);
				}
				Transform val3 = val.Find("RecipePinnerUnpin");
				if ((Object)(object)val3 != (Object)null)
				{
					ApplyTexts(((Component)val3).gameObject, "hint_unpin", GetUnpinKey(), GetPinKey());
				}
			}
		}

		private static Transform FindTemplate(Transform mode, int wantedKeys)
		{
			for (int i = 0; i < mode.childCount; i++)
			{
				Transform child = mode.GetChild(i);
				if (((Object)child).name == "RecipePinnerPin" || ((Object)child).name == "RecipePinnerUnpin")
				{
					continue;
				}
				int num = 0;
				bool flag = false;
				TMP_Text[] componentsInChildren = ((Component)child).GetComponentsInChildren<TMP_Text>(true);
				for (int j = 0; j < componentsInChildren.Length; j++)
				{
					string name = ((Object)((Component)componentsInChildren[j]).gameObject).name;
					if (name == "Key")
					{
						num++;
					}
					else if (name == "Text")
					{
						flag = true;
					}
				}
				if (flag && num == wantedKeys)
				{
					return child;
				}
			}
			return null;
		}

		private static void AddEntry(Transform parent, Transform template, string entryName, string labelKey, KeyCode firstKey, KeyCode secondKey)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, parent);
			((Object)val).name = entryName;
			if (!val.activeSelf)
			{
				val.SetActive(true);
			}
			ApplyTexts(val, labelKey, firstKey, secondKey);
			DebugLogger.Log("Added key hint '" + entryName + "' to '" + ((Object)parent.parent).name + "'");
		}

		private static void ApplyTexts(GameObject entry, string labelKey, KeyCode firstKey, KeyCode secondKey)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			string label = GetLabel(labelKey);
			int num = 0;
			TMP_Text[] componentsInChildren = entry.GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				string name = ((Object)((Component)val).gameObject).name;
				if (name == "Text")
				{
					val.text = label;
				}
				else if (name == "Key")
				{
					val.text = DescribeKey((num == 0) ? firstKey : secondKey);
					num++;
				}
			}
		}

		private unsafe static string DescribeKey(KeyCode code)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if ((int)code == 0)
			{
				return string.Empty;
			}
			string text = ZInput.KeyCodeToDisplayName(code);
			if (string.IsNullOrEmpty(text) || text.Contains("did not have corresponding"))
			{
				return ((object)(*(KeyCode*)(&code))/*cast due to .constrained prefix*/).ToString();
			}
			return text;
		}

		private static string GetLabel(string labelKey)
		{
			RecipePinnerPlugin instance = RecipePinnerPlugin.Instance;
			LocalizationManager localizationManager = (((Object)(object)instance == (Object)null) ? null : instance.LocalizationMgr);
			if (localizationManager == null)
			{
				return labelKey;
			}
			return localizationManager.GetText(labelKey);
		}

		private static KeyCode GetPinKey()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (RecipePinnerPlugin.HotkeyPin != null)
			{
				return RecipePinnerPlugin.HotkeyPin.Value;
			}
			return (KeyCode)325;
		}

		private static KeyCode GetUnpinKey()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (RecipePinnerPlugin.HotkeyUnpin != null)
			{
				return RecipePinnerPlugin.HotkeyUnpin.Value;
			}
			return (KeyCode)304;
		}
	}
	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" },
			{ "forge_route", "Idol" },
			{ "hint_pin", "Pin" },
			{ "hint_unpin", "Unpin" },
			{ "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_name_empty", "Group name cannot be empty" },
			{ "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" },
			{ "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?" },
			{ "close_button", "Close" },
			{ "controls_title", "CONTROLS" },
			{ "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 raw = array2[1].Trim(',', '"', ' ', '\t', '\r');
						raw = UnescapeValue(raw);
						if (!string.IsNullOrEmpty(text6) && !string.IsNullOrEmpty(raw))
						{
							_localizedText[text6] = raw;
							num++;
						}
					}
				}
				if (num < _defaultEnglish.Count / 2)
				{
					DebugLogger.Warning($"Only {num} of {_defaultEnglish.Count} translations were read from {text2}.json - the file format may not be supported (one \"key\": \"value\" pair per line is expected). The missing texts fall back to English.");
				}
				DebugLogger.Log($"Loaded {num} translations from: {text}.json");
			}
			catch (Exception ex)
			{
				DebugLogger.Error("Failed to load language file: " + text3, ex);
			}
		}

		private static string UnescapeValue(string raw)
		{
			if (string.IsNullOrEmpty(raw) || raw.IndexOf('\\') < 0)
			{
				return raw;
			}
			StringBuilder stringBuilder = new StringBuilder(raw.Length);
			for (int i = 0; i < raw.Length; i++)
			{
				char c = raw[i];
				if (c != '\\' || i + 1 >= raw.Length)
				{
					stringBuilder.Append(c);
					continue;
				}
				switch (raw[i + 1])
				{
				case '"':
					stringBuilder.Append('"');
					i++;
					break;
				case 'n':
					stringBuilder.Append('\n');
					i++;
					break;
				case 't':
					stringBuilder.Append('\t');
					i++;
					break;
				case '\\':
					stringBuilder.Append('\\');
					i++;
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}

		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 PinKey;

		public string RawName;

		public string CachedHeader;

		public Sprite Icon;

		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 SingleAmount;

		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 class MyPinMaterialUI : MonoBehaviour
	{
		public Image Icon;

		public Text AmountText;
	}
	public class MyPinItemUI : MonoBehaviour
	{
		public Image Icon;

		public Transform IconRoot;

		public Transform MaterialsRoot;

		public List<MyPinMaterialUI> MaterialCells = new List<MyPinMaterialUI>();

		public VerticalLayoutGroup RowLayout;

		public HorizontalLayoutGroup TopRowLayout;

		public LayoutElement RowHeight;

		public Text NameText;

		public Text CountText;

		public Button DeleteButton;

		public Button PlusButton;

		public Button MinusButton;

		public Button ExpandButton;

		public Text ExpandButtonText;

		public Button DisbandButton;

		public Toggle SelectToggle;

		public string RecipeKey;

		public bool IsGroupItem;

		public bool IsSubItem;

		public string ParentGroupName;

		public static float MaterialsStripHeight => RecipePinnerPlugin.MaterialStripHeight?.Value ?? 24f;

		public void SetActive(bool active)
		{
			((Component)this).gameObject.SetActive(active);
		}

		public void SetMaterialsVisible(bool visible)
		{
			if ((Object)(object)MaterialsRoot != (Object)null && ((Component)MaterialsRoot).gameObject.activeSelf != visible)
			{
				((Component)MaterialsRoot).gameObject.SetActive(visible);
			}
			if ((Object)(object)RowHeight != (Object)null)
			{
				RowHeight.minHeight = (IsSubItem ? 32f : 38f) + (visible ? MaterialsStripHeight : 0f);
			}
		}

		public void SetSubItemStyle(bool isSubItem)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			IsSubItem = isSubItem;
			if ((Object)(object)RowLayout != (Object)null)
			{
				((LayoutGroup)RowLayout).padding = (isSubItem ? new RectOffset(24, 6, 2, 2) : new RectOffset(6, 6, 4, 4));
			}
			LayoutElement component = ((Component)this).GetComponent<LayoutElement>();
			if ((Object)(object)component != (Object)null)
			{
				component.minHeight = (isSubItem ? 32 : 38);
			}
			if ((Object)(object)NameText != (Object)null)
			{
				NameText.fontSize = (isSubItem ? 13 : 15);
			}
			if ((Object)(object)CountText != (Object)null)
			{
				CountText.fontSize = (isSubItem ? 13 : 15);
			}
			Image component2 = ((Component)this).GetComponent<Image>();
			if ((Object)(object)component2 != (Object)null)
			{
				((Graphic)component2).color = (isSubItem ? new Color(0.15f, 0.15f, 0.15f, 0.3f) : new Color(0f, 0f, 0f, 0.25f));
			}
		}

		public void SetSelectionMode(bool selectionMode)
		{
			if ((Object)(object)SelectToggle != (Object)null)
			{
				((Component)SelectToggle).gameObject.SetActive(selectionMode && !IsGroupItem && !IsSubItem);
				if (!selectionMode)
				{
					SelectToggle.isOn = false;
				}
			}
			if ((Object)(object)DeleteButton != (Object)null && selectionMode)
			{
				((Component)DeleteButton).gameObject.SetActive(false);
			}
			if ((Object)(object)PlusButton != (Object)null && selectionMode)
			{
				((Component)PlusButton).gameObject.SetActive(false);
			}
			if ((Object)(object)MinusButton != (Object)null && selectionMode)
			{
				((Component)MinusButton).gameObject.SetActive(false);
			}
			if ((Object)(object)ExpandButton != (Object)null && IsGroupItem)
			{
				((Component)ExpandButton).gameObject.SetActive(!selectionMode);
			}
			if ((Object)(object)DisbandButton != (Object)null && IsGroupItem)
			{
				((Component)DisbandButton).gameObject.SetActive(!selectionMode);
			}
		}
	}
	public class MyPinsPanelUI : MonoBehaviour
	{
		public RectTransform PanelRect;

		public Image BgImage;

		public Transform PinListRoot;

		public Button GroupButton;

		public Button ConfirmButton;

		public Button CancelButton;

		public Button ClearButton;

		public Button CloseButton;

		public ControlsInfoPanel ControlsPanel;

		public Text EmptyText;

		public List<MyPinItemUI> PinItems = new List<MyPinItemUI>();

		private Coroutine _layoutCoroutine;

		public void SetActive(bool active)
		{
			((Component)this).gameObject.SetActive(active);
		}

		public void RefreshLayout()
		{
			if (((Component)this).gameObject.activeInHierarchy)
			{
				if (_layoutCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_layoutCoroutine);
				}
				_layoutCoroutine = ((MonoBehaviour)this).StartCoroutine(FixLayout());
			}
		}

		private void OnDisable()
		{
			_layoutCoroutine = null;
		}

		private IEnumerator FixLayout()
		{
			yield return null;
			if ((Object)(object)PinListRoot != (Object)null)
			{
				Transform pinListRoot = PinListRoot;
				LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((pinListRoot is RectTransform) ? pinListRoot : null));
			}
			if ((Object)(object)PanelRect != (Object)null)
			{
				LayoutRebuilder.ForceRebuildLayoutImmediate(PanelRect);
			}
		}
	}
	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+)(F)?$", 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();
				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),
					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 string BuildRecipeKey(Recipe r)
		{
			if ((Object)(object)r == (Object)null)
			{
				return null;
			}
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || instance.m_recipes == null)
			{
				return ((Object)r).name;
			}
			int num = 0;
			int num2 = -1;
			for (int i = 0; i < instance.m_recipes.Count; i++)
			{
				Recipe val = instance.m_recipes[i];
				if (!((Object)(object)val == (Object)null) && !(((Object)val).name != ((Object)r).name))
				{
					if ((Object)(object)val == (Object)(object)r)
					{
						num2 = num;
					}
					num++;
				}
			}
			if (num <= 1 || num2 < 0)
			{
				return ((Object)r).name;
			}
			return $"{((Object)r).name}#{num2}";
		}

		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;
			}
			int num = name.LastIndexOf('#');
			if (num > 0 && int.TryParse(name.Substring(num + 1), out var result))
			{
				string text = name.Substring(0, num);
				Recipe val = null;
				int num2 = 0;
				foreach (Recipe recipe2 in ObjectDB.instance.m_recipes)
				{
					if (!((Object)(object)recipe2 == (Object)null) && !(((Object)recipe2).name != text))
					{
						if ((Object)(object)val == (Object)null)
						{
							val = recipe2;
						}
						if (num2 == result)
						{
							DebugLogger.Verbose("Resolved colliding recipe key: " + name);
							return recipe2;
						}
						num2++;
					}
				}
				if ((Object)(object)val != (Object)null)
				{
					DebugLogger.Warning($"Recipe key '{name}' is out of range - only {num2} recipe(s) named '{text}'. Falling back to the first.");
					return val;
				}
			}
			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 result2))
				{
					DebugLogger.Warning("Invalid upgrade level in recipe key: " + name);
					return null;
				}
				Recipe recipeByName = GetRecipeByName(name2);
				if ((Object)(object)recipeByName != (Object)null)
				{
					if (!IsValidUpgradeTarget(recipeByName, result2, name))
					{
						return null;
					}
					Recipe val2 = CreateFakeUpgradeRecipe(recipeByName, result2, name);
					if ((Object)(object)val2 != (Object)null)
					{
						return val2;
					}
				}
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name);
			ItemDrop val3 = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val3 != (Object)null)
			{
				Recipe recipe = ObjectDB.instance.GetRecipe(val3.m_itemData);
				if ((Object)(object)recipe != (Object)null)
				{
					DebugLogger.Verbose("Found standard recipe: " + name);
					return recipe;
				}
			}
			Recipe val4 = null;
			foreach (Recipe recipe3 in ObjectDB.instance.m_recipes)
			{
				if (((Object)recipe3).name == name)
				{
					val4 = recipe3;
					break;
				}
			}
			if ((Object)(object)val4 != (Object)null)
			{
				DebugLogger.Verbose("Found recipe in ObjectDB: " + name);
				return val4;
			}
			ZNetScene instance = ZNetScene.instance;
			GameObject val5 = ((instance != null) ? instance.GetPrefab(name) : null);
			if ((Object)(object)val5 != (Object)null)
			{
				Piece component = val5.GetComponent<Piece>();
				if ((Object)(object)component != (Object)null && component.m_resources != null && component.m_resources.Length != 0)
				{
					Recipe val6 = ScriptableObject.CreateInstance<Recipe>();
					((Object)val6).hideFlags = (HideFlags)61;
					((Object)val6).name = name;
					val6.m_item = val5.GetComponent<ItemDrop>();
					val6.m_resources = (Requirement[])component.m_resources.Clone();
					_fakeRecipeCache[name] = val6;
					DebugLogger.Verbose("Created fake recipe for piece: " + name);
					return val6;
				}
			}
			DebugLogger.Warning("Recipe not found anywhere: " + name);
			return null;
		}

		public static bool IsForgeUpgradeKey(string recipeKey)
		{
			if (string.IsNullOrEmpty(recipeKey))
			{
				return false;
			}
			Match match = UpgradeStarRegex.Match(recipeKey);
			if (match.Success)
			{
				return match.Groups[2].Success;
			}
			return false;
		}

		public static string BuildUpgradeLevelSuffix(string recipeKey)
		{
			if (string.IsNullOrEmpty(recipeKey))
			{
				return string.Empty;
			}
			Match match = UpgradeStarRegex.Match(recipeKey);
			if (!match.Success)
			{
				return string.Empty;
			}
			return " ★" + match.Groups[1].Value;
		}

		public static string BuildUpgradeRouteSuffix(string recipeKey)
		{
			if (!IsForgeUpgradeKey(recipeKey))
			{
				return string.Empty;
			}
			string text = null;
			if (Localization.instance != null)
			{
				string text2 = Localization.instance.Localize("$item_upgrader_name");
				if (!string.IsNullOrEmpty(text2) && !text2.Contains("MISSING KEY"))
				{
					text = text2;
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				RecipePinnerPlugin instance = RecipePinnerPlugin.Instance;
				text = (((Object)(object)instance == (Object)null) ? null : instance.LocalizationMgr)?.GetText("forge_route");
			}
			if (string.IsNullOrEmpty(text))
			{
				return string.Empty;
			}
			return " (" + text + ")";
		}

		private Recipe CreateFakeUpgradeRecipe(Recipe baseRecipe, int targetLevel, string customName)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: 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;
			List<Requirement> list = new List<Requirement>();
			Requirement[] resources = baseRecipe.m_resources;
			foreach (Requirement val2 in resources)
			{
				if (val2 != null)
				{
					int amount = val2.GetAmount(targetLevel);
					if (amount > 0)
					{
						Requirement item = new Requirement
						{
							m_resItem = val2.m_resItem,
							m_amount = amount,
							m_amountPerLevel = 0,
							m_recover = val2.m_recover,
							m_upgraderResource = val2.m_upgraderResource
						};
						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;
			bool flag = IsForgeUpgradeKey(customName);
			if (maxQuality < 2 || (targetLevel > maxQuality && !flag))
			{
				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;
							Player localPlayer = Player.m_localPlayer;
							CraftingStation val4 = (((Object)(object)localPlayer == (Object)null) ? null : localPlayer.GetCurrentCraftingStation());
							bool flag3 = (Object)(object)val4 != (Object)null && val4.m_upgrader;
							if (flag3)
							{
								bool flag4 = false;
								if (recipeFromObject.m_resources != null)
								{
									Requirement[] resources = recipeFromObject.m_resources;
									foreach (Requirement val5 in resources)
									{
										if (val5 != null && val5.m_upgraderResource)
										{
											flag4 = true;
											break;
										}
									}
								}
								if (!flag4)
								{
									string text5 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("no_upgrade_cost");
									Player localPlayer2 = Player.m_localPlayer;
									if (localPlayer2 != null)
									{
										((Character)localPlayer2).Message((MessageType)2, text5, 0, (Sprite)null, false);
									}
									return;
								}
							}
							if (quality >= maxQuality && !flag3)
							{
								string text6 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("max_level");
								Player localPlayer3 = Player.m_localPlayer;
								if (localPlayer3 != null)
								{
									((Character)localPlayer3).Message((MessageType)2, text6, 0, (Sprite)null, false);
								}
								return;
							}
							if ((Object)(object)recipeFromObject.m_item == (Object)null)
							{
								DebugLogger.Warning("Cannot pin upgrade: recipe '" + ((Object)recipeFromObject).name + "' has no item.");
								return;
							}
							string name = ((Object)recipeFromObject.m_item).name;
							string text7 = (flag3 ? $"{name} ★{num3}F" : $"{name} ★{num3}");
							if (IsUnpinHotkeyHeld() && !PinnedRecipes.ContainsKey(text7))
							{
								return;
							}
							DebugLogger.Verbose("Attempting to pin hovered recipe...");
							DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})");
							DebugLogger.Log("Attempting to pin upgrade: " + text7 + " (Base: " + name + ")");
							if ((Object)(object)GetRecipeByName(text7) != (Object)null)
							{
								TogglePin(text7);
								return;
							}
							string text8 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("no_upgrade_cost");
							Player localPlayer4 = Player.m_localPlayer;
							if (localPlayer4 != null)
							{
								((Character)localPlayer4).Message((MessageType)2, text8, 0, (Sprite)null, false);
							}
						}
						else
						{
							DebugLogger.Warning("Matched name but could not get ItemData for upgrade.");
						}
					}
					else
					{
						string text9 = BuildRecipeKey(recipeFromObject);
						if (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(text9))
						{
							DebugLogger.Verbose("Attempting to pin hovered recipe...");
							DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})");
							DebugLogger.Log("Matched recipe: " + ((Object)recipeFromObject).name);
							TogglePin(text9);
						}
					}
					return;
				}
			}
		}

		public void TryPinHoveredPiece()
		{
			if ((Object)(object)Hud.instance == (Object)null)
			{
				DebugLogger.Verbose("TryPinHoveredPiece: Hud.instance is null");
				return;
			}
			Piece val = ReflectionHelper.GetBuildMenuPieceUnderPointer();
			if ((Object)(object)val == (Object)null)
			{
				val = ReflectionHelper.GetHoveredPiece(Hud.instance);
			}
			if ((Object)(object)val == (Object)null || val.m_resources == null || val.m_resources.Length == 0)
			{
				DebugLogger.Verbose("TryPinHoveredPiece: no build piece under the pointer");
			}
			else if (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(((Object)val).name))
			{
				DebugLogger.Verbose("Attempting to pin hovered piece...");
				DebugLogger.Log("Pinning piece: " + ((Object)val).name);
				TogglePin(((Object)val).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, false);
							}
							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, false);
							}
							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, false);
						}
						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, false);
							}
						}
						else
						{
							Player localPlayer5 = Player.m_localPlayer;
							if (localPlayer5 != null)
							{
								((Character)localPlayer5).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null, false);
							}
						}
						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, false);
							}
						}
						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, false);
							}
						}
					}
					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, false);
						}
					}
					DebugLogger.Log($"Increased pin count: {recipeName} = {value}");
				}
			}
			else
			{
				if (flag)
				{
					return;
				}
				if (GetEffectivePinCount() < RecipePinnerPlugin.MaximumPins.Value)
				{
					PinnedRecipes.Add(recipeName, 1);
					if (!PinnedRecipeOrder.Contains(reci