Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RecipePinner v1.3.0
plugins/RecipePinner.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("PinRecipe")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("PinRecipe")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("b7fff297-caca-412c-8cb0-52556a76bd3f")] [assembly: AssemblyFileVersion("1.3.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.3.0.0")] namespace ValheimRecipePinner; public class ContainerScanner { public static List<Container> AllContainers = new List<Container>(); private static readonly HashSet<Container> _containerSet = new HashSet<Container>(); internal static readonly object ContainerLock = new object(); public Dictionary<string, int> ContainerCache = new Dictionary<string, int>(); private static readonly HashSet<int> _processedIDs = new HashSet<int>(); private readonly List<Container> _snapshotBuffer = new List<Container>(); private Vector3 _lastScanPos; private int _lastItemCount; private float _scanTimer; private float _moveScanCooldown; private const float MovementThresholdSqr = 4f; private const float MinMoveScanCooldown = 1f; private static volatile bool _isInitializing = false; public void InitializeContainers() { if (!RecipePinnerPlugin.EnableChestScanning.Value) { DebugLogger.Verbose("InitializeContainers skipped — chest scanning disabled"); return; } if (_isInitializing) { DebugLogger.Verbose("InitializeContainers skipped — already initializing"); return; } _isInitializing = true; try { DebugLogger.Verbose("Init containers"); lock (ContainerLock) { if (AllContainers.Count > 0) { DebugLogger.Verbose($"InitializeContainers: list already populated ({AllContainers.Count}), skipping scan"); return; } Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); foreach (Container val in array) { if ((Object)(object)val != (Object)null && _containerSet.Add(val)) { AllContainers.Add(val); if ((Object)(object)((Component)val).GetComponent<ContainerTracker>() == (Object)null) { ((Component)val).gameObject.AddComponent<ContainerTracker>().MyContainer = val; } } } DebugLogger.Verbose($"Tracking {AllContainers.Count} containers"); } } finally { _isInitializing = false; } } public static void ClearAll() { lock (ContainerLock) { AllContainers.Clear(); _containerSet.Clear(); DebugLogger.Verbose("ContainerScanner: all container references cleared"); } } public void UpdateScanning() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } _scanTimer += Time.deltaTime; _moveScanCooldown += Time.deltaTime; bool flag = Vector3.SqrMagnitude(((Component)Player.m_localPlayer).transform.position - _lastScanPos) > 4f && _moveScanCooldown >= 1f; float num = (((Object)(object)InventoryGui.instance != (Object)null && (Object)(object)ReflectionHelper.GetCurrentContainer(InventoryGui.instance) != (Object)null) ? 0.5f : RecipePinnerPlugin.ChestScanInterval.Value); bool flag2 = _scanTimer >= num; if (!flag && !flag2) { return; } int num2 = 0; foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems()) { num2 += allItem.m_stack; } bool flag3 = num2 != _lastItemCount; _lastItemCount = num2; DebugLogger.Verbose($"Scanning containers - Moved: {flag}, InvChanged: {flag3}, Interval: {flag2}"); _scanTimer = 0f; if (flag) { _moveScanCooldown = 0f; } UpdateContainerCache(); } private void UpdateContainerCache() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) ContainerCache.Clear(); if ((Object)(object)Player.m_localPlayer == (Object)null) { DebugLogger.Verbose("Cannot scan - player is null"); return; } Vector3 position = ((Component)Player.m_localPlayer).transform.position; float value = RecipePinnerPlugin.ChestScanRange.Value; float num = value * value; _snapshotBuffer.Clear(); lock (ContainerLock) { _snapshotBuffer.AddRange(AllContainers); } _processedIDs.Clear(); int num2 = 0; int num3 = 0; int num4 = 0; foreach (Container item in _snapshotBuffer) { if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).transform == (Object)null) { num3++; continue; } int instanceID = ((Object)item).GetInstanceID(); if (!_processedIDs.Add(instanceID)) { num3++; continue; } if (Vector3.SqrMagnitude(((Component)item).transform.position - position) > num) { num3++; continue; } bool flag = true; if (ReflectionHelper.CheckContainerAccess != null) { flag = ReflectionHelper.CheckContainerAccess(item, Player.m_localPlayer.GetPlayerID()); } if (!flag) { num4++; continue; } Inventory inventory = item.GetInventory(); if (inventory == null) { continue; } foreach (ItemData allItem in inventory.GetAllItems()) { string name = allItem.m_shared.m_name; if (ContainerCache.TryGetValue(name, out var value2)) { ContainerCache[name] = value2 + allItem.m_stack; } else { ContainerCache[name] = allItem.m_stack; } } num2++; } _lastScanPos = position; DebugLogger.Verbose($"Container scan complete - Scanned: {num2}, Skipped: {num3}, AccessDenied: {num4}, UniqueItems: {ContainerCache.Count}"); } [HarmonyPatch(typeof(Container), "Awake")] [HarmonyPostfix] public static void TrackContainerAwake(Container __instance) { if ((Object)(object)__instance == (Object)null || !RecipePinnerPlugin.EnableChestScanning.Value) { return; } lock (ContainerLock) { if (_containerSet.Add(__instance)) { AllContainers.Add(__instance); (((Component)__instance).gameObject.GetComponent<ContainerTracker>() ?? ((Component)__instance).gameObject.AddComponent<ContainerTracker>()).MyContainer = __instance; DebugLogger.Verbose($"New container tracked: {((Object)__instance).name} (Total: {AllContainers.Count})"); } } } public static void RemoveFromSet(Container c) { _containerSet.Remove(c); } } public class ContainerTracker : MonoBehaviour { public Container MyContainer; private void OnDestroy() { if (ContainerScanner.AllContainers != null && (Object)(object)MyContainer != (Object)null) { lock (ContainerScanner.ContainerLock) { ContainerScanner.AllContainers.Remove(MyContainer); ContainerScanner.RemoveFromSet(MyContainer); DebugLogger.Verbose($"Container removed: {((Object)MyContainer).name} (Remaining: {ContainerScanner.AllContainers.Count})"); } } } } public class DataPersistence { public void SavePins() { try { string savePath = GetSavePath(); if (string.IsNullOrEmpty(savePath)) { DebugLogger.Warning("Cannot save - save path is invalid"); return; } RecipeManager recipeManager = RecipePinnerPlugin.Instance?.RecipeMgr; if (recipeManager == null) { DebugLogger.Warning("Cannot save - RecipeMgr is null"); return; } List<string> list = new List<string>(); HashSet<string> hashSet = new HashSet<string>(); foreach (string item in recipeManager.PinnedRecipeOrder) { int value3; if (!hashSet.Add(item)) { DebugLogger.Warning("Skipping duplicate pin order entry while saving: " + item); } else if (item.StartsWith("GROUP:")) { string text = item.Substring(6); if (!recipeManager.PinGroups.TryGetValue(text, out var value)) { continue; } List<string> list2 = new List<string>(); foreach (string memberRecipeKey in value.MemberRecipeKeys) { int value2; int num = ((!value.MemberCounts.TryGetValue(memberRecipeKey, out value2)) ? 1 : value2); list2.Add($"{EscapeSaveValue(memberRecipeKey)}:{num}"); } string text2 = string.Join(",", list2); list.Add("GROUP:" + EscapeSaveValue(text) + "|" + text2); DebugLogger.Verbose($"Saved group: {text} with {value.MemberRecipeKeys.Count} members"); } else if (recipeManager.PinnedRecipes.TryGetValue(item, out value3)) { list.Add($"{EscapeSaveValue(item)}:{value3}"); } } WriteAllLinesAtomically(savePath, list); int count = recipeManager.PinGroups.Count; DebugLogger.Log($"Saved {list.Count} entries ({list.Count - count} pins, {count} groups) to: {savePath}"); } catch (Exception ex) { DebugLogger.Error("Failed to save pins", ex); } } public void LoadPins() { string savePath = GetSavePath(); if (string.IsNullOrEmpty(savePath)) { DebugLogger.Warning("Cannot load - save path is invalid"); return; } RecipeManager recipeManager = RecipePinnerPlugin.Instance?.RecipeMgr; if (recipeManager == null) { DebugLogger.Warning("Cannot load - RecipeMgr is null"); return; } if (!File.Exists(savePath)) { DebugLogger.Log("No save file found at: " + savePath); return; } try { string[] array = File.ReadAllLines(savePath); Dictionary<string, int> dictionary = new Dictionary<string, int>(); Dictionary<string, PinGroupData> dictionary2 = new Dictionary<string, PinGroupData>(); List<string> list = new List<string>(); HashSet<string> hashSet = new HashSet<string>(); int num = 0; int num2 = 0; int num3 = 0; string[] array2 = array; foreach (string text in array2) { if (string.IsNullOrWhiteSpace(text)) { continue; } if (text.StartsWith("GROUP:")) { string text2 = text.Substring(6); int num4 = FindGroupSeparator(text2); if (num4 > 0 && num4 < text2.Length - 1) { string text3 = UnescapeSaveValue(text2.Substring(0, num4).Trim()); string[] array3 = text2.Substring(num4 + 1).Trim().Split(new char[1] { ',' }); if (!string.IsNullOrEmpty(text3) && array3.Length >= 2) { PinGroupData pinGroupData = new PinGroupData { GroupName = text3 }; string[] array4 = array3; for (int j = 0; j < array4.Length; j++) { string text4 = array4[j].Trim(); if (string.IsNullOrEmpty(text4)) { continue; } int num5 = text4.LastIndexOf(':'); if (num5 > 0 && num5 < text4.Length - 1) { string text5 = UnescapeSaveValue(text4.Substring(0, num5)); int result = 1; int.TryParse(text4.Substring(num5 + 1), out result); if (result < 1) { result = 1; } pinGroupData.MemberRecipeKeys.Add(text5); pinGroupData.MemberCounts[text5] = result; } else { string text6 = UnescapeSaveValue(text4); pinGroupData.MemberRecipeKeys.Add(text6); pinGroupData.MemberCounts[text6] = 1; } } if (pinGroupData.MemberRecipeKeys.Count >= 2) { string item = "GROUP:" + text3; if (!hashSet.Add(item)) { DebugLogger.Warning("Duplicate group entry in save file, keeping first order position and latest data: " + text3); num3++; } else { list.Add(item); } dictionary2[text3] = pinGroupData; num2++; DebugLogger.Verbose($"Loaded group: {text3} with {pinGroupData.MemberRecipeKeys.Count} members"); } else { DebugLogger.Warning("Group '" + text3 + "' has less than 2 members, skipping"); num3++; } } else { DebugLogger.Warning("Invalid group format: " + text); num3++; } } else { DebugLogger.Warning("Invalid group line (missing pipe): " + text); num3++; } continue; } int num6 = text.LastIndexOf(':'); if (num6 > 0 && num6 < text.Length - 1) { string text7 = UnescapeSaveValue(text.Substring(0, num6).Trim()); if (int.TryParse(text.Substring(num6 + 1).Trim(), out var result2)) { if (!hashSet.Add(text7)) { DebugLogger.Warning("Duplicate pin entry in save file, keeping first order position and latest count: " + text7); num3++; } else { list.Add(text7); } dictionary[text7] = result2; num++; } else { DebugLogger.Warning("Invalid count value in save file: " + text); num3++; } } else { string text8 = UnescapeSaveValue(text.Trim()); if (!hashSet.Add(text8)) { DebugLogger.Warning("Duplicate legacy pin entry in save file, keeping first order position and latest count: " + text8); num3++; } else { list.Add(text8); } dictionary[text8] = 1; num++; } } recipeManager.PinnedRecipes.Clear(); recipeManager.PinGroups.Clear(); recipeManager.PinnedRecipeOrder.Clear(); foreach (KeyValuePair<string, int> item2 in dictionary) { recipeManager.PinnedRecipes[item2.Key] = item2.Value; } foreach (KeyValuePair<string, PinGroupData> item3 in dictionary2) { recipeManager.PinGroups[item3.Key] = item3.Value; } recipeManager.PinnedRecipeOrder.AddRange(list); int effectivePinCount = recipeManager.GetEffectivePinCount(); if (effectivePinCount > RecipePinnerPlugin.MaximumPins.Value) { int num7 = recipeManager.TrimToMaximumPins(RecipePinnerPlugin.MaximumPins.Value); DebugLogger.Warning($"Loaded save exceeded max effective pins ({effectivePinCount} > {RecipePinnerPlugin.MaximumPins.Value}) - trimmed {num7} effective pin(s)"); } DebugLogger.Log($"Loaded {num} pins and {num2} groups from: {savePath} (Errors: {num3})"); } catch (Exception ex) { DebugLogger.Error("Failed to load pins", ex); } } private void WriteAllLinesAtomically(string savePath, List<string> lines) { string? directoryName = Path.GetDirectoryName(savePath); if (string.IsNullOrEmpty(directoryName)) { throw new IOException("Invalid save directory for path: " + savePath); } string fileName = Path.GetFileName(savePath); string text = Path.Combine(directoryName, $"{fileName}.{Guid.NewGuid():N}.tmp"); string text2 = savePath + ".bak"; try { File.WriteAllLines(text, lines); if (File.Exists(savePath)) { if (File.Exists(text2)) { File.Delete(text2); } File.Replace(text, savePath, text2, ignoreMetadataErrors: true); } else { File.Move(text, savePath); } } catch { try { if (File.Exists(text)) { File.Delete(text); } } catch (Exception ex) { DebugLogger.Warning("Failed to delete temp save file '" + text + "': " + ex.Message); } throw; } } private static int FindGroupSeparator(string groupContent) { int num = groupContent.LastIndexOf('|'); if (num >= 0) { return num; } return -1; } private static string EscapeSaveValue(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } return value.Replace("%", "%25").Replace("|", "%7C").Replace(",", "%2C") .Replace("\r", "%0D") .Replace("\n", "%0A"); } private static string UnescapeSaveValue(string value) { if (string.IsNullOrEmpty(value) || value.IndexOf('%') < 0) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length); for (int i = 0; i < value.Length; i++) { if (value[i] == '%' && i + 2 < value.Length && IsHexDigit(value[i + 1]) && IsHexDigit(value[i + 2])) { string value2 = value.Substring(i + 1, 2); stringBuilder.Append((char)Convert.ToInt32(value2, 16)); i += 2; } else { stringBuilder.Append(value[i]); } } return stringBuilder.ToString(); } private static bool IsHexDigit(char c) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { if (c >= 'A') { return c <= 'F'; } return false; } return true; } private string GetSavePath() { if ((Object)(object)Player.m_localPlayer == (Object)null) { DebugLogger.Verbose("Cannot get save path - local player is null"); return null; } string playerName = Player.m_localPlayer.GetPlayerName(); if (string.IsNullOrWhiteSpace(playerName)) { DebugLogger.Warning("Cannot get save path - player name is empty"); return null; } string text = Path.Combine(Paths.ConfigPath, "RecipePinner_Data"); if (!Directory.Exists(text)) { try { Directory.CreateDirectory(text); DebugLogger.Log("Created save directory: " + text); } catch (Exception ex) { DebugLogger.Error("Failed to create save directory: " + text, ex); return null; } } string text2 = playerName; char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text2 = text2.Replace(oldChar, '_'); } string text3 = Path.Combine(text, text2 + ".txt"); DebugLogger.Verbose("Save path: " + text3); return text3; } } public static class DebugLogger { private const string Prefix = "[RecipePinner]"; public static void Log(string message) { if (IsDebugEnabled()) { Debug.Log((object)("[RecipePinner] " + message)); } } public static void Warning(string message) { Debug.LogWarning((object)("[RecipePinner] " + message)); } public static void Error(string message) { Debug.LogError((object)("[RecipePinner] " + message)); } public static void Error(string message, Exception ex) { Debug.LogError((object)("[RecipePinner] " + message + "\nException: " + ex.Message + "\nStackTrace: " + ex.StackTrace)); } public static void Verbose(string message) { if (IsDebugEnabled()) { Debug.Log((object)("[RecipePinner] [VERBOSE] " + message)); } } private static bool IsDebugEnabled() { if ((Object)(object)RecipePinnerPlugin.Instance != (Object)null && RecipePinnerPlugin.EnableDebugLogging != null) { return RecipePinnerPlugin.EnableDebugLogging.Value; } return false; } } public class LocalizationManager { private readonly RecipePinnerPlugin _plugin; private readonly Dictionary<string, string> _localizedText = new Dictionary<string, string>(); private static readonly Dictionary<string, string> _defaultEnglish = new Dictionary<string, string> { { "pinned", "Recipe Pinned!" }, { "unpinned", "Pin Removed" }, { "list_full", "List Full!" }, { "added_more", "Added More: {0}x" }, { "decreased", "Decreased: {0}x" }, { "cleared", "Pinned Recipes Cleared" }, { "clear_confirm_hotkey", "Press again to clear all pins" }, { "max_level", "Max Level Reached" }, { "no_upgrade_cost", "No upgrade cost found" }, { "gathering_title", "GATHERING LIST" }, { "gathering_opened", "Gathering List Opened" }, { "gathering_closed", "Gathering List Closed" }, { "gathering_empty", "No Recipes Pinned" }, { "gathering_hint", "Open/Close: {0}" }, { "mypins_title", "MY PINS" }, { "mypins_button", "Pins" }, { "mypins_empty", "No Recipes Pinned" }, { "group_button", "Group" }, { "group_confirm", "Confirm" }, { "group_cancel", "Cancel" }, { "group_name_prompt", "Enter group name:" }, { "group_created", "Group Created: {0}" }, { "group_disbanded", "Group Disbanded: {0}" }, { "group_select_hint", "Select pins to group" }, { "group_min_select", "Select at least 2 pins" }, { "group_need_more", "At least 2 pins needed to create a group" }, { "group_create_failed", "Group could not be created" }, { "group_name_exists", "Group '{0}' already exists" }, { "disband_button", "Disband" }, { "confirm_delete_group", "Delete group \"{0}\" and all member pins?" }, { "confirm_delete_pin", "Delete \"{0}\"?" }, { "confirm_remove_member", "Remove \"{0}\" from group \"{1}\"?" }, { "confirm_button", "Confirm" }, { "cancel_button", "Cancel" }, { "confirm_disband_group", "Disband group \"{0}\"? Members will become individual pins." }, { "clear_button", "Clear" }, { "clear_confirm_msg", "Remove all pins?" }, { "clear_no_pins", "No pins to clear" }, { "group_no_pins", "Not enough pins to group" }, { "close_button", "Close" }, { "controls_title", "CONTROLS" }, { "controls_config_note", "Controls can be changed in\nthe config file." }, { "controls_config_note_single", "Controls can be changed in the config file." }, { "howto_header", "HOW TO USE" }, { "howto_pin", "Hover over a recipe in the crafting menu and press [{0}] to pin it." }, { "howto_unpin", "Hold [{0}] and press [{1}] to unpin a recipe." }, { "howto_toggle_hud", "Press [{0}] to show or hide the pinned recipe overlay." }, { "howto_gathering", "Press [{0}] to open or close the gathering list." }, { "howto_next_page", "Press [{0}] to cycle through HUD pages." }, { "howto_clear_all", "Press [{0}] to remove all pinned recipes." }, { "keybindings_header", "KEY BINDINGS" }, { "ctrl_pin", "Pin Recipe" }, { "ctrl_unpin", "Unpin (hold + Pin Recipe key)" }, { "ctrl_toggle_hud", "Toggle HUD Visibility" }, { "ctrl_gathering", "Toggle Gathering List" }, { "ctrl_next_page", "Next HUD Page" }, { "ctrl_clear_all", "Clear All Pins" } }; public LocalizationManager(RecipePinnerPlugin plugin) { _plugin = plugin; DebugLogger.Log("LocalizationManager init"); } public void LoadTranslations() { _localizedText.Clear(); string text = RecipePinnerPlugin.LanguageOverride?.Value?.Trim(); if (string.IsNullOrEmpty(text) || text.ToLower() == "auto") { text = ((Localization.instance == null) ? "English" : Localization.instance.GetSelectedLanguage()); DebugLogger.Log("Auto-detected language: " + text); } else { DebugLogger.Log("Using forced language: " + text); } string text2 = text; char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text2 = text2.Replace(oldChar, '_'); } string text3 = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)_plugin).Info.Location), "RecipePinner_languages", text2 + ".json"); if (!File.Exists(text3)) { DebugLogger.Log("Language file not found: " + text3 + " - Using default English"); return; } try { string text4 = File.ReadAllText(text3); int num = 0; string[] array = text4.Split(new string[3] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text5 = array[i].Trim(); if (string.IsNullOrEmpty(text5) || text5 == "{" || text5 == "}" || !text5.Contains(":")) { continue; } string[] array2 = text5.Split(new char[1] { ':' }, 2); if (array2.Length == 2) { string text6 = array2[0].Trim(',', '"', ' ', '\t', '\r'); string text7 = array2[1].Trim(',', '"', ' ', '\t', '\r'); text7 = text7.Replace("\\\"", "\"").Replace("\\n", "\n").Replace("\\t", "\t") .Replace("\\\\", "\\"); if (!string.IsNullOrEmpty(text6) && !string.IsNullOrEmpty(text7)) { _localizedText[text6] = text7; num++; } } } DebugLogger.Log($"Loaded {num} translations from: {text}.json"); } catch (Exception ex) { DebugLogger.Error("Failed to load language file: " + text3, ex); } } public string GetText(string key) { if (_localizedText.TryGetValue(key, out var value)) { DebugLogger.Verbose("Translation found for '" + key + "': " + value); return value; } if (_defaultEnglish.TryGetValue(key, out var value2)) { DebugLogger.Verbose("Using default English for '" + key + "': " + value2); return value2; } DebugLogger.Warning("No translation found for key: " + key); return key; } } public class PinnedRecipeData { public Recipe RecipeRef; public string RawName; public string CachedHeader; public Sprite Icon; public int StackCount; public List<PinnedResData> Resources = new List<PinnedResData>(); public bool IsDirty = true; public bool IsGroup; public PinGroupData GroupRef; } public class PinnedResData { public string ItemName; public string CachedName; public Sprite Icon; public int RequiredAmount; public int LastKnownAmount; public int LastKnownInvAmount; public string CachedAmountString; } public class PinGroupData { public string GroupName; public List<string> MemberRecipeKeys = new List<string>(); public Dictionary<string, int> MemberCounts = new Dictionary<string, int>(); public List<PinnedRecipeData> MemberPins = new List<PinnedRecipeData>(); public List<PinnedResData> MergedResources = new List<PinnedResData>(); public List<Sprite> MemberIcons = new List<Sprite>(); public bool IsDirty = true; } public class RecipeManager { public Dictionary<string, int> PinnedRecipes = new Dictionary<string, int>(); public List<string> PinnedRecipeOrder = new List<string>(); public List<PinnedRecipeData> CachedPins = new List<PinnedRecipeData>(); public Dictionary<string, PinGroupData> PinGroups = new Dictionary<string, PinGroupData>(); private readonly Dictionary<string, Recipe> _fakeRecipeCache = new Dictionary<string, Recipe>(); private static readonly Regex CleanNameRegex = new Regex("<.*?>", RegexOptions.Compiled); private static readonly Regex AmountSuffixRegex = new Regex("\\s*[xX]?\\s*\\d+$", RegexOptions.Compiled); private static readonly Regex UpgradeStarRegex = new Regex("\\s*★(\\d+)$", RegexOptions.Compiled); private static readonly Dictionary<Type, FieldInfo> _cachedRecipeFields = new Dictionary<Type, FieldInfo>(); private static readonly Dictionary<Type, PropertyInfo> _cachedRecipeProps = new Dictionary<Type, PropertyInfo>(); private static readonly Dictionary<Type, FieldInfo> _cachedItemFields = new Dictionary<Type, FieldInfo>(); private static readonly Dictionary<Type, PropertyInfo> _cachedItemProps = new Dictionary<Type, PropertyInfo>(); private static readonly Dictionary<Type, PropertyInfo> _cachedElementProps = new Dictionary<Type, PropertyInfo>(); private static readonly Dictionary<Type, FieldInfo> _cachedElementFields = new Dictionary<Type, FieldInfo>(); private static readonly HashSet<Type> _elementLookupFailed = new HashSet<Type>(); public void Cleanup() { DebugLogger.Log("RecipeManager cleanup"); int count = _fakeRecipeCache.Count; foreach (Recipe value in _fakeRecipeCache.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } _fakeRecipeCache.Clear(); DebugLogger.Log($"Cleaned {count} fake recipes"); PinGroups.Clear(); _cachedRecipeFields.Clear(); _cachedRecipeProps.Clear(); _cachedItemFields.Clear(); _cachedItemProps.Clear(); _cachedElementProps.Clear(); _cachedElementFields.Clear(); _elementLookupFailed.Clear(); } public void RefreshRecipeCache() { DebugLogger.Verbose("Refreshing cache"); CachedPins.Clear(); if ((Object)(object)ObjectDB.instance == (Object)null) { DebugLogger.Warning("ObjectDB null, can't refresh"); return; } int num = 0; int num2 = 0; Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (PinGroupData value9 in PinGroups.Values) { foreach (string memberRecipeKey in value9.MemberRecipeKeys) { int value; int num3 = ((!value9.MemberCounts.TryGetValue(memberRecipeKey, out value)) ? 1 : value); if (dictionary.ContainsKey(memberRecipeKey)) { dictionary[memberRecipeKey] += num3; } else { dictionary[memberRecipeKey] = num3; } } } Dictionary<string, PinnedRecipeData> dictionary2 = new Dictionary<string, PinnedRecipeData>(); int num4 = 0; foreach (KeyValuePair<string, PinGroupData> pinGroup in PinGroups) { PinGroupData value2 = pinGroup.Value; value2.MemberPins.Clear(); value2.MergedResources.Clear(); value2.MemberIcons.Clear(); value2.IsDirty = true; Dictionary<string, PinnedResData> dictionary3 = new Dictionary<string, PinnedResData>(); foreach (string memberRecipeKey2 in value2.MemberRecipeKeys) { int value3; int count = ((!value2.MemberCounts.TryGetValue(memberRecipeKey2, out value3)) ? 1 : value3); Recipe recipeByName = GetRecipeByName(memberRecipeKey2); if ((Object)(object)recipeByName == (Object)null) { DebugLogger.Warning("Group '" + value2.GroupName + "' member not found: " + memberRecipeKey2); continue; } PinnedRecipeData pinnedRecipeData = BuildPinnedRecipeData(recipeByName, memberRecipeKey2, count); if (pinnedRecipeData == null) { continue; } value2.MemberPins.Add(pinnedRecipeData); if ((Object)(object)pinnedRecipeData.Icon != (Object)null && value2.MemberIcons.Count < 4) { value2.MemberIcons.Add(pinnedRecipeData.Icon); } foreach (PinnedResData resource in pinnedRecipeData.Resources) { if (dictionary3.TryGetValue(resource.ItemName, out var value4)) { value4.RequiredAmount += resource.RequiredAmount; continue; } dictionary3[resource.ItemName] = new PinnedResData { ItemName = resource.ItemName, CachedName = resource.CachedName, Icon = resource.Icon, RequiredAmount = resource.RequiredAmount, LastKnownAmount = -1, LastKnownInvAmount = -1 }; } } foreach (PinnedResData value10 in dictionary3.Values) { value2.MergedResources.Add(value10); } PinnedRecipeData value5 = new PinnedRecipeData { IsDirty = true, RecipeRef = null, RawName = value2.GroupName, CachedHeader = value2.GroupName, Icon = ((value2.MemberIcons.Count > 0) ? value2.MemberIcons[0] : null), StackCount = 1, Resources = value2.MergedResources, IsGroup = true, GroupRef = value2 }; dictionary2[pinGroup.Key] = value5; num4++; DebugLogger.Verbose($"Group pin built: {value2.GroupName} ({value2.MemberPins.Count} members, {value2.MergedResources.Count} resources)"); } foreach (string item in GetDisplayPinOrder()) { if (item.StartsWith("GROUP:")) { string key = item.Substring(6); if (dictionary2.TryGetValue(key, out var value6)) { CachedPins.Add(value6); } } else { if (!PinnedRecipes.TryGetValue(item, out var value7)) { continue; } if (dictionary.TryGetValue(item, out var value8)) { int num5 = value7 - value8; if (num5 <= 0) { DebugLogger.Verbose($"Skipping grouped recipe (no excess): {item} (claims={value8})"); continue; } value7 = num5; DebugLogger.Verbose($"Grouped recipe excess for overlay: {item} x{num5} (claims={value8})"); } Recipe recipeByName2 = GetRecipeByName(item); if ((Object)(object)recipeByName2 != (Object)null) { PinnedRecipeData pinnedRecipeData2 = BuildPinnedRecipeData(recipeByName2, item, value7); if (pinnedRecipeData2 != null) { CachedPins.Add(pinnedRecipeData2); num++; } else { num2++; } } else { DebugLogger.Warning("Recipe not found: " + item); num2++; } } } DebugLogger.Log($"Cache refreshed: {num} pins, {num4} groups, {num2} failed"); if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)RecipePinnerPlugin.Instance != (Object)null) { RecipePinnerPlugin.Instance.UIMgr.UpdateUI(RecipePinnerPlugin.IsUiVisible); RecipePinnerPlugin.Instance.UIMgr.RefreshMyPinsList(); } } public List<string> GetDisplayPinOrder() { List<string> list = new List<string>(); List<string> list2 = new List<string>(); HashSet<string> hashSet = new HashSet<string>(); Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (string item in PinnedRecipeOrder) { if (!item.StartsWith("GROUP:")) { continue; } string text = item.Substring(6); if (!PinGroups.TryGetValue(text, out var value)) { continue; } foreach (string memberRecipeKey in value.MemberRecipeKeys) { dictionary[memberRecipeKey] = text; } } foreach (string item2 in PinnedRecipeOrder) { if (item2.StartsWith("GROUP:")) { string text2 = item2.Substring(6); if (PinGroups.ContainsKey(text2)) { list.Add(item2); AppendDeferredExcessForGroup(list, list2, hashSet, dictionary, text2); } } else { if (!PinnedRecipes.TryGetValue(item2, out var value2)) { continue; } int groupClaimCount = GetGroupClaimCount(item2); if (groupClaimCount <= 0) { list.Add(item2); } else { if (value2 <= groupClaimCount) { continue; } if (dictionary.ContainsKey(item2)) { if (hashSet.Add(item2)) { list2.Add(item2); } } else { list.Add(item2); } } } } foreach (string item3 in list2) { list.Add(item3); } return list; } private static void AppendDeferredExcessForGroup(List<string> displayOrder, List<string> deferredExcess, HashSet<string> deferredSet, Dictionary<string, string> lastClaimingGroup, string groupName) { int num = 0; while (num < deferredExcess.Count) { string text = deferredExcess[num]; if (lastClaimingGroup.TryGetValue(text, out var value) && value == groupName) { displayOrder.Add(text); deferredSet.Remove(text); deferredExcess.RemoveAt(num); } else { num++; } } } public Recipe GetRecipeByName(string name) { if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } if (_fakeRecipeCache.TryGetValue(name, out var value)) { DebugLogger.Verbose("Found cached fake recipe: " + name); return value; } Match match = UpgradeStarRegex.Match(name); if (match.Success) { string name2 = name.Substring(0, match.Index).Trim(); if (!int.TryParse(match.Groups[1].Value, out var result)) { DebugLogger.Warning("Invalid upgrade level in recipe key: " + name); return null; } Recipe recipeByName = GetRecipeByName(name2); if ((Object)(object)recipeByName != (Object)null) { if (!IsValidUpgradeTarget(recipeByName, result, name)) { return null; } Recipe val = CreateFakeUpgradeRecipe(recipeByName, result, name); if ((Object)(object)val != (Object)null) { return val; } } } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name); ItemDrop val2 = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val2 != (Object)null) { Recipe recipe = ObjectDB.instance.GetRecipe(val2.m_itemData); if ((Object)(object)recipe != (Object)null) { DebugLogger.Verbose("Found standard recipe: " + name); return recipe; } } Recipe val3 = null; foreach (Recipe recipe2 in ObjectDB.instance.m_recipes) { if (((Object)recipe2).name == name) { val3 = recipe2; break; } } if ((Object)(object)val3 != (Object)null) { DebugLogger.Verbose("Found recipe in ObjectDB: " + name); return val3; } ZNetScene instance = ZNetScene.instance; GameObject val4 = ((instance != null) ? instance.GetPrefab(name) : null); if ((Object)(object)val4 != (Object)null) { Piece component = val4.GetComponent<Piece>(); if ((Object)(object)component != (Object)null && component.m_resources != null && component.m_resources.Length != 0) { Recipe val5 = ScriptableObject.CreateInstance<Recipe>(); ((Object)val5).hideFlags = (HideFlags)61; ((Object)val5).name = name; val5.m_item = val4.GetComponent<ItemDrop>(); val5.m_resources = (Requirement[])component.m_resources.Clone(); _fakeRecipeCache[name] = val5; DebugLogger.Verbose("Created fake recipe for piece: " + name); return val5; } } DebugLogger.Warning("Recipe not found anywhere: " + name); return null; } private Recipe CreateFakeUpgradeRecipe(Recipe baseRecipe, int targetLevel, string customName) { //IL_006c: 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_007e: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown if ((Object)(object)baseRecipe == (Object)null) { return null; } if (!IsValidUpgradeTarget(baseRecipe, targetLevel, customName)) { return null; } Recipe val = ScriptableObject.CreateInstance<Recipe>(); ((Object)val).hideFlags = (HideFlags)61; ((Object)val).name = customName; val.m_item = baseRecipe.m_item; val.m_amount = 1; int num = Mathf.Max(1, targetLevel - 1); List<Requirement> list = new List<Requirement>(); Requirement[] resources = baseRecipe.m_resources; foreach (Requirement val2 in resources) { if (val2.m_amountPerLevel > 0) { Requirement item = new Requirement { m_resItem = val2.m_resItem, m_amount = val2.m_amountPerLevel * num, m_amountPerLevel = 0, m_recover = val2.m_recover }; list.Add(item); } } if (list.Count == 0) { Object.Destroy((Object)(object)val); return null; } val.m_resources = list.ToArray(); _fakeRecipeCache[customName] = val; DebugLogger.Verbose("Created fake upgrade recipe: " + customName); return val; } private bool IsValidUpgradeTarget(Recipe baseRecipe, int targetLevel, string customName) { if (targetLevel < 2) { DebugLogger.Warning("Invalid upgrade level for '" + customName + "': target level must be at least 2"); return false; } SharedData val = (baseRecipe.m_item?.m_itemData)?.m_shared; if (val == null) { DebugLogger.Warning("Cannot validate upgrade level for '" + customName + "' - item data is missing"); return false; } int maxQuality = val.m_maxQuality; if (maxQuality < 2 || targetLevel > maxQuality) { DebugLogger.Warning($"Invalid upgrade level for '{customName}': target={targetLevel}, max={maxQuality}"); return false; } return true; } public void ValidateAndCleanPins() { if ((Object)(object)ObjectDB.instance == (Object)null) { DebugLogger.Warning("Cannot validate pins - ObjectDB.instance is null"); return; } DebugLogger.Log("Validating pins"); List<string> list = new List<string>(); foreach (string key in PinnedRecipes.Keys) { if ((Object)(object)GetRecipeByName(key) == (Object)null) { list.Add(key); } } if (list.Count > 0) { foreach (string item in list) { PinnedRecipes.Remove(item); PinnedRecipeOrder.Remove(item); DebugLogger.Warning("Removed invalid recipe: " + item); } DebugLogger.Log($"Removed {list.Count} invalid pins"); } else { DebugLogger.Log("All individual pins valid"); } int num = CleanInvalidGroupMembers(); if (list.Count > 0 || num > 0) { RecipePinnerPlugin.Instance?.DataMgr.SavePins(); } } private int CleanInvalidGroupMembers() { int num = 0; List<string> list = new List<string>(); foreach (KeyValuePair<string, PinGroupData> pinGroup in PinGroups) { string key = pinGroup.Key; PinGroupData value = pinGroup.Value; List<string> list2 = new List<string>(); foreach (string memberRecipeKey in value.MemberRecipeKeys) { if ((Object)(object)GetRecipeByName(memberRecipeKey) == (Object)null) { value.MemberCounts.Remove(memberRecipeKey); num++; DebugLogger.Warning("Removed invalid group member: " + memberRecipeKey + " from group '" + key + "'"); } else { list2.Add(memberRecipeKey); } } if (list2.Count != value.MemberRecipeKeys.Count) { value.MemberRecipeKeys.Clear(); value.MemberRecipeKeys.AddRange(list2); } List<string> list3 = new List<string>(); foreach (string key2 in value.MemberCounts.Keys) { if (!value.MemberRecipeKeys.Contains(key2)) { list3.Add(key2); } } foreach (string item in list3) { value.MemberCounts.Remove(item); } if (value.MemberRecipeKeys.Count < 2) { list.Add(key); } } foreach (string item2 in list) { PinGroups.Remove(item2); PinnedRecipeOrder.Remove("GROUP:" + item2); DebugLogger.Warning("Removed group '" + item2 + "' because it has less than 2 valid members"); } if (num > 0 || list.Count > 0) { DebugLogger.Log($"Removed {num} invalid group member(s) and {list.Count} invalid group(s)"); } return num + list.Count; } public void TryPinHoveredRecipe(InventoryGui gui) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown Transform recipeListRoot = ReflectionHelper.GetRecipeListRoot(gui); if (!(ReflectionHelper.GetAvailableRecipes(gui) is IList list) || (Object)(object)recipeListRoot == (Object)null) { DebugLogger.Verbose("Cannot pin - listRoot or availableRecipes is null"); return; } ScrollRect componentInParent = ((Component)recipeListRoot).GetComponentInParent<ScrollRect>(); bool flag = !((Selectable)gui.m_tabUpgrade).interactable; foreach (Transform item in recipeListRoot) { Transform val = item; if (!((Component)val).gameObject.activeInHierarchy) { continue; } RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null); if ((Object)(object)val2 == (Object)null || !IsVisibleInScroll(val2, componentInParent) || !InputHelper.IsMouseOverRect(val2, logHit: false)) { continue; } string text = ExtractTextFromUI(val); if (string.IsNullOrEmpty(text)) { continue; } string text2 = CleanNameRegex.Replace(text, string.Empty).Trim(); text2 = text2.Replace("\r", "").Replace("\n", ""); string text3 = AmountSuffixRegex.Replace(text2, "").Trim(); int num = -1; for (int i = 0; i < list.Count; i++) { GameObject interfaceElementFromObject = GetInterfaceElementFromObject(list[i]); if (!((Object)(object)interfaceElementFromObject == (Object)null) && ((Object)(object)interfaceElementFromObject == (Object)(object)((Component)val).gameObject || interfaceElementFromObject.transform.IsChildOf(val))) { num = i; break; } } int num2 = -1; foreach (object item2 in list) { num2++; if (num >= 0 && num2 != num) { continue; } Recipe recipeFromObject = GetRecipeFromObject(item2); if (!((Object)(object)recipeFromObject != (Object)null)) { continue; } bool flag2 = num >= 0; if (!flag2) { string rawRecipeName = GetRawRecipeName(recipeFromObject); if (string.IsNullOrEmpty(rawRecipeName)) { continue; } string text4 = rawRecipeName; if (Localization.instance != null) { text4 = Localization.instance.Localize(rawRecipeName); } text4 = text4.Replace("\r", "").Replace("\n", ""); flag2 = text4.Equals(text3, StringComparison.OrdinalIgnoreCase) || text4.Equals(text2, StringComparison.OrdinalIgnoreCase); } if (!flag2) { continue; } if (flag) { ItemData val3 = GetItemDataFromObject(item2) ?? ReflectionHelper.GetCraftUpgradeItem(gui); if (val3 != null) { int quality = val3.m_quality; int num3 = quality + 1; int maxQuality = val3.m_shared.m_maxQuality; if (quality >= maxQuality) { string text5 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("max_level"); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, text5, 0, (Sprite)null); } return; } string name = ((Object)recipeFromObject.m_item).name; string text6 = $"{name} ★{num3}"; if (IsUnpinHotkeyHeld() && !PinnedRecipes.ContainsKey(text6)) { return; } DebugLogger.Verbose("Attempting to pin hovered recipe..."); DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})"); DebugLogger.Log("Attempting to pin upgrade: " + text6 + " (Base: " + name + ")"); if ((Object)(object)GetRecipeByName(text6) != (Object)null) { TogglePin(text6); return; } string text7 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("no_upgrade_cost"); Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, text7, 0, (Sprite)null); } } else { DebugLogger.Warning("Matched name but could not get ItemData for upgrade."); } } else if (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(((Object)recipeFromObject).name)) { DebugLogger.Verbose("Attempting to pin hovered recipe..."); DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})"); DebugLogger.Log("Matched recipe: " + ((Object)recipeFromObject).name); TogglePin(((Object)recipeFromObject).name); } return; } } } public void TryPinHoveredPiece() { if (!((Object)(object)Hud.instance == (Object)null)) { Piece hoveredPiece = ReflectionHelper.GetHoveredPiece(Hud.instance); if ((Object)(object)hoveredPiece != (Object)null && hoveredPiece.m_resources != null && hoveredPiece.m_resources.Length != 0 && (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(((Object)hoveredPiece).name))) { DebugLogger.Verbose("Attempting to pin hovered piece..."); DebugLogger.Log("Pinning piece: " + ((Object)hoveredPiece).name); TogglePin(((Object)hoveredPiece).name); } } } private bool IsUnpinHotkeyHeld() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) ConfigEntry<KeyCode> hotkeyUnpin = RecipePinnerPlugin.HotkeyUnpin; KeyCode val = (KeyCode)((hotkeyUnpin == null) ? 304 : ((int)hotkeyUnpin.Value)); if ((int)val != 0) { return Input.GetKey(val); } return false; } private void TogglePin(string recipeName) { bool flag = IsUnpinHotkeyHeld(); LocalizationManager localizationMgr = RecipePinnerPlugin.Instance.LocalizationMgr; if (PinnedRecipes.TryGetValue(recipeName, out var value)) { if (flag) { int groupClaimCount = GetGroupClaimCount(recipeName); int num = groupClaimCount; value--; if (value < num) { if (groupClaimCount > 0) { value = groupClaimCount; PinnedRecipes[recipeName] = value; string groupContainingRecipe = GetGroupContainingRecipe(recipeName); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "Cannot remove: in group \"" + groupContainingRecipe + "\"", 0, (Sprite)null); } DebugLogger.Log($"Hotkey unpin blocked: {recipeName} min={groupClaimCount}"); } else { PinnedRecipes.Remove(recipeName); PinnedRecipeOrder.Remove(recipeName); Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null); } DebugLogger.Log("Unpinned: " + recipeName); } } else if (value == 0) { PinnedRecipes.Remove(recipeName); PinnedRecipeOrder.Remove(recipeName); Player localPlayer3 = Player.m_localPlayer; if (localPlayer3 != null) { ((Character)localPlayer3).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null); } DebugLogger.Log("Unpinned: " + recipeName); } else { PinnedRecipes[recipeName] = value; int num2 = value - groupClaimCount; if (num2 > 0) { string text = string.Format(localizationMgr.GetText("decreased"), num2); Player localPlayer4 = Player.m_localPlayer; if (localPlayer4 != null) { ((Character)localPlayer4).Message((MessageType)2, text, 0, (Sprite)null); } } else { Player localPlayer5 = Player.m_localPlayer; if (localPlayer5 != null) { ((Character)localPlayer5).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null); } } DebugLogger.Log($"Decreased pin count: {recipeName} = {value}"); } } else { value++; PinnedRecipes[recipeName] = value; int groupClaimCount2 = GetGroupClaimCount(recipeName); if (groupClaimCount2 > 0) { int num3 = value - groupClaimCount2; if (num3 == 1) { if (!PinnedRecipeOrder.Contains(recipeName)) { PinnedRecipeOrder.Add(recipeName); } Player localPlayer6 = Player.m_localPlayer; if (localPlayer6 != null) { ((Character)localPlayer6).Message((MessageType)2, localizationMgr.GetText("pinned"), 0, (Sprite)null); } } else { string text2 = string.Format(localizationMgr.GetText("added_more"), num3); Player localPlayer7 = Player.m_localPlayer; if (localPlayer7 != null) { ((Character)localPlayer7).Message((MessageType)2, text2, 0, (Sprite)null); } } } else { string text3 = string.Format(localizationMgr.GetText("added_more"), value); Player localPlayer8 = Player.m_localPlayer; if (localPlayer8 != null) { ((Character)localPlayer8).Message((MessageType)2, text3, 0, (Sprite)null); } } DebugLogger.Log($"Increased pin count: {recipeName} = {value}"); } } else { if (flag) { return; } if (GetEffectivePinCount() < RecipePinnerPlugin.MaximumPins.Value) { PinnedRecipes.Add(recipeName, 1); if (!PinnedRecipeOrder.Contains(recipeName)) { PinnedRecipeOrder.Add(recipeName); } Player localPlayer9 = Player.m_localPlayer; if (localPlayer9 != null) { ((Character)localPlayer9).Message((MessageType)2, localizationMgr.GetText("pinned"), 0, (Sprite)null); } DebugLogger.Log("Pinned new recipe: " + recipeName); } else { Player localPlayer10 = Player.m_localPlayer; if (localPlayer10 != null) { ((Character)localPlayer10).Message((MessageType)2, localizationMgr.GetText("list_full"), 0, (Sprite)null); } DebugLogger.Warning($"Cannot pin {recipeName} - max pins reached ({RecipePinnerPlugin.MaximumPins.Value})"); } } RefreshRecipeCache(); RecipePinnerPlugin.Instance?.DataMgr.SavePins(); } private Recipe GetRecipeFromObject(object data) { if (data == null) { return null; } Recipe val = (Recipe)((data is Recipe) ? data : null); if (val != null) { return val; } Type type = data.GetType(); if (_cachedRecipeFields.TryGetValue(type, out var value)) { object? value2 = value.GetValue(data); return (Recipe)((value2 is Recipe) ? value2 : null); } if (_cachedRecipeProps.TryGetValue(type, out var value3)) { object? value4 = value3.GetValue(data, null); return (Recipe)((value4 is Recipe) ? value4 : null); } PropertyInfo property = type.GetProperty("Key"); if (property != null) { object? value5 = property.GetValue(data, null); Recipe val2 = (Recipe)((value5 is Recipe) ? value5 : null); if (val2 != null) { _cachedRecipeProps[type] = property; return val2; } } FieldInfo field = type.GetField("m_recipe", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object? value6 = field.GetValue(data); Recipe val3 = (Recipe)((value6 is Recipe) ? value6 : null); if (val3 != null) { _cachedRecipeFields[type] = field; return val3; } } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(Recipe)) { _cachedRecipeFields[type] = fieldInfo; object? value7 = fieldInfo.GetValue(data); return (Recipe)((value7 is Recipe) ? value7 : null); } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.PropertyType == typeof(Recipe) && propertyInfo.CanRead) { _cachedRecipeProps[type] = propertyInfo; object? value8 = propertyInfo.GetValue(data, null); return (Recipe)((value8 is Recipe) ? value8 : null); } } return null; } private ItemData GetItemDataFromObject(object data) { if (data == null) { return null; } Type type = data.GetType(); if (_cachedItemFields.TryGetValue(type, out var value)) { object? value2 = value.GetValue(data); return (ItemData)((value2 is ItemData) ? value2 : null); } if (_cachedItemProps.TryGetValue(type, out var value3)) { object? value4 = value3.GetValue(data, null); return (ItemData)((value4 is ItemData) ? value4 : null); } PropertyInfo property = type.GetProperty("Value"); if (property != null) { object? value5 = property.GetValue(data, null); ItemData val = (ItemData)((value5 is ItemData) ? value5 : null); if (val != null) { _cachedItemProps[type] = property; return val; } } PropertyInfo property2 = type.GetProperty("Item2"); if (property2 != null) { object? value6 = property2.GetValue(data, null); ItemData val2 = (ItemData)((value6 is ItemData) ? value6 : null); if (val2 != null) { _cachedItemProps[type] = property2; return val2; } } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(ItemData)) { _cachedItemFields[type] = fieldInfo; object? value7 = fieldInfo.GetValue(data); return (ItemData)((value7 is ItemData) ? value7 : null); } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.PropertyType == typeof(ItemData) && propertyInfo.CanRead) { _cachedItemProps[type] = propertyInfo; object? value8 = propertyInfo.GetValue(data, null); return (ItemData)((value8 is ItemData) ? value8 : null); } } return null; } private GameObject GetInterfaceElementFromObject(object data) { if (data == null) { return null; } Type type = data.GetType(); if (_elementLookupFailed.Contains(type)) { return null; } if (_cachedElementProps.TryGetValue(type, out var value)) { object? value2 = value.GetValue(data, null); return (GameObject)((value2 is GameObject) ? value2 : null); } if (_cachedElementFields.TryGetValue(type, out var value3)) { object? value4 = value3.GetValue(data); return (GameObject)((value4 is GameObject) ? value4 : null); } PropertyInfo property = type.GetProperty("InterfaceElement", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(GameObject) && property.CanRead) { _cachedElementProps[type] = property; object? value5 = property.GetValue(data, null); return (GameObject)((value5 is GameObject) ? value5 : null); } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.PropertyType == typeof(GameObject) && propertyInfo.CanRead) { _cachedElementProps[type] = propertyInfo; object? value6 = propertyInfo.GetValue(data, null); return (GameObject)((value6 is GameObject) ? value6 : null); } } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(GameObject)) { _cachedElementFields[type] = fieldInfo; object? value7 = fieldInfo.GetValue(data); return (GameObject)((value7 is GameObject) ? value7 : null); } } _elementLookupFailed.Add(type); DebugLogger.Warning("GetInterfaceElementFromObject: no GameObject member on '" + type.Name + "' - falling back to name matching"); return null; } private string ExtractTextFromUI(Transform child) { Text componentInChildren = ((Component)child).GetComponentInChildren<Text>(); if ((Object)(object)componentInChildren != (Object)null) { return componentInChildren.text; } Component[] componentsInChildren = ((Component)child).GetComponentsInChildren<Component>(true); foreach (Component val in componentsInChildren) { if (!((object)val).GetType().Name.Contains("TextMeshPro") && !((object)val).GetType().Name.Contains("TMP_Text")) { continue; } PropertyInfo property = ((object)val).GetType().GetProperty("text"); if (property != null) { string text = property.GetValue(val, null) as string; if (!string.IsNullOrEmpty(text)) { return text; } } } return null; } private string GetRawRecipeName(Recipe r) { if ((Object)(object)r.m_item != (Object)null && r.m_item.m_itemData != null) { return r.m_item.m_itemData.m_shared.m_name; } ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab(((Object)r).name) : null); if ((Object)(object)val != (Object)null) { ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null) { return component.m_itemData.m_shared.m_name; } Piece component2 = val.GetComponent<Piece>(); if ((Object)(object)component2 != (Object)null) { return component2.m_name; } } return null; } private bool IsVisibleInScroll(RectTransform item, ScrollRect scrollRect) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00c3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null || !((Component)item).gameObject.activeInHierarchy) { return false; } if ((Object)(object)scrollRect == (Object)null || (Object)(object)scrollRect.viewport == (Object)null) { return true; } Vector3[] array = (Vector3[])(object)new Vector3[4]; scrollRect.viewport.GetWorldCorners(array); Rect val = default(Rect); ((Rect)(ref val))..ctor(array[0].x, array[0].y, array[2].x - array[0].x, array[2].y - array[0].y); Vector3[] array2 = (Vector3[])(object)new Vector3[4]; item.GetWorldCorners(array2); Vector3 val2 = (array2[0] + array2[2]) / 2f; return ((Rect)(ref val)).Contains(val2); } private PinnedRecipeData BuildPinnedRecipeData(Recipe r, string recipeName, int count) { if ((Object)(object)r == (Object)null) { return null; } PinnedRecipeData pinnedRecipeData = new PinnedRecipeData { IsDirty = true, RecipeRef = r, StackCount = count }; if ((Object)(object)r.m_item != (Object)null && r.m_item.m_itemData != null) { pinnedRecipeData.Icon = r.m_item.m_itemData.GetIcon(); pinnedRecipeData.RawName = r.m_item.m_itemData.m_shared?.m_name; } else if ((Object)(object)r.m_item != (Object)null) { DebugLogger.Warning("BuildPinnedRecipeData: recipe '" + recipeName + "' has item without itemData, using fallback name"); } else { ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab(((Object)r).name) : null); if ((Object)(object)val != (Object)null) { Piece component = val.GetComponent<Piece>(); if ((Object)(object)component != (Object)null) { pinnedRecipeData.Icon = component.m_icon; pinnedRecipeData.RawName = component.m_name; } } } if (string.IsNullOrEmpty(pinnedRecipeData.RawName)) { pinnedRecipeData.RawName = ((Object)r).name; } string text = pinnedRecipeData.RawName; if (Localization.instance != null) { Match match = UpgradeStarRegex.Match(recipeName); text = ((!match.Success) ? Localization.instance.Localize(pinnedRecipeData.RawName) : (Localization.instance.Localize(pinnedRecipeData.RawName) + match.Value)); } text = text.Replace("\r", "").Replace("\n", ""); if (r.m_amount > 1) { text += $" (x{r.m_amount})"; } if (count > 1) { text = $"{count}x {text}"; } pinnedRecipeData.CachedHeader = text; if (r.m_resources == null) { DebugLogger.Warning("BuildPinnedRecipeData: recipe '" + recipeName + "' has null resources, skipping"); return null; } Requirement[] resources = r.m_resources; foreach (Requirement val2 in resources) { if (val2 == null || val2.m_amount <= 0) { continue; } if ((Object)(object)val2.m_resItem == (Object)null || val2.m_resItem.m_itemData == null) { DebugLogger.Warning("BuildPinnedRecipeData: skipping invalid resource in '" + recipeName + "'"); continue; } PinnedResData pinnedResData = new PinnedResData { ItemName = val2.m_resItem.m_itemData.m_shared?.m_name, Icon = val2.m_resItem.m_itemData.GetIcon(), RequiredAmount = val2.m_amount * count, LastKnownAmount = -1, LastKnownInvAmount = -1 }; if (string.IsNullOrEmpty(pinnedResData.ItemName)) { DebugLogger.Warning("BuildPinnedRecipeData: skipping resource with empty item name in '" + recipeName + "'"); continue; } string text2 = pinnedResData.ItemName; if (Localization.instance != null) { text2 = Localization.instance.Localize(pinnedResData.ItemName); } text2 = text2.Replace("\r", "").Replace("\n", ""); pinnedResData.CachedName = text2; pinnedRecipeData.Resources.Add(pinnedResData); } return pinnedRecipeData; } public bool CreateGroup(string groupName, List<string> selectedKeys) { if (string.IsNullOrWhiteSpace(groupName)) { DebugLogger.Warning("CreateGroup: group name is empty"); return false; } if (selectedKeys == null || selectedKeys.Count < 2) { DebugLogger.Warning($"CreateGroup: need at least 2 pins, got {selectedKeys?.Count ?? 0}"); return false; } if (PinGroups.ContainsKey(groupName)) { DebugLogger.Warning("CreateGroup: group '" + groupName + "' already exists"); return false; } PinGroupData pinGroupData = new PinGroupData { GroupName = groupName }; foreach (string selectedKey in selectedKeys) { if (PinnedRecipes.TryGetValue(selectedKey, out var value)) { int groupClaimCount = GetGroupClaimCount(selectedKey); int num = value - groupClaimCount; if (num <= 0) { DebugLogger.Warning($"CreateGroup: recipe key '{selectedKey}' has no ungrouped excess to claim (total={value}, claims={groupClaimCount}), skipping"); continue; } pinGroupData.MemberRecipeKeys.Add(selectedKey); pinGroupData.MemberCounts[selectedKey] = num; DebugLogger.Verbose($"CreateGroup: added member '{selectedKey}' to group '{groupName}' (claim={num}, total={value}, previousClaims={groupClaimCount})"); } else { DebugLogger.Warning("CreateGroup: recipe key '" + selectedKey + "' not found in PinnedRecipes, skipping"); } } if (pinGroupData.MemberRecipeKeys.Count < 2) { DebugLogger.Warning($"CreateGroup: only {pinGroupData.MemberRecipeKeys.Count} valid members, need at least 2"); return false; } PinGroups[groupName] = pinGroupData; string item = "GROUP:" + groupName; PinnedRecipeOrder.Add(item); DebugLogger.Log($"Group created: '{groupName}' with {pinGroupData.MemberRecipeKeys.Count} members (added to end)"); RefreshRecipeCache(); RecipePinnerPlugin.Instance?.DataMgr.SavePins(); return true; } public bool DisbandGroup(string groupName) { if (!PinGroups.TryGetValue(groupName, out var value)) { DebugLogger.Warning("DisbandGroup: group '" + groupName + "' not found"); return false; } PinGroups.Remove(groupName); PinnedRecipeOrder.Remove("GROUP:" + groupName); DebugLogger.Log($"Group disbanded: '{groupName}' ({value.MemberRecipeKeys.Count} members restored)"); RefreshRecipeCache(); RecipePinnerPlugin.Instance?.DataMgr.SavePins(); return true; } public void DecrementGroupMemberCounts(string recipeKey) { string text = null; foreach (string item in PinnedRecipeOrder) { if (item.StartsWith("GROUP:")) { string text2 = item.Substring(6); if (PinGroups.TryGetValue(text2, out var value) && value.MemberCounts.ContainsKey(recipeKey)) { text = text2; } } } if (text == null) { return; } string text3 = text; if (!PinGroups.TryGetValue(text3, out var value2)) { return; } int num = value2.MemberCounts[recipeKey]; int num2 = num - 1; if (num2 <= 0) { value2.MemberCounts.Remove(recipeKey); value2.MemberRecipeKeys.Remove(recipeKey); DebugLogger.Log($"AutoUnpin: '{recipeKey}' claim reached 0, removed from group '{text3}' ({value2.MemberRecipeKeys.Count} remaining)"); if (value2.MemberRecipeKeys.Count < 2) { DebugLogger.Log($"AutoUnpin: Group '{text3}' auto-disbanded ({value2.MemberRecipeKeys.Count} members)"); PinGroups.Remove(text3); PinnedRecipeOrder.Remove("GROUP:" + text3); } } else { value2.MemberCounts[recipeKey] = num2; DebugLogger.Log($"AutoUnpin: '{recipeKey}' claim decremented in group '{text3}': {num}->{num2}"); } } public void RemoveMemberFromGroup(string groupName, string recipeKey) { if (!PinGroups.TryGetValue(groupName, out var value)) { DebugLogger.Warning("RemoveMemberFromGroup: group '" + groupName + "' not found"); return; } if (!value.MemberRecipeKeys.Remove(recipeKey)) { DebugLogger.Warning("RemoveMemberFromGroup: '" + recipeKey + "' not in group '" + groupName + "'"); return; } int value2; int num = ((!value.MemberCounts.TryGetValue(recipeKey, out value2)) ? 1 : value2); value.MemberCounts.Remove(recipeKey); DebugLogger.Log($"Removed '{recipeKey}' from group '{groupName}' (claim={num}, {value.MemberRecipeKeys.Count} remaining)"); if (PinnedRecipes.TryGetValue(recipeKey, out var value3)) { int num2 = value3 - num; if (num2 <= 0) { PinnedRecipes.Remove(recipeKey); PinnedRecipeOrder.Remove(recipeKey); } else { PinnedRecipes[recipeKey] = num2; } } if (value.MemberRecipeKeys.Count < 2) { DebugLogger.Log($"Group '{groupName}' has {value.MemberRecipeKeys.Count} member(s), auto-disbanding"); PinGroups.Remove(groupName); PinnedRecipeOrder.Remove("GROUP:" + groupName); } RefreshRecipeCache(); RecipePinnerPlugin.Instance?.DataMgr.SavePins(); if (GetEffectivePinCount() < 2) { RecipePinnerPlugin.Instance?.UIMgr.CloseGatheringList(); } } public void RemovePinFromMyPinsPanel(string key) { if (PinGroups.TryGetValue(key, out var value)) { foreach (string memberRecipeKey in value.MemberRecipeKeys) { if (PinnedRecipes.TryGetValue(memberRecipeKey, out var value2)) { int value3; int num = ((!value.MemberCounts.TryGetValue(memberRecipeKey, out value3)) ? 1 : value3); int num2 = value2 - num; if (num2 <= 0) { PinnedRecipes.Remove(memberRecipeKey); PinnedRecipeOrder.Remove(memberRecipeKey); DebugLogger.Verbose("Removed group member pin entirely: " + memberRecipeKey); } else { PinnedRecipes[memberRecipeKey] = num2; DebugLogger.Verbose($"Group member pin kept as individual: {memberRecipeKey} x{num2}"); } } } PinGroups.Remove(key); PinnedRecipeOrder.Remove("GROUP:" + key); DebugLogger.Log("Removed group: " + key + " (member excess pins preserved)"); } else { int groupClaimCount = GetGroupClaimCount(key); if (groupClaimCount > 0) { if (PinnedRecipes.TryGetValue(key, out var value4) && value4 > groupClaimCount) { PinnedRecipes[key] = groupClaimCount; DebugLogger.Log($"Removed individual excess for grouped pin: {key} (kept {groupClaimCount} for groups)"); } else { DebugLogger.Log($"No individual excess to remove for: {key} (claims={groupClaimCount})"); } } else { PinnedRecipes.Remove(key); PinnedRecipeOrder.Remove(key); DebugLogger.Log("Removed pin: " + key); } } RefreshRecipeCache(); RecipePinnerPlugin.Instance?.DataMgr.SavePins(); if (GetEffectivePinCount() < 2) { RecipePinnerPlugin.Instance?.UIMgr.CloseGatheringList(); } } public void AdjustPinCount(string key, int delta, bool showMessage = true) { if (!PinnedRecipes.TryGetValue(key, out var value)) { DebugLogger.Warning("AdjustPinCount: recipe '" + key + "' not found"); return; } int groupClaimCount = GetGroupClaimCount(key); int num = ((groupClaimCount <= 0) ? 1 : (groupClaimCount + 1)); value += delta; DebugLogger.Log($"AdjustPinCount: {key} -> {value} (min={num}, claims={groupClaimCount})"); if (value < num) { value = num; DebugLogger.Log($"AdjustPinCount: clamped to minimum {num} for '{key}'"); return; } PinnedRecipes[key] = value; if (showMessage) { LocalizationManager localizationManager = RecipePinnerPlugin.Instance?.LocalizationMgr; if (localizationManager != null) { int num2 = value - groupClaimCount; if (delta > 0) { string text = string.Format(localizationManager.GetText("added_more"), num2); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null); } } else { string text2 = string.Format(localizationManager.GetText("decreased"), num2); Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, text2, 0, (Sprite)null); } } } } RefreshRecipeCache(); RecipePinnerPlugin.Instance?.DataMgr.SavePins(); } public string GetGroupContainingRecipe(string recipeKey) { foreach (KeyValuePair<string, PinGroupData> pinGroup in PinGroups) { if (pinGroup.Value.MemberRecipeKeys.Contains(recipeKey)) { return pinGroup.Key; } } return null; } public int GetGroupClaimCount(string recipeKey) { int num = 0; foreach (PinGroupData value2 in PinGroups.Values) { if (value2.MemberCounts.TryGetValue(recipeKey, out var value)) { num += value; } else if (value2.MemberRecipeKeys.Contains(recipeKey)) { num++; } } return num; } public int TrimToMaximumPins(int maxEffectivePins) { int num = 0; while (GetEffectivePinCount() > maxEffectivePins) { bool flag = false; for (int num2 = PinnedRecipeOrder.Count - 1; num2 >= 0; num2--) { string text = PinnedRecipeOrder[num2]; if (text.StartsWith("GROUP:")) { string key = text.Substring(6); PinnedRecipeOrder.RemoveAt(num2); if (PinGroups.TryGetValue(key, out var value)) { foreach (string memberRecipeKey in value.MemberRecipeKeys) { if (PinnedRecipes.TryGetValue(memberRecipeKey, out var value2)) { int value3; int num3 = ((!value.MemberCounts.TryGetValue(memberRecipeKey, out value3)) ? 1 : value3); int num4 = value2 - num3; if (num4 > 0) { PinnedRecipes[memberRecipeKey] = num4; continue; } PinnedRecipes.Remove(memberRecipeKey); PinnedRecipeOrder.Remove(memberRecipeKey); } } PinGroups.Remove(key); } num++; flag = true; break; } if (PinnedRecipes.TryGetValue(text, out var value4)) { int groupClaimCount = GetGroupClaimCount(text); if (value4 > groupClaimCount) { if (groupClaimCount > 0) { PinnedRecipes[text] = groupClaimCount; } else { PinnedRecipes.Remove(text); PinnedRecipeOrder.RemoveAt(num2); } num++; flag = true; break; } } } if (!flag) { break; } } return num; } public int GetEffectivePinCount() { int num = PinGroups.Count; foreach (KeyValuePair<string, int> pinnedRecipe in PinnedRecipes) { int groupClaimCount = GetGroupClaimCount(pinnedRecipe.Key); if (pinnedRecipe.Value > groupClaimCount) { num++; } } return num; } } [BepInPlugin("com.Kadrio.RecipePinner", "Recipe Pinner", "1.3.0")] public class RecipePinnerPlugin : BaseUnityPlugin { public enum PinLayoutMode { AutoDetect, ForceVertical, ForceHorizontal, ForceBottomRightHorizontal } public class ConfigurationManagerAttributes { public bool? ShowRangeAsPercent; public Action<ConfigEntryBase> CustomDrawer; public bool? Browsable; public string Category; public object DefaultValue; public bool? HideDefaultButton; public bool? HideSettingName; public string Description; public string DispName; public int? Order; public bool? ReadOnly; public bool? IsAdvanced; public Func<object, string> ObjToStr; public Func<string, object> StrToObj; } public static RecipePinnerPlugin Instance; public LocalizationManager LocalizationMgr; public RecipeManager RecipeMgr; public ContainerScanner ContainerMgr; public UIManager UIMgr; public DataPersistence DataMgr; internal bool _mluiMapListEnabled; internal bool _mluiNoMapListEnabled; internal bool _mluiInstalled; private bool _startupInitialized; private string _lastLanguage = ""; private string _currentSessionPlayer; private const float ClearAllConfirmWindow = 2f; private float _clearAllArmedUntil; private static bool _isUiVisible = true; public static ConfigEntry<bool> EnableMod; public static ConfigEntry<string> LanguageOverride; public static ConfigEntry<PinLayoutMode> LayoutModeConfig; public static ConfigEntry<int> MaximumPins; public static ConfigEntry<int> PinsPerPage; public static ConfigEntry<bool> AutoUnpinAfterCrafting; public static ConfigEntry<bool> AutoUnpinAfterBuilding; public static ConfigEntry<KeyCode> HotkeyPin; public static ConfigEntry<KeyCode> HotkeyUnpin; public static ConfigEntry<KeyCode> HotkeyClearAll; public static ConfigEntry<KeyCode> HotkeyToggleVisibility; public static ConfigEntry<KeyCode> HotkeyPageSwitch; public static ConfigEntry<KeyCode> HotkeyGatheringList; public static ConfigEntry<bool> EnableChestScanning; public static ConfigEntry<float> ChestScanRange; public static ConfigEntry<float> ChestScanInterval; public static ConfigEntry<float> UIScale; public static ConfigEntry<float> BackgroundOpacity; public static ConfigEntry<int> FontSizeRecipeName; public static ConfigEntry<int> FontSizeMaterials; public static ConfigEntry<int> HudRecipeIconSize; public static ConfigEntry<int> HudMaterialIconSize; public static ConfigEntry<int> HudGroupIconSize; public static ConfigEntry<bool> EnableCraftReadiness; public static ConfigEntry<Color> ColorHeader; public static ConfigEntry<Color> ColorEnoughInInventory; public static ConfigEntry<Color> ColorEnoughWithChests; public static ConfigEntry<Color> ColorMissing; public static ConfigEntry<Color> ColorCraftReady; public static ConfigEntry<Color> ColorCraftNotReady; public static ConfigEntry<Color> ColorPaginationActive; public static ConfigEntry<float> PaginationInactiveOpacity; public static ConfigEntry<int> PaginationDotSize; public static ConfigEntry<int> PaginationDotSpacing; public static ConfigEntry<bool> EnableGatheringList; public static ConfigEntry<bool> AutoOpenGatheringList; public static ConfigEntry<int> GatheringListColumns; public static ConfigEntry<int> GatheringListFontSizeTitle; public static ConfigEntry<int> GatheringListFontSizeMaterials; public static ConfigEntry<Vector2> ContainerGatheringListPosition; public static ConfigEntry<int> GroupCompactThreshold; public static ConfigEntry<int> GroupCompactMaxRows; public static ConfigEntry<int> GroupIconFontSize; public static ConfigEntry<float> MyPinsPanelWidth; public static ConfigEntry<float> MyPinsPanelHeight; public static ConfigEntry<Vector2> MyPinsPanelPosition; public static ConfigEntry<Color> ButtonTextColor; public static ConfigEntry<Vector2> MyPinsButtonPosition; public static ConfigEntry<int> MyPinsButtonSize; public static ConfigEntry<float> VerticalListWidth; public static ConfigEntry<float> VerticalPinSpacing; public static ConfigEntry<Vector2> VerticalPosition; public static ConfigEntry<float> HorizontalColumnWidth; public static ConfigEntry<float> HorizontalPinSpacing; public static ConfigEntry<Vector2> HorizontalPosition; public static ConfigEntry<float> BottomRightColumnWidth; public static ConfigEntry<float> BottomRightPinSpacing; public static ConfigEntry<Vector2> BottomRightPosition; public static ConfigEntry<bool> EnableDebugLogging; public static bool IsUiVisible => _isUiVisible; internal bool IsPinDataLoaded => _startupInitialized; public bool IsHorizontalMode { get { if (LayoutModeConfig.Value == PinLayoutMode.ForceBottomRightHorizontal) { return true; } if (LayoutModeConfig.Value == PinLayoutMode.ForceHorizontal) { return true; } if (LayoutModeConfig.Value == PinLayoutMode.ForceVertical) { return false; } if (!_mluiInstalled) { return false; } if (Game.m_noMap) { return _mluiNoMapListEnabled; } return _mluiMapListEnabled; } } private void Awake() { //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) Instance = this; BindConfigs(); DebugLogger.Log("Plugin init"); LocalizationMgr = new LocalizationManager(this); RecipeMgr = new RecipeManager(); ContainerMgr = new ContainerScanner(); UIMgr = new UIManager(); DataMgr = new DataPersistence(); DebugLogger.Log("Managers ready"); Harmony val = new Harmony("com.Kadrio.RecipePinner"); val.PatchAll(typeof(RecipePinnerPlugin)); val.PatchAll(typeof(ContainerScanner)); DebugLogger.Log("Patches applied"); } private void Start() { DebugLogger.Log("Start()"); LocalizationMgr.LoadTranslations(); ReadMyLittleUIConfig(); ContainerMgr.InitializeContainers(); DebugLogger.Log("Start done"); } private void OnDestroy() { DebugLogger.Log("OnDestroy"); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && !string.IsNullOrEmpty(localPlayer.GetPlayerName())) { if (EnableMod == null || !EnableMod.Value) { DebugLogger.Verbose("OnDestroy save skipped - mod is disabled"); } else if (!_startupInitialized) { DebugLogger.Verbose("OnDestroy save skipped - pin data not loaded yet"); } else { DataMgr.SavePins(); } } RecipeMgr.Cleanup(); } private void OnApplicationFocus(bool hasFocus) { if (hasFocus) { UIMgr?.ResetMyPinsInputState("application focus restored"); } } private void Update() { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_03f1: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) if (!EnableMod.Value) { return; } ReflectionHelper.UpdateGuiScale(); if (!_startupInitialized && (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_recipes.Count > 0) { DebugLogger.Log("First init"); _lastLanguage = Localization.instance.GetSelectedLanguage(); DataMgr.LoadPins(); RecipeMgr.ValidateAndCleanPins(); RecipeMgr.RefreshRecipeCache(); _startupInitialized = true; DebugLogger.Log($"Init done - {RecipeMgr.PinnedRecipes.Count} pins loaded"); } if (EnableChestScanning.Value && (Object)(object)Player.m_localPlayer != (Object)null && RecipeMgr.CachedPins.Count > 0) { ContainerMgr.UpdateScanning(); } bool flag = (Input.GetKeyDown(HotkeyToggleVisibility.Value) || Input.GetKeyDown(HotkeyPin.Value) || Input.GetKeyDown(HotkeyClearAll.Value) || Input.GetKeyDown(HotkeyPageSwitch.Value) || Input.GetKeyDown(HotkeyGatheringList.Value)) && AreRecipePinnerHotkeysBlocked(); if (Input.GetKeyDown(HotkeyToggleVisibility.Value) && !flag) { _isUiVisible = !_isUiVisible; DebugLogger.Log($"UI visibility toggled: {_isUiVisible}"); } if ((Object)(object)Player.m_localPlayer != (Object)null) { UpdatePlayerSession(); } bool flag2 = (Object)(object)Player.m_localPlayer != (Object)null && ((Character)Player.m_localPlayer).InPlaceMode(); if (Input.GetKeyDown(HotkeyPin.Value) && !flag) { if ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible()) { RecipeMgr.TryPinHoveredRecipe(InventoryGui.instance); } else if (flag2) { RecipeMgr.TryPinHoveredPiece(); } } if (Input.GetKeyDown(HotkeyClearAll.Value) && !flag && (RecipeMgr.PinnedRecipes.Count > 0 || RecipeMgr.PinGroups.Count > 0)) { if (Time.unscaledTime <= _clearAllArmedUntil) { _clearAllArmedUntil = 0f; int count = RecipeMgr.PinnedRecipes.Count; int count2 = RecipeMgr.PinGroups.Count; RecipeMgr.PinnedRecipes.Clear(); RecipeMgr.PinnedRecipeOrder.Clear(); RecipeMgr.PinGroups.Clear(); _isUiVisible = true; RecipeMgr.RefreshRecipeCache(); UIMgr.CloseGatheringList(); UIMgr.RefreshMyPinsList(); DataMgr.SavePins(); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, LocalizationMgr.GetText("cleared"), 0, (Sprite)null); } DebugLogger.Log($"Cleared {count} pinned recipes and {count2} groups"); } else { _clearAllArmedUntil = Time.unscaledTime + 2f; Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, LocalizationMgr.GetText("clear_confirm_hotkey"), 0, (Sprite)null); } DebugLogger.Log("Clear-all armed - press again to confirm"); } } if (Localization.instance != null) { string selectedLanguage = Localization.instance.GetSelectedLanguage(); if (_lastLanguage != selectedLanguage) { DebugLogger.Log("Language changed from " + _lastLanguage + " to " + selectedLanguage); _lastLanguage = selectedLanguage; LocalizationMgr.LoadTranslations(); if ((Object)(object)ObjectDB.instance != (Object)null) { RecipeMgr.RefreshRecipeCache(); } UIMgr?.DestroyUI(); UIMgr?.DestroyMyPinsUI(); } } if (Input.GetKeyDown(HotkeyPageSwitch.Value) && _isUiVisible && !flag) { UIMgr?.CyclePage(); } if (Input.GetKeyDown(HotkeyGatheringList.Value) && !flag && EnableGatheringList.Value) { UIMgr?.ToggleGatheringList(); } if ((Object)(object)Player.m_localPlayer != (Object)null) { UIMgr?.UpdateMyPinsInventoryState(); } } private bool AreRecipePinnerHotkeysBlocked() { if (InputHelper.IsInputBlocked()) { return true; } if (UIMgr != null && UIMgr.IsMyPinsPanelOpen) { return true; } if (ControlsInfoPanel.IsOpen) { return true; } InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance != (Object)null && InventoryGui.IsVisible() && ReflectionHelper.IsBlockingInventoryPanelOpen(instance)) { return true; } return false; } private void UpdatePlayerSession() { if ((Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsDead()) { UIMgr?.UpdateUI(isVisible: false); return; } string playerName = Player.m_localPlayer.GetPlayerName(); if (string.IsNullOrEmpty(playerName)) { return; } if (_currentSessionPlayer != playerName) { DebugLogger.Log("Player session changed from '" + _currentSessionPlayer + "' to '" + playerName + "'"); RecipeMgr.PinnedRecipes.Clear(); RecipeMgr.PinnedRecipeOrder.Clear(); RecipeMgr.CachedPins.Clear(); RecipeMgr.PinGroups.Clear(); UIMgr.DestroyUI(); UIMgr.DestroyMyPinsUI(); _currentSessionPlayer = playerName; DataMgr.LoadPins(); if ((Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_recipes.Count > 0) { RecipeMgr.ValidateAndCleanPins(); } RecipeMgr.RefreshRecipeCache(); } UIMgr.UpdateUI(_isUiVisible); } private void ReadMyLittleUIConfig() { if (!Chainloader.PluginInfos.ContainsKey("shudnal.MyLittleUI")) { _mluiInstalled = false; DebugLogger.Log("MyLittleUI not detected"); return; } _mluiInstalled = true; _mluiMapListEnabled = true; _mluiNoMapListEnabled = true; string path = Path.Combine(Paths.ConfigPath, "shudnal.MyLittleUI.cfg"); if (!File.Exists(path)) { DebugLogger.Log("MyLittleUI installed but config not found"); return; } try { string[] array = File.ReadAllLines(path); string text = ""; string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text2 = array2[i].Trim(); bool value; if (text2.StartsWith("[") && text2.EndsWith("]")) { text = text2; } else if (TryReadBoolConfigValue(text2, "Enable", out value)) { if (text == "[Status effects - Map - List]") { _mluiMapListEnabled = value; } else if (text == "[Status effects - Nomap - List]") { _mluiNoMapListEnabled = value; } } } DebugLogger.Log($"MyLittleUI Config: MapList={_mluiMapListEnabled}, NoMapList={_mluiNoMapListEnabled}"); } catch (Exception ex) { DebugLogger.Error("Error reading MyLittleUI config", ex); } } private static bool TryReadBoolConfigValue(string line, string key, out bool value) { value = false; int num = line.IndexOf('='); if (num <= 0) { return false; } if (!string.Equals(line.Substring(0, num).Trim(), key, StringComparison.OrdinalIgnoreCase)) { return false; } string text = line.Substring(num + 1); int num2 = text.IndexOf('#'); if (num2 >= 0) { text = text.Substring(0, num2); } return bool.TryParse(text.Trim(), out value); } [HarmonyPatch(typeof(Game), "SavePlayerProfile")] [HarmonyPostfix] public static void AutoSavePinsHook() { if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Instance == (Object)null)) { if (EnableMod == null || !EnableMod.Value) { DebugLogger.Verbose("Auto-save skipped - mod is disabled"); return; } if (!Instance.IsPinDataLoaded) { DebugLogger.Verbose("Auto-save skipped - pin data not loaded yet"); return; } DebugLogger.Log("Auto-saving pins"); Instance.DataMgr.SavePins(); } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] [HarmonyPostfix] public static void AutoUnpinHook(InventoryGui __instance) { if (!EnableMod.Value || !AutoUnpinAfterCrafting.Value || (Object)(object)Instance == (Object)null) { return; } Recipe craftRecipe = ReflectionHelper.GetCraftRecipe(__instance); if (!((Object)(object)craftRecipe != (Object)null)) { return; } string text = null; if (!((Selectable)__instance.m_tabUpgrade).interactable) { ItemData craftUpgradeItem = ReflectionHelper.GetCraftUpgradeItem(__instance); if (craftUpgradeItem != null) { string name = ((Object)craftRecipe.m_item).name; int quality = craftUpgradeItem.m_quality; int num = quality + 1; text = $"{name} ★{num}"; DebugLogger.Log($"Upgrade crafted: Unpinning target {text} (Base Level: {quality})"); } } else { text = ((Object)craftRecipe).name; } if (text != null && Instance.RecipeMgr.PinnedRecipes.TryGetValue(text, out var value)) { bool num2 = value > Instance.RecipeMgr.GetGroupClaimCount(text); value--; DebugLogger.Log($"Auto-unpin: {text}, remaining count: {value}"); if (num2) { DebugLogger.Verbose("Auto-unpin: consumed ungrouped copy of '" + text + "', group claims untouched"); } else { Instance.RecipeMgr.DecrementGroupMemberCounts(text); } if (value <= 0) { Instance.RecipeMgr.PinnedRecipes.Remove(text); Instance.RecipeMgr.PinnedRecipeOrder.Remove(text); DebugLogger.Log("Recipe " + text + " fully unpinned"); } else { Instance.RecipeMgr.PinnedRecipes[text] = value; } Instance.RecipeMgr.RefreshRecipeCache(); Instance.DataMgr.SavePins(); if (Instance.RecipeMgr.GetEffectivePinCount() < 2) { Instance.UIMgr.CloseGatheringList(); } } else if (text != null) { string text2 = string.Join(", ", Instance.RecipeMgr.PinnedRecipes.Keys); DebugLogger.Verbose("Auto-unpin: '" + text + "' not found in PinnedRecipes. Current keys: [" + text2 + "]"); } } [HarmonyPatch(typeof(Player), "PlacePiece")] [HarmonyPostfix] public static void AutoUnpinBuildHook(Piece piece) { DebugLogger.Verbose("AutoUnpinBuildHook fired (PlacePiece postfix)"); if ((Object)(object)Instance == (Object)null || !EnableMod.Value || !AutoUnpinAfterBuilding.Value) { DebugLogger.Verbose($"AutoUnpinBuildHook early exit: Instance={(Object)(object)Instance != (Object)null}, EnableMod={EnableMod?.Value}, AutoUnpin={AutoUnpinAfterBuilding?.Value}"); return; } if ((Object)(object)piece == (Object)null) { DebugLogger.Verbose("AutoUnpinBuildHook: piece is null"); return; } string text = ((Object)piece).name.Replace("(Clone)", "").Trim(); if (!Instance.RecipeMgr.PinnedRecipes.TryGetValue(text, out var value)) { DebugLogger.Verbose("AutoUnpinBuildHook: '" + text + "' not pinned"); return; } bool num = value > Instance.RecipeMgr.GetGroupClaimCount(text); value--; DebugLogger.Log($"Auto-unpin (Build): {text}, remaining count: {value}"); if (num) { DebugLogger.Verbose("Auto-unpin (Build): consumed ungrouped copy of '" + text + "', group claims untouched"); } else { Instance.RecipeMgr.DecrementGroupMemberCounts(text); } if (value <= 0) { Instance.RecipeMgr.PinnedRecipes.Remove(text); Instance.RecipeMgr.PinnedRecipeOrder.Remove(text); DebugLogger.Log("Build recipe " + text + " fully unpinned"); } else { Instance.RecipeMgr.PinnedRecipes[text] = value; } Instance.RecipeMgr.RefreshRecipeCache(); Instance.DataMgr.SavePins(); if (Instance.RecipeMgr.GetEffectivePinCount() < 2) { Instance.UIMgr.CloseGatheringList(); } } [HarmonyPatch(typeof(Player), "TakeInput")] [HarmonyPrefix] public static bool Player_TakeInput_BlockDuringDialog() { if (GroupNameDialog.IsDialogOpen || ConfirmDialog.IsDialogOpen) { return false; } if (Instance?.UIMgr != null && Instance.UIMgr.IsMyPinsPanelOpen) { return false; } return true; } [HarmonyPatch(typeof(InventoryGui), "Hide")] [HarmonyPrefix] public static bool InventoryGui_Hide_BlockDuringDialog() { if (GroupNameDialog.IsDialogOpen) { DebugLogger.Verbose("InventoryGui.Hide blocked - GroupNameDialog is open"); return false; } if (ConfirmDialog.IsDialogOpen) { DebugLogger.Verbose("InventoryGui.Hide blocked - ConfirmDialog is open"); return false; } if (ControlsInfoPanel.IsOpen) { ControlsInfoPanel.Instance?.Hide(); DebugLogger.Log("InventoryGui.Hide intercepted (ESC) - closing ControlsInfoPanel only"); return false; } if (Instance?.UIMgr != null && Instance.UIMgr.IsMyPinsPanelOpen) { bool keyDown = Input.GetKeyDown((KeyCode)27); Instance.UIMgr.ToggleMyPinsPanel(); if (keyDown) { DebugLogger.Log("InventoryGui.Hide intercepted (ESC) - closing My Pins panel only"); return false; } DebugLogger.Log("InventoryGui.Hide intercepted (Tab) - closing My Pins panel + inventory"); return true; } return true; } private void BindConfigs() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Expected O, but got Unknown //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Expected O, but got Unknown //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Expected O, but got Unknown //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Expected O, but got Unknown //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Expected O, but got Unknown //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Expected O, but got Unknown //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Expected O, but got Unknown //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_039c: Expected O, but got Unknown //IL_03d4: Unknown result type (might be due to invalid IL or missing references) //IL_03de: Expected O, but got Unknown //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_041f: Expected O, but got Unknown //IL_047e: Unknown result type (might be due to invalid IL or missing references) //IL_0488: Expected O, but got Unknown //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Expected O, but got Unknown //IL_0524: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Expected O, but got Unknown //IL_058d: Unknown result type (might be due to invalid IL or missing references) //IL_0597: Expected O, but got Unknown //IL_05d6: Unknown result type (might be due to invalid IL or missing references) //IL_05e0: Expected O, but got Unknown //IL_0635: Unknown result type (might be due to invalid IL or missing references) //IL_063f: Expected O, but got Unknown //IL_0695: Unknown result type (might be due to invalid IL or missing references) //IL_069f: Expected O, but got Unknown //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_06ff: Expected O, but got Unknown //IL_0755: Unknown result type (might be due to invalid IL or missing references) //IL_075f: Expected O, but got Unknown //IL_07ac: Unknown result type (might be due to invalid IL or missing references) //IL_07b6: Expected O, but got Unknown //IL_07f5: Unknown result type (might be due to invalid IL or missing references) //IL_081b: Unknown result type (might be due to invalid IL or missing references) //IL_0825: Expected O, but got Unknown //IL_0864: Unknown result type (might be due to invalid IL or missing references) //IL_088a: Unknown result type (might be due to invalid IL or missing references) //IL_0894: Expected O, but got Unknown //IL_08d3: Unknown result type (might be due to invalid IL or missing references) //IL_08f9: Unknown result type (might be due to invalid IL or missing references) //IL_0903: Expected O, but got Unknown //IL_0942: Unknown result type (might be due to invalid IL or missing references) //IL_0968: Unknown result type (might be due to invalid IL or missing references) //IL_0972: Expected O, but got Unknown //IL_09b1: Unknown result type (might be due to invalid IL or missing references) //IL_09d7: Unknown result type (might be due to invalid IL or missing references) //IL_09e1: Expected O, but got Unknown //IL_0a20: Unknown result type (might be due to invalid IL or missing references) //IL_0a46: Unknown result type (might be due to invalid IL or missing references) //IL_0a50: Expected O, but got Unknown //IL_0a8f: Unknown result type (might be due to invalid IL or missing references) //IL_0ab5: Unknown result type (might be due to invalid IL or missing references) //IL_0abf: Expected O, but got Unknown //IL_0b1e: Unknown result type (might be due to invalid IL or missing references) //IL_0b28: Expected O, but got Unknown //IL_0b7d: Unknown result type (might be due to invalid IL or missing references) //IL_0b87: Expected O, but got Unknown //IL_0bdb: Unknown result type (might be due to invalid IL or missing references) //IL_0be5: Expected O, but got Unknown //IL_0c32: Unknown result type (might be due to invalid IL or missing references) //IL_0c3c: Expected O, but got Unknown //IL_0c89: Unknown result type (might be due to invalid IL or missing references) //IL_0c93: Expected O, but got Unknown //IL_0cd1: Unknown result type (might be due to invalid IL or missing references) //IL_0cdb: Expected O, but got Unknown //IL_0d30: Unknown result type (might be due to invalid IL or missing references) //IL_0d3a: Expected O, but got Unknown //IL_0d8f: Unknown result type (might be due to invalid IL or missing references) //IL_0d99: Expected O, but got Unknown //IL_0dce: Unknown result type (might be due to invalid IL or missing references) //IL_0df4: Unknown result type (might be due to invalid IL or missing references) //IL_0dfe: Expected O, but got Unknown //IL_0e3c: Unknown result type (might be due to invalid IL or missing references) //IL_0e46: Expected O, but got Unknown //IL_0e9a: Unknown result type (might be due to invalid IL or missing references) //IL_0ea4: Expected O, but got Unknown //IL_0ef9: Unknown result type (might be due to invalid IL or missing references) //IL_0f03: Expected O, but got Unknown //IL_0f62: Unknown result type (might be due to invalid IL or missing references) //IL_0f6c: Expected O, but got Unknown //IL_0fcb: Unknown result type (might be due to invalid IL or missing references) //IL_0fd5: Expected O, but got Unknown //IL_1000: Unknown result type (might be due to invalid IL or missing references) //IL_1026: Unknown result type (might be due to invalid IL or missing references) //IL_1030: Expected O, but got Unknown //IL_106f: Unknown result type (might be due to invalid IL or missing references) //IL_1095: Unknown result type (might be due to invalid IL or missing references) //IL_109f: Expected O, but got Unknown //IL_10d4: Unknown result type (might be due to invalid IL or missing references) //IL_10fa: Unknown result type (might be due to invalid IL or missing references) //IL_1104: Expected O, but got Unknown //IL_115d: Unknown result type (might be due to invalid IL or missing references) //IL_1167: Expected O, but got Unknown //IL_11b8: Unknown result type (might be due to invalid IL or missing references) //IL_11c2: Expected O, but got Unknown //IL_11fd: Unknown result type (might be due to invalid IL or missing references) //IL_1207: Expected O, but got Unknown //IL_1226: Unknown result type (might be due to invalid IL or missing references) //IL_124c: Unknown result type (might be due to invalid IL or missing references) //IL_1256: Expected O, but got Unknown //IL_1291: Unknown result type (might be due to invalid IL or missing references) //IL_129b: Expected O, but got Unknown //IL_12d6: Unknown result type (might be due to invalid IL or missing references) //IL_12e0: Expected O, but got Unknown //IL_12ff: Unknown result type (might be due to invalid IL or missing references) //IL_1325: Unknown result type (might be due to invalid IL or missing references) //IL_132f: Expected O, but got Unknown //IL_136a: Unknown result type (might be due to invalid IL or missing references) //IL_1374: Expected O, but got Unknown //IL_13af: Unknown result type (might be due to invalid IL or missing references) //IL_13b9: Expected O, but got Unknown //IL_13d8: Unkn