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 PathOfValheiman TreePresets v0.2.5
TreePresets.dll
Decompiled a week agousing System; using System.Collections.Concurrent; 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.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using PassiveTree; using PassiveTree.UI; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("TreePresets")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("TreePresets")] [assembly: AssemblyTitle("TreePresets")] [assembly: AssemblyVersion("1.0.0.0")] namespace TreePresets; internal sealed class ActivePresetHud : MonoBehaviour { private const float BottomMargin = 26f; private const float Height = 30f; private GUIStyle textStyle; private void OnGUI() { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.ShowActivePresetHud.Value || (Object)(object)Player.m_localPlayer == (Object)null || TreeWindowState.IsOpen) { return; } int activeSlot = PresetManager.GetActiveSlot(); PresetData presetData = ((activeSlot > 0) ? PresetManager.GetSlot(activeSlot) : null); if (presetData != null) { TreePresetsSkin.EnsureBuilt(); if (textStyle == null) { textStyle = new GUIStyle(GUI.skin.label) { font = ValheimFont.Get(), fontStyle = (FontStyle)1, fontSize = 16, alignment = (TextAnchor)4 }; textStyle.normal.textColor = TreePresetsSkin.TextGold; } string name = presetData.Name; float num = Mathf.Max(200f, (float)name.Length * 10f + 20f); GUI.Label(new Rect(((float)Screen.width - num) / 2f, (float)Screen.height - 30f - 26f, num, 30f), name, textStyle); } } } internal static class DiscordRemoval { private static readonly MethodInfo ReturnFalseMethod = typeof(DiscordRemoval).GetMethod("ReturnFalse", BindingFlags.Static | BindingFlags.NonPublic); public static void Apply(Harmony harmony) { PatchStaticNoop(harmony, "PassiveTree.UI.DiscordInvite", "MaybeShow", "плашка Discord при открытии дерева"); PatchNestedNoop(harmony, "PassiveTree.UI.MenuDiscord", "FejdStartup_Start_Discord", "Postfix", "кнопка Discord в главном меню"); PatchStaticNoop(harmony, "PassiveTree.DiscordLink", "Pump", "фоновый опрос Discord"); PatchStaticNoop(harmony, "PassiveTree.DiscordLink", "Begin", "запуск привязки Discord"); } private static bool ReturnFalse() { return false; } private static void PatchStaticNoop(Harmony harmony, string typeName, string methodName, string humanLabel) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown try { Type type = AccessTools.TypeByName(typeName); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : null); if (methodInfo == null) { LogSkip(humanLabel, typeName + "." + methodName); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(ReturnFalseMethod), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("TreePresets: отключено - " + humanLabel + ".")); } catch (Exception e) { LogSkip(humanLabel, typeName + "." + methodName, e); } } private static void PatchNestedNoop(Harmony harmony, string outerTypeName, string innerTypeName, string methodName, string humanLabel) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown try { Type type = AccessTools.TypeByName(outerTypeName); Type type2 = ((type != null) ? AccessTools.Inner(type, innerTypeName) : null); MethodInfo methodInfo = ((type2 != null) ? AccessTools.Method(type2, methodName, (Type[])null, (Type[])null) : null); if (methodInfo == null) { LogSkip(humanLabel, outerTypeName + "/" + innerTypeName + "." + methodName); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(ReturnFalseMethod), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)("TreePresets: отключено - " + humanLabel + ".")); } } catch (Exception e) { LogSkip(humanLabel, outerTypeName + "/" + innerTypeName + "." + methodName, e); } } private static void LogSkip(string humanLabel, string target, Exception e = null) { Plugin.Log.LogWarning((object)("TreePresets: не удалось отключить \"" + humanLabel + "\" (" + target + ") - в этой версии Path of Valheiman структура другая. " + ((e != null) ? ("Ошибка: " + e.Message) : "Метод не найден.") + " Остальной функционал мода не затронут.")); } } internal static class ExportImportService { public static readonly ConcurrentQueue<Action> MainThreadQueue = new ConcurrentQueue<Action>(); private const string DialogFilter = "Пресет дерева (*.povpreset)|*.povpreset|Все файлы (*.*)|*.*"; public static void RunOnMainThread(Action action) { MainThreadQueue.Enqueue(action); } public static void PumpMainThreadQueue() { Action result; while (MainThreadQueue.TryDequeue(out result)) { try { result(); } catch (Exception ex) { Plugin.Log.LogError((object)("TreePresets main-thread action failed: " + ex)); } } } public static void ExportPresetInteractive(int slot, string description) { PresetData preset = PresetManager.GetSlot(slot); if (preset == null || preset.IsEmpty) { Plugin.Message($"Пресет {slot} пуст - нечего экспортировать."); return; } string version = GameModVersion.GetInstalledModVersion() ?? "unknown"; string suggestedName = SanitizeFileName(preset.Name); RunBackground(delegate { string path = NativeFileDialog.ShowSave("Экспорт пресета дерева Path of Valheiman", "Пресет дерева (*.povpreset)|*.povpreset|Все файлы (*.*)|*.*", suggestedName, "povpreset"); if (!string.IsNullOrEmpty(path)) { string contents = PresetFile.BuildForExport("preset", preset.Name, "", description, preset.NodeIds, preset.Choices, version); File.WriteAllText(path, contents, Encoding.UTF8); RunOnMainThread(delegate { Plugin.Message("Пресет «" + preset.Name + "» сохранён в файл: " + path); }); } }); } public static void ImportGuideInteractive() { RunBackground(delegate { string text = NativeFileDialog.ShowOpen("Импорт билда как гайда", "Пресет дерева (*.povpreset)|*.povpreset|Все файлы (*.*)|*.*"); if (!string.IsNullOrEmpty(text)) { string text2; try { text2 = File.ReadAllText(text, Encoding.UTF8); } catch (Exception ex) { Exception ex2 = ex; Exception e = ex2; RunOnMainThread(delegate { Plugin.Message("Не удалось прочитать файл: " + e.Message); }); return; } if (!PresetFile.TryParse(text2, out var fields, out var parseError)) { RunOnMainThread(delegate { Plugin.Message("Импорт отклонён: " + parseError); }); } else { fields.TryGetValue("game_mod_version", out var fileVersion); fields.TryGetValue("nodes", out var nodes); List<string> nodeIds = (from s in (nodes ?? "").Split(new char[1] { ',' }) select s.Trim() into s where s.Length > 0 select s).ToList(); if (nodeIds.Count == 0) { RunOnMainThread(delegate { Plugin.Message("В файле нет ни одного узла - импорт пропущен."); }); } else { fields.TryGetValue("name", out var name); fields.TryGetValue("author", out var author); fields.TryGetValue("description", out var description); fields.TryGetValue("exported_at", out var exportedAt); RunOnMainThread(delegate { if (!GameModVersion.CanResolveAllNodes(nodeIds, out var missingNodeIds)) { Plugin.Message("Импорт отклонён: гайд не соответствует вашей версии дерева - " + $"в нём {missingNodeIds.Count} из {nodeIds.Count} узлов, которых сейчас нет в дереве " + "(файл был сделан для версии " + (fileVersion ?? "?") + ")."); } else { GuideData guideData = GuideData.Deserialize(null, name, author, description, nodes, fileVersion, exportedAt); GuideManager.AddGuide(guideData); Plugin.Message($"Гайд «{guideData.Name}» импортирован ({guideData.NodeIds.Count} узлов). " + "Он не занимает слот пресета и не меняет ваш билд - только подсказывает."); } }); } } } }); } public static void ExportGuideInteractive(string guideId) { GuideData guide = GuideManager.GetGuide(guideId); if (guide == null) { return; } RunBackground(delegate { string path = NativeFileDialog.ShowSave("Экспорт гайда", "Пресет дерева (*.povpreset)|*.povpreset|Все файлы (*.*)|*.*", SanitizeFileName(guide.Name), "povpreset"); if (!string.IsNullOrEmpty(path)) { string contents = PresetFile.BuildForExport("guide", guide.Name, guide.Author, guide.Description, guide.NodeIds, null, guide.SourceGameModVersion); File.WriteAllText(path, contents, Encoding.UTF8); RunOnMainThread(delegate { Plugin.Message("Гайд сохранён в файл: " + path); }); } }); } private static void RunBackground(Action body) { Thread thread = new Thread((ThreadStart)delegate { try { body(); } catch (Exception ex) { Exception ex2 = ex; Exception e = ex2; RunOnMainThread(delegate { Plugin.Message("Ошибка диалога файла: " + e.Message); }); Plugin.Log.LogError((object)("TreePresets file dialog error: " + e)); } }); thread.IsBackground = true; thread.Start(); } private static string SanitizeFileName(string name) { if (string.IsNullOrWhiteSpace(name)) { return "preset"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } return name; } } internal static class GameModVersion { public const string PathOfValheimanGuid = "treextr.pathofvalheiman"; public static string GetInstalledModVersion() { if (Chainloader.PluginInfos.TryGetValue("treextr.pathofvalheiman", out var value)) { return value.Metadata.Version.ToString(); } return null; } public static string GetTreeContentVersion() { try { return 5.ToString(); } catch { return "?"; } } public static bool CanResolveAllNodes(IEnumerable<string> nodeIds, out List<string> missingNodeIds) { missingNodeIds = new List<string>(); foreach (string nodeId in nodeIds) { if (!Tree.Nodes.TryGetValue(nodeId, out var value) || value == null || value.Broken) { missingNodeIds.Add(nodeId); } } return missingNodeIds.Count == 0; } } internal sealed class GuideData { public string Id = Guid.NewGuid().ToString("N"); public string Name = ""; public string Author = ""; public string Description = ""; public List<string> NodeIds = new List<string>(); public string SourceGameModVersion = ""; public DateTime ImportedAtUtc = DateTime.UtcNow; public string SerializeNodes() { return string.Join(",", NodeIds); } public static GuideData Deserialize(string id, string name, string author, string description, string nodesRaw, string sourceVersion, string importedAtRaw) { GuideData guideData = new GuideData { Id = (string.IsNullOrEmpty(id) ? Guid.NewGuid().ToString("N") : id), Name = (name ?? ""), Author = (author ?? ""), Description = (description ?? ""), SourceGameModVersion = (sourceVersion ?? "") }; if (!string.IsNullOrEmpty(nodesRaw)) { guideData.NodeIds = (from s in nodesRaw.Split(new char[1] { ',' }) select s.Trim() into s where s.Length > 0 select s).ToList(); } if (!string.IsNullOrEmpty(importedAtRaw) && DateTime.TryParse(importedAtRaw, null, DateTimeStyles.RoundtripKind, out var result)) { guideData.ImportedAtUtc = result; } return guideData; } } internal static class PresetFile { public const string Extension = ".povpreset"; private const int FormatVersion = 1; public const string KindPreset = "preset"; public const string KindGuide = "guide"; public static string BuildForExport(string kind, string name, string author, string description, IEnumerable<string> nodeIds, IDictionary<string, int> choices, string gameModVersion) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Path of Valheiman - TreePresets export file"); stringBuilder.AppendLine("format_version=" + 1); stringBuilder.AppendLine("kind=" + kind); stringBuilder.AppendLine("name=" + Escape(name)); stringBuilder.AppendLine("author=" + Escape(author ?? "")); stringBuilder.AppendLine("description=" + Escape(description ?? "")); stringBuilder.AppendLine("game_mod_version=" + gameModVersion); stringBuilder.AppendLine("exported_at=" + DateTime.UtcNow.ToString("o")); stringBuilder.AppendLine("nodes=" + string.Join(",", nodeIds)); if (choices != null && choices.Count > 0) { stringBuilder.AppendLine("choices=" + string.Join(",", choices.Select((KeyValuePair<string, int> kv) => kv.Key + "=" + kv.Value))); } return stringBuilder.ToString(); } public static bool TryParse(string text, out Dictionary<string, string> fields, out string error) { fields = new Dictionary<string, string>(); error = null; string[] array = text.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim('\r', '\n', ' '); if (text2.Length != 0 && !text2.StartsWith("#")) { int num = text2.IndexOf('='); if (num > 0) { fields[text2.Substring(0, num).Trim()] = Unescape(text2.Substring(num + 1)); } } } if (!fields.ContainsKey("kind") || !fields.ContainsKey("nodes")) { error = "Файл повреждён или это не файл пресета TreePresets."; return false; } if (!fields.TryGetValue("format_version", out var value) || value != 1.ToString()) { error = string.Format("Неизвестная версия формата файла ({0} вместо {1}). ", value ?? "?", 1) + "Возможно, файл сделан более новой версией TreePresets."; return false; } return true; } private static string Escape(string s) { return (s ?? "").Replace("\\", "\\\\").Replace("\n", "\\n").Replace("=", "\\="); } private static string Unescape(string s) { return (s ?? "").Replace("\\n", "\n").Replace("\\=", "=").Replace("\\\\", "\\"); } } internal static class GuideHighlighter { private sealed class GuideMark : MonoBehaviour { } private static FieldInfo viewsField; private static bool probed; private static string activeGuideId; private static HashSet<string> activeGuideNodeIds; private static readonly Color HighlightColor = new Color(1f, 0.83f, 0.25f, 0.95f); private static Sprite ringSprite; public static bool IsAvailable { get; private set; } public static string ActiveGuideId => activeGuideId; private static Sprite RingSprite { get { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ringSprite != (Object)null) { return ringSprite; } Texture2D val = new Texture2D(128, 128, (TextureFormat)4, false) { hideFlags = (HideFlags)61, wrapMode = (TextureWrapMode)1 }; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(64f, 64f); Color32[] array = (Color32[])(object)new Color32[16384]; for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { float num = Vector2.Distance(new Vector2((float)j + 0.5f, (float)i + 0.5f), val2); float num2 = ((!(num > 62f)) ? ((!(num < 52f)) ? 1f : Mathf.Clamp01(1f - (52f - num))) : Mathf.Clamp01(1f - (num - 62f))); array[i * 128 + j] = Color32.op_Implicit(new Color(1f, 1f, 1f, num2)); } } val.SetPixels32(array); val.Apply(); ringSprite = Sprite.Create(val, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f)); ((Object)ringSprite).hideFlags = (HideFlags)61; return ringSprite; } } public static void SetActiveGuide(GuideData guide) { activeGuideId = guide?.Id; activeGuideNodeIds = ((guide != null) ? new HashSet<string>(guide.NodeIds) : null); } public static void Tick() { TreeWindow instance = TreeWindow.Instance; if ((Object)(object)instance == (Object)null) { return; } Dictionary<string, NodeView> dictionary = TryGetViews(instance); if (dictionary == null) { return; } foreach (KeyValuePair<string, NodeView> item in dictionary) { string key = item.Key; NodeView value = item.Value; if (!((Object)(object)value == (Object)null)) { bool flag = activeGuideNodeIds != null && activeGuideNodeIds.Contains(key); GuideMark componentInChildren = ((Component)value).GetComponentInChildren<GuideMark>(true); if (flag && (Object)(object)componentInChildren == (Object)null) { CreateMark(value); } else if (!flag && (Object)(object)componentInChildren != (Object)null) { Object.Destroy((Object)(object)((Component)componentInChildren).gameObject); } } } } private static void CreateMark(NodeView view) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("TreePresets_GuideMark", new Type[3] { typeof(RectTransform), typeof(Image), typeof(GuideMark) }); RectTransform val2 = (RectTransform)val.transform; ((Transform)val2).SetParent(((Component)view).transform, false); val2.anchorMin = new Vector2(-0.15f, -0.15f); val2.anchorMax = new Vector2(1.15f, 1.15f); val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; Image component = val.GetComponent<Image>(); component.sprite = RingSprite; component.type = (Type)0; ((Graphic)component).color = HighlightColor; ((Graphic)component).raycastTarget = false; } private static Dictionary<string, NodeView> TryGetViews(TreeWindow window) { if (!probed) { probed = true; try { viewsField = typeof(TreeWindow).GetField("views", BindingFlags.Instance | BindingFlags.NonPublic); IsAvailable = viewsField != null && typeof(Dictionary<string, NodeView>).IsAssignableFrom(viewsField.FieldType); } catch (Exception ex) { IsAvailable = false; Plugin.Log.LogWarning((object)("TreePresets: не удалось найти поле TreeWindow.views, подсветка гайдов отключена. " + ex.Message)); } if (!IsAvailable) { Plugin.Log.LogWarning((object)"TreePresets: подсветка гайдов недоступна в этой версии Path of Valheiman (изменилась внутренняя структура окна дерева). Остальные функции мода (пресеты, экспорт/импорт) работают как обычно."); } } if (!IsAvailable) { return null; } try { return (Dictionary<string, NodeView>)viewsField.GetValue(window); } catch (Exception ex2) { IsAvailable = false; Plugin.Log.LogWarning((object)("TreePresets: доступ к TreeWindow.views перестал работать, подсветка гайдов отключена. " + ex2.Message)); return null; } } } internal static class GuideManager { public const int MaxGuides = 25; private const string ListKey = "treextr.guides.ids"; private const string Prefix = "treextr.guides."; private static Dictionary<string, string> CustomData { get { if (!((Object)(object)Player.m_localPlayer != (Object)null)) { return null; } return Player.m_localPlayer.m_customData; } } public static List<GuideData> GetAll() { List<GuideData> list = new List<GuideData>(); Dictionary<string, string> customData = CustomData; if (customData == null) { return list; } if (!customData.TryGetValue("treextr.guides.ids", out var value) || string.IsNullOrEmpty(value)) { return list; } string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { GuideData guide = GetGuide(array[i]); if (guide != null) { list.Add(guide); } } return list; } public static GuideData GetGuide(string id) { Dictionary<string, string> customData = CustomData; if (customData == null || string.IsNullOrEmpty(id)) { return null; } if (!customData.TryGetValue("treextr.guides." + id + ".nodes", out var value)) { return null; } customData.TryGetValue("treextr.guides." + id + ".name", out var value2); customData.TryGetValue("treextr.guides." + id + ".author", out var value3); customData.TryGetValue("treextr.guides." + id + ".desc", out var value4); customData.TryGetValue("treextr.guides." + id + ".ver", out var value5); customData.TryGetValue("treextr.guides." + id + ".importedAt", out var value6); return GuideData.Deserialize(id, value2, value3, value4, value, value5, value6); } public static bool AddGuide(GuideData guide) { Dictionary<string, string> customData = CustomData; if (customData == null) { return false; } List<string> ids = GetIds(customData); if (ids.Count >= 25) { Plugin.Message($"Достигнут предел в {25} гайдов - удалите старый перед импортом нового."); return false; } ids.Add(guide.Id); customData["treextr.guides.ids"] = string.Join(",", ids); customData["treextr.guides." + guide.Id + ".name"] = guide.Name; customData["treextr.guides." + guide.Id + ".author"] = guide.Author; customData["treextr.guides." + guide.Id + ".desc"] = guide.Description ?? ""; customData["treextr.guides." + guide.Id + ".nodes"] = guide.SerializeNodes(); customData["treextr.guides." + guide.Id + ".ver"] = guide.SourceGameModVersion; customData["treextr.guides." + guide.Id + ".importedAt"] = guide.ImportedAtUtc.ToString("o"); return true; } public static void RemoveGuide(string id) { Dictionary<string, string> customData = CustomData; if (customData != null) { List<string> ids = GetIds(customData); ids.Remove(id); customData["treextr.guides.ids"] = string.Join(",", ids); customData.Remove("treextr.guides." + id + ".name"); customData.Remove("treextr.guides." + id + ".author"); customData.Remove("treextr.guides." + id + ".desc"); customData.Remove("treextr.guides." + id + ".nodes"); customData.Remove("treextr.guides." + id + ".ver"); customData.Remove("treextr.guides." + id + ".importedAt"); } } private static List<string> GetIds(Dictionary<string, string> cd) { if (!cd.TryGetValue("treextr.guides.ids", out var value) || string.IsNullOrEmpty(value)) { return new List<string>(); } return (from s in value.Split(new char[1] { ',' }) where s.Length > 0 select s).ToList(); } } internal static class InputBlocker { private const int SortingOrder = 30000; private static GameObject blockerGo; private static Image image; public static void SetBlockingRect(Rect? guiRect) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (!guiRect.HasValue) { if ((Object)(object)blockerGo != (Object)null) { blockerGo.SetActive(false); } return; } EnsureBlocker(); blockerGo.SetActive(true); Rect value = guiRect.Value; RectTransform rectTransform = ((Graphic)image).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(0f, 1f); rectTransform.pivot = new Vector2(0f, 1f); rectTransform.anchoredPosition = new Vector2(((Rect)(ref value)).x, 0f - ((Rect)(ref value)).y); rectTransform.sizeDelta = new Vector2(((Rect)(ref value)).width, ((Rect)(ref value)).height); } private static void EnsureBlocker() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)blockerGo != (Object)null)) { blockerGo = new GameObject("TreePresets_InputBlocker"); Object.DontDestroyOnLoad((Object)(object)blockerGo); Canvas obj = blockerGo.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 30000; blockerGo.AddComponent<GraphicRaycaster>(); image = blockerGo.AddComponent<Image>(); ((Graphic)image).color = new Color(0f, 0f, 0f, 0f); ((Graphic)image).raycastTarget = true; } } } internal static class Localization { private static bool IsRussian => PlayerPrefs.GetString("language", "English") == "Russian"; public static string T(string ru, string en) { if (!IsRussian) { return en; } return ru; } } internal static class NativeFileDialog { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private class OpenFileName { public int lStructSize = Marshal.SizeOf(typeof(OpenFileName)); public IntPtr hwndOwner = IntPtr.Zero; public IntPtr hInstance = IntPtr.Zero; public string lpstrFilter; public string lpstrCustomFilter; public int nMaxCustFilter; public int nFilterIndex; public string lpstrFile; public int nMaxFile = 4096; public string lpstrFileTitle; public int nMaxFileTitle = 4096; public string lpstrInitialDir; public string lpstrTitle; public int Flags; public short nFileOffset; public short nFileExtension; public string lpstrDefExt; public IntPtr lCustData = IntPtr.Zero; public IntPtr lpfnHook = IntPtr.Zero; public string lpTemplateName; public IntPtr pvReserved = IntPtr.Zero; public int dwReserved; public int flagsEx; } private const int MaxPath = 4096; private const int OFN_EXPLORER = 524288; private const int OFN_FILEMUSTEXIST = 4096; private const int OFN_PATHMUSTEXIST = 2048; private const int OFN_OVERWRITEPROMPT = 2; private const int OFN_NOCHANGEDIR = 8; [DllImport("comdlg32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool GetOpenFileNameW([In][Out] OpenFileName ofn); [DllImport("comdlg32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool GetSaveFileNameW([In][Out] OpenFileName ofn); private static string BuildFilter(string filter) { return filter.Replace('|', '\0') + "\0\0"; } public static string ShowOpen(string title, string filter) { OpenFileName openFileName = new OpenFileName { lpstrFilter = BuildFilter(filter), lpstrFile = new string('\0', 4096), lpstrTitle = title, Flags = 530440 }; if (!GetOpenFileNameW(openFileName)) { return null; } return CleanResult(openFileName.lpstrFile); } public static string ShowSave(string title, string filter, string suggestedFileName, string defaultExt) { string text = suggestedFileName ?? ""; if (text.Length >= 4096) { text = text.Substring(0, 4095); } text = text.PadRight(4096, '\0'); OpenFileName openFileName = new OpenFileName { lpstrFilter = BuildFilter(filter), lpstrFile = text, lpstrTitle = title, lpstrDefExt = defaultExt, Flags = 524298 }; if (!GetSaveFileNameW(openFileName)) { return null; } return CleanResult(openFileName.lpstrFile); } private static string CleanResult(string raw) { if (string.IsNullOrEmpty(raw)) { return null; } int num = raw.IndexOf('\0'); if (num < 0) { return raw; } return raw.Substring(0, num); } } [BepInPlugin("treextr.pathofvalheiman.presets", "Path of Valheiman - Presets", "0.2.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private static class TerminalCommandsPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; public static ConsoleEvent <>9__0_2; internal void <Postfix>b__0_0(ConsoleEventArgs args) { if (!TreeWindowState.IsOpen) { Message(Localization.T("Сначала откройте дерево навыков (P).", "Open the skill tree first (P).")); } else { PresetMageUI.Toggle(); } } internal void <Postfix>b__0_1(ConsoleEventArgs args) { for (int i = 1; i <= 3; i++) { PresetData slot = PresetManager.GetSlot(i); Message((slot == null) ? Localization.T($"{i}: пусто", $"{i}: empty") : string.Format("{0}: {1} - {2} {3}", i, slot.Name, slot.MeaningfulNodeCount, Localization.T("узлов", "nodes"))); } foreach (GuideData item in GuideManager.GetAll()) { Message(string.Format("{0}: {1} - {2} {3}", Localization.T("Гайд", "Guide"), item.Name, item.NodeIds.Count, Localization.T("узлов", "nodes"))); } } internal void <Postfix>b__0_2(ConsoleEventArgs args) { ExportImportService.ImportGuideInteractive(); } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] [HarmonyPostfix] private static void Postfix() { //IL_0033: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate { if (!TreeWindowState.IsOpen) { Message(Localization.T("Сначала откройте дерево навыков (P).", "Open the skill tree first (P).")); } else { PresetMageUI.Toggle(); } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("presets_open", "presets_open - open the presets panel (tree must be open)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate { for (int i = 1; i <= 3; i++) { PresetData slot = PresetManager.GetSlot(i); Message((slot == null) ? Localization.T($"{i}: пусто", $"{i}: empty") : string.Format("{0}: {1} - {2} {3}", i, slot.Name, slot.MeaningfulNodeCount, Localization.T("узлов", "nodes"))); } foreach (GuideData item in GuideManager.GetAll()) { Message(string.Format("{0}: {1} - {2} {3}", Localization.T("Гайд", "Guide"), item.Name, item.NodeIds.Count, Localization.T("узлов", "nodes"))); } }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("presets_list", "presets_list - list all slots and guides", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__0_2; if (obj3 == null) { ConsoleEvent val3 = delegate { ExportImportService.ImportGuideInteractive(); }; <>c.<>9__0_2 = val3; obj3 = (object)val3; } new ConsoleCommand("presets_import", "presets_import - open the file import dialog", (ConsoleEvent)obj3, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } public const string Guid = "treextr.pathofvalheiman.presets"; public const string Name = "Path of Valheiman - Presets"; public const string Version = "0.2.5"; internal static Plugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<bool> FreeSwitching; internal static ConfigEntry<bool> ShowMessages; internal static ConfigEntry<bool> AutoSaveActivePreset; internal static ConfigEntry<float> BackgroundTickInterval; internal static ConfigEntry<bool> ShowActivePresetHud; internal static ConfigEntry<bool> RemoveDiscordFeatures; internal static ConfigEntry<bool> PlayUiClickSound; internal static ConfigEntry<float> TopBarExtraLeftOffset; internal static ConfigEntry<float> PanelTopOffset; private Harmony harmony; private float nextBackgroundTick; private void Awake() { //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; FreeSwitching = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Free preset switching", true, "Переключение пресетов не тратит монеты/глаза грейдваргов на снятие узлов."); ShowMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Show chat messages", true, "Показывать сообщения о результатах операций."); AutoSaveActivePreset = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Auto-save active preset", true, "Пока выбран активный пресет, любые изменения дерева (новые узлы, снятые узлы, выбор мастерства) автоматически записываются обратно в этот же слот."); BackgroundTickInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Background tick interval (seconds)", 0.5f, "Как часто (пока открыто окно дерева) проверять изменения дерева для автосохранения и обновлять подсветку гайда. У Progress нет публичного события изменения, поэтому используется периодическая проверка вместо мгновенной реакции."); ShowActivePresetHud = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "Show active preset HUD", true, "Небольшая постоянная подпись снизу по центру экрана с названием активного пресета."); RemoveDiscordFeatures = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Remove Discord features", true, "Отключает всё, что связано с Discord в самом Path of Valheiman."); PlayUiClickSound = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "Play UI click sound", true, "Проигрывать игровой звук клика при нажатии кнопок этого мода."); TopBarExtraLeftOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Interface", "Top bar left offset", 90f, "Насколько левее сдвинуть кнопку \"Пресеты\" от правого края экрана"); PanelTopOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Interface", "Panel top offset", 56f, "Насколько ниже верхнего края экрана открывается окно пресетов"); harmony = new Harmony("treextr.pathofvalheiman.presets"); harmony.PatchAll(typeof(TerminalCommandsPatch)); if (RemoveDiscordFeatures.Value) { DiscordRemoval.Apply(harmony); } ((Object)((Component)this).gameObject.AddComponent<TopBarUI>()).hideFlags = (HideFlags)61; ((Object)((Component)this).gameObject.AddComponent<PresetMageUI>()).hideFlags = (HideFlags)61; ((Object)((Component)this).gameObject.AddComponent<ActivePresetHud>()).hideFlags = (HideFlags)61; Log.LogInfo((object)"Path of Valheiman - Presets 0.2.5 loaded."); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private void Update() { ExportImportService.PumpMainThreadQueue(); if (!((Object)(object)Player.m_localPlayer == (Object)null) && TreeWindowState.IsOpen && Time.unscaledTime >= nextBackgroundTick) { nextBackgroundTick = Time.unscaledTime + Mathf.Max(0.1f, BackgroundTickInterval.Value); PresetManager.AutoSave.Tick(); GuideHighlighter.Tick(); } } internal static void Message(string text) { if (ShowMessages.Value && (Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false); } Log.LogInfo((object)text); } internal static void MessageQuiet(string text) { Log.LogInfo((object)text); } } internal sealed class PresetData { public string Name = ""; public string Description = ""; public List<string> NodeIds = new List<string>(); public Dictionary<string, int> Choices = new Dictionary<string, int>(); public DateTime SavedAtUtc = DateTime.UtcNow; public int MeaningfulNodeCount { get { string rootId = SafeRootId(); if (string.IsNullOrEmpty(rootId)) { return NodeIds.Count; } return NodeIds.Count((string id) => id != rootId); } } public bool IsEmpty => MeaningfulNodeCount == 0; private static string SafeRootId() { try { return "root"; } catch { return null; } } public string SerializeNodes() { return string.Join(",", NodeIds); } public string SerializeChoices() { return string.Join(",", Choices.Select((KeyValuePair<string, int> kv) => kv.Key + "=" + kv.Value)); } public static PresetData Deserialize(string name, string nodesRaw, string choicesRaw, string savedAtRaw, string description = null) { PresetData presetData = new PresetData { Name = (name ?? ""), Description = (description ?? "") }; if (!string.IsNullOrEmpty(nodesRaw)) { string[] array = nodesRaw.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { presetData.NodeIds.Add(text); } } } if (!string.IsNullOrEmpty(choicesRaw)) { string[] array = choicesRaw.Split(new char[1] { ',' }); foreach (string text2 in array) { int num = text2.IndexOf('='); if (num > 0) { string text3 = text2.Substring(0, num).Trim(); string s = text2.Substring(num + 1).Trim(); if (text3.Length > 0 && int.TryParse(s, out var result)) { presetData.Choices[text3] = result; } } } } if (!string.IsNullOrEmpty(savedAtRaw) && DateTime.TryParse(savedAtRaw, null, DateTimeStyles.RoundtripKind, out var result2)) { presetData.SavedAtUtc = result2; } return presetData; } } public sealed class PresetMage : MonoBehaviour, Hoverable, Interactable { public string GetHoverName() { return "Хранитель пресетов"; } public string GetHoverText() { return "Хранитель пресетов\n[<color=yellow><b>E</b></color>] Открыть"; } public float GetHoverOffset() { return 0f; } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold) { return false; } if (!TreeWindowState.IsOpen) { Plugin.Message(Localization.T("Сначала откройте дерево навыков (P), чтобы управлять пресетами.", "Open the skill tree (P) first to manage presets.")); return true; } PresetMageUI.Toggle(); return true; } public bool UseItem(Humanoid user, ItemData item) { return false; } } internal sealed class PresetMageUI : MonoBehaviour { private const float WindowWidth = 420f; private const float WindowHeight = 480f; private const float ScreenMargin = 16f; private static bool visible; private Rect windowRect = new Rect(0f, 0f, 420f, 480f); private Vector2 guidesScroll; private int renamingSlot = -1; private string renameBuffer = ""; private int exportingPresetSlot = -1; private string exportingGuideId; private string exportDescriptionBuffer = ""; private string viewingGuideDescriptionId; public static void Toggle() { visible = !visible; } public static void Close() { visible = false; } private void OnGUI() { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) if (visible && ((Object)(object)Player.m_localPlayer == (Object)null || !TreeWindowState.IsOpen)) { visible = false; } if (!visible) { ApplyInputBlock(null); return; } TreePresetsSkin.EnsureBuilt(); if (((Rect)(ref windowRect)).x == 0f && ((Rect)(ref windowRect)).y == 0f) { ((Rect)(ref windowRect)).x = (float)Screen.width - 420f - 16f - Plugin.TopBarExtraLeftOffset.Value; ((Rect)(ref windowRect)).y = 16f + Plugin.PanelTopOffset.Value; } windowRect = GUILayout.Window(((Object)this).GetInstanceID(), windowRect, new WindowFunction(DrawWindow), Localization.T("Пресеты", "Presets"), TreePresetsSkin.Window, Array.Empty<GUILayoutOption>()); ApplyInputBlock((Rect)((exportingPresetSlot != -1 || exportingGuideId != null || viewingGuideDescriptionId != null) ? new Rect(0f, 0f, (float)Screen.width, (float)Screen.height) : windowRect)); if (exportingPresetSlot != -1 || exportingGuideId != null) { DrawExportPrompt(); } if (viewingGuideDescriptionId != null) { DrawGuideDescriptionPopup(); } } private void DrawWindow(int id) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.Label(Localization.T("Активные пресеты:", "Active presets:"), TreePresetsSkin.Label, Array.Empty<GUILayoutOption>()); int activeSlot = PresetManager.GetActiveSlot(); for (int i = 1; i <= 3; i++) { DrawPresetRow(i, PresetManager.GetSlot(i), activeSlot); } GUILayout.Space(8f); GUILayout.Label(Localization.T("Гайды:", "Guides:"), TreePresetsSkin.Label, Array.Empty<GUILayoutOption>()); guidesScroll = GUILayout.BeginScrollView(guidesScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(140f) }); List<GuideData> all = GuideManager.GetAll(); if (all.Count == 0) { GUILayout.Label(Localization.T("Пока нет ни одного гайда.", "No guides yet."), TreePresetsSkin.Label, Array.Empty<GUILayoutOption>()); } foreach (GuideData item in all) { DrawGuideRow(item); } GUILayout.EndScrollView(); if (!GuideHighlighter.IsAvailable) { GUILayout.Label(Localization.T("(Подсветка гайдов недоступна в этой версии Path of Valheiman.)", "(Guide highlighting is unavailable in this Path of Valheiman version.)"), TreePresetsSkin.Label, Array.Empty<GUILayoutOption>()); } if (TreePresetsSkin.Btn(Localization.T("Импортировать файл...", "Import file..."), GUILayout.Height(28f))) { ExportImportService.ImportGuideInteractive(); } GUILayout.Space(8f); if (TreePresetsSkin.Btn(Localization.T("Закрыть", "Close"))) { Close(); } GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, 420f, 30f)); } private void DrawPresetRow(int slot, PresetData preset, int active) { GUILayout.BeginVertical(TreePresetsSkin.Box, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (renamingSlot == slot) { renameBuffer = GUILayout.TextField(renameBuffer, TreePresetsSkin.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); if (TreePresetsSkin.Btn("OK", GUILayout.Width(44f))) { PresetManager.RenameSlot(slot, renameBuffer); renamingSlot = -1; } if (TreePresetsSkin.Btn("X", GUILayout.Width(34f))) { renamingSlot = -1; } } else { string text = ((preset == null) ? Localization.T($"{slot}. пусто", $"{slot}. empty") : string.Format("{0}. {1} ({2} {3})", slot, preset.Name, preset.MeaningfulNodeCount, Localization.T("узлов", "nodes"))); if (slot == active) { text = "★ " + text; } GUILayout.Label(text, TreePresetsSkin.Label, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(300f), GUILayout.ExpandWidth(true) }); GUI.enabled = preset != null; if (TreePresetsSkin.Btn("✎", GUILayout.Width(32f))) { renamingSlot = slot; renameBuffer = preset?.Name ?? ""; } GUI.enabled = true; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (TreePresetsSkin.Btn(Localization.T("Выбрать", "Select"), GUILayout.ExpandWidth(true))) { ApplyResult result = PresetManager.LoadSlotIntoCurrentBuild(slot, Plugin.FreeSwitching.Value); ReportApply(slot, result); } GUI.enabled = preset != null && preset.MeaningfulNodeCount > 0; if (TreePresetsSkin.Btn(Localization.T("Экспорт", "Export"), GUILayout.ExpandWidth(true))) { exportingPresetSlot = slot; exportingGuideId = null; exportDescriptionBuffer = preset?.Description ?? ""; } GUI.enabled = true; GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void DrawGuideRow(GuideData guide) { GUILayout.BeginVertical(TreePresetsSkin.Box, Array.Empty<GUILayoutOption>()); bool flag = GuideHighlighter.ActiveGuideId == guide.Id; GUILayout.Label((flag ? "★ " : "") + $"{guide.Name} ({guide.NodeIds.Count})", TreePresetsSkin.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (TreePresetsSkin.Btn(flag ? Localization.T("Скрыть", "Hide") : Localization.T("Подсветить", "Highlight"), GUILayout.ExpandWidth(true))) { GuideHighlighter.SetActiveGuide(flag ? null : guide); } GUI.enabled = !string.IsNullOrWhiteSpace(guide.Description); if (TreePresetsSkin.Btn(Localization.T("Описание", "Notes"), GUILayout.ExpandWidth(true))) { viewingGuideDescriptionId = guide.Id; } GUI.enabled = true; if (TreePresetsSkin.Btn(Localization.T("Экспорт", "Export"), GUILayout.ExpandWidth(true))) { exportingGuideId = guide.Id; exportingPresetSlot = -1; exportDescriptionBuffer = guide.Description ?? ""; } if (TreePresetsSkin.Btn("X", GUILayout.Width(30f))) { if (flag) { GuideHighlighter.SetActiveGuide(null); } GuideManager.RemoveGuide(guide.Id); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void DrawExportPrompt() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) Rect val = default(Rect); ((Rect)(ref val))..ctor((float)(Screen.width - 400) / 2f, (float)(Screen.height - 220) / 2f, 400f, 220f); GUILayout.Window(((Object)this).GetInstanceID() + 1, val, (WindowFunction)delegate { GUILayout.Label(Localization.T("Описание для файла (необязательно) - увидит тот, кто это импортирует:", "Description for the file (optional) - shown to whoever imports it:"), TreePresetsSkin.Label, Array.Empty<GUILayoutOption>()); exportDescriptionBuffer = GUILayout.TextArea(exportDescriptionBuffer, TreePresetsSkin.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(80f) }); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (TreePresetsSkin.Btn(Localization.T("Сохранить как...", "Save as..."), GUILayout.Height(28f))) { if (exportingPresetSlot != -1) { ExportImportService.ExportPresetInteractive(exportingPresetSlot, exportDescriptionBuffer); } else if (exportingGuideId != null) { ExportImportService.ExportGuideInteractive(exportingGuideId); } exportingPresetSlot = -1; exportingGuideId = null; } if (TreePresetsSkin.Btn(Localization.T("Отмена", "Cancel"), GUILayout.Height(28f))) { exportingPresetSlot = -1; exportingGuideId = null; } GUILayout.EndHorizontal(); }, Localization.T("Экспорт", "Export"), TreePresetsSkin.Window, Array.Empty<GUILayoutOption>()); } private void DrawGuideDescriptionPopup() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) GuideData guide = GuideManager.GetGuide(viewingGuideDescriptionId); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)(Screen.width - 400) / 2f, (float)(Screen.height - 260) / 2f, 400f, 260f); GUILayout.Window(((Object)this).GetInstanceID() + 2, val, (WindowFunction)delegate { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (guide != null && !string.IsNullOrEmpty(guide.Author)) { GUILayout.Label(Localization.T("Автор: " + guide.Author, "Author: " + guide.Author), TreePresetsSkin.Label, Array.Empty<GUILayoutOption>()); } GUILayout.Label(Localization.T("Описание:", "Notes:"), TreePresetsSkin.Label, Array.Empty<GUILayoutOption>()); GUILayout.BeginScrollView(Vector2.zero, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(120f) }); GUILayout.TextArea(guide?.Description ?? "", TreePresetsSkin.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.EndScrollView(); if (TreePresetsSkin.Btn(Localization.T("Закрыть", "Close"))) { viewingGuideDescriptionId = null; } }, Localization.T("Описание гайда", "Guide notes"), TreePresetsSkin.Window, Array.Empty<GUILayoutOption>()); } private static void ApplyInputBlock(Rect? rect) { InputBlocker.SetBlockingRect(rect); } private void OnDestroy() { ApplyInputBlock(null); } private static void ReportApply(int slot, ApplyResult result) { switch (result.Outcome) { case ApplyOutcome.Success: Plugin.Message(Localization.T($"Активен пресет {slot}.", $"Preset {slot} is now active.")); break; case ApplyOutcome.Partial: Plugin.Message(Localization.T($"Пресет {slot} применён частично: {result.SkippedNodeIds.Count} узлов не удалось вернуть.", $"Preset {slot} applied partially: {result.SkippedNodeIds.Count} nodes could not be restored.")); break; case ApplyOutcome.EmptySlot: Plugin.Message(Localization.T($"Пресет {slot} пуст.", $"Preset {slot} is empty.")); break; case ApplyOutcome.NoLocalPlayer: Plugin.Message(Localization.T("Нет активного персонажа.", "No active character.")); break; } } } internal enum ApplyOutcome { NoLocalPlayer, EmptySlot, Success, Partial } internal readonly struct ApplyResult { public readonly ApplyOutcome Outcome; public readonly IReadOnlyList<string> SkippedNodeIds; public ApplyResult(ApplyOutcome outcome, IReadOnlyList<string> skipped = null) { Outcome = outcome; SkippedNodeIds = skipped ?? Array.Empty<string>(); } } internal static class PresetManager { internal static class AutoSave { private static HashSet<string> lastSeen; private static Dictionary<string, int> lastSeenChoices; public static void ResetBaseline() { Progress current = Progress.Current; lastSeen = ((current != null) ? new HashSet<string>(current.Allocated) : null); lastSeenChoices = ((current != null) ? new Dictionary<string, int>(current.Choices) : null); } public static void Tick() { if (!Plugin.AutoSaveActivePreset.Value) { return; } int activeSlot = GetActiveSlot(); if (activeSlot == 0) { return; } Progress current = Progress.Current; if (current != null) { if (lastSeen == null) { ResetBaseline(); } else if (!current.Allocated.SetEquals(lastSeen) || !DictionariesEqual(current.Choices, lastSeenChoices)) { SaveCurrentBuildToSlot(activeSlot, null); ResetBaseline(); Plugin.MessageQuiet($"Автосохранение: пресет {activeSlot} обновлён."); } } } private static bool DictionariesEqual(Dictionary<string, int> a, Dictionary<string, int> b) { if (a.Count != b.Count) { return false; } foreach (KeyValuePair<string, int> item in a) { if (!b.TryGetValue(item.Key, out var value) || value != item.Value) { return false; } } return true; } } public const int MaxPresets = 3; private const string KeyPrefix = "treextr.presets."; private const string ActiveKey = "treextr.presets.active"; private static Dictionary<string, string> CustomData { get { if (!((Object)(object)Player.m_localPlayer != (Object)null)) { return null; } return Player.m_localPlayer.m_customData; } } private static string KName(int slot) { return "treextr.presets.slot" + slot + ".name"; } private static string KDescription(int slot) { return "treextr.presets.slot" + slot + ".desc"; } private static string KNodes(int slot) { return "treextr.presets.slot" + slot + ".nodes"; } private static string KChoices(int slot) { return "treextr.presets.slot" + slot + ".choices"; } private static string KSavedAt(int slot) { return "treextr.presets.slot" + slot + ".savedAt"; } public static bool IsSlotValid(int slot) { if (slot >= 1) { return slot <= 3; } return false; } public static int GetActiveSlot() { Dictionary<string, string> customData = CustomData; if (customData == null) { return 0; } if (customData.TryGetValue("treextr.presets.active", out var value) && int.TryParse(value, out var result) && IsSlotValid(result)) { return result; } return 0; } public static void SetActiveSlotSilently(int slot) { Dictionary<string, string> customData = CustomData; if (customData != null) { customData["treextr.presets.active"] = (IsSlotValid(slot) ? slot.ToString() : "0"); } } public static PresetData GetSlot(int slot) { if (!IsSlotValid(slot)) { return null; } Dictionary<string, string> customData = CustomData; if (customData == null) { return null; } customData.TryGetValue(KNodes(slot), out var value); if (string.IsNullOrEmpty(value)) { return null; } customData.TryGetValue(KName(slot), out var value2); customData.TryGetValue(KDescription(slot), out var value3); customData.TryGetValue(KChoices(slot), out var value4); customData.TryGetValue(KSavedAt(slot), out var value5); return PresetData.Deserialize(value2, value, value4, value5, value3); } public static void DeleteSlot(int slot) { if (!IsSlotValid(slot)) { return; } Dictionary<string, string> customData = CustomData; if (customData != null) { customData.Remove(KName(slot)); customData.Remove(KDescription(slot)); customData.Remove(KNodes(slot)); customData.Remove(KChoices(slot)); customData.Remove(KSavedAt(slot)); if (GetActiveSlot() == slot) { SetActiveSlotSilently(0); } } } public static bool SaveCurrentBuildToSlot(int slot, string name, string description = null) { if (!IsSlotValid(slot)) { return false; } Progress current = Progress.Current; Dictionary<string, string> customData = CustomData; if (current == null || customData == null) { return false; } PresetData slot2 = GetSlot(slot); PresetData data = new PresetData { Name = ((!string.IsNullOrWhiteSpace(name)) ? name.Trim() : (slot2?.Name ?? ("Preset " + slot))), Description = (description ?? slot2?.Description ?? ""), NodeIds = new List<string>(current.Allocated), Choices = new Dictionary<string, int>(current.Choices), SavedAtUtc = DateTime.UtcNow }; WriteSlot(slot, data); return true; } public static bool RenameSlot(int slot, string newName) { if (!IsSlotValid(slot) || string.IsNullOrWhiteSpace(newName)) { return false; } Dictionary<string, string> customData = CustomData; PresetData slot2 = GetSlot(slot); if (customData == null || slot2 == null) { return false; } customData[KName(slot)] = newName.Trim(); return true; } public static bool SetSlotDescription(int slot, string description) { if (!IsSlotValid(slot)) { return false; } Dictionary<string, string> customData = CustomData; PresetData slot2 = GetSlot(slot); if (customData == null || slot2 == null) { return false; } customData[KDescription(slot)] = description ?? ""; return true; } private static void WriteSlot(int slot, PresetData data) { Dictionary<string, string> customData = CustomData; if (customData != null) { customData[KName(slot)] = data.Name; customData[KDescription(slot)] = data.Description ?? ""; customData[KNodes(slot)] = data.SerializeNodes(); customData[KChoices(slot)] = data.SerializeChoices(); customData[KSavedAt(slot)] = data.SavedAtUtc.ToString("o"); } } public static ApplyResult LoadSlotIntoCurrentBuild(int slot, bool forceFree) { Progress current = Progress.Current; if (current == null) { return new ApplyResult(ApplyOutcome.NoLocalPlayer); } if (!IsSlotValid(slot)) { return new ApplyResult(ApplyOutcome.EmptySlot); } PresetData slot2 = GetSlot(slot); HashSet<string> hashSet = ((slot2 != null) ? new HashSet<string>(slot2.NodeIds) : new HashSet<string>()); HashSet<string> hashSet2 = new HashSet<string>(current.Allocated); HashSet<string> hashSet3 = new HashSet<string>(hashSet2); hashSet3.ExceptWith(hashSet); HashSet<string> hashSet4 = new HashSet<string>(hashSet); hashSet4.ExceptWith(hashSet2); bool freeAllocation = Progress.FreeAllocation; if (forceFree) { Progress.FreeAllocation = true; } try { if (hashSet3.Count > 0) { current.DeallocateSet((ICollection<string>)hashSet3); } List<string> list = new List<string>(hashSet4); bool flag = true; while (list.Count > 0 && flag) { flag = false; for (int num = list.Count - 1; num >= 0; num--) { string text = list[num]; if (current.CanAllocate(text) && current.Allocate(text)) { list.RemoveAt(num); flag = true; } } } if (slot2 != null) { foreach (KeyValuePair<string, int> choice in slot2.Choices) { if (current.IsAllocated(choice.Key) && current.ChoiceOf(choice.Key) != choice.Value) { current.Choose(choice.Key, choice.Value); } } } current.AfterChange(); SetActiveSlotSilently(slot); SaveCurrentBuildToSlot(slot, slot2?.Name); AutoSave.ResetBaseline(); return (list.Count == 0) ? new ApplyResult(ApplyOutcome.Success) : new ApplyResult(ApplyOutcome.Partial, list); } finally { Progress.FreeAllocation = freeAllocation; } } public static bool CurrentBuildMatches(PresetData preset) { Progress current = Progress.Current; if (current == null || preset == null) { return false; } if (!current.Allocated.SetEquals(preset.NodeIds)) { return false; } if (current.Choices.Count != preset.Choices.Count) { return false; } foreach (KeyValuePair<string, int> choice in preset.Choices) { if (!current.Choices.TryGetValue(choice.Key, out var value) || value != choice.Value) { return false; } } return true; } } internal sealed class TopBarUI : MonoBehaviour { private const float RightMargin = 16f; private const float TopOffset = 35f; private const float ButtonHeight = 32f; private void OnGUI() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (TreeWindowState.IsOpen && !((Object)(object)Player.m_localPlayer == (Object)null)) { TreePresetsSkin.EnsureBuilt(); string text = Localization.T("Пресеты", "Presets"); float num = Mathf.Max(110f, (float)text.Length * 9f + 20f); float num2 = (float)Screen.width - 16f - Plugin.TopBarExtraLeftOffset.Value; if (TreePresetsSkin.Btn(new Rect(num2 - num, 35f, num, 32f), text)) { PresetMageUI.Toggle(); } } } } internal static class TreePresetsSkin { public static readonly Color TextGold = new Color(0.9f, 0.78f, 0.48f); public static readonly Color TextGoldBright = new Color(0.97f, 0.88f, 0.62f); private static bool built; public static GUIStyle Box { get; private set; } public static GUIStyle Button { get; private set; } public static GUIStyle Window { get; private set; } public static GUIStyle Label { get; private set; } public static GUIStyle TextField { get; private set; } public static void EnsureBuilt() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown //IL_00d1: Expected O, but got Unknown //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0106: 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_0115: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0129: Expected O, but got Unknown //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Expected O, but got Unknown //IL_0286: Expected O, but got Unknown //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Expected O, but got Unknown //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Expected O, but got Unknown //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) if (!built) { built = true; Font font = ValheimFont.Get(); Texture2D bg = MakeTex(new Color(0.08f, 0.06f, 0.035f, 0.94f)); Texture2D background = MakeTex(new Color(0.2f, 0.14f, 0.08f, 0.97f)); Texture2D background2 = MakeTex(new Color(0.33f, 0.23f, 0.12f, 0.99f)); Texture2D background3 = MakeTex(new Color(0.05f, 0.04f, 0.03f, 0.92f)); Box = new GUIStyle(GUI.skin.box) { font = font, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, fontSize = 16, border = new RectOffset(3, 3, 3, 3) }; SetAllStates(Box, bg, TextGold); Button = new GUIStyle(GUI.skin.button) { font = font, fontStyle = (FontStyle)1, fontSize = 16, padding = new RectOffset(8, 8, 6, 6), border = new RectOffset(3, 3, 3, 3) }; Button.normal.background = background; Button.normal.textColor = TextGold; Button.hover.background = background2; Button.hover.textColor = TextGoldBright; Button.active.background = background2; Button.active.textColor = TextGoldBright; Button.focused.background = background; Button.focused.textColor = TextGold; Button.onNormal.background = background; Button.onNormal.textColor = TextGold; Button.onHover.background = background2; Button.onHover.textColor = TextGoldBright; Button.onActive.background = background2; Button.onActive.textColor = TextGoldBright; Button.onFocused.background = background; Button.onFocused.textColor = TextGold; Window = new GUIStyle(GUI.skin.window) { font = font, fontStyle = (FontStyle)1, fontSize = 18, padding = new RectOffset(12, 12, 26, 12) }; SetAllStates(Window, bg, TextGoldBright); Label = new GUIStyle(GUI.skin.label) { font = font, fontSize = 15 }; Label.normal.textColor = TextGold; Label.wordWrap = true; TextField = new GUIStyle(GUI.skin.textField) { font = font }; TextField.normal.background = background3; TextField.normal.textColor = Color.white; TextField.focused.background = background3; TextField.focused.textColor = Color.white; } } private static void SetAllStates(GUIStyle style, Texture2D bg, Color textColor) { //IL_0012: 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) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) style.normal.background = bg; style.normal.textColor = textColor; style.hover.background = bg; style.hover.textColor = textColor; style.active.background = bg; style.active.textColor = textColor; style.focused.background = bg; style.focused.textColor = textColor; style.onNormal.background = bg; style.onNormal.textColor = textColor; style.onHover.background = bg; style.onHover.textColor = textColor; style.onActive.background = bg; style.onActive.textColor = textColor; style.onFocused.background = bg; style.onFocused.textColor = textColor; } public static bool Btn(string text, params GUILayoutOption[] options) { EnsureBuilt(); bool num = GUILayout.Button(text, Button, options); if (num) { UiSfx.PlayClick(); } return num; } public static bool Btn(Rect rect, string text) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) EnsureBuilt(); bool num = GUI.Button(rect, text, Button); if (num) { UiSfx.PlayClick(); } return num; } private static Texture2D MakeTex(Color color) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(4, 4); Color[] array = (Color[])(object)new Color[16]; for (int i = 0; i < array.Length; i++) { array[i] = color; } val.SetPixels(array); val.Apply(); ((Object)val).hideFlags = (HideFlags)61; return val; } } internal static class TreeWindowState { public static bool IsOpen { get { try { return TreeWindow.IsOpen; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("TreePresets: TreeWindow.IsOpen недоступен, считаю дерево закрытым: " + ex.Message)); } return false; } } } } internal static class UiSfx { private static readonly string[] CandidatePrefabNames = new string[4] { "sfx_gui_button", "gui_select", "sfx_UI_pick", "gui_click" }; private static AudioClip cachedClip; private static float cachedVolume = 1f; private static bool probed; public static void PlayClick() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.PlayUiClickSound.Value) { return; } if (!probed) { probed = true; TryResolveClip(); } if ((Object)(object)cachedClip == (Object)null) { return; } try { Camera main = Camera.main; Vector3 val = (((Object)(object)main != (Object)null) ? ((Component)main).transform.position : Vector3.zero); AudioSource.PlayClipAtPoint(cachedClip, val, cachedVolume); } catch (Exception ex) { Plugin.Log.LogWarning((object)("TreePresets: не удалось воспроизвести звук клика: " + ex.Message)); } } private static void TryResolveClip() { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } string[] candidatePrefabNames = CandidatePrefabNames; foreach (string text in candidatePrefabNames) { GameObject val; try { val = instance.GetPrefab(text); } catch { val = null; } if (!((Object)(object)val == (Object)null)) { AudioSource component = val.GetComponent<AudioSource>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.clip == (Object)null)) { cachedClip = component.clip; cachedVolume = ((component.volume > 0f) ? component.volume : 1f); Plugin.Log.LogInfo((object)("TreePresets: звук клика взят из префаба \"" + text + "\".")); return; } } } Plugin.Log.LogWarning((object)("TreePresets: не нашёл звук UI-клика ни по одному из известных имён (" + string.Join(", ", CandidatePrefabNames) + "). Звук при нажатии кнопок не будет играть - см. комментарий в UiSfx.cs, как это поправить вручную.")); } } internal static class ValheimFont { private static readonly string[] FontNameHints = new string[5] { "Norse", "AveriaSerifLibre", "valheim", "Immortal", "UnifrakturMaguntia" }; private static Font cached; private static bool probed; public static Font Get() { if (!probed) { probed = true; TryFind(); } return cached; } private static void TryFind() { try { Font[] array = Resources.FindObjectsOfTypeAll<Font>(); if (array.Length == 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"TreePresets: в памяти не найдено ни одного UnityEngine.Font (игра, скорее всего, использует TextMeshPro) - используется системный шрифт."); } return; } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("TreePresets: найденные Font-объекты: " + string.Join(", ", array.Select((Font f) => ((Object)f).name)))); } string[] fontNameHints = FontNameHints; foreach (string hint in fontNameHints) { Font val = ((IEnumerable<Font>)array).FirstOrDefault((Func<Font, bool>)((Font f) => ((Object)f).name.IndexOf(hint, StringComparison.OrdinalIgnoreCase) >= 0)); if ((Object)(object)val != (Object)null) { cached = val; ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)("TreePresets: используется шрифт \"" + ((Object)val).name + "\".")); } return; } } ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)"TreePresets: среди найденных шрифтов нет явного кандидата на \"шрифт Valheim\" - используется системный. Проверьте список выше и допишите нужное имя в ValheimFont.FontNameHints при необходимости."); } } catch (Exception ex) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)("TreePresets: не удалось найти игровой шрифт: " + ex.Message)); } } } }