using System; 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.Security; using System.Security.Permissions; using BepInEx; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyTitle("Pinsanity")] [assembly: AssemblyDescription("Finds and pins nearby resources on the Valheim minimap. Incredibly configurable.")] [assembly: AssemblyCompany("Jeaudoir AKA FoobyScroobs")] [assembly: AssemblyProduct("Valheim Mod")] [assembly: AssemblyCopyright("Copyright Jeaudoir 2026 - MIT License")] [assembly: ComVisible(false)] [assembly: Guid("F4E7D6C5-B8A9-4172-9536-8E4B7A1D2C5F")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 Pinsanity { public class ConfigManager { public class ResourceEntry { public string Id { get; } public string DefaultLabel { get; } public string UserLabel { get; set; } public bool Enabled { get; set; } public bool EnabledByDefault { get; } public string Note { get; } public bool LabelIsReadOnly { get; } public ResourceEntry(string id, string defaultLabel, bool enabledByDefault, string note, bool labelIsReadOnly = false) { Id = id; DefaultLabel = defaultLabel; UserLabel = defaultLabel; Enabled = enabledByDefault; EnabledByDefault = enabledByDefault; Note = note; LabelIsReadOnly = labelIsReadOnly; } } public class ResourceCategory { public string Id { get; } public string DefaultLabel { get; } public string Note { get; } public bool Enabled { get; set; } public List Entries { get; } public ResourceCategory(string id, string defaultLabel, string note, bool enabled = true) { Id = id; DefaultLabel = defaultLabel; Note = note; Enabled = enabled; Entries = new List(); } public ResourceEntry FindEntry(string entryId) { return Entries.FirstOrDefault((ResourceEntry e) => e.Id == entryId); } } public class GeneralConfig { public bool Enabled { get; set; } = true; public int ScanRadius { get; set; } = 100; public int ScanInterval { get; set; } = 5000; public bool ShowLabels { get; set; } = true; public bool IsDebug { get; set; } = false; public bool IsTrace { get; set; } = false; public bool IsBenchmark { get; set; } = false; public int WindowHeight { get; set; } = 0; public string HotkeyString { get; set; } = "F8"; public KeyCode HotkeyCode { get; set; } = (KeyCode)289; } public const float DEFAULT_SCAN_RADIUS = 100f; public const float DEFAULT_SCAN_INTERVAL_MS = 5000f; public const float MIN_SCAN_RADIUS = 1f; public const float MIN_SCAN_INTERVAL_MS = 500f; public const float MAX_SCAN_RADIUS = 500f; public const float MAX_SCAN_INTERVAL_MS = 30000f; private const float DEFAULT_FLOAT_FALLBACK = 1f; public const string HOTKEY_DEFAULT = "F8"; public GeneralConfig General = new GeneralConfig(); private HashSet _loadedKeys = new HashSet(); private bool _configNeedsResave = false; private string configFilePath; private Dictionary> _configSetters; private static readonly Dictionary _intPropertyBounds = new Dictionary { { "ScanRadius", (1f, 500f, 100f) }, { "ScanInterval", (500f, 30000f, 5000f) }, { "WindowHeight", (774f, float.MaxValue, 774f) } }; public List Categories { get; private set; } = new List(); public bool ModEnabled => General.Enabled; public float ScanRadius => General.ScanRadius; public float ScanInterval => General.ScanInterval; public bool ShowLabels => General.ShowLabels; public bool IsDebug => General.IsDebug; public bool IsTrace => General.IsTrace; public bool IsBenchmark => General.IsBenchmark; public bool IsResourceEnabled(string id) { return FindEntry(id)?.Enabled ?? false; } public ResourceCategory FindCategory(string categoryId) { return Categories.FirstOrDefault((ResourceCategory c) => c.Id == categoryId); } public ResourceEntry FindEntry(string entryId) { foreach (ResourceCategory category in Categories) { ResourceEntry resourceEntry = category.FindEntry(entryId); if (resourceEntry != null) { return resourceEntry; } } return null; } public void LoadConfig() { configFilePath = Path.Combine(Paths.ConfigPath, "FoobyScroobs.Pinsanity.cfg"); BuildCategoryDefinitions(); InitializeConfigSetters(); DebugLogger.Trace("Loading config from " + configFilePath); if (!File.Exists(configFilePath)) { DebugLogger.Info("Config file not found, creating default"); SaveConfig(); return; } try { ParseConfigFile(); HandleConfigMigration(); } catch (Exception ex) { DebugLogger.Error("Error loading config", ex); DebugLogger.Info($"Falling back to defaults: Enabled={General.Enabled}, ScanRadius={General.ScanRadius}, ScanInterval={General.ScanInterval}, ShowLabels={General.ShowLabels}, IsDebug={General.IsDebug}, IsTrace={General.IsTrace}, IsBenchmark={General.IsBenchmark}, Hotkey={General.HotkeyString}"); SaveConfig(); } } private void BuildCategoryDefinitions() { Categories = new List(); Dictionary dictionary = new Dictionary(); (string, string, string)[] categories = CategoryDefinitions.Categories; for (int i = 0; i < categories.Length; i++) { (string, string, string) tuple = categories[i]; ResourceCategory resourceCategory = new ResourceCategory(tuple.Item1, tuple.Item2, tuple.Item3); Categories.Add(resourceCategory); dictionary[tuple.Item1] = resourceCategory; } HashSet hashSet = new HashSet(); foreach (ResourceDef definition in RS_ResourceDefinitions.GetDefinitions()) { if (!dictionary.TryGetValue(definition.Category, out var value)) { DebugLogger.Error("BuildCategoryDefinitions: unknown category '" + definition.Category + "' for entry '" + definition.Id + "', skipping"); } else if (hashSet.Contains(definition.Id)) { DebugLogger.Error("BuildCategoryDefinitions: duplicate ID '" + definition.Id + "' in definition table, second entry ignored. Check RS_ResourceDefinitionTable.cs."); } else { hashSet.Add(definition.Id); value.Entries.Add(new ResourceEntry(definition.Id, definition.FriendlyName, definition.EnabledByDefault, definition.Note, definition.IconStrategy == Icon.PickableItem)); } } DebugLogger.Trace($"Built {Categories.Count} categories"); foreach (ResourceCategory category in Categories) { DebugLogger.Trace($" {category.Id}: {category.Entries.Count} entries"); } } private void InitializeConfigSetters() { _configSetters = new Dictionary>(); GenerateSettersForClass(General, "General"); GenerateSettersForCategories(); GenerateSettersForEntries(); DebugLogger.Trace($"Generated {_configSetters.Count} config setters"); } private void GenerateSettersForClass(object instance, string sectionName) { Type type = instance.GetType(); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); PropertyInfo[] array = properties; foreach (PropertyInfo prop in array) { string text = sectionName + "." + prop.Name; if (_configSetters.ContainsKey(text)) { continue; } if (prop.PropertyType == typeof(bool)) { _configSetters[text] = delegate(string v) { prop.SetValue(instance, ParseBool(v)); }; } else if (prop.PropertyType == typeof(int)) { BuildIntSetter(prop, text, instance); } else if (prop.PropertyType == typeof(float)) { string capturedKey = prop.Name; _configSetters[text] = delegate(string v) { prop.SetValue(instance, ParseFloat(v, capturedKey, 1f, 0f)); }; } else if (prop.PropertyType == typeof(string)) { if (prop.Name == "HotkeyString") { string key = sectionName + ".Hotkey"; _configSetters[key] = delegate(string v) { ParseHotkey(v); }; } else { _configSetters[text] = delegate(string v) { prop.SetValue(instance, v); }; } } else if (!(prop.PropertyType == typeof(KeyCode))) { DebugLogger.Warning($"Unknown property type for {text}: {prop.PropertyType}"); } } } private void BuildIntSetter(PropertyInfo prop, string fullKey, object instance) { string capturedKey = prop.Name; float capturedDefault; float capturedMin; float capturedMax; if (_intPropertyBounds.TryGetValue(prop.Name, out (float, float, float) value)) { capturedDefault = value.Item3; (capturedMin, capturedMax, _) = value; } else { capturedDefault = 1f; capturedMin = 1f; capturedMax = float.MaxValue; } _configSetters[fullKey] = delegate(string v) { prop.SetValue(instance, (int)ParseFloat(v, capturedKey, capturedDefault, capturedMin, capturedMax)); }; } private void GenerateSettersForCategories() { (string, string, string)[] categories = CategoryDefinitions.Categories; for (int i = 0; i < categories.Length; i++) { (string, string, string) tuple = categories[i]; if (tuple.Item1 == "ModSettings") { continue; } string id = tuple.Item1; string key = "Resource Categories.Enable" + id; _configSetters[key] = delegate(string v) { bool enabled = ParseBool(v); ResourceCategory resourceCategory = FindCategory(id); if (resourceCategory != null) { resourceCategory.Enabled = enabled; } }; } } private void GenerateSettersForEntries() { foreach (ResourceCategory category in Categories) { foreach (ResourceEntry entry in category.Entries) { string id = entry.Id; string key = "Resources and Labels.Enable" + id; string key2 = "Resources and Labels." + id + "Label"; _configSetters[key] = delegate(string v) { entry.Enabled = ParseBool(v); }; _configSetters[key2] = delegate(string v) { entry.UserLabel = v; }; } } } private void ApplyConfigValue(string section, string key, string value) { try { string text = section + "." + key; if (_configSetters.TryGetValue(text, out var value2)) { value2(value); } else { DebugLogger.Warning("Unknown config key: " + text); } } catch (Exception ex) { DebugLogger.Warning("Failed to set " + section + "." + key + " = " + value + ": " + ex.Message); } } public void SaveConfig() { try { using StreamWriter writer = new StreamWriter(configFilePath); WriteGeneralSection(writer); WriteCategorySection(writer); WriteItemSection(writer); } catch (Exception ex) { DebugLogger.Error("Error saving config", ex); } } private void WriteGeneralSection(StreamWriter writer) { writer.WriteLine("[General]"); writer.WriteLine("# Values are true or false unless otherwise specified"); writer.WriteLine("Enabled = " + General.Enabled.ToString().ToLower()); writer.WriteLine("ShowLabels = " + General.ShowLabels.ToString().ToLower() + " # false hides all labels regardless of individual settings"); writer.WriteLine("IsBenchmark = " + General.IsBenchmark.ToString().ToLower() + " # For those who love to watch their fps: logs a detailed list of 'how long everything takes' for every scan"); writer.WriteLine("IsDebug = " + General.IsDebug.ToString().ToLower() + " # For testers: logs settings changes, pin state changes, and window actions"); writer.WriteLine("IsTrace = " + General.IsTrace.ToString().ToLower() + " # For developers and extreme nerds: incredibly-detailed firehose of pretty-much-everything (requires IsDebug = true)"); writer.WriteLine($"ScanRadius = {General.ScanRadius} # in meters"); writer.WriteLine($"ScanInterval = {General.ScanInterval} # in milliseconds"); writer.WriteLine("Hotkey = " + General.HotkeyString + " # key to open/close the settings window (examples: F7, F8, Delete, Home)"); writer.WriteLine($"WindowHeight = {(((float)General.WindowHeight < 774f) ? Mathf.RoundToInt(1000f) : Mathf.RoundToInt((float)General.WindowHeight))} # window height (default = 1000)"); writer.WriteLine(); } private void WriteCategorySection(StreamWriter writer) { writer.WriteLine("[Resource Categories]"); writer.WriteLine("# Each category can be enabled/disabled by setting it to true/false below."); writer.WriteLine("# Disabling a category overrides all its individual item toggles."); (string, string, string)[] categories = CategoryDefinitions.Categories; for (int i = 0; i < categories.Length; i++) { (string, string, string) tuple = categories[i]; if (!(tuple.Item1 == "ModSettings")) { bool flag = FindCategory(tuple.Item1)?.Enabled ?? true; writer.WriteLine(); if (!string.IsNullOrEmpty(tuple.Item3)) { writer.WriteLine("# " + tuple.Item3); } writer.WriteLine("Enable" + tuple.Item1 + " = " + flag.ToString().ToLower()); } } writer.WriteLine(); } private void WriteItemSection(StreamWriter writer) { writer.WriteLine("[Resources and Labels]"); writer.WriteLine("# Each resource has two settings:"); writer.WriteLine("# Enable{Name} = true/false"); writer.WriteLine("# {Name}Label = display text shown on the minimap pin"); writer.WriteLine(); Dictionary dictionary = (from d in RS_ResourceDefinitions.GetDefinitions() group d by d.Id).ToDictionary((IGrouping g) => g.Key, (IGrouping g) => g.First().Note); foreach (ResourceCategory category in Categories) { if (category.Entries.Count == 0 || category.Id == "ModSettings") { continue; } string text = new string('=', Math.Max(0, 44 - category.DefaultLabel.Length)); writer.WriteLine("# " + category.DefaultLabel + " " + text); foreach (ResourceEntry entry in category.Entries) { writer.WriteLine("Enable" + entry.Id + " = " + entry.Enabled.ToString().ToLower()); writer.WriteLine(entry.Id + "Label = \"" + entry.UserLabel + "\""); if (dictionary.TryGetValue(entry.Id, out var value) && !string.IsNullOrEmpty(value)) { writer.WriteLine("# Note: " + value); } writer.WriteLine(); } } } private void ParseConfigFile() { string[] array = File.ReadAllLines(configFilePath); string text = ""; int num = 0; _loadedKeys.Clear(); DebugLogger.Trace($"Read {array.Length} lines from config"); string[] array2 = array; foreach (string text2 in array2) { num++; string text3 = text2.Trim(); if (!string.IsNullOrEmpty(text3) && !text3.StartsWith("#")) { if (text3.StartsWith("[") && text3.EndsWith("]")) { text = text3.Substring(1, text3.Length - 2); DebugLogger.Trace($"Found section [{text}] at line {num}"); } else if (text3.Contains("=")) { ParseConfigLine(text, text3); } } } DebugLogger.Trace($"Config loaded - ShowLabels: {General.ShowLabels}, ScanInterval: {General.ScanInterval}"); } private void ParseConfigLine(string section, string line) { string[] array = line.Split(new char[1] { '=' }, 2); if (array.Length != 2) { return; } string text = array[0].Trim(); string text2 = array[1].Trim(); if (text2.StartsWith("\"") && text2.EndsWith("\"")) { text2 = text2.Substring(1, text2.Length - 2); } else { int num = text2.IndexOf('#'); if (num >= 0) { text2 = text2.Substring(0, num).Trim(); } } DebugLogger.Trace("Setting " + section + "." + text + " = '" + text2 + "'"); string item = section + "." + text; _loadedKeys.Add(item); ApplyConfigValue(section, text, text2); } private void HandleConfigMigration() { List list = _configSetters.Keys.Where((string k) => !_loadedKeys.Contains(k)).ToList(); if (list.Count > 0 || _configNeedsResave) { if (list.Count > 0) { DebugLogger.Info($"Found {list.Count} missing settings, adding with defaults"); foreach (string item in list) { DebugLogger.DebugMessage(" Missing: " + item); } } if (_configNeedsResave) { DebugLogger.Info("Invalid config values were corrected, resaving"); } SaveConfig(); } else { DebugLogger.Trace("Config is up to date"); } } private bool ParseBool(string value) { return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase); } private float ParseFloat(string value, string key = "float", float defaultValue = 1f, float minValue = 1f, float maxValue = float.MaxValue) { if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { if (result < minValue) { DebugLogger.Warning($"ConfigManager: '{key}' value ({result}) is too low, minimum is {minValue}, resetting to default ({defaultValue})"); _configNeedsResave = true; return defaultValue; } if (result > maxValue) { DebugLogger.Warning($"ConfigManager: '{key}' value ({result}) is too high, maximum is {maxValue}, resetting to default ({defaultValue})"); _configNeedsResave = true; return defaultValue; } return result; } DebugLogger.Warning($"ConfigManager: '{key}' value ('{value}') is not a valid number, resetting to default ({defaultValue})"); _configNeedsResave = true; return defaultValue; } private void ParseHotkey(string value) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (Enum.TryParse(value, out KeyCode result)) { General.HotkeyString = value; General.HotkeyCode = result; } else { DebugLogger.Warning("ConfigManager: 'Hotkey' value '" + value + "' is not a valid KeyCode, using default (F8)"); General.HotkeyString = "F8"; General.HotkeyCode = (KeyCode)Enum.Parse(typeof(KeyCode), "F8"); } } } public static class Constants { public static class Valheim { public const string CloneSuffix = "(Clone)"; public const string PickablePrefix = "Pickable_"; public const string PickedFieldName = "m_picked"; public const string BeehiveHoneyZdoKey = "level"; public const string PlainRockMaterial = "rock1"; public const string MaterialInstanceSuffix = " (Instance)"; } public static class Pins { public const float WorldSizeFixed = 0f; public const string PinTypePrefix = "Icon"; public const int PinTypePrefixLength = 4; } public static class Scanning { public const int LookupPrefixLength = 5; public const float MsPerSecond = 1000f; public const long TicksToNs = 100L; public const string DepositTypeSentinel = "none"; public const float ScanTimerInitialValue = float.MaxValue; } public static class Plugin { public const string Guid = "FoobyScroobs.Pinsanity"; public const string Name = "Pinsanity"; public const string Version = "1.0.0"; public const string ConfigFile = "FoobyScroobs.Pinsanity.cfg"; } public static class GUI { public const int WindowId = 1138; public const string SettingsFieldPrefix = "SettingsField_"; public const string HiddenLabelPlaceholder = "(none)"; public const float MsPerSecond = 1000f; } public static class Config { public const int SectionDividerWidth = 44; public const string SectionGeneral = "General"; public const string SectionCategories = "Resource Categories"; public const string SectionItems = "Resources and Labels"; public const string HotkeyConfigKey = "Hotkey"; } } public static class DebugLogger { private static string Prefix => typeof(BepInExPlugin).Namespace + " "; public static void DebugMessage(string message) { if (BepInExPlugin.ConfigManager != null && BepInExPlugin.ConfigManager.IsDebug) { Debug.Log((object)(Prefix + message)); } } public static void Trace(string message) { if (BepInExPlugin.ConfigManager != null && BepInExPlugin.ConfigManager.IsDebug && BepInExPlugin.ConfigManager.IsTrace) { Debug.Log((object)(Prefix + message)); } } public static void Benchmark(string message) { if (BepInExPlugin.ConfigManager != null && BepInExPlugin.ConfigManager.IsBenchmark) { Debug.Log((object)(Prefix + message)); } } public static void Info(string message) { Debug.Log((object)(Prefix + message)); } public static void Warning(string message) { Debug.LogWarning((object)(Prefix + message)); } public static void Error(string message, Exception ex = null) { if (ex != null) { Debug.LogError((object)(Prefix + message + ": " + ex.Message + "\n" + ex.StackTrace)); } else { Debug.LogError((object)(Prefix + message)); } } } public class GUI_GuiManager { private float _windowCloseTime = 0f; private float _windowEscCloseTime = 0f; private bool _hasWindowEverOpened = false; private bool _isWindowOpen = false; private Rect _windowRect = new Rect(0f, 60f, 920f, 1000f); private Vector2 _resourceScroll = Vector2.zero; private string _selectedCategoryId = null; private bool _weDidPause = false; private bool _isRebuildPending = false; private bool _isResizing = false; private Vector2 _resizeStartMouse = Vector2.zero; private float _resizeStartHeight = 0f; private int _forceCloseCount = 0; private string _errorMessage = null; private bool _isReopenPending = false; private string _permanentErrorMsg = null; private string _editingEntryId = null; private string _editingFieldValue = ""; private string _focusedLabelFieldId = null; private readonly Dictionary _settingsFieldBuffers = new Dictionary(); private static readonly GUIContent _labelPrefixContent = new GUIContent("Label:"); private GUIStyle _windowStyle; private GUIStyle _h1Style; private GUIStyle _h2Style; private GUIStyle _h2WarningStyle; private GUIStyle _h3Style; private GUIStyle _bodyStyle; private GUIStyle _h4Style; private GUIStyle _noteStyle; private GUIStyle _textFieldStyle; private GUIStyle _actionButtonStyle; private GUIStyle _navButtonStyle; private GUIStyle _navButtonActiveStyle; private GUIStyle _hideActiveStyle; private GUIStyle _hiddenPlaceholderStyle; private bool _areStylesBuilt = false; private readonly List _navTextures = new List(); private Texture2D _texToggleOn; private Texture2D _texToggleOff; private Texture2D _texToggleOnHover; private Texture2D _texToggleOffHover; private Texture2D _texResizeGrip; public bool IsWindowOpen => _isWindowOpen; public bool IsPostCloseSuppressing { get { if (!_hasWindowEverOpened) { return false; } float num = (Time.realtimeSinceStartup - _windowCloseTime) * 1000f; if (num < 200f) { DebugLogger.DebugMessage($"Map click suppressed: {num:F0}ms since window close (suppression window: {200f}ms)"); return true; } return false; } } public bool IsPostCloseEscSuppressing { get { if (!_hasWindowEverOpened) { return false; } float num = (Time.realtimeSinceStartup - _windowEscCloseTime) * 1000f; if (num < 80f) { return true; } return false; } } private void DrawWindowContents(int windowId) { ConfigManager configManager = BepInExPlugin.ConfigManager; GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Pinsanity: Settings", _h1Style, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(2f); if (!BepInExPlugin.ConfigManager.ModEnabled) { GUILayout.Label("Note: mod is TURNED OFF. Re-enable it in Settings", _h2WarningStyle, Array.Empty()); } else { GUILayout.Label("Turn resources on/off and customize labels. Changes save to the cfg file immediately.", _h2Style, Array.Empty()); } GUILayout.Space(8f); if (_errorMessage != null) { GUILayout.Label(_errorMessage, _h2WarningStyle, Array.Empty()); GUILayout.Space(5f); } GUILayout.BeginHorizontal(Array.Empty()); DrawNavColumn(configManager); DrawContentColumn(configManager); GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("Close [" + BepInExPlugin.ConfigManager.General.HotkeyString + " or ESC]", _actionButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(((Rect)(ref _windowRect)).width * 0.88f) })) { DebugLogger.Trace("Close button clicked"); ToggleWindow(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); DrawResizeHandle(); } private void DrawNavColumn(ConfigManager cfg) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) }); foreach (ConfigManager.ResourceCategory category in cfg.Categories) { bool flag = category.Id == _selectedCategoryId; GUIStyle val = (flag ? _navButtonActiveStyle : _navButtonStyle); Color color = GUI.color; if (!category.Enabled) { GUI.color = new Color(1f, 1f, 1f, 0.15f); } if (GUILayout.Button(category.DefaultLabel, val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) }) && !flag) { DebugLogger.Trace("Nav: selected category " + category.Id); CommitLabelEdit(); _settingsFieldBuffers.Clear(); GUI.FocusControl(""); _selectedCategoryId = category.Id; _resourceScroll = Vector2.zero; } GUI.color = color; } GUILayout.EndVertical(); } private void DrawContentColumn(ConfigManager cfg) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(16f); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(564f) }); ConfigManager.ResourceCategory resourceCategory = cfg.FindCategory(_selectedCategoryId); if (resourceCategory == null) { DebugLogger.Error("DrawContentColumn: unknown category id '" + _selectedCategoryId + "'. This is a bug. Quit and reload your game to recover. If this persists, please report it!"); GUILayout.EndVertical(); GUILayout.EndHorizontal(); return; } if (resourceCategory.Id == "ModSettings") { DrawModSettingsPanel(cfg); } else { DrawCategoryHeader(resourceCategory); Color color = GUI.color; if (!resourceCategory.Enabled) { GUI.color = new Color(1f, 1f, 1f, 0.15f); } _resourceScroll = GUILayout.BeginScrollView(_resourceScroll, Array.Empty()); if (!string.IsNullOrEmpty(resourceCategory.Note)) { GUILayout.Space(5f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(resourceCategory.Note, _noteStyle, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(5f); } GUILayout.Space(5f); GUILayout.BeginHorizontal(Array.Empty()); float num = 143f; if (GUILayout.Button("All On", _actionButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num) })) { ApplyToEntries(resourceCategory.Entries, "All On: " + resourceCategory.Id, delegate(ConfigManager.ResourceEntry e) { e.Enabled = true; }, cfg); } GUILayout.Space(8.25f); if (GUILayout.Button("All Off", _actionButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num) })) { ApplyToEntries(resourceCategory.Entries, "All Off: " + resourceCategory.Id, delegate(ConfigManager.ResourceEntry e) { e.Enabled = false; }, cfg); } GUILayout.Space(8.25f); if (GUILayout.Button("Reset Labels", _actionButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num) })) { ApplyToEntries(resourceCategory.Entries, "Reset Labels: " + resourceCategory.Id, delegate(ConfigManager.ResourceEntry e) { e.UserLabel = e.DefaultLabel; }, cfg); } GUILayout.EndHorizontal(); GUILayout.Space(20f); foreach (ConfigManager.ResourceEntry entry in resourceCategory.Entries) { DrawEntryRows(entry, cfg); } GUILayout.EndScrollView(); GUI.color = color; } GUILayout.EndVertical(); GUILayout.EndHorizontal(); } private void DrawCategoryHeader(ConfigManager.ResourceCategory cat) { float num = 8.25f; float num2 = Mathf.Max(20f, 30f); GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num2) }); DrawCustomToggle(cat.Enabled, delegate(bool val) { DebugLogger.DebugMessage($"Category toggle: {cat.Id} = {val}"); SetCategoryEnabled(cat, val, BepInExPlugin.ConfigManager); }); GUILayout.Space(num); GUILayout.Label(cat.DefaultLabel, _h3Style, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(20f); } private void DrawResizeHandle() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) float num = 24f; Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref _windowRect)).width - num - 4f, ((Rect)(ref _windowRect)).height - num - 4f, num, num); bool flag = false; if ((int)Event.current.type == 7) { flag = ((Rect)(ref val)).Contains(Event.current.mousePosition); } Color color = GUI.color; if (flag) { GUI.color = Layout.ResizeHandleHoverColor; GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture); } GUI.color = (flag ? Layout.ResizeHandleTextHover : Layout.ResizeHandleTextNormal); GUI.DrawTexture(val, (Texture)(object)_texResizeGrip, (ScaleMode)2); GUI.color = color; if ((int)Event.current.type == 0 && ((Rect)(ref val)).Contains(Event.current.mousePosition) && !_isResizing) { _isResizing = true; _resizeStartMouse = new Vector2(Input.mousePosition.x, (float)Screen.height - Input.mousePosition.y); _resizeStartHeight = ((Rect)(ref _windowRect)).height; Event.current.Use(); DebugLogger.Trace("Resize started"); } } private void DrawSeparator() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0049: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(15f); Color color = GUI.color; GUI.color = Layout.SeparatorColor; Rect lastRect = GUILayoutUtility.GetLastRect(); GUI.DrawTexture(new Rect(0f, ((Rect)(ref lastRect)).yMax, 852f, 1f), (Texture)(object)Texture2D.whiteTexture); GUI.color = color; GUILayout.Space(15f); } private void DrawModSettingsPanel(ConfigManager cfg) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_03af: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_03e2: Unknown result type (might be due to invalid IL or missing references) _resourceScroll = GUILayout.BeginScrollView(_resourceScroll, Array.Empty()); GUILayout.Space(5f); DrawSettingsToggle("Enabled", cfg.General.Enabled, "Enables or disables the entire mod", delegate(bool val) { cfg.General.Enabled = val; if (val) { BepInExPlugin.Scanner.Initialize(); BepInExPlugin.ResetScanTimer(); } else { BepInExPlugin.ClearAllResourcePins(); } cfg.SaveConfig(); }); DrawSettingsToggle("Show Labels", cfg.General.ShowLabels, "Show/hide labels for all resources", delegate(bool val) { cfg.General.ShowLabels = val; _isRebuildPending = true; cfg.SaveConfig(); }); DrawSettingsIntField("Scan Radius", cfg.General.ScanRadius, "How far around the player to scan for objects; default is 100m", delegate(int val) { cfg.General.ScanRadius = (int)Mathf.Clamp((float)val, 1f, 500f); DebugLogger.DebugMessage($"ScanRadius set to {cfg.General.ScanRadius} (input was {val})"); cfg.SaveConfig(); }); DrawSettingsIntField("Scan Interval", cfg.General.ScanInterval, "How often to scan, in milliseconds; default is 5000; min is 500", delegate(int val) { cfg.General.ScanInterval = (int)Mathf.Clamp((float)val, 500f, 30000f); DebugLogger.DebugMessage($"ScanInterval set to {cfg.General.ScanInterval} (input was {val})"); cfg.SaveConfig(); }); DrawSeparator(); GUILayout.Label("All Items", _bodyStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("On", _actionButtonStyle, Array.Empty())) { EnableAllItems(cfg); } GUILayout.Space(8.25f); if (GUILayout.Button("Off", _actionButtonStyle, Array.Empty())) { DisableAllItems(cfg); } GUILayout.Space(8.25f); if (GUILayout.Button("Reset", _actionButtonStyle, Array.Empty())) { ResetAllItems(cfg); } GUILayout.EndHorizontal(); GUILayout.Label("'Off' disables all resources across all categories; 'On' unleashes the full Pinsanity; 'Reset' puts them all back to defaults (recommended settings)", _noteStyle, Array.Empty()); GUILayout.Space(15f); GUILayout.Label("All Categories", _bodyStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("On (Default)", _actionButtonStyle, Array.Empty())) { EnableAllCategories(cfg); } GUILayout.Space(8.25f); if (GUILayout.Button("Off", _actionButtonStyle, Array.Empty())) { DisableAllCategories(cfg); } GUILayout.EndHorizontal(); GUILayout.Label("Does not change individual resource settings", _noteStyle, Array.Empty()); GUILayout.Space(15f); GUILayout.Label("All Labels", _bodyStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Clear All", _actionButtonStyle, Array.Empty())) { ClearAllLabels(cfg); } GUILayout.Space(8.25f); if (GUILayout.Button("Reset All", _actionButtonStyle, Array.Empty())) { ResetAllLabels(cfg); } GUILayout.EndHorizontal(); GUILayout.Label("To show labels for 1 or 2 resources: 'Clear All' empties all labels, then you can customize labels for individual resources", _noteStyle, Array.Empty()); GUILayout.Space(15f); if (GUILayout.Button("Reset Everything", _actionButtonStyle, Array.Empty())) { ResetEverything(cfg); } GUILayout.Label("Resets resources, categories, labels, and general mod settings", _noteStyle, Array.Empty()); DrawSeparator(); DrawSettingsToggle("Benchmarking", cfg.General.IsBenchmark, "Logs a detailed list of 'how long it all takes' for every scan", delegate(bool val) { cfg.General.IsBenchmark = val; cfg.SaveConfig(); }); DrawSettingsToggle("Debug Logging", cfg.General.IsDebug, "For testers: logs settings changes, pin state changes, and window actions", delegate(bool val) { cfg.General.IsDebug = val; cfg.SaveConfig(); }); Color color = GUI.color; if (!cfg.General.IsDebug) { GUI.color = new Color(1f, 1f, 1f, 0.15f); } DrawSettingsToggle("Trace Logging", cfg.General.IsTrace, "For developers and extreme nerds: incredibly-detailed firehose of pretty-much-everything; requires debug on", delegate(bool val) { cfg.General.IsTrace = val; cfg.SaveConfig(); }); GUI.color = color; GUILayout.EndScrollView(); } private void DrawSettingsToggle(string label, bool current, string note, Action onChange) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) float num = 8.25f; float num2 = Mathf.Max(20f, 30f); GUILayout.BeginVertical(Array.Empty()); GUILayout.Space(5f); GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num2) }); DrawCustomToggle(current, onChange); GUILayout.Space(num); GUILayout.Label(label, _bodyStyle, Array.Empty()); if (Layout.DebugBorders) { DrawDebugBorder(GUILayoutUtility.GetLastRect(), Color.yellow); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(20f + num + 4f); GUILayout.Label(note, _noteStyle, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(5f); GUILayout.EndVertical(); } private void DrawSettingsIntField(string label, int current, string note, Action onChange) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Invalid comparison between Unknown and I4 float num = 8.25f; float num2 = 145.2f; float num3 = 88f; string text = "SettingsField_" + label.Replace(" ", ""); GUILayout.BeginVertical(Array.Empty()); GUILayout.Space(5f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(label, _bodyStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2) }); if (Layout.DebugBorders) { DrawDebugBorder(GUILayoutUtility.GetLastRect(), Color.red); } GUILayout.Space(num); string value; bool flag = _settingsFieldBuffers.TryGetValue(text, out value); string text2 = (flag ? value : current.ToString()); GUI.SetNextControlName(text); string text3 = GUILayout.TextField(text2, _textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num3) }); if (Layout.DebugBorders) { DrawDebugBorder(GUILayoutUtility.GetLastRect(), Color.cyan); } bool flag2 = GUI.GetNameOfFocusedControl() == text; if (text3 != text2) { _settingsFieldBuffers[text] = text3; } else if (flag && !flag2 && (int)Event.current.type == 7) { _settingsFieldBuffers.Remove(text); if (int.TryParse(value, out var result)) { onChange(result); } else { DebugLogger.DebugMessage("Settings field rejected: " + text + " could not parse '" + value + "'"); } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(note, _noteStyle, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(5f); GUILayout.EndVertical(); } private void DrawEntryRows(ConfigManager.ResourceEntry entry, ConfigManager cfg) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: 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_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: 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_0317: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_04e1: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_054c: Unknown result type (might be due to invalid IL or missing references) float num = 8.25f; float num2 = 77f; float num3 = num2 + num; GUILayout.BeginVertical(Array.Empty()); GUILayout.Space(5f); float num4 = Mathf.Max(20f, 30f); GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num4) }); GUILayout.Space(20f); DrawCustomToggle(entry.Enabled, delegate(bool val) { DebugLogger.Trace($"Resource toggle: {entry.Id} = {val}"); SetResourceEnabled(entry, val, cfg); }); Color color = GUI.color; if (!entry.Enabled) { GUI.color = new Color(1f, 1f, 1f, 0.15f); } GUILayout.Space(num); GUILayout.Label(entry.DefaultLabel, _bodyStyle, Array.Empty()); GUILayout.EndHorizontal(); if (entry.Enabled) { color = GUI.color; } if (entry.LabelIsReadOnly) { if (!string.IsNullOrEmpty(entry.Note)) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(52.25f); GUILayout.Label(entry.Note, _noteStyle, Array.Empty()); GUILayout.EndHorizontal(); } GUI.color = color; GUILayout.Space(5f); GUILayout.EndVertical(); return; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(40f + num + 4f); float num5 = _h4Style.CalcSize(_labelPrefixContent).x + num; GUILayout.Label("Label:", _h4Style, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num5) }); if (Layout.DebugBorders) { DrawDebugBorder(GUILayoutUtility.GetLastRect(), Color.red); } GUILayout.Space(num + -11f); string text = "LabelField_" + entry.Id; string nameOfFocusedControl = GUI.GetNameOfFocusedControl(); GUI.SetNextControlName(text); string text2 = ((_editingEntryId == entry.Id) ? _editingFieldValue : entry.UserLabel); if (string.IsNullOrEmpty(entry.UserLabel) && !(_editingEntryId == entry.Id) && nameOfFocusedControl != text) { GUILayout.TextField("(none)", _hiddenPlaceholderStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(240f) }); if (Layout.DebugBorders) { DrawDebugBorder(GUILayoutUtility.GetLastRect(), Color.cyan); } } else { string newFieldValue = GUILayout.TextField(text2, _textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(240f) }); if (Layout.DebugBorders) { DrawDebugBorder(GUILayoutUtility.GetLastRect(), Color.cyan); } HandleLabelFieldInput(entry, text, text2, newFieldValue, nameOfFocusedControl); } GUILayout.Space(num); bool flag = string.IsNullOrEmpty((_editingEntryId == entry.Id) ? _editingFieldValue : entry.UserLabel); GUI.enabled = !flag; if (GUILayout.Button("None", flag ? _hideActiveStyle : _actionButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(66f) })) { DebugLogger.DebugMessage("Label hidden: " + entry.Id); _editingEntryId = null; SetResourceLabel(entry, "", cfg); } GUI.enabled = true; GUILayout.Space(num); if (entry.UserLabel != entry.DefaultLabel || (_editingEntryId == entry.Id && _editingFieldValue != entry.DefaultLabel)) { if (GUILayout.Button("Reset", _actionButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num2) })) { DebugLogger.DebugMessage("Label reset: " + entry.Id); _editingEntryId = null; SetResourceLabel(entry, entry.DefaultLabel, cfg); } } else { GUILayout.Space(num3); } GUILayout.EndHorizontal(); if (Layout.DebugBorders) { DrawDebugBorder(GUILayoutUtility.GetLastRect(), Color.yellow); } if (!string.IsNullOrEmpty(entry.Note)) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(40f + num + 4f); GUILayout.Label(entry.Note, _noteStyle, Array.Empty()); GUILayout.EndHorizontal(); } GUI.color = color; GUILayout.Space(5f); GUILayout.EndVertical(); } private void HandleLabelFieldInput(ConfigManager.ResourceEntry entry, string controlName, string currentFieldValue, string newFieldValue, string focusedControl) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Invalid comparison between Unknown and I4 //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Invalid comparison between Unknown and I4 if (focusedControl == controlName && _focusedLabelFieldId != controlName) { _focusedLabelFieldId = controlName; if ((int)Event.current.type == 7) { DebugLogger.Trace("Label field focused: " + controlName); } } else if (focusedControl != controlName && _focusedLabelFieldId == controlName) { _focusedLabelFieldId = null; } if (newFieldValue != currentFieldValue) { if (_editingEntryId != entry.Id) { CommitLabelEdit(); _editingEntryId = entry.Id; } _editingFieldValue = newFieldValue; } if (_editingEntryId == entry.Id && focusedControl != controlName && (int)Event.current.type == 7) { CommitLabelEdit(); } } private void DrawCustomToggle(bool current, Action onChange) { //IL_0021: 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_0042: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) float num = 20f; Rect rect = GUILayoutUtility.GetRect(num, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(num), GUILayout.Height(num) }); ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + 8f; bool flag = ((Rect)(ref rect)).Contains(Event.current.mousePosition); Texture2D val = ((!current) ? (flag ? _texToggleOffHover : _texToggleOff) : (flag ? _texToggleOnHover : _texToggleOn)); GUI.DrawTexture(rect, (Texture)(object)val, (ScaleMode)2); if ((int)Event.current.type == 0 && ((Rect)(ref rect)).Contains(Event.current.mousePosition)) { DebugLogger.Trace($"Toggle clicked: {current} -> {!current}"); onChange(!current); Event.current.Use(); } } private void DrawDebugBorder(Rect r, Color color) { //IL_0012: 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) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0069: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) if (Layout.DebugBorders) { Color color2 = GUI.color; GUI.color = color; float num = 1f; GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y, ((Rect)(ref r)).width, num), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y + ((Rect)(ref r)).height, ((Rect)(ref r)).width, num), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y, num, ((Rect)(ref r)).height), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref r)).x + ((Rect)(ref r)).width, ((Rect)(ref r)).y, num, ((Rect)(ref r)).height), (Texture)(object)Texture2D.whiteTexture); GUI.color = color2; } } public void Cleanup() { _areStylesBuilt = false; if ((Object)(object)_texToggleOn != (Object)null) { Object.Destroy((Object)(object)_texToggleOn); _texToggleOn = null; } if ((Object)(object)_texToggleOff != (Object)null) { Object.Destroy((Object)(object)_texToggleOff); _texToggleOff = null; } if ((Object)(object)_texToggleOnHover != (Object)null) { Object.Destroy((Object)(object)_texToggleOnHover); _texToggleOnHover = null; } if ((Object)(object)_texToggleOffHover != (Object)null) { Object.Destroy((Object)(object)_texToggleOffHover); _texToggleOffHover = null; } if ((Object)(object)_texResizeGrip != (Object)null) { Object.Destroy((Object)(object)_texResizeGrip); _texResizeGrip = null; } foreach (Texture2D navTexture in _navTextures) { if ((Object)(object)navTexture != (Object)null) { Object.Destroy((Object)(object)navTexture); } } _navTextures.Clear(); } public void GenerateToggleTextures() { //IL_0022: 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_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_0058: 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_0073: 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: Unknown result type (might be due to invalid IL or missing references) Cleanup(); DebugLogger.Trace("Generating toggle textures..."); int size = Mathf.RoundToInt(20f) * 2; _texToggleOn = GUI_TextureUtils.MakeCircleTexture(size, Layout.ToggleOnFill, Layout.ToggleOnBorder, 2f); _texToggleOff = GUI_TextureUtils.MakeCircleTexture(size, Layout.ToggleOffFill, Layout.ToggleOffBorder, 2f); _texToggleOnHover = GUI_TextureUtils.MakeCircleTexture(size, Layout.ToggleOnHoverFill, Layout.ToggleOnHoverBorder, 2f); _texToggleOffHover = GUI_TextureUtils.MakeCircleTexture(size, Layout.ToggleOffHoverFill, Layout.ToggleOffHoverBorder, 2f); _texResizeGrip = GUI_TextureUtils.MakeResizeGripTexture(24, 4, 3, Color.white); DebugLogger.Trace("Toggle textures generated"); } public void Initialize() { ConfigManager configManager = BepInExPlugin.ConfigManager; if ((float)configManager.General.WindowHeight >= 774f) { ((Rect)(ref _windowRect)).height = configManager.General.WindowHeight; DebugLogger.DebugMessage($"Restored window height: {((Rect)(ref _windowRect)).height:F0}"); } else { DebugLogger.DebugMessage("No saved window height or too small, using default"); } if (configManager.Categories.Count > 0) { _selectedCategoryId = configManager.Categories[0].Id; } DebugLogger.Trace("GUI_GuiManager initialized"); DebugLogger.Trace("Press " + BepInExPlugin.ConfigManager.General.HotkeyString + " in-game to open the settings window"); } private void BuildDerivedStyles(GUISkin skin) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0175: 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_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Expected O, but got Unknown //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Expected O, but got Unknown //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Expected O, but got Unknown //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) GUI_StyleManager styleManager = BepInExPlugin.StyleManager; Font fontBody = styleManager.FontBody; Font font = styleManager.FontTitle ?? fontBody; _h1Style = MakeLabelStyle(skin, 40, (FontStyle)0, Layout.H1Color, font); _h2Style = MakeLabelStyle(skin, 22, (FontStyle)0, Layout.H2Color, fontBody); _h2WarningStyle = MakeLabelStyle(skin, 22, (FontStyle)0, Layout.H2WarningColor, fontBody); _h3Style = MakeLabelStyle(skin, 22, (FontStyle)1, Layout.H3Color, fontBody); _bodyStyle = MakeLabelStyle(skin, 22, (FontStyle)0, Layout.BodyColor, fontBody); _h4Style = MakeLabelStyle(skin, 22, (FontStyle)0, Layout.H4Color, fontBody, wordWrap: false, 3); _noteStyle = MakeLabelStyle(skin, 18, (FontStyle)0, Layout.NoteColor, fontBody, wordWrap: true); _textFieldStyle = new GUIStyle(skin.textField); _actionButtonStyle = new GUIStyle(skin.button); _windowStyle = new GUIStyle(skin.window); _navButtonStyle = new GUIStyle(skin.button); _navButtonStyle.alignment = (TextAnchor)3; _navButtonStyle.fontSize = 22; _navButtonStyle.padding = Layout.NavButtonPadding; _navButtonStyle.normal.background = TrackNavTex(Layout.NavButtonNormalBgColor); _navButtonStyle.hover.background = TrackNavTex(Layout.NavButtonHoverBgColor); _navButtonStyle.normal.textColor = Layout.H4Color; _navButtonStyle.hover.textColor = Color.white; _navButtonStyle.border = Layout.ZeroBorder; _navButtonActiveStyle = new GUIStyle(_navButtonStyle); _navButtonActiveStyle.normal.background = TrackNavTex(new Color(Layout.H1Color.r, Layout.H1Color.g, Layout.H1Color.b, 0.85f)); _navButtonActiveStyle.normal.textColor = Color.black; _navButtonActiveStyle.hover.background = TrackNavTex(new Color(Layout.H1Color.r, Layout.H1Color.g, Layout.H1Color.b, 1f)); _navButtonActiveStyle.hover.textColor = Color.black; _navButtonActiveStyle.fontStyle = (FontStyle)1; _hideActiveStyle = new GUIStyle(skin.button); _hideActiveStyle.normal.textColor = Color.yellow; _hideActiveStyle.hover.textColor = Color.yellow; _hiddenPlaceholderStyle = new GUIStyle(skin.textField); _hiddenPlaceholderStyle.normal.textColor = Layout.HiddenLabelTextColor; _hiddenPlaceholderStyle.focused.textColor = Layout.HiddenLabelTextColor; _hiddenPlaceholderStyle.fontStyle = (FontStyle)2; } private Texture2D TrackNavTex(Color color) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) Texture2D val = BepInExPlugin.StyleManager.MakeTexture(color, tracked: false); _navTextures.Add(val); return val; } private GUIStyle MakeLabelStyle(GUISkin skin, int size, FontStyle fontStyle, Color color, Font font = null, bool wordWrap = false, int paddingTopAdd = 0) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0016: 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) GUIStyle val = new GUIStyle(skin.label); val.fontSize = size; val.fontStyle = fontStyle; val.normal.textColor = color; if ((Object)(object)font != (Object)null) { val.font = font; } if (wordWrap) { val.wordWrap = true; } if (paddingTopAdd != 0) { RectOffset padding = val.padding; padding.top += paddingTopAdd; } return val; } public void Update() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) KeyCode hotkeyCode = BepInExPlugin.ConfigManager.General.HotkeyCode; if (Input.GetKeyDown(hotkeyCode)) { DebugLogger.Trace($"{hotkeyCode} pressed"); ToggleWindow(); } if (Input.GetKeyDown((KeyCode)27)) { if (_isWindowOpen) { DebugLogger.DebugMessage("ESC pressed: closing window"); ToggleWindow(closedViaEsc: true); } else if (IsPostCloseEscSuppressing) { float num = (Time.realtimeSinceStartup - _windowEscCloseTime) * 1000f; DebugLogger.DebugMessage($"ESC suppressed: {num:F0}ms since window close (suppression window: {80f}ms)"); } } if (_isReopenPending) { _isReopenPending = false; _isWindowOpen = true; PauseGame(); LockPlayerInput(); BepInExPlugin.ResetPatchSuppressionFlags(); DebugLogger.Warning($"Window reopened after force-close (attempt {_forceCloseCount})"); } if (_isResizing) { if (Input.GetMouseButton(0)) { float num2 = (float)Screen.height - Input.mousePosition.y; float num3 = num2 - _resizeStartMouse.y; ((Rect)(ref _windowRect)).height = Mathf.Max(774f, _resizeStartHeight + num3); } else { _isResizing = false; DebugLogger.DebugMessage($"Window resized to height: {((Rect)(ref _windowRect)).height:F0}"); } } } public void OnGUI() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0036: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Invalid comparison between Unknown and I4 //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Invalid comparison between Unknown and I4 //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Expected O, but got Unknown //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Invalid comparison between Unknown and I4 if (_permanentErrorMsg != null) { GUIStyle val = new GUIStyle(GUI.skin.label); val.fontSize = 22; val.fontStyle = (FontStyle)1; val.normal.textColor = Color.red; GUI.Label(new Rect(((float)Screen.width - 800f) * 0.5f, ((float)Screen.height - 180f) * 0.5f, 800f, 180f), _permanentErrorMsg, val); } else { if (!_isWindowOpen) { return; } try { GUI_StyleManager styleManager = BepInExPlugin.StyleManager; styleManager.EnsureSkinBuilt(); if ((Object)(object)styleManager.BuiltSkin == (Object)null) { DebugLogger.Error("OnGUI: BuiltSkin is null after EnsureSkinBuilt(), skipping frame"); return; } GUI.skin = styleManager.BuiltSkin; if (!_areStylesBuilt) { BuildDerivedStyles(styleManager.BuiltSkin); _areStylesBuilt = true; } if (_editingEntryId != null && (int)Event.current.type == 4 && ((int)Event.current.keyCode == 13 || (int)Event.current.keyCode == 271)) { DebugLogger.Trace("Enter pressed in label field: committing"); CommitLabelEdit(); Event.current.Use(); } GUI.color = new Color(0f, 0f, 0f, 0.65f); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; GL.sRGBWrite = false; _windowRect = GUI.Window(1138, _windowRect, new WindowFunction(DrawWindowContents), "", _windowStyle); GL.sRGBWrite = true; } catch (Exception ex) { DebugLogger.Error("OnGUI exception, closing window to prevent log spam", ex); ForceCloseWindow(); } } } private void ToggleWindow(bool closedViaEsc = false) { if (_permanentErrorMsg != null) { return; } if (!_isWindowOpen && (Object)(object)Game.instance == (Object)null) { DebugLogger.DebugMessage("Ignoring request for opening settings window (not in-game)"); return; } if (!_isWindowOpen && Menu.IsActive()) { DebugLogger.DebugMessage("Ignoring request for opening settings window (ESC menu is open)"); return; } if (!_isWindowOpen && (Object)(object)Player.m_localPlayer == (Object)null) { DebugLogger.DebugMessage("Ignoring request for opening settings window (character not loaded)"); return; } _isWindowOpen = !_isWindowOpen; if (_isWindowOpen) { OpenWindow(); } else { CloseWindow(closedViaEsc); } } private void OpenWindow() { _hasWindowEverOpened = true; ((Rect)(ref _windowRect)).x = ((float)Screen.width - 920f) * 0.5f; ((Rect)(ref _windowRect)).width = 920f; if (_selectedCategoryId == null) { _selectedCategoryId = BepInExPlugin.ConfigManager.Categories[0].Id; } DebugLogger.DebugMessage($"Settings window OPENED, height: {((Rect)(ref _windowRect)).height:F0}"); _editingEntryId = null; try { PauseGame(); LockPlayerInput(); } catch (Exception ex) { DebugLogger.Error("ToggleWindow: exception during open, restoring state", ex); UnpauseGame(); UnlockPlayerInput(); _isWindowOpen = false; return; } BepInExPlugin.ResetPatchSuppressionFlags(); } private void CloseWindow(bool closedViaEsc) { try { CommitLabelEdit(); FlushSettingsFieldBuffers(); _settingsFieldBuffers.Clear(); DebugLogger.DebugMessage("Settings window CLOSED"); if (_isRebuildPending) { DebugLogger.DebugMessage("Pending rebuild: clearing pins, reinitializing scanner"); BepInExPlugin.ClearAllResourcePins(); BepInExPlugin.Scanner.Initialize(); _isRebuildPending = false; } BepInExPlugin.ResetScanTimer(); ConfigManager configManager = BepInExPlugin.ConfigManager; if (((Rect)(ref _windowRect)).height != (float)configManager.General.WindowHeight) { configManager.General.WindowHeight = Mathf.RoundToInt(((Rect)(ref _windowRect)).height); configManager.SaveConfig(); DebugLogger.DebugMessage($"Window height saved: {((Rect)(ref _windowRect)).height:F0}"); } else { DebugLogger.Trace("Window height unchanged, skipping save"); } _errorMessage = null; _forceCloseCount = 0; _windowCloseTime = Time.realtimeSinceStartup; if (Minimap.IsOpen()) { DebugLogger.DebugMessage($"Window closed, map click suppression active for {200f}ms"); } if (closedViaEsc) { _windowEscCloseTime = Time.realtimeSinceStartup; DebugLogger.DebugMessage($"Window closed, ESC suppression active for {80f}ms"); } } finally { UnpauseGame(); UnlockPlayerInput(); } } private void ForceCloseWindow() { try { CommitLabelEdit(); FlushSettingsFieldBuffers(); _settingsFieldBuffers.Clear(); _editingEntryId = null; } finally { _isWindowOpen = false; _windowCloseTime = Time.realtimeSinceStartup; UnpauseGame(); UnlockPlayerInput(); } _forceCloseCount++; DebugLogger.Warning($"ForceCloseWindow called (attempt {_forceCloseCount})"); if (_forceCloseCount <= 3) { _errorMessage = $"Pinsanity: window was restarted after an internal error (#{_forceCloseCount})."; _isReopenPending = true; } else { _permanentErrorMsg = "Pinsanity: Wow! I didn't think anyone would ever see this. Well, the settings window has been disabled due to repeated errors. The mod is still running normally, but to use the settings GUI, you'll need to restart the game. Please report this (share what you were trying to do) on Nexus Mods or GitHub. Thanks!"; DebugLogger.Error($"ForceCloseWindow: permanent shutdown after {4} failures. Window disabled for this session; displaying big ugly error message"); } } private void PauseGame() { if ((Object)(object)Game.instance != (Object)null) { if (!Game.IsPaused()) { DebugLogger.Trace("Pausing game"); Game.Pause(); _weDidPause = true; } else { DebugLogger.Trace("Game already paused, not pausing again"); _weDidPause = false; } } else { DebugLogger.Trace("Game.instance null, skipping pause"); _weDidPause = false; } } private void UnpauseGame() { if ((Object)(object)Game.instance != (Object)null && _weDidPause) { DebugLogger.Trace("Unpausing game"); Game.Unpause(); } else { DebugLogger.Trace("Skipping unpause (we did not pause the game)"); } _weDidPause = false; } private void LockPlayerInput() { DebugLogger.Trace("Releasing cursor"); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } private void UnlockPlayerInput() { DebugLogger.Trace("Re-locking cursor"); Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; } private void SetResourceEnabled(ConfigManager.ResourceEntry entry, bool val, ConfigManager cfg) { entry.Enabled = val; _isRebuildPending = true; cfg.SaveConfig(); } private void SetResourceLabel(ConfigManager.ResourceEntry entry, string label, ConfigManager cfg) { entry.UserLabel = label; _isRebuildPending = true; cfg.SaveConfig(); } private void SetCategoryEnabled(ConfigManager.ResourceCategory cat, bool val, ConfigManager cfg) { cat.Enabled = val; _isRebuildPending = true; cfg.SaveConfig(); } private void ApplyToEntries(IEnumerable entries, string logMessage, Action entryAction, ConfigManager cfg, bool andSave = true) { DebugLogger.DebugMessage(logMessage); foreach (ConfigManager.ResourceEntry entry in entries) { DebugLogger.Trace(" " + logMessage + ": '" + entry.Id + "'"); entryAction(entry); } _isRebuildPending = true; if (andSave) { cfg.SaveConfig(); } } private void ApplyToCategories(ConfigManager cfg, string logMessage, Action action, bool andSave = true) { DebugLogger.DebugMessage(logMessage); foreach (ConfigManager.ResourceCategory category in cfg.Categories) { DebugLogger.Trace(" " + logMessage + ": '" + category.Id + "'"); action(category); } _isRebuildPending = true; if (andSave) { cfg.SaveConfig(); } } private void CommitLabelEdit() { if (_editingEntryId == null) { return; } ConfigManager.ResourceEntry resourceEntry = BepInExPlugin.ConfigManager.FindEntry(_editingEntryId); if (resourceEntry != null) { string text = _editingFieldValue.Trim(); if (text != resourceEntry.UserLabel) { DebugLogger.DebugMessage("Label committed: " + resourceEntry.Id + " = \"" + text + "\""); SetResourceLabel(resourceEntry, text, BepInExPlugin.ConfigManager); } else { DebugLogger.Trace("Label unchanged after trim: " + resourceEntry.Id + " (no save)"); } } _editingEntryId = null; } private void FlushSettingsFieldBuffers() { ConfigManager cfg = BepInExPlugin.ConfigManager; FlushNumericSetting(cfg, "ScanRadius", 1f, 500f, delegate(int val) { cfg.General.ScanRadius = val; }); FlushNumericSetting(cfg, "ScanInterval", 500f, 30000f, delegate(int val) { cfg.General.ScanInterval = val; }); } private void FlushNumericSetting(ConfigManager cfg, string fieldSuffix, float min, float max, Action apply) { string key = "SettingsField_" + fieldSuffix; if (_settingsFieldBuffers.TryGetValue(key, out var value) && int.TryParse(value, out var result)) { apply((int)Mathf.Clamp((float)result, min, max)); DebugLogger.DebugMessage($"Flushed {fieldSuffix}: {result} -> clamped [{min},{max}]"); cfg.SaveConfig(); } } private void EnableAllItems(ConfigManager cfg, bool andSave = true) { ApplyToEntries(cfg.Categories.SelectMany((ConfigManager.ResourceCategory c) => c.Entries), "Enable All Items", delegate(ConfigManager.ResourceEntry e) { e.Enabled = true; }, cfg, andSave); } private void DisableAllItems(ConfigManager cfg, bool andSave = true) { ApplyToEntries(cfg.Categories.SelectMany((ConfigManager.ResourceCategory c) => c.Entries), "Disable All Items", delegate(ConfigManager.ResourceEntry e) { e.Enabled = false; }, cfg, andSave); } private void ResetAllItems(ConfigManager cfg, bool andSave = true) { ApplyToEntries(cfg.Categories.SelectMany((ConfigManager.ResourceCategory c) => c.Entries), "Reset All Items", delegate(ConfigManager.ResourceEntry e) { e.Enabled = e.EnabledByDefault; }, cfg, andSave); } private void ClearAllLabels(ConfigManager cfg, bool andSave = true) { ApplyToEntries(cfg.Categories.SelectMany((ConfigManager.ResourceCategory c) => c.Entries), "Clear All Labels", delegate(ConfigManager.ResourceEntry e) { e.UserLabel = ""; }, cfg, andSave); } private void ResetAllLabels(ConfigManager cfg, bool andSave = true) { ApplyToEntries(cfg.Categories.SelectMany((ConfigManager.ResourceCategory c) => c.Entries), "Reset All Labels", delegate(ConfigManager.ResourceEntry e) { e.UserLabel = e.DefaultLabel; }, cfg, andSave); } private void EnableAllCategories(ConfigManager cfg, bool andSave = true) { ApplyToCategories(cfg, "Enable All Categories", delegate(ConfigManager.ResourceCategory c) { c.Enabled = true; }, andSave); } private void DisableAllCategories(ConfigManager cfg, bool andSave = true) { ApplyToCategories(cfg, "Disable All Categories", delegate(ConfigManager.ResourceCategory c) { c.Enabled = false; }, andSave); } private void ResetGeneralSettings(ConfigManager cfg, bool andSave = true) { cfg.General.Enabled = true; cfg.General.ShowLabels = true; cfg.General.IsDebug = false; cfg.General.IsTrace = false; cfg.General.IsBenchmark = false; cfg.General.ScanRadius = 100; cfg.General.ScanInterval = 5000; DebugLogger.DebugMessage("Reset General Settings"); if (andSave) { cfg.SaveConfig(); } } private void ResetEverything(ConfigManager cfg) { DebugLogger.Info("Reset everything (all resources, categories, labels, mod settings) to factory defaults"); ResetGeneralSettings(cfg, andSave: false); EnableAllCategories(cfg, andSave: false); ResetAllItems(cfg, andSave: false); ResetAllLabels(cfg, andSave: false); cfg.SaveConfig(); } } public static class Layout { public static readonly bool DebugBorders = false; public static readonly Color WindowBgColor = Hex("020202D9"); public const int WindowPadding = 20; public const float NavColumnWidth = 300f; public const float ContentColumnWidth = 580f; public const float WindowWidth = 920f; public const float WindowStartY = 60f; public const float WindowStartHeight = 1000f; public const float MinWindowHeight = 774f; public const float ResizeHandleSize = 24f; public const float OverlayOpacity = 0.65f; public const float PostCloseClickSuppressionMs = 200f; public const float PostCloseEscSuppressionMs = 80f; public const int H1FontSize = 40; public const FontStyle H1FontStyle = 0; public static readonly Color H1Color = Hex("FFA100"); public const int H2FontSize = 22; public const FontStyle H2FontStyle = 0; public static readonly Color H2Color = Hex("CC8100"); public const int H2WarningFontSize = 22; public const FontStyle H2WarningFontStyle = 0; public static readonly Color H2WarningColor = Hex("FF3030"); public const int H3FontSize = 22; public const FontStyle H3FontStyle = 1; public static readonly Color H3Color = Hex("FFFFFF"); public const int H4FontSize = 22; public const FontStyle H4FontStyle = 0; public static readonly Color H4Color = Hex("DDDDDD"); public const int BodyFontSize = 22; public const FontStyle BodyFontStyle = 0; public static readonly Color BodyColor = Hex("FFFFFF"); public const int NoteTextSize = 18; public const FontStyle NoteTextStyle = 0; public static readonly Color NoteColor = Hex("b5b5b5"); public const bool NoteWordWrap = true; public const int FieldFontSize = 24; public const float LabelFieldWidth = 240f; public static readonly RectOffset FieldPadding = new RectOffset(6, 6, 4, 4); public static readonly Color FieldBgColor = Hex("080808"); public static readonly Color FieldHoverBgColor = Hex("191919"); public static readonly Color FieldFocusedBgColor = Hex("202020"); public static readonly Color FieldActiveBgColor = Hex("404040"); public static readonly Color FieldTextColor = Hex("FFFFFF"); public const int SolidColorTextureSize = 2; public const float ToggleSize = 20f; public const float ToggleBorderSize = 2f; public const float ToggleVerticalOffset = 8f; public const int ToggleTextureScale = 2; public static readonly Color ToggleOnBorder = Hex("111111"); public static readonly Color ToggleOnFill = Hex("FF8000"); public static readonly Color ToggleOffBorder = Hex("808080"); public static readonly Color ToggleOffFill = Hex("040404"); public static readonly Color ToggleOnHoverBorder = Hex("FFFFFF"); public static readonly Color ToggleOnHoverFill = Hex("FF8000"); public static readonly Color ToggleOffHoverBorder = Hex("7E7E7E"); public static readonly Color ToggleOffHoverFill = Hex("404040"); public static readonly Color ButtonBgColor = Hex("0F0F0F"); public static readonly Color ButtonHoverColor = Hex("505050"); public static readonly Color ButtonActiveColor = Hex("808080"); public static readonly Color ButtonTextColor = Hex("FFFFFF"); public const int ButtonPadding = 5; public static readonly RectOffset ZeroBorder = new RectOffset(0, 0, 0, 0); public static readonly Color NavButtonNormalBgColor = Hex("1A1A1A"); public static readonly Color NavButtonHoverBgColor = Hex("333333"); public const float NavButtonActiveAlpha = 0.85f; public const float NavButtonFullAlpha = 1f; public static readonly RectOffset NavButtonPadding = new RectOffset(12, 8, 8, 8); public static readonly Color ScrollBgColor = Hex("1F1F1F"); public static readonly Color ScrollThumbColor = Hex("595959"); public static readonly Color ScrollThumbHover = Hex("808080"); public const float ResizeHandleInset = 4f; public const int ResizeHandleDotSize = 4; public const int ResizeHandleDotGap = 3; public const int ResizeHandleTexSize = 24; public const int ResizeGripGridSize = 3; public const int ResizeGripMargin = 3; public static readonly Color ResizeHandleHoverColor = Hex("202020E6"); public static readonly Color HiddenLabelTextColor = Hex("808080"); public static readonly Color ResizeHandleTextNormal = Hex("BFBFBFE6"); public static readonly Color ResizeHandleTextHover = Hex("FFFFFF"); public const float PermanentErrorWidth = 800f; public const float PermanentErrorHeight = 180f; public const int PermanentErrorFontSize = 22; public const int MaxForceCloseRetries = 3; public const string PermanentErrorMessage = "Pinsanity: Wow! I didn't think anyone would ever see this. Well, the settings window has been disabled due to repeated errors. The mod is still running normally, but to use the settings GUI, you'll need to restart the game. Please report this (share what you were trying to do) on Nexus Mods or GitHub. Thanks!"; public const float SectionBreakSpacingMultiplier = 3f; public const float DebugBorderThickness = 1f; public const float RowSpacing = 5f; public const float CategorySpacing = 20f; public const float EntryIndent = 20f; public const int LabelPrefixPaddingTop = 3; public const float LabelFieldOffsetX = -11f; public const float LabelRowIndentOffset = 4f; public const float ContentPaddingLeft = 16f; public const float SettingsNotePaddingLeft = 4f; public const float DisabledOpacity = 0.15f; public static readonly Color SeparatorColor = Hex("FFFFFF4D"); public const float SeparatorPadding = 15f; public const float SeparatorWidthOffset = -28f; public const float RowHeightPadding = 8f; public const float EntryGapMultiplier = 0.375f; public const float TitleSpacing = 2f; public const float SubtitleSpacing = 8f; public const float PreCloseButtonSpacing = 6f; public const float CloseButtonWidthFraction = 0.88f; public const float CategoryButtonWidthMultiplier = 6.5f; public const float ResetButtonWidthMultiplier = 3.5f; public const float HideButtonWidthMultiplier = 3f; public const float SettingsLabelWidthMultiplier = 6.6f; public const float SettingsFieldWidthMultiplier = 4f; public const string NoteSettingsEnabled = "Enables or disables the entire mod"; public const string NoteSettingsShowLabels = "Show/hide labels for all resources"; public const string NoteSettingsScanRadius = "How far around the player to scan for objects; default is 100m"; public const string NoteSettingsScanInterval = "How often to scan, in milliseconds; default is 5000; min is 500"; public const string NoteSettingsAllItems = "'Off' disables all resources across all categories; 'On' unleashes the full Pinsanity; 'Reset' puts them all back to defaults (recommended settings)"; public const string NoteSettingsAllCategories = "Does not change individual resource settings"; public const string NoteSettingsAllLabels = "To show labels for 1 or 2 resources: 'Clear All' empties all labels, then you can customize labels for individual resources"; public const string NoteSettingsResetEverything = "Resets resources, categories, labels, and general mod settings"; public const string NoteSettingsIsBenchmark = "Logs a detailed list of 'how long it all takes' for every scan"; public const string NoteSettingsIsDebug = "For testers: logs settings changes, pin state changes, and window actions"; public const string NoteSettingsIsTrace = "For developers and extreme nerds: incredibly-detailed firehose of pretty-much-everything; requires debug on"; private static Color Hex(string hex) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); ColorUtility.TryParseHtmlString("#" + hex, ref result); return result; } } public class GUI_StyleManager { private readonly List _skinTextures = new List(); private Font _fontBody = null; private Font _fontTitle = null; public GUISkin BuiltSkin { get; private set; } internal Font FontBody => _fontBody; internal Font FontTitle => _fontTitle; public void Initialize() { DebugLogger.Trace("GUI_StyleManager initialized"); } public void EnsureSkinBuilt() { if ((Object)(object)_fontBody == (Object)null || (Object)(object)_fontTitle == (Object)null) { FindFonts(); } if ((Object)(object)BuiltSkin == (Object)null) { DebugLogger.Trace("GUI_StyleManager: EnsureSkinBuilt(): first OnGUI call"); BuildSkin(); } } private void FindFonts() { Font[] array = Resources.FindObjectsOfTypeAll(); foreach (Font val in array) { if (((Object)val).name == "AveriaSerifLibre-Bold") { _fontBody = val; } if (((Object)val).name == "Norsebold") { _fontTitle = val; } } if ((Object)(object)_fontBody == (Object)null) { DebugLogger.Error("Font not found: AveriaSerifLibre-Bold"); } else { DebugLogger.Trace("Font found: AveriaSerifLibre-Bold"); } if ((Object)(object)_fontTitle == (Object)null) { DebugLogger.Error("Font not found: Norsebold"); } else { DebugLogger.Trace("Font found: Norsebold"); } } private void BuildSkin() { DebugLogger.Trace("GUI_StyleManager: BuildSkin()"); foreach (Texture2D skinTexture in _skinTextures) { if ((Object)(object)skinTexture != (Object)null) { Object.Destroy((Object)(object)skinTexture); } } _skinTextures.Clear(); if ((Object)(object)BuiltSkin != (Object)null) { Object.Destroy((Object)(object)BuiltSkin); } GUISkin val = Object.Instantiate(GUI.skin); val.label.font = _fontBody; val.textField.font = _fontTitle; val.button.font = _fontBody; Object.DontDestroyOnLoad((Object)(object)val); BuildWindowStyle(val); BuildTextFieldStyle(val); BuildButtonStyle(val); BuildScrollbarStyle(val); BuiltSkin = val; DebugLogger.Trace("GUI_StyleManager: BuildSkin() complete"); } private void BuildWindowStyle(GUISkin skin) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown Texture2D background = MakeTexture(Layout.WindowBgColor); skin.window.normal.background = background; skin.window.onNormal.background = background; skin.window.padding = new RectOffset(20, 20, 20, 20); } private void BuildTextFieldStyle(GUISkin skin) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: 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_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) skin.textField.fontSize = 24; skin.textField.padding = Layout.FieldPadding; Texture2D background = MakeTexture(Layout.FieldBgColor); Texture2D background2 = MakeTexture(Layout.FieldHoverBgColor); Texture2D background3 = MakeTexture(Layout.FieldFocusedBgColor); Texture2D background4 = MakeTexture(Layout.FieldActiveBgColor); skin.textField.normal.background = background; skin.textField.normal.textColor = Layout.FieldTextColor; skin.textField.hover.background = background2; skin.textField.hover.textColor = Layout.FieldTextColor; skin.textField.focused.background = background3; skin.textField.focused.textColor = Layout.FieldTextColor; skin.textField.active.background = background4; skin.textField.active.textColor = Layout.FieldTextColor; skin.textField.onNormal.background = background; skin.textField.onNormal.textColor = Layout.FieldTextColor; skin.textField.onHover.background = background2; skin.textField.onHover.textColor = Layout.FieldTextColor; skin.textField.onFocused.background = background3; skin.textField.onFocused.textColor = Layout.FieldTextColor; skin.textField.onActive.background = background4; skin.textField.onActive.textColor = Layout.FieldTextColor; skin.settings.cursorColor = Layout.FieldTextColor; } private void BuildButtonStyle(GUISkin skin) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0032: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) Texture2D background = MakeTexture(Layout.ButtonBgColor); Texture2D background2 = MakeTexture(Layout.ButtonHoverColor); Texture2D background3 = MakeTexture(Layout.ButtonActiveColor); RectOffset padding = new RectOffset(5, 5, 5, 5); skin.button.fontSize = 22; skin.button.padding = padding; skin.button.border = Layout.ZeroBorder; skin.button.normal.background = background; skin.button.normal.textColor = Layout.ButtonTextColor; skin.button.hover.background = background2; skin.button.hover.textColor = Layout.ButtonTextColor; skin.button.active.background = background3; skin.button.active.textColor = Layout.ButtonTextColor; skin.button.onNormal.background = background; skin.button.onNormal.textColor = Layout.ButtonTextColor; skin.button.onHover.background = background2; skin.button.onHover.textColor = Layout.ButtonTextColor; skin.button.onActive.background = background3; skin.button.onActive.textColor = Layout.ButtonTextColor; } private void BuildScrollbarStyle(GUISkin skin) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) Texture2D background = MakeTexture(Layout.ScrollBgColor); Texture2D background2 = MakeTexture(Layout.ScrollThumbColor); Texture2D background3 = MakeTexture(Layout.ScrollThumbHover); skin.verticalScrollbar.normal.background = background; skin.verticalScrollbar.hover.background = background; skin.verticalScrollbarThumb.normal.background = background2; skin.verticalScrollbarThumb.hover.background = background3; skin.verticalScrollbarThumb.active.background = background3; skin.horizontalScrollbar.normal.background = background; skin.horizontalScrollbar.hover.background = background; skin.horizontalScrollbarThumb.normal.background = background2; skin.horizontalScrollbarThumb.hover.background = background3; skin.horizontalScrollbarThumb.active.background = background3; } internal Texture2D MakeTexture(Color color, bool tracked = true) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected O, but got Unknown //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_002b: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(2, 2); val.SetPixels((Color[])(object)new Color[4] { color, color, color, color }); val.Apply(); Object.DontDestroyOnLoad((Object)(object)val); if (tracked) { _skinTextures.Add(val); } return val; } public void Cleanup() { foreach (Texture2D skinTexture in _skinTextures) { if ((Object)(object)skinTexture != (Object)null) { Object.Destroy((Object)(object)skinTexture); } } _skinTextures.Clear(); if ((Object)(object)BuiltSkin != (Object)null) { Object.Destroy((Object)(object)BuiltSkin); BuiltSkin = null; } } } public static class GUI_TextureUtils { public static Texture2D MakeCircleTexture(int size, Color fillColor, Color borderColor, float borderSize) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: 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_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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) //IL_0138: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(size, size, (TextureFormat)5, false); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; float num = (float)(size - 1) * 0.5f; float num2 = (float)(size - 1) * 0.5f; float num3 = (float)(size - 1) * 0.5f; Color bottom = default(Color); Color top = default(Color); Color val2 = default(Color); for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { float num4 = (float)j - num; float num5 = (float)i - num2; float num6 = Mathf.Sqrt(num4 * num4 + num5 * num5); float num7 = num3 - num6; if (num7 < -1f) { val.SetPixel(j, i, Color.clear); continue; } float num8 = Mathf.Clamp01(num7 + 0.5f); float num9 = num6 - (num3 - borderSize); float num10 = Mathf.Clamp01(num9 + 0.5f); if (num10 > 0f) { ((Color)(ref bottom))..ctor(fillColor.r, fillColor.g, fillColor.b, num8); ((Color)(ref top))..ctor(borderColor.r, borderColor.g, borderColor.b, num8 * num10); val2 = AlphaBlend(bottom, top); } else { ((Color)(ref val2))..ctor(fillColor.r, fillColor.g, fillColor.b, num8); } val.SetPixel(j, i, val2); } } val.Apply(); Object.DontDestroyOnLoad((Object)(object)val); return val; } public static Texture2D MakeResizeGripTexture(int texSize, int dotSize, int dotGap, Color dotColor) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0122: 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_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(texSize, texSize, (TextureFormat)5, false); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; for (int i = 0; i < texSize; i++) { for (int j = 0; j < texSize; j++) { val.SetPixel(j, i, Color.clear); } } int num = 3; int num2 = dotSize + dotGap; int num3 = 3; int num4 = num * num2 - dotGap; int num5 = texSize - num4 - num3; int num6 = texSize - num4 - num3; Color top = default(Color); for (int k = 0; k < num; k++) { for (int l = 0; l < num; l++) { if (l < k) { continue; } int num7 = num5 + l * num2 + dotSize / 2; int num8 = num6 + k * num2 + dotSize / 2; float num9 = (float)dotSize * 0.5f; for (int m = 0; m < texSize; m++) { for (int n = 0; n < texSize; n++) { float num10 = Mathf.Sqrt((float)((n - num7) * (n - num7) + (m - num8) * (m - num8))); float num11 = Mathf.Clamp01(num9 - num10 + 0.5f); if (!(num11 <= 0f)) { Color pixel = val.GetPixel(n, m); ((Color)(ref top))..ctor(dotColor.r, dotColor.g, dotColor.b, dotColor.a * num11); val.SetPixel(n, m, AlphaBlend(pixel, top)); } } } } } val.Apply(); Object.DontDestroyOnLoad((Object)(object)val); return val; } private static Color AlphaBlend(Color bottom, Color top) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_0049: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_00c4: Unknown result type (might be due to invalid IL or missing references) float num = top.a + bottom.a * (1f - top.a); if (num < 0.0001f) { return Color.clear; } float num2 = (top.r * top.a + bottom.r * bottom.a * (1f - top.a)) / num; float num3 = (top.g * top.a + bottom.g * bottom.a * (1f - top.a)) / num; float num4 = (top.b * top.a + bottom.b * bottom.a * (1f - top.a)) / num; return new Color(num2, num3, num4, num); } } [BepInPlugin("FoobyScroobs.Pinsanity", "Pinsanity", "1.0.0")] public class BepInExPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(GameCamera), "LateUpdate")] private static class GameCamera_LateUpdate_Patch { internal static bool _loggedSuppression; private static bool Prefix() { if (GuiManager.IsWindowOpen) { LogSuppressionOnce(ref _loggedSuppression, "GameCamera.LateUpdate suppressed (will not log again until window reopens)"); return false; } _loggedSuppression = false; return true; } } [HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")] private static class GameCamera_UpdateMouseCapture_Patch { internal static bool _loggedSuppression; private static bool Prefix() { if (GuiManager.IsWindowOpen) { LogSuppressionOnce(ref _loggedSuppression, "GameCamera.UpdateMouseCapture suppressed (will not log again until window reopens)"); return false; } _loggedSuppression = false; return true; } } [HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })] private static class ZInput_GetButtonDown_Patch { internal static bool _loggedSuppression; private static bool Prefix(ref bool __result) { if (GuiManager.IsWindowOpen) { LogSuppressionOnce(ref _loggedSuppression, "ZInput.GetButtonDown suppressed"); __result = false; return false; } if (GuiManager.IsPostCloseEscSuppressing) { __result = false; return false; } _loggedSuppression = false; return true; } } [HarmonyPatch(typeof(ZInput), "GetKeyDown", new Type[] { typeof(KeyCode), typeof(bool) })] private static class ZInput_GetKeyDown_Patch { internal static bool _loggedSuppression; private static bool Prefix(ref bool __result) { if (GuiManager.IsWindowOpen) { LogSuppressionOnce(ref _loggedSuppression, "ZInput.GetKeyDown suppressed"); __result = false; return false; } if (GuiManager.IsPostCloseEscSuppressing) { __result = false; return false; } _loggedSuppression = false; return true; } } [HarmonyPatch(typeof(Minimap), "Update")] private static class Minimap_Update_Patch { private static bool Prefix() { return !IsMinimapBlocked(); } } [HarmonyPatch(typeof(Minimap), "OnMapLeftClick")] private static class Minimap_OnMapLeftClick_Patch { private static bool Prefix() { return !IsMinimapClickBlocked(); } } [HarmonyPatch(typeof(Minimap), "OnMapRightClick")] private static class Minimap_OnMapRightClick_Patch { private static bool Prefix() { return !IsMinimapBlocked(); } } [HarmonyPatch(typeof(Minimap), "OnMapDblClick")] private static class Minimap_OnMapDblClick_Patch { private static bool Prefix() { return !IsMinimapClickBlocked(); } } [HarmonyPatch(typeof(Minimap), "SelectIcon")] private static class Minimap_SelectIcon_Patch { private static bool Prefix() { return !IsMinimapBlocked(); } } [HarmonyPatch(typeof(Minimap), "ToggleIconFilter")] private static class Minimap_ToggleIconFilter_Patch { private static bool Prefix() { return !IsMinimapBlocked(); } } private static float _scanTimer = float.MaxValue; private static Dictionary resourcePins = new Dictionary(); private static List trackedResources = new List(); private static Sprite defaultPinSprite = null; public static long PinAddTicks = 0L; public static ConfigManager ConfigManager { get; private set; } public static RS_ResourceScanner Scanner { get; private set; } public static GUI_GuiManager GuiManager { get; private set; } public static GUI_StyleManager StyleManager { get; private set; } private void Awake() { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown ConfigManager = new ConfigManager(); Scanner = new RS_ResourceScanner(); GuiManager = new GUI_GuiManager(); StyleManager = new GUI_StyleManager(); ConfigManager.LoadConfig(); Scanner.Initialize(); GuiManager.Initialize(); StyleManager.Initialize(); GuiManager.GenerateToggleTextures(); Harmony val = new Harmony("FoobyScroobs.Pinsanity"); val.PatchAll(typeof(BepInExPlugin).Assembly); DebugLogger.Trace($"Harmony patches applied: {val.GetPatchedMethods().Count()} methods patched"); DebugLogger.Info("Pinsanity loaded! Press " + ConfigManager.General.HotkeyString + " in-game to open the settings window."); } private void Update() { GuiManager.Update(); if (!ConfigManager.ModEnabled || (Object)(object)Player.m_localPlayer == (Object)null || Time.timeScale == 0f || GuiManager.IsWindowOpen) { return; } float num = ConfigManager.ScanInterval / 1000f; _scanTimer += Time.unscaledDeltaTime; if (_scanTimer >= num) { if (ConfigManager.IsDebug) { DebugLogger.Benchmark($"=== STARTING SCAN (interval: {ConfigManager.ScanInterval}ms) ==="); } Scanner.ScanAndUpdate(); _scanTimer = 0f; } } private void OnGUI() { GuiManager.OnGUI(); } public static void AddResourcePin(GameObject resource, string label, Sprite icon, PinType pinType = 3) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) AddResourcePin(resource, resource.transform.position, label, icon, pinType); } public static void AddResourcePin(GameObject resource, Vector3 position, string label, Sprite icon, PinType pinType = 3) { //IL_0034: 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_003e: 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) if (!resourcePins.ContainsKey(resource) && !((Object)(object)Minimap.instance == (Object)null)) { Stopwatch stopwatch = Stopwatch.StartNew(); PinData val = Minimap.instance.AddPin(position, pinType, label, false, false, 0L, default(PlatformUserID)); stopwatch.Stop(); PinAddTicks += stopwatch.ElapsedTicks; if ((Object)(object)defaultPinSprite == (Object)null && (Object)(object)val.m_icon != (Object)null) { defaultPinSprite = val.m_icon; } if ((Object)(object)icon != (Object)null) { val.m_icon = icon; val.m_worldSize = 0f; } val.m_name = label; resourcePins[resource] = val; trackedResources.Add(resource); } } public static void RemoveResourcePin(GameObject resource) { if (resourcePins.ContainsKey(resource)) { if ((Object)(object)Minimap.instance != (Object)null && resourcePins[resource] != null) { Minimap.instance.RemovePin(resourcePins[resource]); } resourcePins.Remove(resource); } trackedResources.Remove(resource); } public static void UpdateResourcePin(GameObject resource, string newLabel, Sprite newIcon) { if (!resourcePins.TryGetValue(resource, out var value) || value == null) { return; } if (value.m_name != newLabel) { value.m_name = newLabel; } if ((Object)(object)newIcon != (Object)null) { value.m_icon = newIcon; value.m_worldSize = 0f; } else { if ((Object)(object)defaultPinSprite != (Object)null) { value.m_icon = defaultPinSprite; } else { DebugLogger.Warning("UpdateResourcePin: defaultPinSprite is null, cannot reset icon for " + ((Object)resource).name); } value.m_worldSize = 0f; } DebugLogger.Trace($"Updated pin for {((Object)resource).name}: label='{newLabel}', hasCustomIcon={(Object)(object)newIcon != (Object)null}"); } public static List GetTrackedResources() { return new List(trackedResources); } public static bool IsTracked(GameObject resource) { return resourcePins.ContainsKey(resource); } public static string GetPinLabel(GameObject resource) { if (resourcePins.TryGetValue(resource, out var value)) { return value.m_name; } return null; } public static void ClearAllResourcePins() { foreach (KeyValuePair resourcePin in resourcePins) { if ((Object)(object)Minimap.instance != (Object)null && resourcePin.Value != null) { Minimap.instance.RemovePin(resourcePin.Value); } } resourcePins.Clear(); trackedResources.Clear(); Scanner?.ClearRuntimeState(); } public static void ResetScanTimer() { _scanTimer = float.MaxValue; } private void OnDestroy() { Scanner?.Shutdown(); ClearAllResourcePins(); GuiManager?.Cleanup(); StyleManager?.Cleanup(); } public static void ResetPatchSuppressionFlags() { GameCamera_LateUpdate_Patch._loggedSuppression = false; GameCamera_UpdateMouseCapture_Patch._loggedSuppression = false; ZInput_GetButtonDown_Patch._loggedSuppression = false; ZInput_GetKeyDown_Patch._loggedSuppression = false; } private static bool IsMinimapBlocked() { return GuiManager != null && GuiManager.IsWindowOpen; } private static bool IsMinimapClickBlocked() { return GuiManager != null && (GuiManager.IsWindowOpen || GuiManager.IsPostCloseSuppressing); } private static void LogSuppressionOnce(ref bool logged, string message) { if (!logged) { DebugLogger.Trace(message); logged = true; } } } public class RS_FragmentProcessor { private readonly RS_PinFormatter _pinFormatter; private readonly RS_ResourceScanner.ScanTimings _timings; private readonly Dictionary> _knownFragments = new Dictionary>(); public RS_FragmentProcessor(RS_PinFormatter pinFormatter, RS_ResourceScanner.ScanTimings timings) { _pinFormatter = pinFormatter; _timings = timings; } public void ProcessOrMaintainDeposit(GameObject depositParent, ResourceDef def, HashSet foundResources, HashSet foundDepositParentIds, ref int newPins) { int instanceID = ((Object)depositParent).GetInstanceID(); foundDepositParentIds.Add(instanceID); Stopwatch stopwatch = Stopwatch.StartNew(); try { if (_knownFragments.TryGetValue(instanceID, out var value)) { MaintainDeposit(depositParent, def, value, foundResources, ref newPins); } else { PinNewDeposit(depositParent, def, instanceID, foundResources, ref newPins); } } catch (Exception ex) { DebugLogger.Error("ProcessOrMaintainDeposit failed for '" + ((Object)depositParent).name + "': " + ex.Message, ex); } finally { _timings.FragmentPin += stopwatch.ElapsedTicks; } } public void ClearKnownFragments() { _knownFragments.Clear(); DebugLogger.Trace("RS_FragmentProcessor: known fragments cleared"); } private void PinNewDeposit(GameObject depositParent, ResourceDef def, int instanceId, HashSet foundResources, ref int newPins) { //IL_0061: 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_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0129: 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_013c: Unknown result type (might be due to invalid IL or missing references) DebugLogger.DebugMessage($"RS_FragmentProcessor.PinNewDeposit: {((Object)depositParent).name} [ID:{instanceId}], def={def.Id}"); Transform transform = depositParent.transform; if (transform.childCount == 0) { return; } Sprite cachedIcon = _pinFormatter.GetCachedIcon(depositParent, def); string label = _pinFormatter.GetLabel(depositParent, def); PinType pinType = _pinFormatter.GetPinType(def); Transform val = ((def.MaterialTarget.GetValueOrDefault() == MT.Child) ? transform : ((Component)transform.GetChild(0)).transform); List list = new List(); for (int i = 0; i < val.childCount; i++) { GameObject gameObject = ((Component)val.GetChild(i)).gameObject; if (def.MaterialTarget.GetValueOrDefault() == MT.Child && !((Object)gameObject).name.StartsWith(def.ChildName)) { continue; } list.Add(gameObject); if (gameObject.activeSelf) { MeshRenderer component = gameObject.GetComponent(); Vector3 val2; if (!((Object)(object)component != (Object)null)) { val2 = gameObject.transform.position; } else { Bounds bounds = ((Renderer)component).bounds; val2 = ((Bounds)(ref bounds)).center; } Vector3 pinPosition = val2; foundResources.Add(gameObject); PinShallowFragment(gameObject, pinPosition, label, cachedIcon, pinType, ref newPins); } } _knownFragments[instanceId] = list; DebugLogger.DebugMessage($"PinNewDeposit: stored {list.Count} fragments for deposit [ID:{instanceId}]"); } private void PinShallowFragment(GameObject fragment, Vector3 pinPosition, string label, Sprite icon, PinType pinType, ref int newPins) { //IL_0018: 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_002e: Unknown result type (might be due to invalid IL or missing references) if (!BepInExPlugin.IsTracked(fragment)) { DebugLogger.Trace($"PinNewDeposit: pinning {((Object)fragment).name} at {pinPosition}"); BepInExPlugin.AddResourcePin(fragment, pinPosition, label, icon, pinType); newPins++; } } private void MaintainDeposit(GameObject depositParent, ResourceDef def, List fragments, HashSet foundResources, ref int newPins) { int num = 0; int num2 = 0; for (int i = 0; i < fragments.Count; i++) { GameObject val = fragments[i]; if (!((Object)(object)val == (Object)null)) { if (val.activeSelf) { num++; foundResources.Add(val); } else if (BepInExPlugin.IsTracked(val)) { DebugLogger.DebugMessage("MaintainDeposit: " + ((Object)val).name + " inactive, removing pin"); BepInExPlugin.RemoveResourcePin(val); num2++; _timings.MinedFragRemovals++; } } } DebugLogger.Trace("RS_FragmentProcessor.MaintainDeposit: " + ((Object)depositParent).name + " " + $"[ID:{((Object)depositParent).GetInstanceID()}] ({def.Id})" + ((num2 > 0) ? " UPDATED" : "") + ": " + $"{fragments.Count} known fragments ({num} active" + ((num2 > 0) ? $"; {num2} removed" : "; no changes since last scan") + ")"); } public void ProcessDeepFragmentedDeposit(GameObject depositParent, ResourceDef def, HashSet foundResources, ref int newPins) { //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0267: 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) ZNetView component = depositParent.GetComponent(); string text = (((Object)(object)component != (Object)null && component.IsValid()) ? ((object)(ZDOID)(ref component.GetZDO().m_uid)).ToString() : "no-zdo"); DebugLogger.Trace($"ProcessDeepFragmentedDeposit called for {((Object)depositParent).name} [ID:{((Object)depositParent).GetInstanceID()}] [ZDO:{text}], def={def.Id}"); if ((Object)(object)component == (Object)null || !component.IsValid()) { DebugLogger.DebugMessage($" Skipping {((Object)depositParent).name} [ID:{((Object)depositParent).GetInstanceID()}]: no valid ZNetView"); return; } Stopwatch stopwatch = Stopwatch.StartNew(); try { GameObject val = RS_ResourceScanner.FindChildByName(depositParent, def.ChildName); if ((Object)(object)val == (Object)null) { DebugLogger.DebugMessage("ProcessDeepFragmentedDeposit: child '" + def.ChildName + "' not found on " + ((Object)depositParent).name + ", skipping"); return; } GameObject val2 = RS_ResourceScanner.FindChildByName(val, def.GrandchildName); if ((Object)(object)val2 == (Object)null) { DebugLogger.DebugMessage("ProcessDeepFragmentedDeposit: grandchild '" + def.GrandchildName + "' not found under '" + ((Object)val).name + "', skipping"); return; } Transform transform = val2.transform; if (transform.childCount == 0) { return; } Sprite cachedIcon = _pinFormatter.GetCachedIcon(depositParent, def); string label = _pinFormatter.GetLabel(depositParent, def); PinType pinType = _pinFormatter.GetPinType(def); for (int i = 0; i < transform.childCount; i++) { GameObject gameObject = ((Component)transform.GetChild(i)).gameObject; if (!((Object)gameObject).name.StartsWith(def.GreatGrandchildName)) { continue; } MeshRenderer component2 = gameObject.GetComponent(); if (!((Object)(object)component2 == (Object)null) && ((Renderer)component2).enabled) { Bounds bounds = ((Renderer)component2).bounds; Vector3 val3; if (!(((Bounds)(ref bounds)).center != Vector3.zero)) { val3 = gameObject.transform.position; } else { bounds = ((Renderer)component2).bounds; val3 = ((Bounds)(ref bounds)).center; } Vector3 pinPosition = val3; foundResources.Add(gameObject); PinDeepFragment(gameObject, pinPosition, def, label, cachedIcon, pinType, ref newPins); } } } catch (Exception ex) { DebugLogger.Error("ProcessDeepFragmentedDeposit failed for '" + ((Object)depositParent).name + "': " + ex.Message, ex); } finally { _timings.DeepFragment += stopwatch.ElapsedTicks; } } private void PinDeepFragment(GameObject fragment, Vector3 pinPosition, ResourceDef def, string label, Sprite icon, PinType pinType, ref int newPins) { //IL_001a: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!BepInExPlugin.IsTracked(fragment)) { DebugLogger.Trace($" Pinning fragment {((Object)fragment).name} at {pinPosition}"); BepInExPlugin.AddResourcePin(fragment, pinPosition, label, icon, pinType); newPins++; DebugLogger.Trace($" Added deep fragment pin for {def.Id} at {pinPosition}"); } else { DebugLogger.Trace(" Fragment " + ((Object)fragment).name + " already tracked, skipping"); } } } public static class RS_IconManager { public static Sprite GetIcon(GameObject source, ResourceDef def) { if (def == null) { return null; } try { return (Sprite)(def.IconStrategy switch { Icon.PinType => null, Icon.Pickable => ResolveIconFromComponent(source, (Func)((Pickable c) => c.m_itemPrefab)) ?? ResolveIconFromObjectDB(GetCleanItemName(source)), Icon.PickableItem => ResolveIconFromComponent(source, (Func)delegate(PickableItem c) { ItemDrop itemPrefab = c.m_itemPrefab; return (itemPrefab != null) ? ((Component)itemPrefab).gameObject : null; }), Icon.DropOnDestroyed => ResolveIconFromDropOnDestroyed(source) ?? ResolveIconFromDB(def.IconHint), Icon.ObjectDB => ResolveIconFromDB(def.IconHint), _ => null, }); } catch (Exception ex) { DebugLogger.Error("RS_IconManager.GetIcon error for " + def.Id + ": " + ex.Message, ex); return null; } } public static Sprite GetIcon(GameObject source, string itemNameHint) { if (!string.IsNullOrEmpty(itemNameHint)) { return ResolveIconFromDB(itemNameHint); } if ((Object)(object)source != (Object)null) { return ResolveIconFromComponent(source, (Func)delegate(PickableItem c) { ItemDrop itemPrefab2 = c.m_itemPrefab; return (itemPrefab2 != null) ? ((Component)itemPrefab2).gameObject : null; }) ?? ResolveIconFromComponent(source, (Func)delegate(Pickable c) { GameObject itemPrefab = c.m_itemPrefab; return (itemPrefab != null) ? itemPrefab.gameObject : null; }) ?? ResolveIconFromDropOnDestroyed(source); } return null; } private static Sprite ResolveIconFromComponent(GameObject source, Func prefabSelector) where T : Component { if ((Object)(object)source == (Object)null) { return null; } T component = source.GetComponent(); if ((Object)(object)component == (Object)null) { return null; } GameObject val = prefabSelector(component); if ((Object)(object)val == (Object)null) { return null; } ItemDrop component2 = val.GetComponent(); object result; if (component2 == null) { result = null; } else { ItemData itemData = component2.m_itemData; result = ((itemData != null) ? itemData.GetIcon() : null); } return (Sprite)result; } private static Sprite ResolveIconFromDropOnDestroyed(GameObject source) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { return null; } List list = source.GetComponent()?.m_dropWhenDestroyed?.m_drops; if (list == null || list.Count == 0) { return null; } GameObject item = list[0].m_item; ItemDrop val = ((item != null) ? item.GetComponent() : null); object result; if (val == null) { result = null; } else { ItemData itemData = val.m_itemData; result = ((itemData != null) ? itemData.GetIcon() : null); } return (Sprite)result; } private static Sprite ResolveIconFromDB(string itemName) { return ResolveIconFromZNetScene(itemName) ?? ResolveIconFromObjectDB(itemName); } private static Sprite ResolveIconFromZNetScene(string itemName) { if (string.IsNullOrEmpty(itemName) || (Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(itemName); if ((Object)(object)prefab == (Object)null) { return null; } DebugLogger.Trace("RS_IconManager: Found '" + itemName + "' in ZNetScene"); ItemDrop component = prefab.GetComponent(); object result; if (component == null) { result = null; } else { ItemData itemData = component.m_itemData; result = ((itemData != null) ? itemData.GetIcon() : null); } return (Sprite)result; } private static Sprite ResolveIconFromObjectDB(string itemName) { if (string.IsNullOrEmpty(itemName) || (Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(itemName); if ((Object)(object)itemPrefab != (Object)null) { DebugLogger.Trace("RS_IconManager: Found '" + itemName + "' in ObjectDB (exact)"); ItemDrop component = itemPrefab.GetComponent(); object result; if (component == null) { result = null; } else { ItemData itemData = component.m_itemData; result = ((itemData != null) ? itemData.GetIcon() : null); } return (Sprite)result; } DebugLogger.Warning("RS_IconManager: No icon found for '" + itemName + "'"); return null; } private static string GetCleanItemName(GameObject source) { try { if ((Object)(object)source == (Object)null) { return null; } Pickable component = source.GetComponent(); if ((Object)(object)component?.m_itemPrefab != (Object)null) { string text = ((Object)component.m_itemPrefab).name; if (text.StartsWith("Pickable_")) { text = text.Substring("Pickable_".Length); } if (text.EndsWith("(Clone)")) { text = text.Substring(0, text.Length - "(Clone)".Length); } return text; } } catch (Exception ex) { DebugLogger.Error("RS_IconManager.GetCleanItemName error: " + ex.Message, ex); } return null; } } public class RS_PinFormatter { private readonly Dictionary _iconCache; public RS_PinFormatter(Dictionary iconCache) { _iconCache = iconCache; } public Sprite GetCachedIcon(GameObject source, ResourceDef def) { if (def.IconStrategy == Icon.PickableItem) { return RS_IconManager.GetIcon(source, def); } if (!_iconCache.TryGetValue(def.Id, out var value)) { value = RS_IconManager.GetIcon(source, def); if ((Object)(object)value != (Object)null) { _iconCache[def.Id] = value; } else { DebugLogger.DebugMessage("RS_PinFormatter.GetCachedIcon: NULL icon for '" + def.Id + "', will retry next scan"); } } return value; } public string ApplyShowLabels(string label) { return BepInExPlugin.ConfigManager.ShowLabels ? label : ""; } public string GetLabel(GameObject source, ResourceDef def) { if (def.IconStrategy == Icon.PickableItem) { return ApplyShowLabels(GetDungeonLootLabel(source, def)); } return ApplyShowLabels(def.GetLabel(BepInExPlugin.ConfigManager)); } private string GetDungeonLootLabel(GameObject source, ResourceDef def) { try { PickableItem component = source.GetComponent(); if ((Object)(object)component?.m_itemPrefab != (Object)null) { ItemDrop component2 = ((Component)component.m_itemPrefab).GetComponent(); if (component2?.m_itemData?.m_shared != null) { string text = ((Localization.instance != null) ? Localization.instance.Localize(component2.m_itemData.m_shared.m_name) : component2.m_itemData.m_shared.m_name); int stack = component.m_stack; return (stack > 1) ? $"{text} ({stack})" : text; } } } catch (Exception ex) { DebugLogger.Error("Error getting dungeon loot label: " + ex.Message, ex); } DebugLogger.DebugMessage("GetDungeonLootLabel: falling back to def label for " + ((Object)source).name); return def.GetLabel(BepInExPlugin.ConfigManager); } public PinType GetPinType(ResourceDef def) { //IL_004c: 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_0047: Unknown result type (might be due to invalid IL or missing references) if (def.IconStrategy == Icon.PinType && !string.IsNullOrEmpty(def.IconHint) && def.IconHint.StartsWith("Icon") && int.TryParse(def.IconHint.Substring(4), out var result)) { return (PinType)result; } return (PinType)3; } } public enum MT { Self, Child, GCFrags, GGChild } public enum Icon { Pickable, PickableItem, DropOnDestroyed, ObjectDB, PinType } public class Filter { public enum FilterType { None, NameExcludes, HoneyCount, ChestContents } public static readonly Filter HoneyCount = new Filter(FilterType.HoneyCount); public FilterType Type { get; private set; } public string Value { get; private set; } public string ValueLower { get; private set; } private Filter(FilterType type, string value = null) { Type = type; Value = value; ValueLower = value?.ToLower(); } public static Filter ChestContents() { return new Filter(FilterType.ChestContents); } public static Filter ChestContents(string itemName) { return new Filter(FilterType.ChestContents, itemName); } public static Filter NameExcludes(string value) { return new Filter(FilterType.NameExcludes, value); } public static Filter MaterialHint(string value) { return new Filter(FilterType.None, value); } public static Filter MH(string value) { return MaterialHint(value); } public static Filter CC() { return ChestContents(); } public static Filter CC(string itemName) { return ChestContents(itemName); } } public class ResourceDef { public string Id { get; set; } public string FriendlyName { get; set; } public string ParentName { get; set; } public string ChildName { get; set; } public string GrandchildName { get; set; } public string GreatGrandchildName { get; set; } public string Category { get; set; } public bool CheckMPicked { get; set; } public Filter ResourceFilter { get; set; } public MT? MaterialTarget { get; set; } public Icon IconStrategy { get; set; } public string IconHint { get; set; } public bool EnabledByDefault { get; set; } public string Note { get; set; } public string GetLabel(ConfigManager config) { ConfigManager.ResourceEntry resourceEntry = config.FindEntry(Id); if (resourceEntry == null) { DebugLogger.Warning("ResourceDef.GetLabel: no config entry found for '" + Id + "', using FriendlyName as fallback"); return FriendlyName; } return resourceEntry.UserLabel; } public bool IsEnabled(ConfigManager config) { if (!config.IsResourceEnabled(Id)) { return false; } ConfigManager.ResourceCategory resourceCategory = config.FindCategory(Category); if (resourceCategory != null) { return resourceCategory.Enabled; } DebugLogger.Error("Resource '" + Id + "' belongs to an unknown category '" + Category + "' and will be skipped. This is a bug; please report it ... or fix it, if you're playing with the code yourself :)"); return false; } } public static class RS_ResourceDefinitions { private static FieldInfo pickedField; private static bool _hasLoggedPickedFieldWarning; static RS_ResourceDefinitions() { _hasLoggedPickedFieldWarning = false; pickedField = typeof(Pickable).GetField("m_picked", BindingFlags.Instance | BindingFlags.NonPublic); if (pickedField == null) { DebugLogger.Error("RS_ResourceDefinitions: reflection field 'm_picked' not found on Pickable: CheckMPicked will always return false. This likely means Valheim renamed the field after a game update."); } } public static bool IsPickableAvailable(GameObject candidate) { Pickable component = candidate.GetComponent(); if ((Object)(object)component != (Object)null && pickedField != null) { return !(bool)pickedField.GetValue(component); } if (!_hasLoggedPickedFieldWarning) { _hasLoggedPickedFieldWarning = true; DebugLogger.Error("IsPickableAvailable: game code has changed! pickedField is now null, and all CheckMPicked resources will be pinned regardless of picked state. See startup error for cause."); } return false; } public static List GetDefinitions() { return RS_ResourceDefinitionTable.GetDefinitions(); } } public static class Cat { public const string GeneralRes = "GeneralResources"; public const string Edibles = "Edibles"; public const string Farmables = "Farmables"; public const string Trees = "Trees"; public const string Minables = "Minables"; public const string Fragments = "Fragments"; public const string Materials = "Materials"; public const string Valuables = "Valuables"; public const string Dungeons = "Dungeons"; public const string Vegvisirs = "Vegvisirs"; public const string Allies = "Allies"; public const string Respawners = "Respawners"; public const string ModSettings = "ModSettings"; } public static class CategoryDefinitions { public static readonly (string Id, string DisplayName, string Note)[] Categories = new(string, string, string)[13] { ("GeneralResources", "General Resources", null), ("Edibles", "Edibles", "Pickable foods not plantable by players. Most respawn, some do not."), ("Farmables", "Farmables", "Seeds found in nature, and planted by players"), ("Trees", "Trees", "Excludes 'small' variants for noise reduction"), ("Minables", "Minables", "Raw deposits and ores collected by pickaxe"), ("Fragments", "Minable Fragments", "Once stricken, many deposits break into nodes (fragments). Hundreds of map pins can produce noticeable lag during scans; recommended only when actively mining, best disabled during normal exploration."), ("Materials", "Materials", "'Things that help you make other things' ... I had a hard time categorizing these :P"), ("Valuables", "Valuables + Misc", "Things of definite value, and of questionable value"), ("Dungeons", "Dungeon Entrances", null), ("Vegvisirs", "Boss + Vegvisirs", "Things that help you find and summon bosses"), ("Allies", "Allies", "The enemies of your enemies"), ("Respawners", "Respawners", "Spawners that constantly produce on a timer"), ("ModSettings", "Mod Settings", null) }; } public static class RS_ResourceDefinitionTable { private const string BirchTreeVariants = "Birch1(Clone)|Birch2(Clone)|Birch1_aut(Clone)|Birch2_aut(Clone)"; private const string MudPileVariants = "mudpile_beacon(Clone)|mudpile2(Clone)|mudpile2"; private const string SkeletalRemainsVariants = "Pickable_ForestCryptRemains01(Clone)|Pickable_ForestCryptRemains02(Clone)|Pickable_ForestCryptRemains03(Clone)|Pickable_ForestCryptRemains04(Clone)"; private const string DungeonCryptLootVariants = "Pickable_ForestCryptRandom(Clone)|Pickable_SunkenCryptRandom(Clone)|Pickable_MountainCaveRandom(Clone)"; private const string MistlandsDvExcavationVariants = "Mistlands_Excavation1(Clone)|Mistlands_Excavation2(Clone)"; private const string MistlandsDvBaseVariants = "Mistlands_GuardTower1_new|Mistlands_GuardTower2_new|Mistlands_GuardTower3_new|Mistlands_Lighthouse1_new|Mistlands_Harbour1(Clone)"; private const string SoftTissueVariants = "giant_brain(Clone)|dvergrprops_crate(Clone)"; private const string AllTreasureChests = "TreasureChest_meadows(Clone)|TreasureChest_meadows_buried(Clone)|TreasureChest_blackforest(Clone)|TreasureChest_fCrypt(Clone)|TreasureChest_forestcrypt(Clone)|TreasureChest_trollcave(Clone)|TreasureChest_swamp(Clone)|TreasureChest_sunkencrypt(Clone)|TreasureChest_mountains(Clone)|TreasureChest_heath(Clone)|TreasureChest_plains_stone(Clone)|TreasureChest_dvergrtown(Clone)|TreasureChest_ashland_stone(Clone)|TreasureChest_charredfortress(Clone)"; public static List GetDefinitions() { return new List { TrackResource("Branches", "Branch", "Pickable_Branch(Clone)", "model", null, null, "GeneralResources", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: false, "For the very-early game; off by default (quite spammy)"), TrackResource("Stones", "Stone", "Pickable_Stone(Clone)", "model", null, null, "GeneralResources", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: false, "Irrelevant once you have a pickaxe; off by default (also spammy)"), TrackResource("Flint", "Flint", "Pickable_Flint(Clone)", "model", null, null, "GeneralResources", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("HousesFarmsVillages", "Free Wood!", "WoodHouse|WoodFarm1(Clone)|WoodVillage1(Clone)", null, null, null, "GeneralResources", checkMPicked: false, null, null, Icon.PinType, "Icon1", enabledByDefault: true, "Houses, farms and draugr villages. Pin will persist on the map even after all objects are destroyed"), TrackResource("ShipWreck", "Shipwreck", "ShipWreck", null, null, null, "GeneralResources", checkMPicked: false, null, null, Icon.ObjectDB, "FineWood", enabledByDefault: true, "Pin will persist after objects are destroyed. Sorry."), TrackResource("MorgenHolePile", "Morgen Hole Pile", "morgenhole_pile", null, null, null, "GeneralResources", checkMPicked: false, null, null, Icon.PinType, "Icon3", enabledByDefault: true, "Destructible piles in putrid holes (Ashlands)"), TrackResource("RedMushrooms", "Red Mushroom", "Pickable_Mushroom(Clone)", "visual", null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("Raspberries", "Raspberries", "RaspberryBush(Clone)", null, null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("Dandelions", "Dandelion", "Pickable_Dandelion(Clone)", "visual", null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: false, "Not edible until The Bog Witch (Swamp); off by default"), TrackResource("Blueberries", "Blueberries", "BlueberryBush(Clone)", null, null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("YellowMushrooms", "Yellow Mushroom", "Pickable_Mushroom_yellow(Clone)", "visual", null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("Thistle", "Thistle", "Pickable_Thistle(Clone)", "visual", "default", null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("MeatPile", "Meat Pile", "Pickable_MeatPile(Clone)", null, null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, "RottenMeat", enabledByDefault: true, "Found in frost caves and putrid holes"), TrackResource("Cloudberries", "Cloudberries", "CloudberryBush(Clone)", null, null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("JotunPuffs", "Jotun Puffs", "Pickable_Mushroom_JotunPuffs(Clone)", "visual", null, null, "Edibles", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("Magecap", "Magecap", "Pickable_Mushroom_Magecap(Clone)", "visual", null, null, "Edibles", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("DVBarrelTastyMead", "Dvergr Barrel", "dvergrprops_barrel(Clone)", null, null, null, "Edibles", checkMPicked: false, null, null, Icon.ObjectDB, "MeadTasty", enabledByDefault: true, "Source of tasty mead"), TrackResource("RoyalJelly", "Royal Jelly", "Pickable_RoyalJelly(Clone)", "Hide", null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("VineberryCluster", "Vineberry Cluster", "VineAsh(Clone)", "Berries", null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("Fiddlehead", "Fiddlehead", "Pickable_Fiddlehead(Clone)", "visual", null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("PickableSmokePuff", "Smoke Puff", "Pickable_SmokePuff(Clone)", null, null, null, "Edibles", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("VoltureEgg", "Volture Egg", "Pickable_VoltureEgg(Clone)", "model", null, null, "Edibles", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("BeehiveWild", "Beehive", "Beehive(Clone)", "Model", null, null, "Farmables", checkMPicked: false, null, null, Icon.ObjectDB, "QueenBee", enabledByDefault: true, "The naturally-occurring beehive"), TrackResource("BeehiveBuilt", "Honey", "piece_beehive(Clone)", null, null, null, "Farmables", checkMPicked: false, Filter.HoneyCount, null, Icon.ObjectDB, "Honey", enabledByDefault: true, "Shows the amount of honey ready for picking from player-made beehives; only appears when value is 1 or greater"), TrackResource("CarrotSeeds", "Carrot Seeds", "Pickable_SeedCarrot(Clone)", "flower", null, null, "Farmables", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("Carrots", "Carrot", "Pickable_Carrot(Clone)", "carrot", null, null, "Farmables", checkMPicked: false, Filter.NameExcludes("Seed"), null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("TurnipSeeds", "Turnip Seeds", "Pickable_SeedTurnip(Clone)", "flower", null, null, "Farmables", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("Turnips", "Turnip", "Pickable_Turnip(Clone)", "turnip", null, null, "Farmables", checkMPicked: false, Filter.NameExcludes("Seed"), null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("OnionSeedsInMtnChest", "Onion Seeds", "TreasureChest_mountains(Clone)", null, null, null, "Farmables", checkMPicked: false, Filter.CC("$item_onionseeds"), null, Icon.ObjectDB, "OnionSeeds", enabledByDefault: true, "Natural (found in mountain chests)"), TrackResource("OnionSeeds", "Onion Seeds", "Pickable_SeedOnion(Clone)", "healthy_grown", null, null, "Farmables", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, "Grown by players"), TrackResource("Onions", "Onion", "Pickable_Onion(Clone)", "onion", null, null, "Farmables", checkMPicked: false, Filter.NameExcludes("Seed"), null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("BarleyBoth", "Barley", "Pickable_Barley_Wild(Clone)|Pickable_Barley(Clone)", "barley_sapling", null, null, "Farmables", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, "Includes both types (found in Fuling villages and grown by players)"), TrackResource("FlaxBoth", "Flax", "Pickable_Flax_Wild(Clone)|Pickable_Flax(Clone)", "ripe", null, null, "GeneralResources", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, "Includes both types (found in Fuling villages and grown by players)"), TrackResource("BeechTree", "Beech Tree", "Beech1(Clone)", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "Wood", enabledByDefault: false, "Incredibly abundant, best left off!"), TrackResource("TreeStump", "Tree Stump", "stubbe(Clone)", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "Wood", enabledByDefault: false, "For the early game (stone/flint axes); off by default"), TrackResource("BirchTreeAll", "Birch Tree", "Birch1(Clone)|Birch2(Clone)|Birch1_aut(Clone)|Birch2_aut(Clone)", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "FineWood", enabledByDefault: true, "Found in the meadows and plains"), TrackResource("OakTree", "Oak Tree", "Oak1(Clone)", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "FineWood", enabledByDefault: true, null), TrackResource("FirTree", "Fir Tree", "FirTree(Clone)", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "Wood", enabledByDefault: false, "Incredibly abundant, best left off"), TrackResource("PineTree", "Pine Tree", "Pinetree_01(Clone)", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "RoundLog", enabledByDefault: false, "More common than you realize; off by default"), TrackResource("AncientTree", "Ancient Tree", "SwampTree1(Clone)", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "ElderBark", enabledByDefault: true, null), TrackResource("YggaShoot", "Yggdrasil Shoot", "YggaShoot", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "YggdrasilWood", enabledByDefault: true, null), TrackResource("ScorchedTree", "Scorched Tree", "AshlandsTree", null, null, null, "Trees", checkMPicked: false, null, null, Icon.ObjectDB, "Blackwood", enabledByDefault: true, null), TrackResource("Copper", "Copper Deposit", "rock4_copper(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.ObjectDB, "CopperOre", enabledByDefault: true, null), TrackResource("Tin", "Tin Deposit", "MineRock_Tin(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.DropOnDestroyed, null, enabledByDefault: true, null), TrackResource("AbyssalBarnacle", "Abyssal Barnacle", "Leviathan(Clone)", "visual", "minestuff", "Barnacle", "Minables", checkMPicked: false, null, MT.GGChild, Icon.ObjectDB, "Chitin", enabledByDefault: true, "Source of chitin"), TrackResource("MudPile", "Muddy Scrap Pile", "mudpile_beacon(Clone)|mudpile2(Clone)|mudpile2", null, null, null, "Minables", checkMPicked: false, null, null, Icon.ObjectDB, "IronScrap", enabledByDefault: true, "Detects piles inside and outside crypts"), TrackResource("Silver", "Silver Vein", "silvervein(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.ObjectDB, "SilverOre", enabledByDefault: true, null), TrackResource("Obsidian", "Obsidian Deposit", "MineRock_Obsidian(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.DropOnDestroyed, null, enabledByDefault: false, "Off by default (incredibly abundant and easy to find)"), TrackResource("AncientArmour", "Ancient Armour", "giant_helmet", null, null, null, "Minables", checkMPicked: false, null, null, Icon.PinType, "Icon2", enabledByDefault: true, "Generic 'hammer' icon (drops both copper and iron scraps)"), TrackResource("AncientSword", "Ancient Sword", "giant_sword", null, null, null, "Minables", checkMPicked: false, null, null, Icon.PinType, "Icon2", enabledByDefault: true, "Generic 'hammer' icon (drops both copper and iron scraps)"), TrackResource("GiantSkull", "Giant Skull", "giant_skull(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.ObjectDB, "BlackMarble", enabledByDefault: true, null), TrackResource("SoftTissue", "Soft Tissue", "giant_brain(Clone)|dvergrprops_crate(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.ObjectDB, "Softtissue", enabledByDefault: true, "Marks Dvergr crates and deposits found inside giant skulls"), TrackResource("GiantRibs", "Giant Ribs", "giant_ribs(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.ObjectDB, "BlackMarble", enabledByDefault: true, null), TrackResource("FlametalSpireFrags", "Flametal Ore", "LeviathanLava(Clone)", "visual", "minestuff", "default", "Minables", checkMPicked: false, null, MT.GGChild, Icon.ObjectDB, "FlametalOreNew", enabledByDefault: true, "Natural deposits: a spiral of 27 fragments by default"), TrackResource("FlamsInDvCrate", "Flametal Ore", "dvergrprops_crate_ashlands(Clone)", null, null, null, "Minables", checkMPicked: false, null, null, Icon.ObjectDB, "FlametalOreNew", enabledByDefault: true, "'Free flams!' found in Dvergr crates"), TrackResource("FlamsInCharChest", "Flametal Ore", "TreasureChest_charredfortress(Clone)", null, null, null, "Minables", checkMPicked: false, Filter.CC("$item_flametalore"), null, Icon.ObjectDB, "FlametalOreNew", enabledByDefault: true, "'Free flams!' found in chests of charred fortresses"), TrackResource("CopperFragments", "Copper Fragment", "___MineRock5 m_meshFilter", "Rock4_cell", null, null, "Fragments", checkMPicked: false, Filter.MH("rock1_copper"), MT.Child, Icon.ObjectDB, "CopperOre", enabledByDefault: false, "130 pins per deposit, can get laggy"), TrackResource("MudPileFragments", "Muddy Scrap Fragment", "___MineRock5 m_meshFilter", "Sphere", null, null, "Fragments", checkMPicked: false, Filter.MH("mudpile"), MT.Child, Icon.ObjectDB, "IronScrap", enabledByDefault: false, "20-30 pins per pile; may induce frustration (indoor piles are impossible to mine 100%)"), TrackResource("SilverFragments", "Silver Vein Fragment", "___MineRock5 m_meshFilter", "SilverVein_cell", null, null, "Fragments", checkMPicked: false, Filter.MH("rock3_silver"), MT.Child, Icon.ObjectDB, "SilverOre", enabledByDefault: false, "90 pins per new deposit, can get laggy"), TrackResource("AncientArmourFragments", "Ancient Armour Fragment", "___MineRock5 m_meshFilter", "Helmet1_Destruction", null, null, "Fragments", checkMPicked: false, Filter.MH("GiantRustHelm_mat"), MT.GCFrags, Icon.PinType, "Icon3", enabledByDefault: true, "33 pins per helmet; generic dot icon (drops both copper and iron scraps)"), TrackResource("AncientSwordFragments", "Ancient Sword Fragment", "___MineRock5 m_meshFilter", "Sword_Hilt_Destruction", null, null, "Fragments", checkMPicked: false, Filter.MH("GiantRustSword_mat"), MT.GCFrags, Icon.PinType, "Icon3", enabledByDefault: true, "15 pins per sword; generic dot icon (drops both copper and iron scraps)"), TrackResource("SkullFragments", "Skull Fragment", "___MineRock5 m_meshFilter", "SkullTestfractured", null, null, "Fragments", checkMPicked: false, Filter.MH("giant_skeleton_exterior"), MT.GCFrags, Icon.ObjectDB, "BlackMarble", enabledByDefault: false, "130 pins per skull, only use when desperate for more marble"), TrackResource("BrainFragments", "Soft Tissue Fragment", "___MineRock5 m_meshFilter", "BrainTestFractured", null, null, "Fragments", checkMPicked: false, Filter.MH("giant_brain"), MT.GCFrags, Icon.ObjectDB, "Softtissue", enabledByDefault: false, "130 pins per deposit, can get laggy"), TrackResource("RibFragments", "Rib Fragment", "___MineRock5 m_meshFilter", "RibsTestFractured", null, null, "Fragments", checkMPicked: false, Filter.MH("giant_skeleton_exterior"), MT.GCFrags, Icon.ObjectDB, "BlackMarble", enabledByDefault: false, "132 pins per ribcage, only use when desperate for more marble"), TrackResource("SurtlingCores", "Surtling Core", "Pickable_SurtlingCoreStand(Clone)", null, null, null, "Materials", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("GuckSack", "Guck Sack", "GuckSack(Clone)|GuckSack_small(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.ObjectDB, "Guck", enabledByDefault: true, null), TrackResource("FenrisHair1", "Fenris Hair", "hanging_hairstrands(Clone)", null, null, null, "Materials", checkMPicked: true, null, null, Icon.ObjectDB, "WolfHairBundle", enabledByDefault: true, null), TrackResource("FenrisHair2", "Fenris Hair", "fenrirhide_hanging(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.ObjectDB, "WolfHairBundle", enabledByDefault: true, "Yes, two separate Fenris Hair items ... annoying but unavoidable"), TrackResource("MountainCaveCrystal", "Crystal", "Pickable_MountainCaveCrystal", null, null, null, "Materials", checkMPicked: true, null, null, Icon.ObjectDB, "Crystal", enabledByDefault: true, null), TrackResource("TarBoth", "Tar", "Pickable_Tar(Clone)|Pickable_TarBig(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("AncientRoot", "Ancient Root", "YggdrasilRoot(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.ObjectDB, "Sap", enabledByDefault: true, "Source of sap"), TrackResource("BlackCores", "Black Core", "Pickable_BlackCoreStand(Clone)", "visual", null, null, "Materials", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("DvergrExtractor", "Dvergr Extractor", "dvergrprops_crate_long(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.ObjectDB, "DvergrNeedle", enabledByDefault: true, null), TrackResource("BlueJute", "Blue Jute", "dvergrprops_banner(Clone)|dvergrprops_curtain(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.ObjectDB, "JuteBlue", enabledByDefault: true, null), TrackResource("RedJute", "Red Jute", "cloth_hanging_long(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.ObjectDB, "JuteRed", enabledByDefault: true, null), TrackResource("MoltenCores", "Molten Core", "Pickable_MoltenCoreStand(Clone)", null, null, null, "Materials", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("UnstableLavaRock", "Unstable Lava Rock", "UnstableLavaRock(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.ObjectDB, "ProustitePowder", enabledByDefault: true, "Source of proustite powder. Also a tripping hazard."), TrackResource("PickableSulfurRock", "Sulfur Rock", "Pickable_SulfurRock(Clone)", null, null, null, "Materials", checkMPicked: false, null, null, Icon.DropOnDestroyed, null, enabledByDefault: true, "Pickable, if you can fly"), TrackResource("TreasureChestALL", "Chest", "TreasureChest_meadows(Clone)|TreasureChest_meadows_buried(Clone)|TreasureChest_blackforest(Clone)|TreasureChest_fCrypt(Clone)|TreasureChest_forestcrypt(Clone)|TreasureChest_trollcave(Clone)|TreasureChest_swamp(Clone)|TreasureChest_sunkencrypt(Clone)|TreasureChest_mountains(Clone)|TreasureChest_heath(Clone)|TreasureChest_plains_stone(Clone)|TreasureChest_dvergrtown(Clone)|TreasureChest_ashland_stone(Clone)|TreasureChest_charredfortress(Clone)", null, null, null, "Valuables", checkMPicked: false, Filter.CC(), null, Icon.PinType, "Icon3", enabledByDefault: true, null), TrackResource("SilvNeckInMeadChest", "Silver Necklace", "TreasureChest_meadows_buried(Clone)", null, null, null, "Valuables", checkMPicked: false, Filter.CC("$item_silvernecklace"), null, Icon.ObjectDB, "SilverNecklace", enabledByDefault: true, "ONLY the ones contained in buried meadows chests"), TrackResource("SkeletalRemains", "Skeletal Remains", "Pickable_ForestCryptRemains01(Clone)|Pickable_ForestCryptRemains02(Clone)|Pickable_ForestCryptRemains03(Clone)|Pickable_ForestCryptRemains04(Clone)", "crypt_skeleton_pile|crypt_skeleton_laying", null, null, "Valuables", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("GreydwarfBarrel", "Greydwarf Barrel", "barrell(Clone)", null, null, null, "Valuables", checkMPicked: false, null, null, Icon.PinType, "Icon3", enabledByDefault: true, "Barrels found near Greydwarf houses; generic icon (contain a variety of items)"), TrackResource("DungeonCryptLoot", "Dungeon Loot", "Pickable_ForestCryptRandom(Clone)|Pickable_SunkenCryptRandom(Clone)|Pickable_MountainCaveRandom(Clone)", null, null, null, "Valuables", checkMPicked: false, null, null, Icon.PickableItem, null, enabledByDefault: true, "Includes a wide variety of items (coins, rubies, pearls, withered bones, meat piles, etc). PLEASE NOTE: labels for these are automatic and cannot be customized, sorry!"), TrackResource("DvergrMineCoins", "Coin Pile", "Pickable_DvergrMineTreasure(Clone)", null, null, null, "Valuables", checkMPicked: false, null, null, Icon.Pickable, null, enabledByDefault: true, "Coin piles inside infested mines; number of coins is determined randomly at time of picking. Schrodinger's wallet."), TrackResource("DvergrLantern", "Dvergr Lantern", "Pickable_DvergrLantern(Clone)", null, null, null, "Valuables", checkMPicked: false, null, null, Icon.ObjectDB, "Lantern", enabledByDefault: true, null), TrackResource("AsksvinCarrion", "Asksvin Carrion", "asksvin_carrion", null, null, null, "Valuables", checkMPicked: false, null, null, Icon.ObjectDB, "AsksvinCarrionSkull", enabledByDefault: true, null), TrackResource("PickablePotShard", "Pot Shard", "Pickable_Pot_Shard(Clone)", null, null, null, "Valuables", checkMPicked: false, null, null, Icon.ObjectDB, "Pot_Shard_Green", enabledByDefault: true, null), TrackResource("AncientPot", "Ancient Pot", "ashland_pot*", null, null, null, "Valuables", checkMPicked: false, null, null, Icon.ObjectDB, "Pot_Shard_Green", enabledByDefault: true, null), TrackResource("DyrnBladeFragment", "Dyrnwyn Blade Fragment", "Pickable_Swordpiece2(Clone)", null, null, null, "Valuables", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("DyrnTipFragment", "Dyrnwyn Tip Fragment", "Pickable_Swordpiece3(Clone)", null, null, null, "Valuables", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("BurialChambers", "Burial Chambers", "Crypt", "Gateway", null, null, "Dungeons", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("TrollCave", "Troll Cave", "TrollCave", "Gateway", null, null, "Dungeons", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("SwampCrypt", "Swamp Crypt", "SunkenCrypt", "exterior", "Gateway", null, "Dungeons", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("FrostCave", "Frost Cave", "MountainCave", "Gateway", null, null, "Dungeons", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("InfestedMine", "Infested Mine", "Mistlands_DvergrTownEntrance", "Gateway", null, null, "Dungeons", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("PutridHole", "Putrid Hole", "MorgenHole", "Gateway", null, null, "Dungeons", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("CharredFortress", "Charred Fortress", "CharredFortress(Clone)", "fort", null, null, "Dungeons", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, "Technically not a 'dungeon' but ... y'know"), TrackResource("DragonEggs", "Dragon Egg", "Pickable_DragonEgg(Clone)", "visual", null, null, "Vegvisirs", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("FulingTotems", "Fuling Totem", "goblin_totempole(Clone)", null, null, null, "Vegvisirs", checkMPicked: true, null, null, Icon.Pickable, null, enabledByDefault: true, null), TrackResource("SealbreakerFragment", "Sealbreaker Fragment", "blackmarble_altar_crystal(Clone)", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.ObjectDB, "DvergrKeyFragment", enabledByDefault: true, null), TrackResource("CharBellFragment", "Bell Fragment", "Charred_altar_bellfragment(Clone)", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.ObjectDB, "BellFragment", enabledByDefault: true, null), TrackResource("VegEikthyr", "Vegvisir Eikthyr", "Vegvisir_Eikthyr", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("VegGDKing", "Vegvisir The Elder", "Vegvisir_GDKing", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("VegBonemass", "Vegvisir Bonemass", "Vegvisir_Bonemass", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("VegDragonQueen", "Vegvisir Moder", "Vegvisir_DragonQueen", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("VegGoblinKing", "Vegvisir Yagluth", "Vegvisir_GoblinKing", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("VegSeekerQueen", "Vegvisir The Queen", "Vegvisir_SeekerQueen", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("VegFader", "Vegvisir Fader", "Vegvisir_Fader", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("Veg_pl_of_myst_1", "Vegvisir Place of Mystery 1", "Vegvisir_placeofmystery", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("Veg_pl_of_myst_2", "Vegvisir Place of Mystery 2", "Vegvisir_placeofmystery_2", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("Veg_pl_of_myst_3", "Vegvisir Place of Mystery 3", "Vegvisir_placeofmystery_3", null, null, null, "Vegvisirs", checkMPicked: false, null, null, Icon.PinType, "Icon9", enabledByDefault: true, null), TrackResource("MistlandsDVExcv", "Dvergr Excavation", "Mistlands_Excavation1(Clone)|Mistlands_Excavation2(Clone)", null, null, null, "Allies", checkMPicked: false, null, null, Icon.PinType, "Icon1", enabledByDefault: true, "Excludes 'Excavation 3' which is overrun by seekers. You're welcome."), TrackResource("MistlandsDVBase", "Dvergr Base (Mistlands)", "Mistlands_GuardTower1_new|Mistlands_GuardTower2_new|Mistlands_GuardTower3_new|Mistlands_Lighthouse1_new|Mistlands_Harbour1(Clone)", null, null, null, "Allies", checkMPicked: false, null, null, Icon.PinType, "Icon1", enabledByDefault: true, "Includes guard towers, lighthouses and harbours"), TrackResource("AshlandsDVBase", "Dvergr Base (Ashlands)", "CharredTowerRuins1_dvergr", null, null, null, "Allies", checkMPicked: false, null, null, Icon.PinType, "Icon1", enabledByDefault: true, null), TrackResource("GreydwarfSpawner", "Greydwarf Nest", "Spawner_GreydwarfNest(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, "Endless source of greydwarf eyes"), TrackResource("SkeletonSpawner", "Evil Bone Pile", "BonePileSpawner(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("SkeletonSpawnpoint", "Skeleton Spawnpoint", "Spawner_Skeleton_respawn_30(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, "30-minute skeleton respawner"), TrackResource("DraugrSpawner", "Body Pile", "Spawner_DraugrPile(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, "Endless source of entrails and rare Draugr Elite Trophies"), TrackResource("SurtlingSpawnpoint", "Surtling Spawnpoint", "Spawner_imp_respawn(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("BlobTarSpawner", "Growth Spawnpoint", "Spawner_BlobTar_respawn_30(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("CharredSpawner", "Monument of Torment", "Spawner_CharredStone(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("CharredSpawnerElite", "Monument of Torment Elite", "Spawner_CharredStone_Elite(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null), TrackResource("MorgenSpawner", "Morgen Spawner", "Spawner_Morgen_wakeup(Clone)", null, null, null, "Respawners", checkMPicked: false, null, null, Icon.PinType, "Icon6", enabledByDefault: true, null) }; } private static ResourceDef TrackResource(string id, string friendlyName, string parentName, string childName, string grandchildName, string greatGrandchildName, string category, bool checkMPicked, Filter filter, MT? materialTarget, Icon iconStrategy, string iconHint, bool enabledByDefault, string note) { return new ResourceDef { Id = id, FriendlyName = friendlyName, ParentName = parentName, ChildName = childName, GrandchildName = grandchildName, GreatGrandchildName = greatGrandchildName, Category = category, CheckMPicked = checkMPicked, ResourceFilter = filter, MaterialTarget = materialTarget, IconStrategy = iconStrategy, IconHint = iconHint, EnabledByDefault = enabledByDefault, Note = note }; } } public class RS_ResourceScanner { public class ScanTimings { public long DictLookup = 0L; public long NumericStrip = 0L; public long PrefixScan = 0L; public long ChildLookup = 0L; public long FragmentPin = 0L; public long DeepFragment = 0L; public long MPicked = 0L; public long TrackedCheck = 0L; public long IconLookup = 0L; public long LabelGet = 0L; public long PinType = 0L; public long PinAdd = 0L; public long Beehive = 0L; public long Chest = 0L; public int MinedFragRemovals = 0; public void Reset() { DictLookup = (NumericStrip = (PrefixScan = (ChildLookup = (FragmentPin = (DeepFragment = 0L))))); MPicked = (TrackedCheck = (IconLookup = (LabelGet = 0L))); PinType = (PinAdd = (Beehive = (Chest = 0L))); MinedFragRemovals = 0; } } private List _definitions; private Dictionary> _lookup; private List<(string Prefix, List Defs)> _prefixEntries; private HashSet _knownPrefixes; private Dictionary _beehiveHoneyCounts = new Dictionary(); private readonly ScanTimings _timings = new ScanTimings(); private int _objectsInRange = 0; private int _objectsAfterPrefilter = 0; private int _dictMatches = 0; private int _numericStripMatches = 0; private int _definitionMatches = 0; private Dictionary _iconCache = new Dictionary(); private Dictionary _depositTypeCache = new Dictionary(); private List<(GameObject resource, ResourceDef def)> _deferredChestItems = new List<(GameObject, ResourceDef)>(); private readonly List _inRangeObjects = new List(); private readonly List _candidateObjects = new List(); private readonly HashSet _foundResources = new HashSet(); private readonly HashSet _foundDepositParentIds = new HashSet(); private readonly List _staleBeehiveList = new List(); private readonly List _staleDepositIdList = new List(); private bool _isScanning = false; private RS_PinFormatter _pinFormatter; private RS_FragmentProcessor _fragmentProcessor; private RS_SpecialCaseProcessor _specialCaseProcessor; private bool IsBenchmark => BepInExPlugin.ConfigManager?.IsBenchmark ?? false; public RS_ResourceScanner() { _definitions = RS_ResourceDefinitions.GetDefinitions(); } public void Initialize() { ClearRuntimeState(); _lookup = new Dictionary>(StringComparer.Ordinal); _prefixEntries = new List<(string, List)>(); foreach (ResourceDef definition in _definitions) { if (!definition.IsEnabled(BepInExPlugin.ConfigManager)) { continue; } string parentName = definition.ParentName; if (parentName.Contains("|")) { string[] array = parentName.Split(new char[1] { '|' }); foreach (string text in array) { AddToLookup(text.Trim(), definition); } } else { AddToLookup(parentName, definition); } } _knownPrefixes = new HashSet(StringComparer.Ordinal); foreach (string key in _lookup.Keys) { _knownPrefixes.Add((key.Length >= 5) ? key.Substring(0, 5) : key); } foreach (var prefixEntry in _prefixEntries) { HashSet knownPrefixes = _knownPrefixes; string item; if (prefixEntry.Prefix.Length < 5) { (item, _) = prefixEntry; } else { item = prefixEntry.Prefix.Substring(0, 5); } knownPrefixes.Add(item); } _pinFormatter = new RS_PinFormatter(_iconCache); _fragmentProcessor = new RS_FragmentProcessor(_pinFormatter, _timings); _specialCaseProcessor = new RS_SpecialCaseProcessor(_beehiveHoneyCounts, _deferredChestItems, _pinFormatter, _timings); SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneLoaded += OnSceneLoaded; DebugLogger.Trace($"RS_ResourceScanner initialized: {_lookup.Count} exact keys, {_prefixEntries.Count} prefix keys, {_knownPrefixes.Count} known prefixes"); } public void Shutdown() { SceneManager.sceneLoaded -= OnSceneLoaded; } public void ClearRuntimeState() { _beehiveHoneyCounts.Clear(); _depositTypeCache.Clear(); _fragmentProcessor?.ClearKnownFragments(); _specialCaseProcessor?.ClearChestLabels(); DebugLogger.Trace("RS_ResourceScanner: runtime caches cleared"); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!(((Scene)(ref scene)).name != "main")) { _iconCache.Clear(); _depositTypeCache.Clear(); _fragmentProcessor?.ClearKnownFragments(); DebugLogger.Trace("RS_ResourceScanner: caches cleared on scene load (" + ((Scene)(ref scene)).name + ")"); } } private void AddToLookup(string key, ResourceDef def) { if (key.EndsWith("*")) { string text = key.Substring(0, key.Length - 1); foreach (var prefixEntry in _prefixEntries) { if (prefixEntry.Prefix == text) { prefixEntry.Defs.Add(def); return; } } _prefixEntries.Add((text, new List { def })); } else { if (!_lookup.TryGetValue(key, out var value)) { value = new List(); _lookup[key] = value; } value.Add(def); } } public void ScanAndUpdate() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null || _isScanning) { return; } _isScanning = true; try { Stopwatch stopwatch = Stopwatch.StartNew(); Stopwatch stepTimer = Stopwatch.StartNew(); Vector3 position = ((Component)Player.m_localPlayer).transform.position; float scanRadius = BepInExPlugin.ConfigManager.ScanRadius; DateTime now = DateTime.Now; DebugLogger.Benchmark($"=== SCAN STARTED === {now:HH:mm:ss.fffffff}"); GameObject[] allObjects = StepFindAllObjects(stepTimer); StepFilterByDistance(allObjects, position, scanRadius, stepTimer); StepPrefixFilter(stepTimer); int num = StepMatchClassifyAndPin(stepTimer); StepCleanupBeehives(stepTimer); int num2 = StepRemoveStalePins(stepTimer); stopwatch.Stop(); DateTime now2 = DateTime.Now; long num3 = (now2 - now).Ticks * 100; DebugLogger.Benchmark($"=== SCAN COMPLETE: {num} added, {num2} removed, {BepInExPlugin.GetTrackedResources().Count} total === {now2:HH:mm:ss.fffffff} (actual: {num3:N0} ns / {stopwatch.ElapsedMilliseconds} ms)"); } finally { _isScanning = false; } } private GameObject[] StepFindAllObjects(Stopwatch stepTimer) { stepTimer.Restart(); GameObject[] array = Object.FindObjectsByType((FindObjectsSortMode)0); DebugLogger.Benchmark($"Step 1 - FindObjectsByType: {array.Length} objects ({stepTimer.ElapsedMilliseconds} ms)"); return array; } private void StepFilterByDistance(GameObject[] allObjects, Vector3 playerPos, float radius, Stopwatch stepTimer) { //IL_002e: 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) stepTimer.Restart(); _inRangeObjects.Clear(); foreach (GameObject val in allObjects) { if (!((Object)(object)val == (Object)null) && Vector3.Distance(playerPos, val.transform.position) <= radius) { _inRangeObjects.Add(val); } } DebugLogger.Benchmark($"Step 2 - Distance filter: {_inRangeObjects.Count} in range ({stepTimer.ElapsedMilliseconds} ms)"); } private void StepPrefixFilter(Stopwatch stepTimer) { stepTimer.Restart(); _candidateObjects.Clear(); foreach (GameObject inRangeObject in _inRangeObjects) { string name = ((Object)inRangeObject).name; if (name.Length >= 5) { string item = name.Substring(0, 5); if (_knownPrefixes.Contains(item)) { _candidateObjects.Add(inRangeObject); } } else if (_knownPrefixes.Contains(name)) { _candidateObjects.Add(inRangeObject); } } DebugLogger.Benchmark($"Step 3 - Prefix filter: {_candidateObjects.Count} candidates from {_inRangeObjects.Count} in range ({stepTimer.ElapsedMilliseconds} ms)"); } private int StepMatchClassifyAndPin(Stopwatch stepTimer) { stepTimer.Restart(); _timings.Reset(); _objectsAfterPrefilter = _candidateObjects.Count; _objectsInRange = _inRangeObjects.Count; _dictMatches = 0; _numericStripMatches = 0; _definitionMatches = 0; BepInExPlugin.PinAddTicks = 0L; _foundResources.Clear(); _foundDepositParentIds.Clear(); int newPins = 0; _deferredChestItems.Clear(); foreach (GameObject candidateObject in _candidateObjects) { ProcessObject(candidateObject, _foundResources, _foundDepositParentIds, ref newPins); } foreach (var (chest, def) in _deferredChestItems) { _specialCaseProcessor.ProcessChestItem(chest, def); } DebugLogger.Benchmark(string.Format("Step 4 - Match, classify, and pin: {0} new {1} ({2} ms)", newPins, (newPins == 1) ? "pin" : "pins", stepTimer.ElapsedMilliseconds)); if (IsBenchmark) { LogScanTimingBreakdown(stepTimer); } return newPins; } private void StepCleanupBeehives(Stopwatch stepTimer) { stepTimer.Restart(); _staleBeehiveList.Clear(); foreach (GameObject key in _beehiveHoneyCounts.Keys) { if ((Object)(object)key == (Object)null || !_foundResources.Contains(key)) { _staleBeehiveList.Add(key); } } foreach (GameObject staleBeehive in _staleBeehiveList) { _beehiveHoneyCounts.Remove(staleBeehive); } DebugLogger.Benchmark($"Step 5 - Beehive cleanup: {_staleBeehiveList.Count} removed ({stepTimer.ElapsedMilliseconds} ms)"); } private int StepRemoveStalePins(Stopwatch stepTimer) { stepTimer.Restart(); int num = 0; foreach (GameObject trackedResource in BepInExPlugin.GetTrackedResources()) { if ((Object)(object)trackedResource == (Object)null || !trackedResource.activeSelf || !_foundResources.Contains(trackedResource)) { BepInExPlugin.RemoveResourcePin(trackedResource); num++; } } _staleDepositIdList.Clear(); foreach (int key in _depositTypeCache.Keys) { if (!_foundDepositParentIds.Contains(key)) { _staleDepositIdList.Add(key); } } foreach (int staleDepositId in _staleDepositIdList) { _depositTypeCache.Remove(staleDepositId); } num += _timings.MinedFragRemovals; DebugLogger.Benchmark($"Step 6 - Cleanup of picked/farmed/mined/out of range pins: {num} removed ({stepTimer.ElapsedMilliseconds} ms)"); return num; } private void LogScanTimingBreakdown(Stopwatch stepTimer) { double num = 1000f / (float)Stopwatch.Frequency; long num2 = _timings.DictLookup + _timings.NumericStrip + _timings.PrefixScan; long num3 = _timings.ChildLookup + _timings.FragmentPin + _timings.DeepFragment; long mPicked = _timings.MPicked; long num4 = _timings.TrackedCheck + _timings.IconLookup + _timings.LabelGet + _timings.PinType + _timings.PinAdd; long num5 = _timings.Beehive + _timings.Chest; long num6 = num2 + num3 + mPicked + num4 + num5; DebugLogger.Benchmark(" Step 4 breakdown:"); DebugLogger.Benchmark($" Objects in range: {_objectsInRange}"); DebugLogger.Benchmark($" After pre-filter: {_objectsAfterPrefilter}"); DebugLogger.Benchmark($" Dict matches: {_dictMatches}"); DebugLogger.Benchmark($" NumericStrip matches: {_numericStripMatches}"); DebugLogger.Benchmark($" Definition matches: {_definitionMatches}"); DebugLogger.Benchmark($" Lookup: {(double)num2 * num:F2} ms (dict {(double)_timings.DictLookup * num:F2} + strip {(double)_timings.NumericStrip * num:F2} + prefix {(double)_timings.PrefixScan * num:F2})"); DebugLogger.Benchmark($" Hierarchy & frags: {(double)num3 * num:F2} ms (child {(double)_timings.ChildLookup * num:F2} + frags {(double)_timings.FragmentPin * num:F2} + deep {(double)_timings.DeepFragment * num:F2})"); DebugLogger.Benchmark($" Validation: {(double)mPicked * num:F2} ms (mpicked {(double)_timings.MPicked * num:F2})"); DebugLogger.Benchmark($" Pinning: {(double)num4 * num:F2} ms (icon {(double)_timings.IconLookup * num:F2} + label {(double)_timings.LabelGet * num:F2} + add {(double)_timings.PinAdd * num:F2})"); DebugLogger.Benchmark($" Special cases: {(double)num5 * num:F2} ms (beehive {(double)_timings.Beehive * num:F2} + chest {(double)_timings.Chest * num:F2})"); DebugLogger.Benchmark($" Minimap.AddPin: {(double)BepInExPlugin.PinAddTicks * num:F2} ms"); DebugLogger.Benchmark($" Total accounted: {(double)num6 * num:F2} ms"); DebugLogger.Benchmark($" Unaccounted: {(double)stepTimer.ElapsedMilliseconds - (double)num6 * num:F2} ms (likely Valheim minimap overhead: more pins = bigger delay)"); } private void ProcessObject(GameObject candidate, HashSet foundResources, HashSet foundDepositParentIds, ref int newPins) { string name = ((Object)candidate).name; Stopwatch timer = Stopwatch.StartNew(); if (!TryMatchDefs(name, out var defs, timer)) { return; } foreach (ResourceDef item in defs) { if (item.MaterialTarget.GetValueOrDefault() == MT.Child || item.MaterialTarget.GetValueOrDefault() == MT.GCFrags) { continue; } if (item.MaterialTarget.GetValueOrDefault() == MT.GGChild) { _definitionMatches++; _fragmentProcessor.ProcessDeepFragmentedDeposit(candidate, item, foundResources, ref newPins); continue; } Filter resourceFilter = item.ResourceFilter; if (resourceFilter != null && resourceFilter.Type == Filter.FilterType.HoneyCount) { _definitionMatches++; _specialCaseProcessor.ProcessBeehive(candidate, item, foundResources, ref newPins); continue; } Filter resourceFilter2 = item.ResourceFilter; if (resourceFilter2 != null && resourceFilter2.Type == Filter.FilterType.ChestContents) { _definitionMatches++; _specialCaseProcessor.ProcessChest(candidate, item, foundResources, ref newPins); } else { _definitionMatches++; PinStandardResource(candidate, item, foundResources, foundDepositParentIds, ref newPins); } } bool flag = false; foreach (ResourceDef item2 in defs) { if (item2.MaterialTarget.GetValueOrDefault() == MT.Child || item2.MaterialTarget.GetValueOrDefault() == MT.GCFrags) { flag = true; break; } } if (flag) { RouteFragment(candidate, defs, foundResources, foundDepositParentIds, ref newPins); } } private bool TryMatchDefs(string candidateName, out List defs, Stopwatch timer) { timer.Restart(); bool flag = _lookup.TryGetValue(candidateName, out defs); _timings.DictLookup += timer.ElapsedTicks; if (flag) { _dictMatches++; return true; } timer.Restart(); string text = StripNumericSuffix(candidateName); if (text != null) { flag = _lookup.TryGetValue(text, out defs); } _timings.NumericStrip += timer.ElapsedTicks; if (flag) { _numericStripMatches++; return true; } timer.Restart(); for (int i = 0; i < _prefixEntries.Count; i++) { if (candidateName.StartsWith(_prefixEntries[i].Prefix, StringComparison.Ordinal)) { defs = _prefixEntries[i].Defs; flag = true; break; } } _timings.PrefixScan += timer.ElapsedTicks; return flag; } private void RouteFragment(GameObject candidate, List matchedDefs, HashSet foundResources, HashSet foundDepositParentIds, ref int newPins) { int instanceID = ((Object)candidate).GetInstanceID(); if (_depositTypeCache.TryGetValue(instanceID, out var value)) { foundDepositParentIds.Add(instanceID); if (value == "none") { return; } { foreach (ResourceDef matchedDef in matchedDefs) { if (matchedDef.Id != value) { continue; } _definitionMatches++; _fragmentProcessor.ProcessOrMaintainDeposit(candidate, matchedDef, foundResources, foundDepositParentIds, ref newPins); break; } return; } } MeshRenderer component = candidate.GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)((Renderer)component).sharedMaterial == (Object)null) { DebugLogger.DebugMessage($"RouteFragment: {((Object)candidate).name} [ID:{instanceID}] has no MeshRenderer; sentinel cached"); _depositTypeCache[instanceID] = "none"; foundDepositParentIds.Add(instanceID); return; } string text = ((Object)((Renderer)component).sharedMaterial).name; if (text.EndsWith(" (Instance)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - " (Instance)".Length); } DebugLogger.Trace($"RouteFragment: {((Object)candidate).name} [ID:{instanceID}] parent material='{text}'"); if (text == "rock1") { DebugLogger.Trace($"RouteFragment: {((Object)candidate).name} [ID:{instanceID}] plain rock; sentinel cached"); _depositTypeCache[instanceID] = "none"; foundDepositParentIds.Add(instanceID); return; } foreach (ResourceDef matchedDef2 in matchedDefs) { if ((matchedDef2.MaterialTarget.GetValueOrDefault() != MT.Child && matchedDef2.MaterialTarget.GetValueOrDefault() != MT.GCFrags) || matchedDef2.ResourceFilter?.ValueLower == null || !text.ToLower().Contains(matchedDef2.ResourceFilter.ValueLower) || (matchedDef2.ResourceFilter.ValueLower == "giant_skeleton_exterior" && !IsSkeletonExteriorMatch(candidate, matchedDef2))) { continue; } DebugLogger.DebugMessage($"RouteFragment: {((Object)candidate).name} [ID:{instanceID}] identified as {matchedDef2.Id}; caching"); _depositTypeCache[instanceID] = matchedDef2.Id; foundDepositParentIds.Add(instanceID); _definitionMatches++; _fragmentProcessor.ProcessOrMaintainDeposit(candidate, matchedDef2, foundResources, foundDepositParentIds, ref newPins); return; } DebugLogger.DebugMessage($"RouteFragment: {((Object)candidate).name} [ID:{instanceID}] unrecognized material '{text}'; sentinel cached"); _depositTypeCache[instanceID] = "none"; foundDepositParentIds.Add(instanceID); } private static bool IsSkeletonExteriorMatch(GameObject candidate, ResourceDef def) { if (candidate.transform.childCount == 0) { return false; } string name = ((Object)candidate.transform.GetChild(0)).name; return name.StartsWith(def.ChildName); } private string StripNumericSuffix(string name) { string text = name; if (text.EndsWith("(Clone)")) { text = text.Substring(0, text.Length - "(Clone)".Length); } text = StripParentheticalSuffix(text); int num = text.Length; while (num > 0 && char.IsDigit(text[num - 1])) { num--; } if (text == name) { return null; } if (num == 0) { return null; } return text.Substring(0, num); } private static string StripParentheticalSuffix(string name) { if (!name.EndsWith(")")) { return name; } int num = name.LastIndexOf('('); if (num <= 0 || name[num - 1] != ' ') { return name; } string text = name.Substring(num + 1, name.Length - num - 2); if (text.Length == 0) { return name; } string text2 = text; foreach (char c in text2) { if (!char.IsDigit(c)) { return name; } } return name.Substring(0, num - 1); } private void PinStandardResource(GameObject candidate, ResourceDef def, HashSet foundResources, HashSet foundDepositParentIds, ref int newPins) { //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) Stopwatch stopwatch = Stopwatch.StartNew(); Filter resourceFilter = def.ResourceFilter; if (resourceFilter != null && resourceFilter.Type == Filter.FilterType.NameExcludes && ((Object)candidate).name.Contains(def.ResourceFilter.Value)) { return; } stopwatch.Restart(); if (!string.IsNullOrEmpty(def.ChildName)) { GameObject val = FindChildByName(candidate, def.ChildName); if ((Object)(object)val == (Object)null) { _timings.ChildLookup += stopwatch.ElapsedTicks; return; } if (!string.IsNullOrEmpty(def.GrandchildName)) { GameObject val2 = FindChildByName(val, def.GrandchildName); if ((Object)(object)val2 == (Object)null) { _timings.ChildLookup += stopwatch.ElapsedTicks; return; } } } _timings.ChildLookup += stopwatch.ElapsedTicks; stopwatch.Restart(); if (def.CheckMPicked && !RS_ResourceDefinitions.IsPickableAvailable(candidate)) { _timings.MPicked += stopwatch.ElapsedTicks; return; } _timings.MPicked += stopwatch.ElapsedTicks; foundResources.Add(candidate); stopwatch.Restart(); bool flag = BepInExPlugin.IsTracked(candidate); _timings.TrackedCheck += stopwatch.ElapsedTicks; if (!flag) { stopwatch.Restart(); Sprite cachedIcon = _pinFormatter.GetCachedIcon(candidate, def); _timings.IconLookup += stopwatch.ElapsedTicks; stopwatch.Restart(); string label = _pinFormatter.GetLabel(candidate, def); _timings.LabelGet += stopwatch.ElapsedTicks; stopwatch.Restart(); PinType pinType = _pinFormatter.GetPinType(def); _timings.PinType += stopwatch.ElapsedTicks; stopwatch.Restart(); BepInExPlugin.AddResourcePin(candidate, label, cachedIcon, pinType); newPins++; _timings.PinAdd += stopwatch.ElapsedTicks; DebugLogger.Trace($"Added pin for {def.Id} at {candidate.transform.position}"); } } internal static GameObject FindChildByName(GameObject parent, string childName) { if ((Object)(object)parent == (Object)null) { return null; } bool hasMultiple = childName.Contains("|"); Transform transform = parent.transform; for (int i = 0; i < transform.childCount; i++) { Transform child = transform.GetChild(i); if (ChildNameMatches(child, childName, hasMultiple)) { return ((Component)child).gameObject; } } for (int j = 0; j < transform.childCount; j++) { GameObject val = FindChildByName(((Component)transform.GetChild(j)).gameObject, childName); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static bool ChildNameMatches(Transform child, string childName, bool hasMultiple) { if (hasMultiple) { string[] array = childName.Split(new char[1] { '|' }); foreach (string text in array) { if (((Object)child).name == text || ((Object)child).name.StartsWith(text)) { return true; } } return false; } return ((Object)child).name == childName || ((Object)child).name.StartsWith(childName); } } public class RS_SpecialCaseProcessor { private readonly Dictionary _beehiveHoneyCounts; private readonly List<(GameObject resource, ResourceDef def)> _deferredChestItems; private readonly RS_PinFormatter _pinFormatter; private readonly RS_ResourceScanner.ScanTimings _timings; private readonly Dictionary _chestLabels = new Dictionary(); public RS_SpecialCaseProcessor(Dictionary beehiveHoneyCounts, List<(GameObject resource, ResourceDef def)> deferredChestItems, RS_PinFormatter pinFormatter, RS_ResourceScanner.ScanTimings timings) { _beehiveHoneyCounts = beehiveHoneyCounts; _deferredChestItems = deferredChestItems; _pinFormatter = pinFormatter; _timings = timings; } public void ProcessChest(GameObject chest, ResourceDef def, HashSet foundResources, ref int newPins) { //IL_00de: Unknown result type (might be due to invalid IL or missing references) Stopwatch stopwatch = Stopwatch.StartNew(); try { foundResources.Add(chest); bool flag = BepInExPlugin.IsTracked(chest); string value = def.ResourceFilter.Value; if (!string.IsNullOrEmpty(value)) { _deferredChestItems.Add((chest, def)); return; } string text = _pinFormatter.ApplyShowLabels(BuildChestLabel(chest, def)); string value2; bool flag2 = _chestLabels.TryGetValue(chest, out value2) && value2 != text; if (flag && flag2) { BepInExPlugin.RemoveResourcePin(chest); flag = false; DebugLogger.DebugMessage("Chest label changed: '" + value2 + "' -> '" + text + "', refreshing pin"); } if (!flag) { BepInExPlugin.AddResourcePin(chest, text, null, _pinFormatter.GetPinType(def)); newPins++; } _chestLabels[chest] = text; } catch (Exception ex) { DebugLogger.Error("Error processing chest: " + ex.Message, ex); } finally { _timings.Chest += stopwatch.ElapsedTicks; } } public void ClearChestLabels() { _chestLabels.Clear(); DebugLogger.Trace("RS_SpecialCaseProcessor: chest labels cleared"); } public void ProcessChestItem(GameObject chest, ResourceDef def) { //IL_0181: Unknown result type (might be due to invalid IL or missing references) Stopwatch stopwatch = Stopwatch.StartNew(); try { Container component = chest.GetComponent(); if ((Object)(object)component == (Object)null) { return; } Inventory inventory = component.GetInventory(); if (inventory == null) { return; } string value = def.ResourceFilter.Value; int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { string[] obj = new string[5] { "Chest item: m_name='", allItem?.m_shared?.m_name, "', prefab='", null, null }; object obj2; if (allItem == null) { obj2 = null; } else { GameObject dropPrefab = allItem.m_dropPrefab; obj2 = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } obj[3] = (string)obj2; obj[4] = "'"; DebugLogger.Trace(string.Concat(obj)); if (allItem?.m_shared?.m_name == value) { num += allItem.m_stack; } } if (num > 0) { string text = _pinFormatter.ApplyShowLabels($"{def.GetLabel(BepInExPlugin.ConfigManager)} ({num})"); Sprite icon = RS_IconManager.GetIcon(null, def.IconHint); if (BepInExPlugin.IsTracked(chest)) { BepInExPlugin.UpdateResourcePin(chest, text, icon); } else { BepInExPlugin.AddResourcePin(chest, text, icon, _pinFormatter.GetPinType(def)); } DebugLogger.DebugMessage($"Chest contains {num}x {value}"); } } catch (Exception ex) { DebugLogger.Error("Error processing chest item: " + ex.Message, ex); } finally { _timings.Chest += stopwatch.ElapsedTicks; } } private string BuildChestLabel(GameObject chest, ResourceDef def) { string label = def.GetLabel(BepInExPlugin.ConfigManager); Container component = chest.GetComponent(); if ((Object)(object)component != (Object)null) { Inventory inventory = component.GetInventory(); if (inventory != null) { return (inventory.GetAllItems().Count == 0) ? (label + " (empty)") : label; } } return label; } public void ProcessBeehive(GameObject beehive, ResourceDef def, HashSet foundResources, ref int newPins) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) Stopwatch stopwatch = Stopwatch.StartNew(); try { int beehiveHoneyCount = GetBeehiveHoneyCount(beehive); int value; bool flag = _beehiveHoneyCounts.TryGetValue(beehive, out value) && value != beehiveHoneyCount; if (beehiveHoneyCount <= 0) { _beehiveHoneyCounts.Remove(beehive); return; } foundResources.Add(beehive); bool flag2 = BepInExPlugin.IsTracked(beehive); if (flag2 && flag) { BepInExPlugin.RemoveResourcePin(beehive); flag2 = false; DebugLogger.DebugMessage($"Beehive honey amount changed: {value} -> {beehiveHoneyCount}, refreshing pin"); } if (!flag2) { string label = _pinFormatter.ApplyShowLabels($"{def.GetLabel(BepInExPlugin.ConfigManager)} ({beehiveHoneyCount})"); Sprite cachedIcon = _pinFormatter.GetCachedIcon(beehive, def); BepInExPlugin.AddResourcePin(beehive, label, cachedIcon, _pinFormatter.GetPinType(def)); newPins++; DebugLogger.DebugMessage($"Added beehive pin with {beehiveHoneyCount} honey"); } _beehiveHoneyCounts[beehive] = beehiveHoneyCount; } catch (Exception ex) { DebugLogger.Error("Error processing beehive: " + ex.Message, ex); } finally { _timings.Beehive += stopwatch.ElapsedTicks; } } private int GetBeehiveHoneyCount(GameObject beehive) { try { ZNetView component = beehive.GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { ZDO zDO = component.GetZDO(); if (zDO != null) { return zDO.GetInt("level", 0); } } } catch (Exception ex) { DebugLogger.Error("Error getting honey count: " + ex.Message, ex); } return 0; } } }