Decompiled source of PoV Tree Helper v0.3.2

BepInEx/plugins/PoVTreeHelper/PoVTreePlanner.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PassiveTree;
using PassiveTree.UI;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("PoVTreePlanner")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.2.0")]
[assembly: AssemblyInformationalVersion("0.3.2")]
[assembly: AssemblyProduct("PoVTreePlanner")]
[assembly: AssemblyTitle("PoVTreePlanner")]
[assembly: AssemblyVersion("0.3.2.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 PoVTreePlanner
{
	internal sealed class EffectTarget
	{
		public Node Node;

		public int Option = -1;

		public bool Held;

		public bool Granted;

		public string Location;
	}
	internal sealed class EffectGroup
	{
		public string Title;

		public string Description;

		public string Category;

		public string Search;

		public bool Major;

		public bool Choice;

		public readonly List<EffectTarget> Targets = new List<EffectTarget>();
	}
	internal static class Catalogue
	{
		private static readonly MethodInfo CraftSource = AccessTools.Method(typeof(TreeWindow), "CraftSearchSource", (Type[])null, (Type[])null);

		private static readonly MethodInfo CraftShelf = AccessTools.Method(typeof(TreeWindow), "CraftShelf", (Type[])null, (Type[])null);

		public static Node[] Source(TreeWindow window)
		{
			if (!window.CraftMode)
			{
				return Tree.Nodes.Values.OrderBy<Node, string>((Node n) => n.Id, StringComparer.Ordinal).ToArray();
			}
			if (!CraftTree.Loaded)
			{
				return Array.Empty<Node>();
			}
			return (CraftSource.Invoke(window, null) as IEnumerable<Node>)?.Where((Node n) => CraftTree.Nodes.ContainsKey(n.Id)).OrderBy<Node, string>((Node n) => n.Id, StringComparer.Ordinal).ToArray() ?? Array.Empty<Node>();
		}

		public static Node[] AllocatedSource(TreeWindow window)
		{
			Progress current = Progress.Current;
			if (window == null || current == null)
			{
				return Array.Empty<Node>();
			}
			if (window.CraftMode && (!CraftTree.Loaded || current.Craft == null))
			{
				return Array.Empty<Node>();
			}
			IEnumerable<string> source;
			if (!window.CraftMode)
			{
				source = current.Allocated.Concat(current.FreeByAffix);
			}
			else
			{
				IEnumerable<string> taken = current.Craft.Taken;
				source = taken;
			}
			Dictionary<string, Node> dictionary = (window.CraftMode ? CraftTree.SearchNodes : Tree.Nodes);
			List<Node> list = new List<Node>();
			foreach (string item in source.Where((string id) => id != null).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string id) => id, StringComparer.Ordinal))
			{
				if (dictionary.TryGetValue(item, out var value) && value != null)
				{
					list.Add(value);
				}
			}
			return list.ToArray();
		}

		public static bool Held(TreeWindow window, Node node)
		{
			Progress current = Progress.Current;
			if (current != null)
			{
				if (!window.CraftMode)
				{
					return current.IsAllocated(node.Id);
				}
				return current.Craft.Taken.Contains(node.Id);
			}
			return false;
		}

		public static void Append(TreeWindow window, Node node, Dictionary<string, EffectGroup> groups)
		{
			if (node == null || node.Broken || node.Id == "root" || (window.CraftMode && (!CraftTree.Nodes.TryGetValue(node.Id, out var value) || value.Entry)))
			{
				return;
			}
			Progress current = Progress.Current;
			if (current == null)
			{
				return;
			}
			bool flag = Held(window, node);
			bool flag2 = window.IsPending(node.Id);
			string text = (window.CraftMode ? ((string)CraftShelf.Invoke(window, new object[1] { node.Id })) : TreeWords.Say(node.DisplayCategory));
			string text2 = NodeTitle(window, node);
			string location = (string.IsNullOrWhiteSpace(text) ? text2 : (text + " · " + text2));
			if (!flag && !flag2 && ((node.Mods != null && node.Mods.Count > 0) || (!node.IsMastery && !string.IsNullOrWhiteSpace(node.Extra))))
			{
				Add(groups, text2, node.Description, text, node, -1, held: false, location);
			}
			if (node.Options == null)
			{
				return;
			}
			int num = ((!flag) ? (-1) : (window.CraftMode ? current.Craft.ChoiceOf(node.Id) : current.ChoiceOf(node.Id)));
			int num2 = window.PendingChoiceOf(node.Id);
			for (int i = 0; i < node.Options.Count; i++)
			{
				NodeOption val = node.Options[i];
				if (val == null || val.Closed || i == num || i == num2)
				{
					continue;
				}
				if (window.CraftMode)
				{
					if (!CraftMasteries.Works(val) || current.Craft.UsedElsewhere(node, i, node.Id))
					{
						continue;
					}
				}
				else if (current.MasteryOptionSelected(node.Id, i))
				{
					continue;
				}
				Add(groups, val.Title, OptionDescription(val, window.CraftMode), text, node, i, flag, location);
			}
		}

		public static void AppendAllocated(TreeWindow window, Node node, Dictionary<string, EffectGroup> groups)
		{
			if (window == null || node == null || node.Broken || node.Id == null)
			{
				return;
			}
			Progress current = Progress.Current;
			if (current == null || (window.CraftMode && (current.Craft == null || !CraftTree.Loaded)) || !(window.CraftMode ? CraftTree.SearchNodes : Tree.Nodes).TryGetValue(node.Id, out var value) || value != node)
			{
				return;
			}
			bool flag = (window.CraftMode ? current.Craft.Taken.Contains(node.Id) : current.Allocated.Contains(node.Id));
			bool flag2 = !window.CraftMode && !flag && current.FreeByAffix.Contains(node.Id);
			if (!flag && !flag2)
			{
				return;
			}
			bool flag3 = !window.CraftMode && current.AreaFx != null && current.AreaFx.Muted.Contains(node.Id);
			bool flag4 = !flag3 || (current.AreaFx != null && current.AreaFx.Granted.Contains(node.Id));
			string text = (window.CraftMode ? ((string)CraftShelf.Invoke(window, new object[1] { node.Id })) : TreeWords.Say(node.DisplayCategory));
			string text2 = NodeTitle(window, node);
			string location = (string.IsNullOrWhiteSpace(text) ? text2 : (text + " · " + text2));
			if (flag4 && ((node.Mods != null && node.Mods.Count > 0) || (!node.IsMastery && !string.IsNullOrWhiteSpace(node.Extra))))
			{
				AddTarget(groups, text2, node.Description, text, node, -1, held: true, location).Granted = flag2;
			}
			if (flag3 || !node.IsMastery || node.Options == null)
			{
				return;
			}
			int num = (window.CraftMode ? current.Craft.ChoiceOf(node.Id) : current.ChoiceOf(node.Id));
			if (num >= 0 && num < node.Options.Count)
			{
				NodeOption val = node.Options[num];
				if (val != null && !val.Broken && (!window.CraftMode || !CraftMasterySwitches.Off(val)))
				{
					AddTarget(groups, val.Title, OptionDescription(val, window.CraftMode), text, node, num, held: true, location).Granted = flag2;
				}
			}
		}

		private static void Add(Dictionary<string, EffectGroup> groups, string title, string body, string category, Node node, int option, bool held, string location)
		{
			AddTarget(groups, title, body, category, node, option, held, location);
		}

		private static EffectTarget AddTarget(Dictionary<string, EffectGroup> groups, string title, string body, string category, Node node, int option, bool held, string location)
		{
			title = Words.Plain(title);
			body = Words.Plain(body).Trim();
			category = Words.Plain(category);
			string key = ((option >= 0) ? "option\n" : "base\n") + title + "\n" + body + "\n" + category;
			if (!groups.TryGetValue(key, out var value))
			{
				value = new EffectGroup
				{
					Title = title,
					Description = body,
					Category = category,
					Choice = (option >= 0),
					Major = (!node.IsSmall && option < 0)
				};
				groups.Add(key, value);
			}
			value.Major |= !node.IsSmall && option < 0;
			EffectTarget effectTarget = new EffectTarget
			{
				Node = node,
				Option = option,
				Held = held,
				Location = Words.Plain(location)
			};
			value.Targets.Add(effectTarget);
			return effectTarget;
		}

		public static string NodeTitle(TreeWindow window, Node node)
		{
			if (window.CraftMode && CraftTree.Nodes.TryGetValue(node.Id, out var value))
			{
				return L.Id("craft.node." + node.Id + ".name", value.Name);
			}
			return node.Title;
		}

		private static string OptionDescription(NodeOption option, bool craft)
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			if (!craft)
			{
				if (!string.IsNullOrWhiteSpace(option.Text))
				{
					list.Add(option.Text);
				}
				if (!string.IsNullOrWhiteSpace(option.Told))
				{
					list.Add(option.Told);
				}
				if (list.Count == 0 && option.Mods != null && option.Mods.Count > 0)
				{
					list.Add(StatText.DescribeAll((IEnumerable<Mod>)option.Mods));
				}
				return string.Join("\n", list.Distinct<string>(StringComparer.Ordinal));
			}
			Stat val = default(Stat);
			if (!string.IsNullOrWhiteSpace(option.Told))
			{
				list.Add(option.Told);
			}
			else if (CraftMasteries.TryFlag(option, ref val))
			{
				list.Add(StatText.Describe(val, 1f));
			}
			if (option.Mods != null && option.Mods.Count > 0)
			{
				list.Add(StatText.DescribeAll((IEnumerable<Mod>)option.Mods));
			}
			Mod[] array = default(Mod[]);
			if (CraftMasteries.TryLines(option, ref array) && array != null && array.Length != 0)
			{
				list.Add(StatText.DescribeAll((IEnumerable<Mod>)array));
			}
			return string.Join("\n", list.Distinct<string>(StringComparer.Ordinal));
		}

		public static void Finish(IEnumerable<EffectGroup> groups)
		{
			foreach (EffectGroup group in groups)
			{
				group.Search = group.Title + "\n" + group.Description + "\n" + group.Category + "\n" + string.Join("\n", group.Targets.Select((EffectTarget t) => t.Node.Id + " " + t.Location));
			}
		}
	}
	internal sealed class CataloguePanel : MonoBehaviour
	{
		private const int PageSize = 7;

		private static readonly Color Gold = new Color(0.91f, 0.78f, 0.48f);

		private static readonly Color Body = new Color(0.9f, 0.87f, 0.79f);

		private static readonly MethodInfo FocusNode = AccessTools.Method(typeof(TreeWindow), "ShowAt", new Type[2]
		{
			typeof(Vector2),
			typeof(float)
		}, (Type[])null);

		private TreeWindow window;

		private GameObject backdrop;

		private Button toggle;

		private Button route;

		private Button previousTarget;

		private Button nextTarget;

		private Button contentViewButton;

		private Button nameViewButton;

		private Button distanceSortButton;

		private Button unallocatedButton;

		private Button allocatedButton;

		private Button showNodeButton;

		private RectTransform panel;

		private TMP_InputField search;

		private TextMeshProUGUI heading;

		private TextMeshProUGUI status;

		private TextMeshProUGUI pageText;

		private TextMeshProUGUI detailTitle;

		private TextMeshProUGUI detailBody;

		private TextMeshProUGUI detailLocation;

		private TextMeshProUGUI targetText;

		private TextMeshProUGUI hint;

		private TextMeshProUGUI viewLabel;

		private TextMeshProUGUI searchPlaceholder;

		private readonly List<Button> rows = new List<Button>();

		private readonly List<Button> filterButtons = new List<Button>();

		private readonly List<TextMeshProUGUI> rowLabels = new List<TextMeshProUGUI>();

		private readonly Dictionary<EffectGroup, int> groupDistances = new Dictionary<EffectGroup, int>();

		private readonly Dictionary<EffectGroup, int> nearestTargets = new Dictionary<EffectGroup, int>();

		private readonly Dictionary<EffectGroup, EffectTarget> chosenTargets = new Dictionary<EffectGroup, EffectTarget>();

		private List<EffectGroup> groups = new List<EffectGroup>();

		private List<EffectGroup> filtered = new List<EffectGroup>();

		private EffectGroup selected;

		private int page;

		private int targetIndex;

		private int filter;

		private int generation;

		private int distanceGeneration;

		private bool craft;

		private bool dirty;

		private bool building;

		private bool contentView = true;

		private bool sortByDistance;

		private bool distanceBusy;

		private bool allocatedView;

		private float rebuildAt;

		private float distanceCheckAt;

		private string language;

		private string sourceLanguage;

		private string distanceError;

		private object character;

		private object progress;

		private Coroutine build;

		private Coroutine distanceBuild;

		private DistanceRanking distances;

		private DistanceRanking distanceCandidate;

		public bool Open
		{
			get
			{
				if ((Object)(object)backdrop != (Object)null)
				{
					return backdrop.activeInHierarchy;
				}
				return false;
			}
		}

		private bool DistanceEnabled
		{
			get
			{
				if (!allocatedView && sortByDistance)
				{
					return Plugin.DistanceAvailable;
				}
				return false;
			}
		}

		private EffectTarget Target
		{
			get
			{
				if (selected != null && targetIndex >= 0 && targetIndex < selected.Targets.Count)
				{
					return selected.Targets[targetIndex];
				}
				return null;
			}
		}

		public static CataloguePanel Get(TreeWindow w)
		{
			if (!((Object)(object)w == (Object)null))
			{
				return ((Component)w).GetComponent<CataloguePanel>();
			}
			return null;
		}

		public static void Ensure(TreeWindow w)
		{
			if (!((Object)(object)w == (Object)null) && !((Object)(object)w.Root == (Object)null))
			{
				CataloguePanel cataloguePanel = Get(w) ?? ((Component)w).gameObject.AddComponent<CataloguePanel>();
				cataloguePanel.window = w;
				if ((Object)(object)cataloguePanel.toggle == (Object)null)
				{
					cataloguePanel.Create();
				}
			}
		}

		private void Create()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: 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_0224: Expected O, but got Unknown
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Expected O, but got Unknown
			//IL_029c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02df: Expected O, but got Unknown
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_039d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: Expected O, but got Unknown
			//IL_0413: Unknown result type (might be due to invalid IL or missing references)
			//IL_0422: Unknown result type (might be due to invalid IL or missing references)
			//IL_0431: Unknown result type (might be due to invalid IL or missing references)
			//IL_043d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0456: Expected O, but got Unknown
			//IL_0476: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_04de: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f7: Expected O, but got Unknown
			//IL_0512: Unknown result type (might be due to invalid IL or missing references)
			//IL_0521: Unknown result type (might be due to invalid IL or missing references)
			//IL_0530: Unknown result type (might be due to invalid IL or missing references)
			//IL_053c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0555: Expected O, but got Unknown
			//IL_0576: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0605: Unknown result type (might be due to invalid IL or missing references)
			//IL_061e: Expected O, but got Unknown
			//IL_0696: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d9: Expected O, but got Unknown
			//IL_06f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_072d: Unknown result type (might be due to invalid IL or missing references)
			//IL_073c: Unknown result type (might be due to invalid IL or missing references)
			//IL_074b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0757: Unknown result type (might be due to invalid IL or missing references)
			//IL_0770: Expected O, but got Unknown
			//IL_0787: Unknown result type (might be due to invalid IL or missing references)
			//IL_07c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0815: Unknown result type (might be due to invalid IL or missing references)
			//IL_0853: Unknown result type (might be due to invalid IL or missing references)
			//IL_0862: Unknown result type (might be due to invalid IL or missing references)
			//IL_0871: Unknown result type (might be due to invalid IL or missing references)
			//IL_087d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0896: Expected O, but got Unknown
			//IL_08b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_08c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_08cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_08db: Unknown result type (might be due to invalid IL or missing references)
			//IL_08f4: Expected O, but got Unknown
			//IL_0914: Unknown result type (might be due to invalid IL or missing references)
			//IL_0923: Unknown result type (might be due to invalid IL or missing references)
			//IL_0932: Unknown result type (might be due to invalid IL or missing references)
			//IL_093e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0957: Expected O, but got Unknown
			//IL_0977: Unknown result type (might be due to invalid IL or missing references)
			//IL_0986: Unknown result type (might be due to invalid IL or missing references)
			//IL_0995: Unknown result type (might be due to invalid IL or missing references)
			//IL_09a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_09ba: Expected O, but got Unknown
			Hide();
			rows.Clear();
			rowLabels.Clear();
			filterButtons.Clear();
			selected = null;
			toggle = UiKit.Button(window.Root, Words.Get("catalogue.open"), new Vector2(1f, 1f), new Vector2(-183f, -104f), new Vector2(190f, 38f), (UnityAction)delegate
			{
				Show();
			}, 16f, false, (Color?)null);
			((Object)((Component)toggle).gameObject).name = "PoVTreePlannerButton";
			backdrop = UiKit.Go("PoVTreePlannerModal", window.Root);
			UiKit.Stretch(backdrop, 0f, 0f, 0f, 0f);
			Image obj = backdrop.AddComponent<Image>();
			((Graphic)obj).color = new Color(0f, 0f, 0f, 0.7f);
			((Graphic)obj).raycastTarget = true;
			GameObject val = UiKit.Go("Panel", backdrop.transform);
			panel = UiKit.Rect(val, new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(1120f, 780f), (Vector2?)null);
			Image obj2 = val.AddComponent<Image>();
			obj2.sprite = UiKit.ArtPanel;
			obj2.type = (Type)1;
			((Graphic)obj2).color = Color.white;
			((Graphic)obj2).raycastTarget = true;
			heading = Label(val.transform, "Title", "", 27f, Gold, 24f, -25f, 650f, 40f, (TextAlignmentOptions)257);
			unallocatedButton = UiKit.Button(val.transform, "", new Vector2(0f, 1f), new Vector2(784f, -44f), new Vector2(164f, 36f), (UnityAction)delegate
			{
				SetAllocatedView(value: false);
			}, 16f, false, (Color?)null);
			allocatedButton = UiKit.Button(val.transform, "", new Vector2(0f, 1f), new Vector2(964f, -44f), new Vector2(164f, 36f), (UnityAction)delegate
			{
				SetAllocatedView(value: true);
			}, 16f, false, (Color?)null);
			UiKit.Button(val.transform, "×", new Vector2(1f, 1f), new Vector2(-32f, -40f), new Vector2(42f, 36f), new UnityAction(Hide), 24f, false, (Color?)null);
			hint = Label(val.transform, "Hint", Words.Get("ui.search_effects_and_inspect_their_nodes_allocate_change_choices"), 16f, Body, 24f, -72f, 1072f, 32f, (TextAlignmentOptions)257);
			search = CreateSearch(val.transform);
			string[] array = new string[3]
			{
				Words.Get("filter.all"),
				Words.Get("filter.major"),
				Words.Get("filter.choices")
			};
			for (int num = 0; num < array.Length; num++)
			{
				int index = num;
				filterButtons.Add(UiKit.Button(val.transform, array[num], new Vector2(0f, 1f), new Vector2((float)(82 + num * 146), -174f), new Vector2(136f, 34f), (UnityAction)delegate
				{
					filter = index;
					page = 0;
					ApplyFilter();
				}, 16f, false, (Color?)null));
			}
			distanceSortButton = UiKit.Button(val.transform, "", new Vector2(0f, 1f), new Vector2(545f, -174f), new Vector2(190f, 34f), (UnityAction)delegate
			{
				SetDistanceSort(!sortByDistance);
			}, 15f, false, (Color?)null);
			viewLabel = Label(val.transform, "ListViewLabel", Words.Get("view.label"), 15f, Body, 650f, -160f, 100f, 30f, (TextAlignmentOptions)257);
			contentViewButton = UiKit.Button(val.transform, "", new Vector2(0f, 1f), new Vector2(852f, -174f), new Vector2(164f, 34f), (UnityAction)delegate
			{
				SetContentView(value: true);
			}, 16f, false, (Color?)null);
			nameViewButton = UiKit.Button(val.transform, "", new Vector2(0f, 1f), new Vector2(1020f, -174f), new Vector2(152f, 34f), (UnityAction)delegate
			{
				SetContentView(value: false);
			}, 16f, false, (Color?)null);
			RefreshViewButtons();
			status = Label(val.transform, "Status", "", 15f, Gold, 24f, -202f, 1072f, 30f, (TextAlignmentOptions)257);
			for (int num2 = 0; num2 < 7; num2++)
			{
				int index2 = num2;
				Button val2 = UiKit.Button(val.transform, "", new Vector2(0f, 1f), new Vector2(281f, (float)(-273 - num2 * 64)), new Vector2(514f, 60f), (UnityAction)delegate
				{
					SelectRow(index2);
				}, 17f, false, (Color?)null);
				TextMeshProUGUI componentInChildren = ((Component)val2).GetComponentInChildren<TextMeshProUGUI>();
				((TMP_Text)componentInChildren).alignment = (TextAlignmentOptions)4097;
				((TMP_Text)componentInChildren).enableAutoSizing = false;
				((TMP_Text)componentInChildren).fontSize = 17f;
				((TMP_Text)componentInChildren).richText = false;
				((TMP_Text)componentInChildren).overflowMode = (TextOverflowModes)1;
				rows.Add(val2);
				rowLabels.Add(componentInChildren);
			}
			UiKit.Button(val.transform, "‹", new Vector2(0f, 0f), new Vector2(54f, 35f), new Vector2(58f, 34f), (UnityAction)delegate
			{
				if (page > 0)
				{
					page--;
					RenderPage();
				}
			}, 22f, false, (Color?)null);
			pageText = Label(val.transform, "Page", "", 16f, Body, 120f, -724f, 300f, 38f, (TextAlignmentOptions)514);
			UiKit.Button(val.transform, "›", new Vector2(0f, 0f), new Vector2(508f, 35f), new Vector2(58f, 34f), (UnityAction)delegate
			{
				if ((page + 1) * 7 < filtered.Count)
				{
					page++;
					RenderPage();
				}
			}, 22f, false, (Color?)null);
			detailTitle = Label(val.transform, "EffectTitle", "", 23f, Gold, 568f, -242f, 524f, 58f, (TextAlignmentOptions)257);
			detailLocation = Label(val.transform, "Location", "", 15f, Body, 568f, -306f, 524f, 60f, (TextAlignmentOptions)257);
			detailBody = CreateDetailScroll(val.transform);
			targetText = Label(val.transform, "Target", "", 15f, Body, 630f, -640f, 398f, 34f, (TextAlignmentOptions)514);
			previousTarget = UiKit.Button(val.transform, "‹", new Vector2(0f, 1f), new Vector2(596f, -658f), new Vector2(48f, 32f), (UnityAction)delegate
			{
				ChangeTarget(-1);
			}, 20f, false, (Color?)null);
			nextTarget = UiKit.Button(val.transform, "›", new Vector2(0f, 1f), new Vector2(1064f, -658f), new Vector2(48f, 32f), (UnityAction)delegate
			{
				ChangeTarget(1);
			}, 20f, false, (Color?)null);
			showNodeButton = UiKit.Button(val.transform, Words.Get("action.show"), new Vector2(0f, 0f), new Vector2(687f, 42f), new Vector2(226f, 46f), new UnityAction(ShowSelected), 18f, false, (Color?)null);
			route = UiKit.Button(val.transform, Words.Get("action.route"), new Vector2(0f, 0f), new Vector2(971f, 42f), new Vector2(226f, 46f), new UnityAction(QueueSelected), 18f, true, (Color?)null);
			backdrop.SetActive(false);
			Words.RefreshLanguage();
			language = Words.Language;
			RefreshLocalizedText();
		}

		private void RefreshLocalizedText()
		{
			((TMP_Text)((Component)toggle).GetComponentInChildren<TextMeshProUGUI>()).text = Words.Get("catalogue.open");
			((TMP_Text)hint).text = Words.Get("ui.search_effects_and_inspect_their_nodes_allocate_change_choices");
			((TMP_Text)viewLabel).text = Words.Get("view.label");
			((TMP_Text)searchPlaceholder).text = Words.Get("ui.search_name_effect_category_or_node_id");
			string[] array = new string[3] { "filter.all", "filter.major", "filter.choices" };
			for (int i = 0; i < filterButtons.Count; i++)
			{
				((TMP_Text)((Component)filterButtons[i]).GetComponentInChildren<TextMeshProUGUI>()).text = Words.Get(array[i]);
			}
			((TMP_Text)((Component)showNodeButton).GetComponentInChildren<TextMeshProUGUI>()).text = Words.Get("action.show");
			RefreshHeading();
			RefreshViewButtons();
			RefreshStatus();
			RenderPage();
			SetDetail(preserveScroll: true);
		}

		private static TextMeshProUGUI Label(Transform parent, string name, string text, float size, Color color, float x, float y, float width, float height, TextAlignmentOptions alignment = (TextAlignmentOptions)257)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI obj = UiKit.Text(parent, name, text, size, color, alignment, false);
			UiKit.Rect(((Component)obj).gameObject, new Vector2(0f, 1f), new Vector2(x, y), new Vector2(width, height), (Vector2?)new Vector2(0f, 1f));
			((TMP_Text)obj).richText = false;
			return obj;
		}

		private TMP_InputField CreateSearch(Transform parent)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Expected O, but got Unknown
			GameObject val = UiKit.Go("Search", parent);
			UiKit.Rect(val, new Vector2(0f, 1f), new Vector2(24f, -112f), new Vector2(1072f, 38f), (Vector2?)new Vector2(0f, 1f));
			Image val2 = val.AddComponent<Image>();
			((Graphic)val2).color = new Color(0.06f, 0.07f, 0.09f, 0.92f);
			GameObject val3 = UiKit.Go("Viewport", val.transform);
			UiKit.Stretch(val3, 12f, 12f, 3f, 3f);
			val3.AddComponent<RectMask2D>();
			TextMeshProUGUI val4 = UiKit.Text(val3.transform, "Text", "", 18f, Body, (TextAlignmentOptions)4097, false);
			UiKit.Stretch(((Component)val4).gameObject, 0f, 0f, 0f, 0f);
			((TMP_Text)val4).richText = false;
			TextMeshProUGUI val5 = (searchPlaceholder = UiKit.Text(val3.transform, "Placeholder", Words.Get("ui.search_name_effect_category_or_node_id"), 17f, new Color(0.6f, 0.6f, 0.58f), (TextAlignmentOptions)4097, false));
			UiKit.Stretch(((Component)val5).gameObject, 0f, 0f, 0f, 0f);
			TMP_InputField obj = val.AddComponent<TMP_InputField>();
			((Selectable)obj).targetGraphic = (Graphic)(object)val2;
			obj.textViewport = (RectTransform)val3.transform;
			obj.textComponent = (TMP_Text)(object)val4;
			obj.placeholder = (Graphic)(object)val5;
			obj.fontAsset = ((TMP_Text)val4).font;
			obj.lineType = (LineType)0;
			obj.characterLimit = 200;
			((UnityEvent<string>)(object)obj.onValueChanged).AddListener((UnityAction<string>)delegate
			{
				page = 0;
				ApplyFilter();
			});
			return obj;
		}

		private TextMeshProUGUI CreateDetailScroll(Transform parent)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: 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_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Expected O, but got Unknown
			GameObject val = UiKit.Go("DetailsScroll", parent);
			UiKit.Rect(val, new Vector2(0f, 1f), new Vector2(568f, -370f), new Vector2(524f, 258f), (Vector2?)new Vector2(0f, 1f));
			Image obj = val.AddComponent<Image>();
			((Graphic)obj).color = new Color(0f, 0f, 0f, 0.08f);
			((Graphic)obj).raycastTarget = true;
			GameObject val2 = UiKit.Go("Viewport", val.transform);
			UiKit.Stretch(val2, 0f, 0f, 0f, 0f);
			val2.AddComponent<RectMask2D>();
			TextMeshProUGUI obj2 = UiKit.Text(val2.transform, "Description", "", 18f, Body, (TextAlignmentOptions)257, false);
			RectTransform rectTransform = ((TMP_Text)obj2).rectTransform;
			rectTransform.anchorMin = new Vector2(0f, 1f);
			rectTransform.anchorMax = new Vector2(1f, 1f);
			rectTransform.pivot = new Vector2(0.5f, 1f);
			rectTransform.sizeDelta = new Vector2(-18f, 0f);
			rectTransform.anchoredPosition = Vector2.zero;
			((TMP_Text)obj2).richText = false;
			((TMP_Text)obj2).textWrappingMode = (TextWrappingModes)1;
			((Component)obj2).gameObject.AddComponent<ContentSizeFitter>().verticalFit = (FitMode)2;
			ScrollRect obj3 = val.AddComponent<ScrollRect>();
			obj3.viewport = (RectTransform)val2.transform;
			obj3.content = rectTransform;
			obj3.horizontal = false;
			obj3.vertical = true;
			obj3.movementType = (MovementType)2;
			obj3.scrollSensitivity = 30f;
			return obj2;
		}

		public void Show()
		{
			//IL_00bf: 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)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.CatalogueEnabled.Value && TreeWindow.IsOpen)
			{
				window.FocusSearch(false);
				window.HideTooltip();
				Words.RefreshLanguage();
				craft = window.CraftMode;
				language = Words.Language;
				sourceLanguage = L.CurrentLanguage();
				character = Player.m_localPlayer;
				progress = Progress.Current;
				sortByDistance = Plugin.SortByDistance.Value;
				contentView = Plugin.EffectContentView.Value;
				RefreshLocalizedText();
				backdrop.SetActive(true);
				backdrop.transform.SetAsLastSibling();
				Rect rect = ((RectTransform)window.Root).rect;
				float num = ((Rect)(ref rect)).width / 1160f;
				rect = ((RectTransform)window.Root).rect;
				float num2 = Mathf.Min(1f, Mathf.Min(num, ((Rect)(ref rect)).height / 820f));
				((Transform)panel).localScale = Vector3.one * num2;
				RefreshHeading();
				page = 0;
				selected = null;
				SetDetail();
				StartBuild();
			}
		}

		private void RefreshHeading()
		{
			((TMP_Text)heading).text = (allocatedView ? Words.Get("catalogue.allocated_heading") : Words.Get("catalogue.unallocated_heading")) + (craft ? Words.Get("tree.crafting") : Words.Get("tree.skill"));
		}

		private void SetAllocatedView(bool value)
		{
			if (allocatedView != value)
			{
				allocatedView = value;
				page = 0;
				RefreshHeading();
				RefreshViewButtons();
				StartBuild();
			}
		}

		public void Hide()
		{
			if ((Object)(object)search != (Object)null)
			{
				search.DeactivateInputField(false);
			}
			if ((Object)(object)backdrop != (Object)null)
			{
				backdrop.SetActive(false);
			}
			generation++;
			if (build != null)
			{
				((MonoBehaviour)this).StopCoroutine(build);
			}
			build = null;
			building = false;
			InvalidateDistances();
		}

		public void MarkDirty()
		{
			InvalidateDistances();
			dirty = true;
			if (Open)
			{
				generation++;
				if (build != null)
				{
					((MonoBehaviour)this).StopCoroutine(build);
				}
				build = null;
				building = false;
				rebuildAt = Time.unscaledTime + 0.2f;
				RefreshStatus();
				RenderPage();
				SetDetail(preserveScroll: true);
			}
		}

		private void Update()
		{
			if ((Object)(object)toggle != (Object)null)
			{
				((Component)toggle).gameObject.SetActive(Plugin.CatalogueEnabled.Value);
			}
			if ((Object)(object)toggle != (Object)null && language != Words.Language)
			{
				language = Words.Language;
				RefreshLocalizedText();
			}
			if (!Open)
			{
				return;
			}
			if (!Plugin.CatalogueEnabled.Value)
			{
				Hide();
				return;
			}
			if (!TreeWindow.IsOpen || window.CraftMode != craft || sourceLanguage != L.CurrentLanguage() || character != Player.m_localPlayer || progress != Progress.Current)
			{
				Hide();
				return;
			}
			if (contentView != Plugin.EffectContentView.Value)
			{
				SetContentView(Plugin.EffectContentView.Value, persist: false);
			}
			if (sortByDistance != Plugin.SortByDistance.Value)
			{
				SetDistanceSort(Plugin.SortByDistance.Value, persist: false);
			}
			if (DistanceEnabled && distances != null && Time.unscaledTime >= distanceCheckAt)
			{
				distanceCheckAt = Time.unscaledTime + 0.5f;
				if (!distances.Matches(window))
				{
					MarkDirty();
				}
			}
			if (dirty && !building && Time.unscaledTime >= rebuildAt)
			{
				StartBuild();
			}
		}

		private void StartBuild()
		{
			dirty = false;
			generation++;
			InvalidateDistances();
			chosenTargets.Clear();
			if (build != null)
			{
				((MonoBehaviour)this).StopCoroutine(build);
			}
			building = true;
			selected = null;
			SetDetail();
			RefreshStatus();
			groups.Clear();
			filtered.Clear();
			RenderPage();
			build = ((MonoBehaviour)this).StartCoroutine(Build(generation, allocatedView));
		}

		private IEnumerator Build(int token, bool allocated)
		{
			yield return null;
			while (!window.Ready)
			{
				if (token != generation || !Open)
				{
					yield break;
				}
				yield return null;
			}
			Dictionary<string, EffectGroup> collected = new Dictionary<string, EffectGroup>(StringComparer.Ordinal);
			Node[] source;
			try
			{
				source = (allocated ? Catalogue.AllocatedSource(window) : Catalogue.Source(window));
			}
			catch (Exception error)
			{
				Plugin.Warn("Catalogue source", error);
				source = Array.Empty<Node>();
			}
			int i = 0;
			while (i < source.Length)
			{
				if (token != generation || !Open)
				{
					yield break;
				}
				try
				{
					if (allocated)
					{
						Catalogue.AppendAllocated(window, source[i], collected);
					}
					else
					{
						Catalogue.Append(window, source[i], collected);
					}
				}
				catch (Exception ex)
				{
					Plugin.WarnOnce("catalogue-entry", "Some node descriptions could not be read: " + ex.Message);
				}
				if ((i & 0x1F) == 31)
				{
					yield return null;
				}
				int num = i + 1;
				i = num;
			}
			if (token == generation && Open)
			{
				Catalogue.Finish(collected.Values);
				groups = collected.Values.ToList();
				SortGroups();
				building = false;
				build = null;
				selected = null;
				ApplyFilter();
				EnsureDistances();
			}
		}

		private void InvalidateDistances(bool keepCompleted = false)
		{
			distanceGeneration++;
			if (distanceBuild != null)
			{
				((MonoBehaviour)this).StopCoroutine(distanceBuild);
			}
			distanceBuild = null;
			distanceBusy = false;
			distanceCandidate = null;
			distanceError = null;
			if (!keepCompleted)
			{
				distances = null;
			}
			groupDistances.Clear();
			nearestTargets.Clear();
		}

		private void EnsureDistances()
		{
			if (!DistanceEnabled || !Open || building || dirty || distanceBusy || distanceError != null)
			{
				return;
			}
			if (distances != null)
			{
				if (distances.Success && distances.Matches(window))
				{
					return;
				}
				InvalidateDistances();
			}
			distanceBusy = true;
			distanceCandidate = new DistanceRanking();
			RefreshStatus();
			SetDetail(preserveScroll: true);
			distanceBuild = ((MonoBehaviour)this).StartCoroutine(BuildDistances(distanceGeneration, distanceCandidate));
		}

		private IEnumerator BuildDistances(int token, DistanceRanking candidate)
		{
			yield return null;
			IEnumerator walk = candidate.Build(window);
			string error = null;
			try
			{
				while (token == distanceGeneration && Open && DistanceEnabled)
				{
					bool flag;
					try
					{
						flag = walk.MoveNext();
					}
					catch (Exception ex)
					{
						Plugin.Warn("Distance ranking", ex);
						error = ex.Message;
						break;
					}
					if (!flag)
					{
						break;
					}
					RefreshStatus();
					yield return walk.Current;
				}
			}
			finally
			{
				(walk as IDisposable)?.Dispose();
			}
			if (token == distanceGeneration && Open && DistanceEnabled)
			{
				distanceBuild = null;
				distanceBusy = false;
				distanceCandidate = null;
				if (error != null || !candidate.Success)
				{
					distanceError = error ?? candidate.Error ?? Words.Get("ui.unavailable");
					ApplyFilter(preserveSelection: true);
				}
				else if (!candidate.Matches(window))
				{
					MarkDirty();
				}
				else
				{
					distances = candidate;
					distanceCheckAt = Time.unscaledTime + 0.5f;
					ApplyFilter(preserveSelection: true);
				}
			}
		}

		private static string ListContent(EffectGroup group)
		{
			string text = string.Join(" ", (group.Description ?? "").Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries));
			if (text.Length != 0)
			{
				return text;
			}
			return group.Title;
		}

		private void SortGroups()
		{
			groups = (contentView ? groups.OrderBy<EffectGroup, string>(ListContent, StringComparer.CurrentCultureIgnoreCase).ThenBy<EffectGroup, string>((EffectGroup g) => g.Title, StringComparer.CurrentCultureIgnoreCase).ToList() : groups.OrderBy<EffectGroup, string>((EffectGroup g) => g.Title, StringComparer.CurrentCultureIgnoreCase).ThenBy<EffectGroup, string>((EffectGroup g) => g.Description, StringComparer.Ordinal).ToList());
		}

		private void SetContentView(bool value, bool persist = true)
		{
			contentView = value;
			if (persist)
			{
				Plugin.EffectContentView.Value = value;
			}
			RefreshViewButtons();
			SortGroups();
			ApplyFilter(preserveSelection: true);
		}

		private void SetDistanceSort(bool value, bool persist = true)
		{
			sortByDistance = value;
			if (persist)
			{
				Plugin.SortByDistance.Value = value;
			}
			if (!DistanceEnabled)
			{
				InvalidateDistances(keepCompleted: true);
			}
			else
			{
				distanceError = null;
			}
			RefreshViewButtons();
			ApplyFilter(preserveSelection: true);
			EnsureDistances();
		}

		private void RefreshViewButtons()
		{
			((TMP_Text)((Component)unallocatedButton).GetComponentInChildren<TextMeshProUGUI>()).text = ((!allocatedView) ? "✓ " : "") + Words.Get("catalogue.unallocated");
			((TMP_Text)((Component)allocatedButton).GetComponentInChildren<TextMeshProUGUI>()).text = (allocatedView ? "✓ " : "") + Words.Get("catalogue.allocated");
			((TMP_Text)((Component)contentViewButton).GetComponentInChildren<TextMeshProUGUI>()).text = (contentView ? "✓ " : "") + Words.Get("view.effects");
			((TMP_Text)((Component)nameViewButton).GetComponentInChildren<TextMeshProUGUI>()).text = ((!contentView) ? "✓ " : "") + Words.Get("view.name");
			((Selectable)distanceSortButton).interactable = !allocatedView && Plugin.DistanceAvailable;
			((TMP_Text)((Component)distanceSortButton).GetComponentInChildren<TextMeshProUGUI>()).text = (DistanceEnabled ? Words.Get("sort.distance") : (contentView ? Words.Get("sort.text") : Words.Get("sort.name")));
		}

		private void RefreshStatus()
		{
			if (!((Object)(object)status == (Object)null))
			{
				if (building || dirty)
				{
					((TMP_Text)status).text = (allocatedView ? Words.Get("ui.collecting_allocated_effects") : Words.Get("ui.collecting_unallocated_effects"));
				}
				else if (distanceBusy)
				{
					((TMP_Text)status).text = string.Format(Words.Get("ui.calculating_node_distances_0_1_you_can_keep_browsing"), distanceCandidate?.Processed ?? 0, distanceCandidate?.Total ?? 0);
				}
				else if (!allocatedView && sortByDistance && !Plugin.DistanceAvailable)
				{
					((TMP_Text)status).text = Words.Get("ui.distance_sorting_is_unavailable_for_this_pov_version_using");
				}
				else if (DistanceEnabled && distanceError != null)
				{
					((TMP_Text)status).text = Words.Get("ui.distance_calculation_failed_using_the_default_order_toggle_distance");
				}
				else
				{
					((TMP_Text)status).text = string.Format(Words.Get("ui.0_effects_1_shown_identical_effects_grouped"), groups.Count, filtered.Count);
				}
			}
		}

		private static bool Contains(string value, string query)
		{
			if (value != null)
			{
				return value.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private void RankGroup(EffectGroup group, string query)
		{
			bool flag = query.Length == 0 || Contains(group.Title, query) || Contains(group.Description, query) || Contains(group.Category, query);
			int num = int.MaxValue;
			int num2 = -1;
			for (int i = 0; i < group.Targets.Count; i++)
			{
				EffectTarget effectTarget = group.Targets[i];
				if (flag || Contains(effectTarget.Node.Id, query) || Contains(effectTarget.Location, query))
				{
					int num3 = distances.DistanceTo(effectTarget.Node.Id);
					if (num2 < 0 || num3 < num)
					{
						num = num3;
						num2 = i;
					}
				}
			}
			groupDistances[group] = num;
			nearestTargets[group] = Math.Max(0, num2);
		}

		private int PreferredTarget(EffectGroup group)
		{
			if (chosenTargets.TryGetValue(group, out var value))
			{
				int num = group.Targets.IndexOf(value);
				if (num >= 0)
				{
					return num;
				}
			}
			if (!DistanceEnabled || !nearestTargets.TryGetValue(group, out var value2))
			{
				return 0;
			}
			return value2;
		}

		private static string DistanceText(int distance)
		{
			return distance switch
			{
				int.MaxValue => Words.Get("distance.none"), 
				0 => Words.Get("distance.connected"), 
				_ => distance + Words.Get("distance.nodes"), 
			};
		}

		private void ApplyFilter(bool preserveSelection = false)
		{
			if (DistanceEnabled && distances != null && !distances.Matches(window))
			{
				MarkDirty();
				return;
			}
			string query = (((Object)(object)search == (Object)null) ? "" : search.text.Trim());
			filtered = groups.Where((EffectGroup g) => (filter != 1 || (g.Major && !g.Choice)) && (filter != 2 || g.Choice) && (query.Length == 0 || g.Search.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)).ToList();
			groupDistances.Clear();
			nearestTargets.Clear();
			if (DistanceEnabled && distances != null && distances.Success)
			{
				foreach (EffectGroup item in filtered)
				{
					RankGroup(item, query);
				}
				filtered = filtered.OrderBy((EffectGroup group) => groupDistances[group]).ToList();
			}
			bool flag = selected != null && !filtered.Contains(selected);
			if (flag)
			{
				selected = null;
				targetIndex = 0;
			}
			int num = targetIndex;
			if (selected != null)
			{
				targetIndex = PreferredTarget(selected);
			}
			if (preserveSelection && selected != null)
			{
				int num2 = filtered.IndexOf(selected);
				if (num2 >= 0)
				{
					page = num2 / 7;
				}
			}
			page = Math.Min(page, Math.Max(0, (filtered.Count - 1) / 7));
			RefreshStatus();
			RenderPage();
			SetDetail(preserveSelection && !flag && num == targetIndex);
		}

		private void RenderPage()
		{
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < rows.Count; i++)
			{
				int num = page * 7 + i;
				((Component)rows[i]).gameObject.SetActive(num < filtered.Count);
				if (num < filtered.Count)
				{
					EffectGroup effectGroup = filtered[num];
					int value;
					string text = ((DistanceEnabled && groupDistances.TryGetValue(effectGroup, out value)) ? ("[" + DistanceText(value) + "] ") : "");
					((TMP_Text)rowLabels[i]).text = text + (contentView ? ListContent(effectGroup) : effectGroup.Title) + ((effectGroup.Targets.Count > 1) ? ("  ×" + effectGroup.Targets.Count) : "") + (effectGroup.Choice ? Words.Get("ui.choice") : "");
					((TMP_Text)rowLabels[i]).textWrappingMode = (TextWrappingModes)(contentView ? 1 : 0);
					((TMP_Text)rowLabels[i]).maxVisibleLines = ((!contentView) ? 1 : 2);
					((Graphic)rowLabels[i]).color = ((effectGroup == selected) ? Gold : Body);
				}
			}
			((TMP_Text)pageText).text = page + 1 + " / " + Math.Max(1, (filtered.Count + 7 - 1) / 7);
		}

		private void SelectRow(int row)
		{
			int num = page * 7 + row;
			if (num < filtered.Count)
			{
				selected = filtered[num];
				targetIndex = PreferredTarget(selected);
				RenderPage();
				SetDetail();
			}
		}

		private void ChangeTarget(int delta)
		{
			if (selected != null)
			{
				targetIndex = (targetIndex + delta + selected.Targets.Count) % selected.Targets.Count;
				chosenTargets[selected] = selected.Targets[targetIndex];
				SetDetail();
			}
		}

		private void SetDetail(bool preserveScroll = false)
		{
			//IL_027e: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI componentInChildren = ((Component)route).GetComponentInChildren<TextMeshProUGUI>();
			object text;
			if (!allocatedView)
			{
				EffectTarget target = Target;
				text = ((target != null && target.Held) ? Words.Get("action.choices") : Words.Get("action.route"));
			}
			else
			{
				text = Words.Get("action.details");
			}
			((TMP_Text)componentInChildren).text = (string)text;
			EffectTarget target2 = Target;
			bool interactable;
			if (target2 == null)
			{
				((TMP_Text)detailTitle).text = Words.Get("ui.select_an_effect");
				((TMP_Text)detailLocation).text = "";
				((TMP_Text)detailBody).text = Words.Get("ui.select_an_effect_to_see_its_description_and_nodes");
				((TMP_Text)targetText).text = "";
				((Selectable)route).interactable = false;
				Button obj = previousTarget;
				interactable = (((Selectable)nextTarget).interactable = false);
				((Selectable)obj).interactable = interactable;
				return;
			}
			((TMP_Text)detailTitle).text = selected.Title;
			((TMP_Text)detailLocation).text = target2.Location + "\n" + target2.Node.Id;
			((TMP_Text)detailBody).text = (string.IsNullOrWhiteSpace(selected.Description) ? Words.Get("catalogue.native_details") : selected.Description) + (selected.Choice ? ("\n\n" + Words.Get("ui.choice_effect_alternatives_on_this_node_are_exclusive_review")) : "");
			if (allocatedView)
			{
				TextMeshProUGUI obj2 = detailBody;
				((TMP_Text)obj2).text = ((TMP_Text)obj2).text + "\n\n" + Words.Get("ui.this_entry_belongs_to_an_allocated_node_open_its");
				if (target2.Granted)
				{
					TextMeshProUGUI obj3 = detailBody;
					((TMP_Text)obj3).text = ((TMP_Text)obj3).text + "\n" + Words.Get("ui.this_node_is_granted_by_another_effect_check_its");
				}
			}
			if (DistanceEnabled)
			{
				if (distances != null && distances.Success)
				{
					TextMeshProUGUI val = detailBody;
					((TMP_Text)val).text = ((TMP_Text)val).text + "\n\n" + Words.Get("ui.additional_connections") + DistanceText(distances.DistanceTo(target2.Node.Id)) + Words.Get("ui.selected_location_any_extra_cost_to_purchase_or_change");
				}
				else if (distanceBusy)
				{
					TextMeshProUGUI obj4 = detailBody;
					((TMP_Text)obj4).text = ((TMP_Text)obj4).text + "\n\n" + Words.Get("ui.calculating_distance_any_extra_choice_purchase_or_change_cost");
				}
			}
			if (!preserveScroll)
			{
				((TMP_Text)detailBody).rectTransform.anchoredPosition = Vector2.zero;
			}
			((TMP_Text)targetText).text = string.Format(Words.Get("ui.node_location_0_1"), targetIndex + 1, selected.Targets.Count);
			Button obj5 = previousTarget;
			interactable = (((Selectable)nextTarget).interactable = selected.Targets.Count > 1);
			((Selectable)obj5).interactable = interactable;
			((Selectable)route).interactable = !dirty && !building && (target2.Held || Plugin.RoutingAvailable);
		}

		private void ShowSelected()
		{
			if (SelectionIsCurrent())
			{
				EffectTarget target = Target;
				if (target != null)
				{
					Hide();
					Focus(target.Node);
					window.ShowTooltip(target.Node);
				}
			}
		}

		private void QueueSelected()
		{
			if (!SelectionIsCurrent())
			{
				return;
			}
			EffectTarget target = Target;
			if (target == null)
			{
				return;
			}
			string message;
			Node focus;
			if (allocatedView)
			{
				if (!Catalogue.Held(window, target.Node))
				{
					MarkDirty();
					return;
				}
				Hide();
				Focus(target.Node);
				window.PinTooltip(target.Node);
			}
			else if (Catalogue.Held(window, target.Node) || window.IsPending(target.Node.Id))
			{
				Hide();
				Focus(target.Node);
				window.PinTooltip(target.Node);
			}
			else if (!Plugin.SelectRoute(window, target.Node, out message, out focus))
			{
				((TMP_Text)status).text = message;
			}
			else
			{
				Hide();
				Focus(focus ?? target.Node);
				Plugin.Notify(message);
			}
		}

		private bool SelectionIsCurrent()
		{
			if (dirty || building)
			{
				RefreshStatus();
				return false;
			}
			if (!TreeWindow.IsOpen || window.CraftMode != craft || character != Player.m_localPlayer || progress != Progress.Current)
			{
				Hide();
				return false;
			}
			if (DistanceEnabled && distances != null && !distances.Matches(window))
			{
				MarkDirty();
				return false;
			}
			return true;
		}

		internal void Focus(Node node)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			CNode value;
			Vector2 val = ((window.CraftMode && CraftTree.Nodes.TryGetValue(node.Id, out value)) ? value.Pos : node.Pos);
			FocusNode.Invoke(window, new object[2] { val, 0.85f });
		}

		private void OnDestroy()
		{
			Hide();
			if ((Object)(object)backdrop != (Object)null)
			{
				Object.Destroy((Object)(object)backdrop);
			}
			if ((Object)(object)toggle != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)toggle).gameObject);
			}
		}
	}
	internal sealed class CompatibilityReport
	{
		public bool KnownBuild { get; internal set; }

		public bool Catalogue { get; internal set; }

		public bool Routing { get; internal set; }

		public bool Distance { get; internal set; }

		public string Hash { get; internal set; }

		public string BuildReason { get; internal set; }

		public string CatalogueReason { get; internal set; }

		public string RoutingReason { get; internal set; }

		public string DistanceReason { get; internal set; }
	}
	internal static class Compatibility
	{
		private sealed class ContractSet
		{
			private const BindingFlags Members = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

			private readonly Assembly assembly;

			private readonly Func<string, bool> omit;

			private readonly Dictionary<string, Type> types = new Dictionary<string, Type>(StringComparer.Ordinal);

			private readonly Dictionary<Type, MethodInfo[]> methods = new Dictionary<Type, MethodInfo[]>();

			private readonly List<string> errors = new List<string>();

			internal bool Passed => errors.Count == 0;

			internal string Reason
			{
				get
				{
					if (!Passed)
					{
						return string.Join("; ", errors.Take(6)) + ((errors.Count > 6) ? ("; and " + (errors.Count - 6) + " more missing contracts") : "");
					}
					return "Required native signatures are present.";
				}
			}

			internal ContractSet(Assembly assembly, Func<string, bool> omit)
			{
				this.assembly = assembly;
				this.omit = omit;
			}

			internal void Run(Action<ContractSet> inspect)
			{
				try
				{
					inspect(this);
				}
				catch (Exception ex)
				{
					errors.Add("Metadata inspection failed: " + ex.GetType().Name + ": " + ex.Message);
				}
			}

			internal Type Native(string name)
			{
				string text = "PassiveTree." + name;
				if (!types.TryGetValue(text, out var value))
				{
					types.Add(text, value = assembly?.GetType(text, throwOnError: false));
				}
				return value;
			}

			private static string Label(Type type)
			{
				return type?.FullName ?? "<missing type>";
			}

			private void Require(string key, Func<bool> present, string expected)
			{
				try
				{
					if ((omit != null && omit(key)) || !present())
					{
						errors.Add(key + " [expected " + expected + "]");
					}
				}
				catch (Exception ex)
				{
					errors.Add(key + " [" + ex.GetType().Name + "]");
				}
			}

			internal void Field(Type type, string name, Type expected, bool isStatic = false, bool direct = true)
			{
				Require(Label(type) + "." + name, delegate
				{
					FieldInfo fieldInfo = type?.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					return expected != null && fieldInfo != null && fieldInfo.FieldType == expected && fieldInfo.IsStatic == isStatic && (!direct || fieldInfo.IsPublic);
				}, (isStatic ? "static " : "instance ") + Label(expected) + " field");
			}

			internal void Property(Type type, string name, Type expected, bool isStatic = false, bool direct = true)
			{
				Require(Label(type) + "." + name, delegate
				{
					PropertyInfo propertyInfo = type?.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					MethodInfo methodInfo = propertyInfo?.GetGetMethod(nonPublic: true);
					return expected != null && propertyInfo != null && propertyInfo.PropertyType == expected && propertyInfo.GetIndexParameters().Length == 0 && methodInfo != null && methodInfo.IsStatic == isStatic && (!direct || methodInfo.IsPublic);
				}, (isStatic ? "static " : "instance ") + Label(expected) + " getter");
			}

			internal void Method(Type type, string name, Type result, bool isStatic, bool direct, params Type[] args)
			{
				string key = Label(type) + "." + name + "(" + string.Join(",", args.Select(Label)) + ")";
				Require(key, delegate
				{
					if (type == null || result == null || args.Any((Type t) => t == null))
					{
						return false;
					}
					if (!methods.TryGetValue(type, out var value))
					{
						methods.Add(type, value = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
					}
					return value.Any((MethodInfo method) => method.Name == name && method.IsStatic == isStatic && method.ReturnType == result && (!direct || method.IsPublic) && !method.ContainsGenericParameters && !method.IsAbstract && (from p in method.GetParameters()
						select p.ParameterType).SequenceEqual(args));
				}, (isStatic ? "static " : "instance ") + Label(result) + " method");
			}

			internal void Pending(bool mutation)
			{
				Type type = Native("UI.TreeWindow");
				FieldInfo field = type?.GetField("pendingAdd", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				Require(Label(type) + ".pendingAdd", () => field != null && !field.IsStatic && typeof(IEnumerable<string>).IsAssignableFrom(field.FieldType), "instance IEnumerable<string> field");
				if (mutation)
				{
					Type type2 = field?.FieldType;
					string[] array = new string[3] { "Contains", "Add", "Remove" };
					foreach (string name in array)
					{
						Method(type2, name, typeof(bool), false, false, typeof(string));
					}
					Method(type2, "Clear", typeof(void), false, false);
				}
			}

			internal void EnumValues(Type type, params object[] pairs)
			{
				for (int i = 0; i < pairs.Length; i += 2)
				{
					string name = (string)pairs[i];
					int value = (int)pairs[i + 1];
					Require(Label(type) + "." + name, () => type != null && type.IsEnum && Enum.GetUnderlyingType(type) == typeof(int) && type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetRawConstantValue() is int num && num == value, "enum value " + value);
				}
			}
		}

		internal const string VerifiedHash = "E72B03AD5896B484560D4DE3ADD0EEA307E1566D1F274A8847B38F22D8869F03";

		internal static CompatibilityReport Inspect(string assemblyPath)
		{
			Assembly assembly = typeof(Progress).Assembly;
			string hash = null;
			string text = null;
			try
			{
				if (!string.Equals(Path.GetFullPath(assemblyPath), Path.GetFullPath(assembly.Location), StringComparison.OrdinalIgnoreCase))
				{
					text = "The supplied path is not the loaded PoV assembly; its hash was not used.";
				}
				else
				{
					using FileStream inputStream = File.OpenRead(assemblyPath);
					using SHA256 sHA = SHA256.Create();
					hash = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "");
				}
			}
			catch (Exception ex)
			{
				text = "Could not read the loaded PoV hash: " + ex.Message;
			}
			CompatibilityReport compatibilityReport = InspectAssembly(assembly, hash);
			if (text != null)
			{
				compatibilityReport.BuildReason = text + " Features use their structural results.";
			}
			return compatibilityReport;
		}

		internal static CompatibilityReport InspectAssembly(Assembly native, string hash, Func<string, bool> omit = null)
		{
			CompatibilityReport obj = new CompatibilityReport
			{
				Hash = (hash ?? ""),
				KnownBuild = string.Equals(hash, "E72B03AD5896B484560D4DE3ADD0EEA307E1566D1F274A8847B38F22D8869F03", StringComparison.OrdinalIgnoreCase)
			};
			obj.BuildReason = (obj.KnownBuild ? "Verified PoV 5.1.2 reference build." : "Unverified PoV build. Passing signatures enable individual features; changed behavior is not certified.");
			ContractSet contractSet = new ContractSet(native, omit);
			contractSet.Run(CatalogueContracts);
			obj.Catalogue = contractSet.Passed;
			obj.CatalogueReason = contractSet.Reason;
			ContractSet contractSet2 = new ContractSet(native, omit);
			contractSet2.Run(RouteContracts);
			obj.Routing = contractSet2.Passed;
			obj.RoutingReason = contractSet2.Reason;
			ContractSet contractSet3 = new ContractSet(native, omit);
			contractSet3.Run(DistanceContracts);
			obj.Distance = contractSet3.Passed;
			obj.DistanceReason = contractSet3.Reason;
			return obj;
		}

		private static Type ListOf(Type type)
		{
			return Generic(typeof(List<>), type);
		}

		private static Type EnumerableOf(Type type)
		{
			return Generic(typeof(IEnumerable<>), type);
		}

		private static Type MapOf(Type key, Type value)
		{
			return Generic(typeof(Dictionary<, >), key, value);
		}

		private static Type Generic(Type definition, params Type[] args)
		{
			if (!args.Any((Type t) => t == null))
			{
				return definition.MakeGenericType(args);
			}
			return null;
		}

		private static void SourceContracts(ContractSet c)
		{
			Type type = c.Native("UI.TreeWindow");
			Type type2 = c.Native("Node");
			Type type3 = c.Native("Progress");
			Type type4 = c.Native("CraftProgress");
			Type type5 = c.Native("Tree");
			Type type6 = c.Native("CraftTree");
			Type type7 = c.Native("CraftTree+CNode");
			c.Property(type, "CraftMode", typeof(bool));
			c.Property(type, "IsOpen", typeof(bool), isStatic: true);
			c.Property(type3, "Current", type3, isStatic: true);
			c.Field(type3, "Craft", type4);
			c.Field(type3, "Allocated", typeof(HashSet<string>));
			c.Field(type4, "Taken", typeof(HashSet<string>));
			c.Method(type3, "IsAllocated", typeof(bool), false, true, typeof(string));
			c.Field(type5, "Nodes", MapOf(typeof(string), type2), isStatic: true);
			c.Field(type6, "Nodes", MapOf(typeof(string), type7), isStatic: true);
			c.Property(type6, "SearchNodes", MapOf(typeof(string), type2), isStatic: true);
			c.Field(type2, "Id", typeof(string));
			c.Field(type2, "Broken", typeof(bool));
			c.Field(type2, "Options", ListOf(c.Native("NodeOption")));
			c.Property(type2, "IsMastery", typeof(bool));
			c.Field(type7, "Entry", typeof(bool));
		}

		private static void CatalogueContracts(ContractSet c)
		{
			SourceContracts(c);
			Type type = c.Native("UI.TreeWindow");
			Type type2 = c.Native("Node");
			Type type3 = c.Native("Progress");
			Type type4 = c.Native("CraftProgress");
			Type type5 = c.Native("NodeOption");
			Type type6 = c.Native("Mod");
			Type type7 = c.Native("Stat");
			Type type8 = c.Native("CraftTree+CNode");
			Type type9 = c.Native("GemEffects+AreaFx");
			Type type10 = c.Native("UI.UiKit");
			c.Field(type3, "FreeByAffix", typeof(HashSet<string>));
			c.Property(c.Native("CraftTree"), "Loaded", typeof(bool), isStatic: true);
			c.Field(type, "Instance", type, isStatic: true);
			c.Field(type, "escFrame", typeof(int), isStatic: true, direct: false);
			c.Property(type, "Root", typeof(Transform));
			c.Property(type, "Ready", typeof(bool));
			c.Method(type, "BuildCanvas", typeof(void), false, false);
			c.Method(type, "Update", typeof(void), false, false);
			c.Method(type, "Close", typeof(void), false, true);
			c.Method(type, "SetCraftMode", typeof(void), false, false, typeof(bool));
			c.Method(type, "Bind", typeof(void), false, false, type3);
			c.Method(type, "Refresh", typeof(void), false, true);
			c.Method(type, "RefreshCraftStateNow", typeof(void), false, false);
			c.Method(type, "CraftSearchSource", ListOf(type2), false, false);
			c.Method(type, "CraftShelf", typeof(string), false, false, typeof(string));
			c.Method(type, "ShowAt", typeof(void), false, false, typeof(Vector2), typeof(float));
			c.Method(type, "FocusSearch", typeof(void), false, true, typeof(bool));
			c.Method(type, "HideTooltip", typeof(void), false, true);
			c.Method(type, "ShowTooltip", typeof(void), false, true, type2);
			c.Method(type, "PinTooltip", typeof(void), false, true, type2);
			c.Method(type, "IsPending", typeof(bool), false, true, typeof(string));
			c.Method(type, "PendingChoiceOf", typeof(int), false, true, typeof(string));
			c.Method(type3, "ChoiceOf", typeof(int), false, true, typeof(string));
			c.Method(type3, "MasteryOptionSelected", typeof(bool), false, true, typeof(string), typeof(int));
			c.Method(type4, "ChoiceOf", typeof(int), false, true, typeof(string));
			c.Method(type4, "UsedElsewhere", typeof(bool), false, true, type2, typeof(int), typeof(string));
			c.Field(type3, "AreaFx", type9);
			c.Field(type9, "Muted", typeof(HashSet<string>));
			c.Field(type9, "Granted", typeof(HashSet<string>));
			c.Field(type2, "Mods", ListOf(type6));
			c.Field(type2, "Extra", typeof(string));
			c.Field(type2, "Pos", typeof(Vector2));
			string[] array = new string[3] { "Title", "Description", "DisplayCategory" };
			foreach (string name in array)
			{
				c.Property(type2, name, typeof(string));
			}
			c.Property(type2, "IsSmall", typeof(bool));
			c.Field(type8, "Name", typeof(string));
			c.Field(type8, "Pos", typeof(Vector2));
			c.Field(type5, "Mods", ListOf(type6));
			c.Field(type5, "Broken", typeof(bool));
			c.Property(type5, "Closed", typeof(bool));
			array = new string[3] { "Title", "Text", "Told" };
			foreach (string name2 in array)
			{
				c.Property(type5, name2, typeof(string));
			}
			c.Method(c.Native("TreeWords"), "Say", typeof(string), true, true, typeof(string));
			c.Method(c.Native("L"), "Id", typeof(string), true, true, typeof(string), typeof(string));
			c.Method(c.Native("L"), "CurrentLanguage", typeof(string), true, true);
			c.Method(c.Native("CraftMasteries"), "Works", typeof(bool), true, true, type5);
			c.Method(c.Native("CraftMasteries"), "TryFlag", typeof(bool), true, true, type5, type7?.MakeByRefType());
			c.Method(c.Native("CraftMasteries"), "TryLines", typeof(bool), true, true, type5, type6?.MakeArrayType().MakeByRefType());
			c.Method(c.Native("CraftMasterySwitches"), "Off", typeof(bool), true, true, type5);
			c.Method(c.Native("StatText"), "Describe", typeof(string), true, true, type7, typeof(float));
			c.Method(c.Native("StatText"), "DescribeAll", typeof(string), true, true, EnumerableOf(type6));
			c.Property(type10, "ArtPanel", typeof(Sprite), isStatic: true);
			c.Method(type10, "Go", typeof(GameObject), true, true, typeof(string), typeof(Transform));
			c.Method(type10, "Rect", typeof(RectTransform), true, true, typeof(GameObject), typeof(Vector2), typeof(Vector2), typeof(Vector2), typeof(Vector2?));
			c.Method(type10, "Stretch", typeof(RectTransform), true, true, typeof(GameObject), typeof(float), typeof(float), typeof(float), typeof(float));
			c.Method(type10, "Text", typeof(TextMeshProUGUI), true, true, typeof(Transform), typeof(string), typeof(string), typeof(float), typeof(Color), typeof(TextAlignmentOptions), typeof(bool));
			c.Method(type10, "Button", typeof(Button), true, true, typeof(Transform), typeof(string), typeof(Vector2), typeof(Vector2), typeof(Vector2), typeof(UnityAction), typeof(float), typeof(bool), typeof(Color?));
		}

		private static void ReadStateContracts(ContractSet c)
		{
			SourceContracts(c);
			Type type = c.Native("UI.TreeWindow");
			Type type2 = c.Native("Progress");
			c.Property(type, "RespecMode", typeof(bool));
			c.Field(type, "bound", type2, isStatic: false, direct: false);
			c.Pending(mutation: false);
			c.Field(type, "pendingChoice", typeof(Dictionary<string, int>), isStatic: false, direct: false);
			c.Field(type, "refundSel", typeof(HashSet<string>), isStatic: false, direct: false);
			c.Field(type, "refundOpt", typeof(HashSet<string>), isStatic: false, direct: false);
			c.Field(type, "switchOpt", typeof(Dictionary<string, int>), isStatic: false, direct: false);
			c.Field(type, "pendingAreas", typeof(HashSet<string>), isStatic: false, direct: false);
			c.Field(type, "applyingBatch", typeof(bool), isStatic: false, direct: false);
			c.Field(type, "craftBuilding", typeof(bool), isStatic: false, direct: false);
			c.Field(type2, "Choices", typeof(Dictionary<string, int>));
			c.Field(c.Native("CraftProgress"), "Choice", typeof(Dictionary<string, string>));
		}

		private static void GraphContracts(ContractSet c)
		{
			Type type = c.Native("UI.TreeWindow");
			Type type2 = c.Native("Node");
			Type type3 = c.Native("Progress");
			Type type4 = c.Native("CraftTree+CNode");
			Type type5 = c.Native("NodeSize");
			Type type6 = c.Native("Tree");
			Type type7 = c.Native("TreePaths+Def");
			c.EnumValues(type5, "Root", 0, "Mastery", 4, "Travel", 5, "Cross", 6);
			c.Field(type2, "Size", type5);
			c.Field(type2, "MasteryUnlocks", typeof(List<string>));
			c.Field(type4, "Size", type5);
			c.Field(type4, "Prof", typeof(string));
			c.Property(type3, "GatesTaken", typeof(int));
			c.Property(type3, "GateLimit", typeof(int));
			c.Field(type6, "FreeGates", typeof(bool), isStatic: true);
			c.Method(type6, "Around", typeof(IEnumerable<string>), true, true, type2);
			c.Property(c.Native("TreePaths"), "Chosen", type7, isStatic: true);
			c.Method(c.Native("TreePaths"), "GateOf", typeof(string), true, true, type7);
			c.Method(c.Native("TreeAreas"), "Visible", typeof(bool), true, true, type2, type3);
			c.Field(c.Native("CraftTree"), "Links", typeof(List<KeyValuePair<string, string>>), isStatic: true);
			c.Method(c.Native("CraftProgress"), "MasteredAccess", typeof(bool), false, true, typeof(string));
			c.Method(type, "NeighbourTaken", typeof(bool), false, false, type2);
			c.Method(type, "CraftSeen", typeof(bool), false, false, type4);
		}

		private static void ChoiceContracts(ContractSet c)
		{
			Type type = c.Native("UI.TreeWindow");
			Type type2 = c.Native("Node");
			Type type3 = c.Native("NodeOption");
			c.Method(c.Native("Progress"), "TwoStep", typeof(bool), true, true, type2);
			c.Method(c.Native("CraftProgress"), "FirstFree", typeof(int), false, true, type2, typeof(string));
			c.Method(c.Native("CraftMasteries"), "Works", typeof(bool), true, true, type3);
			c.Property(type3, "Closed", typeof(bool));
			c.Field(type3, "Broken", typeof(bool));
			c.Method(type, "NothingToChoose", typeof(bool), false, true, type2);
			c.Method(type, "MainOptionTaken", typeof(bool), false, false, type2, typeof(int), typeof(string));
			c.Method(type, "CraftOptionTaken", typeof(bool), false, false, type2, typeof(int), typeof(string));
			c.Method(type, "SessionTravelChoice", typeof(bool), false, false, type2, typeof(int).MakeByRefType());
		}

		private static void RouteContracts(ContractSet c)
		{
			ReadStateContracts(c);
			GraphContracts(c);
			ChoiceContracts(c);
			Type type = c.Native("UI.TreeWindow");
			Type type2 = c.Native("Node");
			c.Property(c.Native("Tree"), "Gates", typeof(IEnumerable<string>), isStatic: true);
			c.Pending(mutation: true);
			c.Property(type, "PendingPointsLeft", typeof(int), isStatic: false, direct: false);
			c.Field(type, "rootedPFrame", typeof(int), isStatic: false, direct: false);
			c.Field(type, "gatesFrame", typeof(int), isStatic: false, direct: false);
			c.Method(type, "IsPending", typeof(bool), false, true, typeof(string));
			c.Method(type, "CanQueue", typeof(bool), false, true, type2);
			c.Method(type, "Queue", typeof(void), false, true, type2);
			c.Method(type, "QueueWithChoice", typeof(void), false, true, type2, typeof(int));
			c.Method(type, "NodeClicked", typeof(void), false, true, type2, typeof(bool), typeof(bool));
			c.Method(type, "PinTooltip", typeof(void), false, true, type2);
			c.Method(type, "CraftQueued", typeof(void), false, false, c.Native("CraftTree+CNode"), typeof(bool));
			string[] array = new string[4] { "CraftClosePending", "RefreshCraftState", "RefreshConfirmBar", "CraftHeader" };
			foreach (string name in array)
			{
				c.Method(type, name, typeof(void), false, false);
			}
			c.Method(type, "Refresh", typeof(void), false, true);
		}

		private static void DistanceContracts(ContractSet c)
		{
			ReadStateContracts(c);
			GraphContracts(c);
			ChoiceContracts(c);
			Type type = c.Native("UI.TreeWindow");
			Type type2 = c.Native("Progress");
			Type type3 = c.Native("Node");
			c.Field(type2, "FreeByAffix", typeof(HashSet<string>));
			c.Field(type, "craftTaken", typeof(HashSet<string>), isStatic: false, direct: false);
			c.Field(type, "craftAdj", typeof(Dictionary<string, List<string>>), isStatic: false, direct: false);
			c.Method(type, "CraftHeldOrPending", typeof(bool), false, false, typeof(string));
			c.Method(type, "GatesAfterPending", typeof(int), false, true);
			c.Field(type3, "Links", typeof(List<string>));
			c.Field(type2, "FreeAllocation", typeof(bool), isStatic: true);
			c.Property(type2, "JewelGates", typeof(HashSet<string>));
			c.Method(c.Native("Tree"), "IsGate", typeof(bool), true, true, typeof(string));
			c.Method(c.Native("TreeAreas"), "FreeAllocatable", typeof(bool), true, true, type2, type3);
			c.Property(c.Native("CraftTree"), "Generation", typeof(int), isStatic: true);
		}
	}
	public static class ConfigStorage
	{
		public const string FileName = "PoVTreeHelper.cfg";

		public static ConfigFile Attach(BaseUnityPlugin plugin, Action<string> warning)
		{
			if (plugin == null)
			{
				throw new ArgumentNullException("plugin");
			}
			ConfigFile config = plugin.Config;
			try
			{
				FieldInfo field = typeof(BaseUnityPlugin).GetField("<Config>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
				if (field == null || field.FieldType != typeof(ConfigFile) || field.IsStatic)
				{
					throw new NotSupportedException("BepInEx plugin configuration storage is unavailable.");
				}
				ConfigFile val = Open(config, plugin.Info.Metadata, warning);
				if (val != config)
				{
					field.SetValue(plugin, val);
				}
				if (plugin.Config != val)
				{
					throw new NotSupportedException("BepInEx did not retain the selected configuration.");
				}
				return plugin.Config;
			}
			catch (Exception ex)
			{
				if (config != null)
				{
					config.SaveOnConfigSet = false;
				}
				Warn(warning, "Cannot attach PoVTreeHelper.cfg; retaining the previous configuration without automatic saving. " + ex.Message);
				return config;
			}
		}

		public static ConfigFile Open(ConfigFile previous, BepInPlugin metadata, Action<string> warning)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Expected O, but got Unknown
			if (previous == null)
			{
				throw new ArgumentNullException("previous");
			}
			try
			{
				string text = Path.Combine(Path.GetDirectoryName(previous.ConfigFilePath), "PoVTreeHelper.cfg");
				if (string.Equals(Path.GetFullPath(text), Path.GetFullPath(previous.ConfigFilePath), StringComparison.Ordinal))
				{
					return previous;
				}
				if (Directory.Exists(text))
				{
					throw new IOException("The configuration path is a directory.");
				}
				if (!File.Exists(text) && File.Exists(previous.ConfigFilePath))
				{
					CopyLegacy(previous.ConfigFilePath, text);
				}
				return new ConfigFile(text, false, metadata)
				{
					SaveOnConfigSet = previous.SaveOnConfigSet
				};
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is SecurityException)
			{
				previous.SaveOnConfigSet = false;
				Warn(warning, "Cannot open PoVTreeHelper.cfg; using the previous configuration without automatic saving. " + ex.Message);
				return previous;
			}
		}

		private static void CopyLegacy(string source, string destination)
		{
			string text = destination + ".migrating-" + Guid.NewGuid().ToString("N");
			bool flag = false;
			try
			{
				using (FileStream fileStream = File.OpenRead(source))
				{
					using FileStream destination2 = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None);
					flag = true;
					fileStream.CopyTo(destination2);
				}
				try
				{
					File.Move(text, destination);
				}
				catch (IOException)
				{
					if (!File.Exists(destination))
					{
						throw;
					}
				}
			}
			finally
			{
				if (flag && File.Exists(text))
				{
					try
					{
						File.Delete(text);
					}
					catch (IOException)
					{
					}
					catch (UnauthorizedAccessException)
					{
					}
				}
			}
		}

		private static void Warn(Action<string> warning, string message)
		{
			try
			{
				warning?.Invoke(message);
			}
			catch
			{
			}
		}
	}
	internal sealed class DistanceRanking
	{
		internal bool Success;

		internal string Error;

		internal int Processed;

		internal int Total;

		internal Dictionary<string, int> Distances = new Dictionary<string, int>(StringComparer.Ordinal);

		private static readonly Type WindowType = typeof(TreeWindow);

		private static readonly FieldInfo Bound = AccessTools.Field(WindowType, "bound");

		private static readonly FieldInfo PendingAdd = AccessTools.Field(WindowType, "pendingAdd");

		private static readonly FieldInfo PendingChoice = AccessTools.Field(WindowType, "pendingChoice");

		private static readonly FieldInfo RefundSel = AccessTools.Field(WindowType, "refundSel");

		private static readonly FieldInfo RefundOpt = AccessTools.Field(WindowType, "refundOpt");

		private static readonly FieldInfo SwitchOpt = AccessTools.Field(WindowType, "switchOpt");

		private static readonly FieldInfo PendingAreas = AccessTools.Field(WindowType, "pendingAreas");

		private static readonly FieldInfo Applying = AccessTools.Field(WindowType, "applyingBatch");

		private static readonly FieldInfo CraftBuilding = AccessTools.Field(WindowType, "craftBuilding");

		private static readonly FieldInfo CraftTaken = AccessTools.Field(WindowType, "craftTaken");

		private static readonly FieldInfo CraftAdj = AccessTools.Field(WindowType, "craftAdj");

		private static readonly MethodInfo NeighbourTaken = AccessTools.Method(WindowType, "NeighbourTaken", new Type[1] { typeof(Node) }, (Type[])null);

		private static readonly MethodInfo CraftSeen = AccessTools.Method(WindowType, "CraftSeen", new Type[1] { typeof(CNode) }, (Type[])null);

		private static readonly MethodInfo CraftHeldOrPending = AccessTools.Method(WindowType, "CraftHeldOrPending", new Type[1] { typeof(string) }, (Type[])null);

		private static readonly long SliceTicks = Math.Max(1L, Stopwatch.Frequency * 3 / 1000);

		private TreeWindow ownerWindow;

		private Progress owner;

		private CraftProgress craftOwner;

		private bool craftMode;

		private bool freeGates;

		private bool freeAllocation;

		private int sourceCount;

		private int craftGeneration;

		private int nativeCraftCount;

		private int gatesTaken;

		private int gatesAfterPending;

		private int gateLimit;

		private object chosenPath;

		private string selectedGate;

		private IDictionary<string, Node> source;

		private Dictionary<string, List<string>> craftAdjacency;

		private HashSet<string> held;

		private HashSet<string> already;

		private HashSet<string> pending;

		private HashSet<string> freeByAffix;

		private HashSet<string> jewelGates;

		private HashSet<string> nativeCraftTaken;

		private string[] pendingOrder;

		private Dictionary<string, int> pendingChoices;

		private Dictionary<string, int> mainChoices;

		private Dictionary<string, string> craftChoices;

		private readonly Dictionary<string, bool> craftHeldCache = new Dictionary<string, bool>(StringComparer.Ordinal);

		internal static bool NativeContractsPresent
		{
			get
			{
				if (RoutePlanner.ChoiceContractsPresent && Bound != null && Bound.FieldType == typeof(Progress) && PendingAdd != null && typeof(IEnumerable<string>).IsAssignableFrom(PendingAdd.FieldType) && PendingChoice != null && PendingChoice.FieldType == typeof(Dictionary<string, int>) && RefundSel != null && RefundSel.FieldType == typeof(HashSet<string>) && RefundOpt != null && RefundOpt.FieldType == typeof(HashSet<string>) && SwitchOpt != null && SwitchOpt.FieldType == typeof(Dictionary<string, int>) && PendingAreas != null && PendingAreas.FieldType == typeof(HashSet<string>) && Applying != null && Applying.FieldType == typeof(bool) && CraftBuilding != null && CraftBuilding.FieldType == typeof(bool) && CraftTaken != null && CraftTaken.FieldType == typeof(HashSet<string>) && CraftAdj != null && CraftAdj.FieldType == typeof(Dictionary<string, List<string>>) && NeighbourTaken != null && NeighbourTaken.ReturnType == typeof(bool) && CraftSeen != null && CraftSeen.ReturnType == typeof(bool) && CraftHeldOrPending != null)
				{
					return CraftHeldOrPending.ReturnType == typeof(bool);
				}
				return false;
			}
		}

		internal int DistanceTo(string id)
		{
			if (!Success || id == null || !Distances.TryGetValue(id, out var value))
			{
				return int.MaxValue;
			}
			return value;
		}

		internal IEnumerator Build(TreeWindow window)
		{
			Success = false;
			Error = null;
			DistanceRanking distanceRanking = this;
			DistanceRanking distanceRanking2 = this;
			int processed = 0;
			distanceRanking2.Total = 0;
			distanceRanking.Processed = processed;
			Distances.Clear();
			craftHeldCache.Clear();
			IEnumerator work = BuildCore(window);
			try
			{
				while (true)
				{
					bool flag = false;
					try
					{
						flag = work.MoveNext();
					}
					catch (Exception innerException)
					{
						while (innerException is TargetInvocationException && innerException.InnerException != null)
						{
							innerException = innerException.InnerException;
						}
						Error = Words.Get("distance.could_not_calculate_node_distances") + innerException.Message;
						Success = false;
						Distances.Clear();
					}
					if (flag && Error == null)
					{
						yield return work.Current;
						continue;
					}
					break;
				}
			}
			finally
			{
				IDisposable disposable = work as IDisposable;
				if (disposable != null)
				{
					disposable.Dispose();
				}
			}
		}

		private IEnumerator BuildCore(TreeWindow window)
		{
			Capture(window);
			Dictionary<string, RouteVertex> graph = new Dictionary<string, RouteVertex>(StringComparer.Ordinal);
			List<string> frontier = new List<string>();
			HashSet<string> terminals = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> zero = new HashSet<string>(already, StringComparer.Ordinal);
			Total = Math.Max(1, sourceCount * 6 + (craftMode ? CraftTree.Links.Count : 0) + already.Count);
			Stopwatch slice = Stopwatch.StartNew();
			foreach (KeyValuePair<string, Node> item in source)
			{
				Node value = item.Value;
				if (value != null && !value.Broken && (int)value.Size != 0)
				{
					bool flag = !already.Contains(item.Key);
					if (craftMode)
					{
						if (!CraftTree.Nodes.TryGetValue(item.Key, out var value2))
						{
							flag = false;
						}
						else if (value2.Entry || (int)value2.Size == 0)
						{
							zero.Add(item.Key);
							flag = false;
						}
						else if (flag)
						{
							if (value2.Prof == "road3" && (int)value2.Size != 5 && (int)value2.Size != 6)
							{
								flag = false;
							}
							else if (!(bool)CraftSeen.Invoke(window, new object[1] { value2 }) || craftOwner.MasteredAccess(item.Key))
							{
								flag = false;
							}
						}
					}
					else if (owner.IsAllocated(item.Key))
					{
						zero.Add(item.Key);
						flag = false;
					}
					else if (flag)
					{
						if (!TreeAreas.Visible(value, owner))
						{
							flag = false;
						}
						else if (Tree.IsGate(item.Key) && (freeGates || gatesTaken >= gateLimit || item.Key != selectedGate))
						{
							flag = false;
						}
						else if ((int)value.Size != 4 && value.MasteryUnlocks != null && !value.MasteryUnlocks.Any(already.Contains))
						{
							flag = false;
						}
					}
					if (flag)
					{
						graph.Add(item.Key, new RouteVertex
						{
							Id = item.Key,
							Expand = ((int)value.Size != 4)
						});
						bool rooted = true;
						if (craftMode ? CraftFrontier(item.Key) : MainFrontier(value, out rooted))
						{
							frontier.Add(item.Key);
							if (!craftMode && !rooted)
							{
								terminals.Add(item.Key);
							}
						}
					}
				}
				Processed++;
				if (slice.ElapsedTicks >= SliceTicks)
				{
					yield return null;
					slice.Restart();
					EnsureIdentity(window);
				}
			}
			if (craftMode)
			{
				foreach (KeyValuePair<string, string> link in CraftTree.Links)
				{
					if (graph.TryGetValue(link.Key, out var value3) && graph.ContainsKey(link.Value))
					{
						value3.Next.Add(link.Value);
					}
					if (graph.TryGetValue(link.Value, out var value4) && graph.ContainsKey(link.Key))
					{
						value4.Next.Add(link.Key);
					}
					Processed++;
					if (slice.ElapsedTicks >= SliceTicks)
					{
						yield return null;
						slice.Restart();
						EnsureIdentity(window);
					}
				}
			}
			else
			{
				foreach (RouteVertex vertex in graph.Values)
				{
					foreach (string item2 in Tree.Around(source[vertex.Id]))
					{
						if (graph.ContainsKey(item2))
						{
							vertex.Next.Add(item2);
						}
						if (slice.ElapsedTicks >= SliceTicks)
						{
							yield return null;
							slice.Restart();
							EnsureIdentity(window);
						}
					}
					Processed++;
				}
			}
			DistanceWalk reachable = new DistanceWalk(graph, frontier, terminals);
			while (reachable.Step())
			{
				Processed++;
				if (slice.ElapsedTicks >= SliceTicks)
				{
					yield return null;
					slice.Restart();
					EnsureIdentity(window);
				}
			}
			foreach (string key in reachable.Distances.Keys)
			{
				if (!RoutePlanner.DistanceNodeUsable(window, source[key]))
				{
					graph.Remove(key);
				}
				Processed++;
				if (slice.ElapsedTicks >= SliceTicks)
				{
					yield return null;
					slice.Restart();
					EnsureIdentity(window);
				}
			}
			DistanceWalk walk = new DistanceWalk(graph, frontier, terminals);
			while (walk.Step())
			{
				Processed++;
				if (slice.ElapsedTicks >= SliceTicks)
				{
					yield return null;
					slice.Restart();
					EnsureIdentity(window);
				}
			}
			foreach (string item3 in zero)
			{
				if (source.ContainsKey(item3))
				{
					walk.Distances[item3] = 0;
				}
				Processed++;
				if (slice.ElapsedTicks >= SliceTicks)
				{
					yield return null;
					slice.Restart();
					EnsureIdentity(window);
				}
			}
			if (!StateMatches(window))
			{
				throw Changed();
			}
			Distances = walk.Distances;
			Total = Math.Max(Total, Processed);
			Processed = Total;
			Success = true;
		}

		private void Capture(TreeWindow window)
		{
			if (!Plugin.DistanceAvailable || !NativeContractsPresent)
			{
				throw new InvalidOperationException(Words.Get("distance.distance_sorting_is_not_verified_for_this_pov_build"));
			}
			if (window == null || !TreeWindow.IsOpen || Progress.Current == null || Bound.GetValue(window) != Progress.Current)
			{
				throw new InvalidOperationException(Words.Get("distance.open_the_current_character_s_skill_or_crafting_tree"));
			}
			EnsureReady(window);
			ownerWindow = window;
			owner = Progress.Current;
			craftMode = window.CraftMode;
			craftOwner = (craftMode ? owner.Craft : null);
			if (craftMode && craftOwner == null)
			{
				throw Changed();
			}
			source = (craftMode ? CraftTree.SearchNodes : Tree.Nodes);
			sourceCount = source.Count;
			craftGeneration = CraftTree.Generation;
			chosenPath = (craftMode ? null : TreePaths.Chosen);
			if (!craftMode && chosenPath == null)
			{
				throw new InvalidOperationException(Words.Get("distance.choose_your_starting_path_in_pov_first"));
			}
			selectedGate = (craftMode ? null : TreePaths.GateOf(TreePaths.Chosen));
			pendingOrder = ((IEnumerable<string>)PendingAdd.GetValue(window)).ToArray();
			pending = new HashSet<string>(pendingOrder, StringComparer.Ordinal);
			held = new HashSet<string>(craftMode ? craftOwner.Taken : owner.Allocated, StringComparer.Ordinal);
			already = new HashSet<string>(held, StringComparer.Ordinal);
			already.UnionWith(pending);
			pendingChoices = new Dictionary<string, int>((Dictionary<string, int>)PendingChoice.GetValue(window), StringComparer.Ordinal);
			mainChoices = (craftMode ? null : new Dictionary<string, int>(owner.Choices, StringComparer.Ordinal));
			craftChoices = (craftMode ? new Dictionary<string, string>(craftOwner.Choice, StringComparer.Ordinal) : null);
			freeByAffix = new HashSet<string>(owner.FreeByAffix, StringComparer.Ordinal);
			jewelGates = new HashSet<string>(owner.JewelGates, StringComparer.Ordinal);
			freeGates = Tree.FreeGates;
			freeAllocation = Progress.FreeAllocation;
			gateLimit = owner.GateLimit;
			gatesTaken = ((!craftMode) ? owner.GatesTaken : 0);
			gatesAfterPending = ((!craftMode) ? window.GatesAfterPending() : 0);
			if (craftMode)
			{
				nativeCraftTaken = new HashSet<string>((HashSet<string>)CraftTaken.GetValue(window), StringComparer.Ordinal);
				nativeCraftCount = CraftTree.Nodes.Count;
				craftAdjacency = (Dictionary<string, List<string>>)CraftAdj.GetValue(window);
			}
		}

		private bool MainFrontier(Node n, out bool rooted)
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Invalid comparison between Unknown and I4
			rooted = false;
			if (owner.IsAllocated(n.Id) || pending.Contains(n.Id) || (freeGates && Tree.IsGate(n.Id)) || chosenPath == null)
			{
				return false;
			}
			if (gatesTaken == 0 && freeGates)
			{
				bool flag = false;
				bool flag2 = false;
				foreach (string link in n.Links)
				{
					if (Tree.IsGate(link) && link != selectedGate && !jewelGates.Contains(link))
					{
						flag = true;
					}
					if (pending.Contains(link) || (held.Contains(link) && link != "root"))
					{
						flag2 = true;
					}
				}
				if (flag && !flag2 && !n.Links.Contains(selectedGate))
				{
					return false;
				}
			}
			if ((int)n.Size != 4 && n.MasteryUnlocks != null && !n.MasteryUnlocks.Any((string id) => pending.Contains(id) || owner.IsAllocated(id)))
			{
				return false;
			}
			if (!freeAllocation && !freeGates && Tree.IsGate(n.Id) && gatesAfterPending >= gateLimit)
			{
				return false;
			}
			rooted = (bool)NeighbourTaken.Invoke(ownerWindow, new object[1] { n });
			if (!(freeAllocation | rooted))
			{
				return TreeAreas.FreeAllocatable(owner, n);
			}
			return true;
		}

		private bool CraftFrontier(string id)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Invalid comparison between Unknown and I4
			if (nativeCraftTaken.Contains(id) || pending.Contains(id) || !craftAdjacency.TryGetValue(id, out var value))
			{
				return false;
			}
			foreach (string item in value)
			{
				if (CraftTree.Nodes.TryGetValue(item, out var value2) && (int)value2.Size != 4)
				{
					if (!craftHeldCache.TryGetValue(item, out var value3))
					{
						value3 = (bool)CraftHeldOrPending.Invoke(ownerWindow, new object[1] { item });
						craftHeldCache.Add(item, value3);
					}
					if (value3)
					{
						return true;
					}
				}
			}
			return false;
		}

		internal bool Matches(TreeWindow window)
		{
			if (!Success)
			{
				return false;
			}
			try
			{
				return StateMatches(window);
			}
			catch
			{
				return false;
			}
		}

		private bool StateMatches(TreeWindow window)
		{
			EnsureIdentity(window);
			if (!pendingOrder.SequenceEqual((IEnumerable<string>)PendingAdd.GetValue(window)) || !SameMap(pendingChoices, (Dictionary<string, int>)PendingChoice.GetValue(window)) || !held.SetEquals(craftMode ? craftOwner.Taken : owner.Allocated) || !freeByAffix.SetEquals(owner.FreeByAffix) || !jewelGates.SetEquals(owner.JewelGates))
			{
				return false;
			}
			if (craftMode)
			{
				if (nativeCraftTaken.SetEquals((HashSet<string>)CraftTaken.GetValue(window)))
				{
					return SameMap(craftChoices, craftOwner.Choice);
				}
				return false;
			}
			return SameMap(mainChoices, owner.Choices);
		}

		private void EnsureIdentity(TreeWindow window)
		{
			if (!Plugin.DistanceAvailable || ownerWindow != window || owner != Progress.Current || Bound.GetValue(window) != owner || window.CraftMode != craftMode || source != (craftMode ? CraftTree.SearchNodes : Tree.Nodes) || source.Count != sourceCount || CraftTree.Generation != craftGeneration || chosenPath != (craftMode ? null : TreePaths.Chosen) || Tree.FreeGates != freeGates || Progress.FreeAllocation != freeAllocation || owner.GateLimit != gateLimit || (craftMode && (craftOwner != owner.Craft || nativeCraftCount != CraftTree.Nodes.Count || craftAdjacency != CraftAdj.GetValue(window))))
			{
				throw Changed();
			}
			EnsureReady(window);
		}

		private static void EnsureReady(TreeWindow window)
		{
			if ((bool)Applying.GetValue(window) || (bool)CraftBuilding.GetValue(window) || window.RespecMode || ((HashSet<string>)RefundSel.GetValue(window)).Count != 0 || ((HashSet<string>)RefundOpt.GetValue(window)).Count != 0 || ((Dictionary<string, int>)SwitchOpt.GetValue(window)).Count != 0 || ((HashSet<string>)PendingAreas.GetValue(window)).Count != 0)
			{
				throw new InvalidOperationException(Words.Get("distance.finish_loading_applying_changes_or_cancelling_refunds_before_sorting"));
			}
		}

		private static bool SameMap<T>(IDictionary<string, T> before, IDictionary<string, T> now)
		{
			if (before == null || now == null || before.Count != now.Count)
			{
				return false;
			}
			foreach (KeyValuePair<string, T> item in before)
			{
				if (!now.TryGetValue(item.Key, out var value) || !EqualityComparer<T>.Default.Equals(item.Value, value))
				{
					return false;
				}
			}
			return true;
		}

		private static InvalidOperationException Changed()
		{
			return new InvalidOperationException(Words.Get("distance.the_tree_or_selection_changed_sort_by_distance_again"));
		}
	}
	internal static class LanguagePack
	{
		private static readonly Regex Key = new Regex("^[a-zA-Z0-9_.-]+$", RegexOptions.Compiled);

		private static readonly Regex Argument = new Regex("(?<!\\{)\\{(\\d+)(?:,-?\\d+)?(?::[^{}]*)?\\}(?!\\})", RegexOptions.Compiled);

		internal static string SelectLanguage(string language)
		{
			if (!string.Equals(language?.Trim(), "Korean", StringComparison.OrdinalIgnoreCase))
			{
				return "english";
			}
			return "korean";
		}

		internal static Dictionary<string, string> Read(TextReader reader)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			int num = 0;
			string text;
			while ((text = reader.ReadLine()) != null)
			{
				num++;
				if (num == 1)
				{
					text = text.TrimStart('\ufeff');
				}
				if (!string.IsNullOrWhiteSpace(text) && !text.TrimStart().StartsWith("#", StringComparison.Ordinal))
				{
					int num2 = text.IndexOf('\t');
					if (num2 < 1)
					{
						throw new FormatException("Missing translation separator at line " + num);
					}
					string text2 = text.Substring(0, num2).Trim();
					if (text2.StartsWith("@", StringComparison.Ordinal))
					{
						text2 = text2.Substring(1);
					}
					if (!Key.IsMatch(text2) || dictionary.ContainsKey(text2))
					{
						throw new FormatException("Invalid or duplicate translation key at line " + num);
					}
					dictionary.Add(text2, Unescape(text.Substring(num2 + 1)));
				}
			}
			return dictionary;
		}

		private static string Unescape(string value)
		{
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			for (int i = 0; i < value.Length; i++)
			{
				char c = value[i];
				if (c != '\\')
				{
					stringBuilder.Append(c);
					continue;
				}
				if (++i == value.Length)
				{
					throw new FormatException("Incomplete translation escape.");
				}
				switch (value[i])
				{
				case '\\':
					stringBuilder.Append('\\');
					break;
				case 'n':
					stringBuilder.Append('\n');
					break;
				case 'r':
					stringBuilder.Append('\r');
					break;
				case 't':
					stringBuilder.Append('\t');
					break;
				default:
					throw new FormatException("Unsupported translation escape.");
				}
			}
			return stringBuilder.ToString();
		}

		internal static bool Compatible(string original, string translated)
		{
			if (string.IsNullOrWhiteSpace(translated))
			{
				return false;
			}
			string[] array = (from Match m in Argument.Matches(original)
				select m.Groups[1].Value).OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray();
			string[] second = (from Match m in Argument.Matches(translated)
				select m.Groups[1].Value).OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray();
			if (!array.SequenceEqual(second))
			{
				return false;
			}
			try
			{
				int num = ((array.Length == 0) ? (-1) : array.Max((string x) => int.Parse(x, CultureInfo.InvariantCulture)));
				if (num > 16)
				{
					return false;
				}
				string.Format(CultureInfo.InvariantCulture, translated, Enumerable.Repeat((object)0, num + 1).ToArray());
				return true;
			}
			catch (FormatException)
			{
				return false;
			}
			catch (OverflowException)
			{
				return false;
			}
		}

		internal static void Overlay(Dictionary<string, string> target, IDictionary<string, string> candidate, IDictionary<string, string> baseline)
		{
			foreach (KeyValuePair<string, string> item in candidate)
			{
				if (baseline.TryGetValue(item.Key, out var value) && Compatible(value, item.Value))
				{
					target[item.Key] = item.Value;
				}
			}
		}
	}
	[BepInPlugin("vmxlf.povtreeplanner", "PoV Tree Helper", "0.3.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "vmxlf.povtreeplanner";

		public const string VerifiedPoV = "E72B03AD5896B484560D4DE3ADD0EEA307E1566D1F274A8847B38F22D8869F03";

		internal static ConfigEntry<bool> CatalogueEnabled;

		internal static ConfigEntry<bool> AutoRouteOnClick;

		internal static ConfigEntry<bool> EffectContentView;

		internal static ConfigEntry<bool> SortByDistance;

		internal static bool CatalogueAvailable;

		internal static bool RoutingAvailable;

		internal static bool DistanceAvailable;

		private static Plugin instance;

		private static bool nativeClick;

		private static readonly HashSet<string> reported = new HashSet<string>(StringComparer.Ordinal);

		private static readonly FieldInfo EscapeFrame = AccessTools.Field(typeof(TreeWindow), "escFrame");

		private Harmony catalogueHarmony;

		private Harmony routingHarmony;

		private bool languageHooked;

		private void Awake()
		{
			instance = this;
			ConfigFile obj = ConfigStorage.Attach((BaseUnityPlugin)(object)this, delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			});
			Words.RefreshLanguage();
			CatalogueEnabled = obj.Bind<bool>("Features", "UnallocatedEffects", true, Words.Get("config.catalogue"));
			AutoRouteOnClick = obj.Bind<bool>("Features", "AutoRouteOnClick", true, Words.Get("config.route"));
			EffectContentView = obj.Bind<bool>("Display", "EffectContentView", true, Words.Get("config.effect_view"));
			SortByDistance = obj.Bind<bool>("Display", "SortByDistance", false, Words.Get("config.distance"));
			try
			{
				Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(Words.RefreshLanguage));
				languageHooked = true;
				CompatibilityReport compatibilityReport = Compatibility.Inspect(typeof(Progress).Assembly.Location);
				if (!compatibilityReport.KnownBuild)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Unverified PoV build: checking catalogue, routing and distance features independently. Passing checks enable a feature; they do not prove gameplay compatibility.");
				}
				EnableCatalogue(compatibilityReport);
				EnableRouting(compatibilityReport);
				DistanceAvailable = CatalogueAvailable && compatibilityReport.Distance && DistanceRanking.NativeContractsPresen