Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PoV Tree Helper v0.3.2
BepInEx/plugins/PoVTreeHelper/PoVTreePlanner.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
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