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.5.1
plugins/RecipePinner.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Kadrio")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Pin crafting recipes and building pieces to your Valheim HUD.")] [assembly: AssemblyFileVersion("1.5.1.0")] [assembly: AssemblyInformationalVersion("1.5.1+1ce90562bb720c66ecba93bd0b9768897b010ef5")] [assembly: AssemblyProduct("Recipe Pinner")] [assembly: AssemblyTitle("RecipePinner")] [assembly: AssemblyVersion("1.5.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ValheimRecipePinner { public class ConfirmDialog : MonoBehaviour { [CompilerGenerated] private static class <>O { public static UnityAction <0>__PlayButtonSFX; } public RectTransform DialogRect; public Image BgImage; public Image OverlayBg; public Text MessageText; public Button ConfirmButton; public Button CancelButton; public Action OnConfirm; public Action OnCancel; public static bool IsDialogOpen; private bool _listenersWired; public void SetActive(bool active) { ((Component)this).gameObject.SetActive(active); IsDialogOpen = active; } public void Show(string message, Action onConfirm, Action onCancel = null) { OnConfirm = onConfirm; OnCancel = onCancel; if ((Object)(object)MessageText != (Object)null) { MessageText.text = message; } WireButtonListeners(); ((Component)this).transform.SetAsLastSibling(); SetActive(active: true); DebugLogger.Log("ConfirmDialog shown: " + message); } private void WireButtonListeners() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown if (_listenersWired) { return; } if ((Object)(object)ConfirmButton != (Object)null) { ((UnityEventBase)ConfirmButton.onClick).RemoveAllListeners(); ((UnityEvent)ConfirmButton.onClick).AddListener(new UnityAction(OnConfirmClicked)); ButtonClickedEvent onClick = ConfirmButton.onClick; object obj = <>O.<0>__PlayButtonSFX; if (obj == null) { UnityAction val = UIBuilder.PlayButtonSFX; <>O.<0>__PlayButtonSFX = val; obj = (object)val; } ((UnityEvent)onClick).AddListener((UnityAction)obj); } if ((Object)(object)CancelButton != (Object)null) { ((UnityEventBase)CancelButton.onClick).RemoveAllListeners(); ((UnityEvent)CancelButton.onClick).AddListener(new UnityAction(OnCancelClicked)); ButtonClickedEvent onClick2 = CancelButton.onClick; object obj2 = <>O.<0>__PlayButtonSFX; if (obj2 == null) { UnityAction val2 = UIBuilder.PlayButtonSFX; <>O.<0>__PlayButtonSFX = val2; obj2 = (object)val2; } ((UnityEvent)onClick2).AddListener((UnityAction)obj2); } _listenersWired = true; } private void OnConfirmClicked() { if (IsDialogOpen && ((Component)this).gameObject.activeSelf) { DebugLogger.Log("ConfirmDialog: Confirm clicked"); SetActive(active: false); OnConfirm?.Invoke(); } } private void OnCancelClicked() { if (IsDialogOpen && ((Component)this).gameObject.activeSelf) { DebugLogger.Log("ConfirmDialog: Cancel clicked"); SetActive(active: false); OnCancel?.Invoke(); } } private void Update() { if (!((Component)this).gameObject.activeSelf) { return; } bool keyDown = Input.GetKeyDown((KeyCode)27); bool flag = Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271); try { if (ZInput.instance != null) { ZInput.ResetButtonStatus("Use"); ZInput.ResetButtonStatus("Attack"); ZInput.ResetButtonStatus("SecondAttack"); ZInput.ResetButtonStatus("Block"); ZInput.ResetButtonStatus("Inventory"); ZInput.ResetButtonStatus("Hide"); } } catch (Exception) { } if (keyDown) { OnCancelClicked(); } else if (flag) { OnConfirmClicked(); } } private void OnDestroy() { if (IsDialogOpen) { IsDialogOpen = false; DebugLogger.Warning("ConfirmDialog: Force-closed on destroy"); } } } 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 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) { _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 ControlsInfoPanel : MonoBehaviour { [CompilerGenerated] private static class <>O { public static UnityAction <0>__PlayButtonSFX; } public Button CloseButton; public Transform RowsParent; public Button InfoButton; private Font _font; private Sprite _badgeSprite; private bool _listenersWired; public static ControlsInfoPanel Instance { get; private set; } public static bool IsOpen { get; private set; } private void Awake() { Instance = this; } private void OnDisable() { IsOpen = false; } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } IsOpen = false; } public void Initialize(Font font, Sprite badgeSprite) { _font = font; _badgeSprite = badgeSprite; WireListeners(); } public void Show() { ((Component)this).gameObject.SetActive(true); RefreshContent(); if ((Object)(object)RowsParent != (Object)null) { LayoutRebuilder.ForceRebuildLayoutImmediate(((Component)this).GetComponent<RectTransform>()); } IsOpen = true; if ((Object)(object)InfoButton != (Object)null) { ((Component)InfoButton).gameObject.SetActive(false); } DebugLogger.Log("ControlsInfoPanel: opened"); } public void Hide() { ((Component)this).gameObject.SetActive(false); IsOpen = false; if ((Object)(object)InfoButton != (Object)null) { ((Component)InfoButton).gameObject.SetActive(true); } DebugLogger.Log("ControlsInfoPanel: closed"); } private void WireListeners() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown if (_listenersWired) { return; } if ((Object)(object)CloseButton != (Object)null) { ((UnityEventBase)CloseButton.onClick).RemoveAllListeners(); ((UnityEvent)CloseButton.onClick).AddListener(new UnityAction(Hide)); ButtonClickedEvent onClick = CloseButton.onClick; object obj = <>O.<0>__PlayButtonSFX; if (obj == null) { UnityAction val = UIBuilder.PlayButtonSFX; <>O.<0>__PlayButtonSFX = val; obj = (object)val; } ((UnityEvent)onClick).AddListener((UnityAction)obj); } _listenersWired = true; } private void RefreshContent() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)RowsParent == (Object)null) { return; } foreach (Transform item in RowsParent) { Object.Destroy((Object)(object)((Component)item).gameObject); } LocalizationManager localizationManager = RecipePinnerPlugin.Instance?.LocalizationMgr; ConfigEntry<KeyCode> hotkeyPin = RecipePinnerPlugin.HotkeyPin; string text = FormatKey((KeyCode)((hotkeyPin == null) ? 325 : ((int)hotkeyPin.Value))); ConfigEntry<KeyCode> hotkeyUnpin = RecipePinnerPlugin.HotkeyUnpin; string text2 = FormatKey((KeyCode)((hotkeyUnpin == null) ? 304 : ((int)hotkeyUnpin.Value))); ConfigEntry<KeyCode> hotkeyToggleVisibility = RecipePinnerPlugin.HotkeyToggleVisibility; string text3 = FormatKey((KeyCode)((hotkeyToggleVisibility == null) ? 288 : ((int)hotkeyToggleVisibility.Value))); ConfigEntry<KeyCode> hotkeyGatheringList = RecipePinnerPlugin.HotkeyGatheringList; string text4 = FormatKey((KeyCode)((hotkeyGatheringList == null) ? 289 : ((int)hotkeyGatheringList.Value))); ConfigEntry<KeyCode> hotkeyPageSwitch = RecipePinnerPlugin.HotkeyPageSwitch; string text5 = FormatKey((KeyCode)((hotkeyPageSwitch == null) ? 308 : ((int)hotkeyPageSwitch.Value))); ConfigEntry<KeyCode> hotkeyClearAll = RecipePinnerPlugin.HotkeyClearAll; string text6 = FormatKey((KeyCode)((hotkeyClearAll == null) ? 112 : ((int)hotkeyClearAll.Value))); CreateSectionHeader(localizationManager?.GetText("howto_header") ?? "HOW TO USE"); CreateInstructionRow(string.Format(localizationManager?.GetText("howto_pin") ?? "Hover over a recipe in the crafting menu and press [{0}] to pin it.", text)); CreateInstructionRow(string.Format(localizationManager?.GetText("howto_unpin") ?? "Hold [{0}] and press [{1}] to unpin a recipe.", text2, text)); CreateInstructionRow(string.Format(localizationManager?.GetText("howto_toggle_hud") ?? "Press [{0}] to show or hide the pinned recipe overlay.", text3)); CreateInstructionRow(string.Format(localizationManager?.GetText("howto_gathering") ?? "Press [{0}] to open or close the gathering list.", text4)); CreateInstructionRow(string.Format(localizationManager?.GetText("howto_next_page") ?? "Press [{0}] to cycle through HUD pages.", text5)); CreateInstructionRow(string.Format(localizationManager?.GetText("howto_clear_all") ?? "Press [{0}] to remove all pinned recipes.", text6)); CreateSectionHeader(localizationManager?.GetText("keybindings_header") ?? "KEY BINDINGS"); CreateRow(localizationManager?.GetText("ctrl_pin") ?? "Pin Recipe", text); CreateRow(localizationManager?.GetText("ctrl_unpin") ?? "Unpin (hold + Pin key)", text2); CreateRow(localizationManager?.GetText("ctrl_toggle_hud") ?? "Toggle HUD Visibility", text3); CreateRow(localizationManager?.GetText("ctrl_gathering") ?? "Toggle Gathering List", text4); CreateRow(localizationManager?.GetText("ctrl_next_page") ?? "Next HUD Page", text5); CreateRow(localizationManager?.GetText("ctrl_clear_all") ?? "Clear All Pins", text6); } private void CreateSectionHeader(string text) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_font == (Object)null)) { GameObject val = new GameObject("SectionHeader", new Type[1] { typeof(RectTransform) }) { layer = 5 }; val.transform.SetParent(RowsParent, false); Text obj = val.AddComponent<Text>(); obj.text = text; obj.font = _font; obj.fontSize = 15; obj.fontStyle = (FontStyle)1; obj.alignment = (TextAnchor)3; ((Graphic)obj).color = new Color(1f, 0.718f, 0.357f, 1f); ((Graphic)obj).raycastTarget = false; LayoutElement obj2 = val.AddComponent<LayoutElement>(); obj2.flexibleWidth = 1f; obj2.minHeight = 22f; obj2.preferredHeight = 22f; } } private void CreateInstructionRow(string text) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_font == (Object)null)) { GameObject val = new GameObject("InstructionRow", new Type[1] { typeof(RectTransform) }) { layer = 5 }; val.transform.SetParent(RowsParent, false); Text obj = val.AddComponent<Text>(); obj.text = "• " + text; obj.font = _font; obj.fontSize = 14; obj.fontStyle = (FontStyle)0; obj.alignment = (TextAnchor)0; ((Graphic)obj).color = new Color(0.85f, 0.82f, 0.7f, 1f); ((Graphic)obj).raycastTarget = false; obj.horizontalOverflow = (HorizontalWrapMode)0; obj.verticalOverflow = (VerticalWrapMode)1; LayoutElement obj2 = val.AddComponent<LayoutElement>(); obj2.flexibleWidth = 1f; obj2.minHeight = 18f; obj2.preferredHeight = 42f; } } private void CreateRow(string label, string keyText) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: 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) if (!((Object)(object)_font == (Object)null)) { GameObject val = new GameObject("ControlRow", new Type[1] { typeof(RectTransform) }) { layer = 5 }; val.transform.SetParent(RowsParent, false); HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f; ((LayoutGroup)obj).padding = new RectOffset(0, 0, 2, 2); LayoutElement obj2 = val.AddComponent<LayoutElement>(); obj2.flexibleWidth = 1f; obj2.minHeight = 26f; GameObject val2 = new GameObject("Label", new Type[1] { typeof(RectTransform) }) { layer = 5 }; val2.transform.SetParent(val.transform, false); Text obj3 = val2.AddComponent<Text>(); obj3.text = label; obj3.font = _font; obj3.fontSize = 15; ((Graphic)obj3).color = new Color(0.88f, 0.84f, 0.72f, 1f); obj3.alignment = (TextAnchor)3; ((Graphic)obj3).raycastTarget = false; val2.AddComponent<LayoutElement>().flexibleWidth = 1f; GameObject val3 = new GameObject("KeyBadge", new Type[1] { typeof(RectTransform) }) { layer = 5 }; val3.transform.SetParent(val.transform, false); Image val4 = val3.AddComponent<Image>(); ((Graphic)val4).color = new Color(0.1f, 0.08f, 0.04f, 0.9f); ((Graphic)val4).raycastTarget = false; if ((Object)(object)_badgeSprite != (Object)null) { val4.sprite = _badgeSprite; val4.type = (Type)((_badgeSprite.border != Vector4.zero) ? 1 : 0); } LayoutElement obj4 = val3.AddComponent<LayoutElement>(); obj4.preferredWidth = 130f; obj4.flexibleWidth = 0f; obj4.minHeight = 22f; GameObject val5 = new GameObject("KeyText", new Type[1] { typeof(RectTransform) }) { layer = 5 }; val5.transform.SetParent(val3.transform, false); RectTransform component = val5.GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = new Vector2(4f, 2f); component.offsetMax = new Vector2(-4f, -2f); Text obj5 = val5.AddComponent<Text>(); obj5.text = keyText; obj5.font = _font; obj5.fontSize = 13; obj5.fontStyle = (FontStyle)1; ((Graphic)obj5).color = new Color(1f, 0.78f, 0.38f, 1f); obj5.alignment = (TextAnchor)4; ((Graphic)obj5).raycastTarget = false; } } public unsafe static string FormatKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Expected I4, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected I4, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Expected I4, but got Unknown if ((int)key <= 27) { if ((int)key <= 9) { if ((int)key == 8) { return "Backspace"; } if ((int)key == 9) { return "Tab"; } } else { if ((int)key == 13) { return "Enter"; } if ((int)key == 27) { return "Escape"; } } } else if ((int)key <= 127) { if ((int)key == 32) { return "Space"; } if ((int)key == 127) { return "Delete"; } } else { switch (key - 256) { default: switch (key - 303) { case 1: return "Left Shift"; case 0: return "Right Shift"; case 5: return "Left Alt"; case 4: return "Right Alt"; case 3: return "Left Ctrl"; case 2: return "Right Ctrl"; } switch (key - 323) { case 0: return "Left Mouse"; case 1: return "Right Mouse"; case 2: return "Middle Mouse"; case 3: return "Mouse 4"; case 4: return "Mouse 5"; } break; case 15: return "Numpad Enter"; case 17: return "Up Arrow"; case 18: return "Down Arrow"; case 20: return "Left Arrow"; case 19: return "Right Arrow"; case 0: return "Numpad 0"; case 1: return "Numpad 1"; case 2: return "Numpad 2"; case 3: return "Numpad 3"; case 4: return "Numpad 4"; case 5: return "Numpad 5"; case 6: return "Numpad 6"; case 7: return "Numpad 7"; case 8: return "Numpad 8"; case 9: return "Numpad 9"; case 24: return "Page Up"; case 25: return "Page Down"; case 22: return "Home"; case 23: return "End"; case 21: return "Insert"; case 10: case 11: case 12: case 13: case 14: case 16: break; } } return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); } } 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 GatheringItemUI : MonoBehaviour { public Image Icon; public Text AmountText; public int LastHave = int.MinValue; public int LastRequired = int.MinValue; public bool LastComplete; public void SetActive(bool active) { ((Component)this).gameObject.SetActive(active); } } public class GatheringListUI : MonoBehaviour { public RectTransform PanelRect; public Image BgImage; public Transform ItemListRoot; public Text HintText; public List<GatheringItemUI> ItemSlots = new List<GatheringItemUI>(); private Coroutine _layoutCoroutine; public void SetActive(bool active) { ((Component)this).gameObject.SetActive(active); } public void RefreshLayout() { if (((Component)this).gameObject.activeInHierarchy) { if (_layoutCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_layoutCoroutine); } _layoutCoroutine = ((MonoBehaviour)this).StartCoroutine(FixLayout()); } } private void OnDisable() { _layoutCoroutine = null; } private IEnumerator FixLayout() { yield return null; if ((Object)(object)ItemListRoot != (Object)null) { Transform itemListRoot = ItemListRoot; LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((itemListRoot is RectTransform) ? itemListRoot : null)); } if ((Object)(object)PanelRect != (Object)null) { LayoutRebuilder.ForceRebuildLayoutImmediate(PanelRect); } } public void ApplyColumns(int configCols) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ItemListRoot == (Object)null) { return; } GridLayoutGroup component = ((Component)ItemListRoot).GetComponent<GridLayoutGroup>(); if (!((Object)(object)component == (Object)null)) { VerticalLayoutGroup component2 = ((Component)this).GetComponent<VerticalLayoutGroup>(); float num = (((Object)(object)component2 != (Object)null) ? ((float)(((LayoutGroup)component2).padding.left + ((LayoutGroup)component2).padding.right)) : 22f); float x = component.cellSize.x; float x2 = component.spacing.x; if (configCols > 0) { component.constraintCount = configCols; float num2 = (float)configCols * (x + x2) - x2 + num; PanelRect.sizeDelta = new Vector2(num2, PanelRect.sizeDelta.y); } else { float num3 = PanelRect.sizeDelta.x - num; int constraintCount = Mathf.Max(1, Mathf.FloorToInt((num3 + x2) / (x + x2))); component.constraintCount = constraintCount; } } } } public class GatheringItemData { public string ItemName; public Sprite Icon; public int TotalRequired; public int TotalHave; public bool IsComplete; public int Stamp; } public class GroupNameDialog : MonoBehaviour { [CompilerGenerated] private static class <>O { public static UnityAction <0>__PlayButtonSFX; } public RectTransform DialogRect; public Image BgImage; public Image OverlayBg; public InputField NameInput; public Button ConfirmButton; public Button CancelButton; public Func<string, bool> OnConfirm; public Action OnCancel; public static bool IsDialogOpen; private bool _inputLocked; private bool _listenersWired; public void SetActive(bool active) { if (active && (Object)(object)NameInput == (Object)null) { ((Component)this).gameObject.SetActive(false); UnlockGameInput(); IsDialogOpen = false; DebugLogger.Error("GroupNameDialog: NameInput is null, dialog closed to avoid input lock"); return; } ((Component)this).gameObject.SetActive(active); if (active) { WireButtonListeners(); LockGameInput(); ClearInput(); } else { UnlockGameInput(); } } private void WireButtonListeners() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown if (_listenersWired) { return; } if ((Object)(object)ConfirmButton != (Object)null) { ((UnityEventBase)ConfirmButton.onClick).RemoveAllListeners(); ((UnityEvent)ConfirmButton.onClick).AddListener(new UnityAction(OnConfirmClicked)); ButtonClickedEvent onClick = ConfirmButton.onClick; object obj = <>O.<0>__PlayButtonSFX; if (obj == null) { UnityAction val = UIBuilder.PlayButtonSFX; <>O.<0>__PlayButtonSFX = val; obj = (object)val; } ((UnityEvent)onClick).AddListener((UnityAction)obj); } if ((Object)(object)CancelButton != (Object)null) { ((UnityEventBase)CancelButton.onClick).RemoveAllListeners(); ((UnityEvent)CancelButton.onClick).AddListener(new UnityAction(OnCancelClicked)); ButtonClickedEvent onClick2 = CancelButton.onClick; object obj2 = <>O.<0>__PlayButtonSFX; if (obj2 == null) { UnityAction val2 = UIBuilder.PlayButtonSFX; <>O.<0>__PlayButtonSFX = val2; obj2 = (object)val2; } ((UnityEvent)onClick2).AddListener((UnityAction)obj2); } if ((Object)(object)NameInput != (Object)null) { ((UnityEventBase)NameInput.onEndEdit).RemoveAllListeners(); ((UnityEvent<string>)(object)NameInput.onEndEdit).AddListener((UnityAction<string>)OnInputEndEdit); } _listenersWired = true; } private void OnInputEndEdit(string text) { if (IsDialogOpen && ((Component)this).gameObject.activeSelf && (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271))) { DebugLogger.Log("GroupNameDialog: Enter via onEndEdit"); OnConfirmClicked(); } } private void OnConfirmClicked() { if (!IsDialogOpen || !((Component)this).gameObject.activeSelf) { return; } InputField nameInput = NameInput; string text = ((nameInput == null) ? null : nameInput.text?.Trim()); if (!string.IsNullOrEmpty(text)) { DebugLogger.Log("GroupNameDialog: Confirm clicked with name '" + text + "'"); Func<string, bool> onConfirm = OnConfirm; if (onConfirm == null || onConfirm(text)) { SetActive(active: false); } else { ClearInput(); } } else { string text2 = (RecipePinnerPlugin.Instance?.LocalizationMgr)?.GetText("group_name_empty") ?? "Group name cannot be empty"; if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)2, text2, 0, (Sprite)null, false); } } } private void OnCancelClicked() { if (IsDialogOpen && ((Component)this).gameObject.activeSelf) { DebugLogger.Log("GroupNameDialog: Cancel clicked"); SetActive(active: false); OnCancel?.Invoke(); } } private void ClearInput() { if ((Object)(object)NameInput != (Object)null) { NameInput.text = ""; NameInput.ActivateInputField(); ((Selectable)NameInput).Select(); } } private void LockGameInput() { if (!_inputLocked) { IsDialogOpen = true; _inputLocked = true; DebugLogger.Log("GroupNameDialog: Game input locked (Harmony patches active)"); } } private void UnlockGameInput() { if (_inputLocked) { IsDialogOpen = false; _inputLocked = false; DebugLogger.Log("GroupNameDialog: Game input unlocked"); } } private void OnDestroy() { if (_inputLocked) { UnlockGameInput(); DebugLogger.Warning("GroupNameDialog: Force-unlocked input on destroy"); } } private void Update() { if (!((Component)this).gameObject.activeSelf) { return; } bool keyDown = Input.GetKeyDown((KeyCode)27); bool flag = Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271); if (IsDialogOpen) { Input.ResetInputAxes(); try { if (ZInput.instance != null) { ZInput.ResetButtonStatus("Forward"); ZInput.ResetButtonStatus("Backward"); ZInput.ResetButtonStatus("Left"); ZInput.ResetButtonStatus("Right"); ZInput.ResetButtonStatus("Jump"); ZInput.ResetButtonStatus("Crouch"); ZInput.ResetButtonStatus("Run"); ZInput.ResetButtonStatus("Use"); ZInput.ResetButtonStatus("Attack"); ZInput.ResetButtonStatus("SecondAttack"); ZInput.ResetButtonStatus("Block"); ZInput.ResetButtonStatus("Inventory"); ZInput.ResetButtonStatus("Hide"); ZInput.ResetButtonStatus("Sit"); ZInput.ResetButtonStatus("GPower"); ZInput.ResetButtonStatus("Emote1"); ZInput.ResetButtonStatus("Emote2"); } } catch (Exception) { } } if (IsDialogOpen) { if (keyDown) { DebugLogger.Log("GroupNameDialog: Escape pressed, cancelling"); OnCancelClicked(); } else if (flag) { DebugLogger.Log("GroupNameDialog: Enter pressed, confirming"); OnConfirmClicked(); } } } } public static class KeyHintInjector { private const string PinEntryName = "RecipePinnerPin"; private const string UnpinEntryName = "RecipePinnerUnpin"; private static KeyHints _injectedInto; public static void EnsureInjected() { KeyHints instance = KeyHints.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)_injectedInto == (Object)(object)instance)) { _injectedInto = instance; InjectInto(instance.m_inventoryHints); InjectInto(instance.m_inventoryWithContainerHints); InjectInto(instance.m_buildHints); } } public static void RefreshKeys() { KeyHints instance = KeyHints.instance; if (!((Object)(object)instance == (Object)null)) { RefreshBar(instance.m_inventoryHints); RefreshBar(instance.m_inventoryWithContainerHints); RefreshBar(instance.m_buildHints); } } public static void UpdateHintVisibility() { KeyHints instance = KeyHints.instance; if (!((Object)(object)instance == (Object)null)) { bool flag = HasHotkeyRemovablePin(); bool flag2 = IsBuildMenuOpen(); SetEntriesActive(instance.m_inventoryHints, pinVisible: true, flag); SetEntriesActive(instance.m_inventoryWithContainerHints, pinVisible: true, flag); SetEntriesActive(instance.m_buildHints, flag2, flag2 && flag); } } private static bool HasHotkeyRemovablePin() { RecipePinnerPlugin instance = RecipePinnerPlugin.Instance; if ((Object)(object)instance == (Object)null) { return false; } RecipeManager recipeMgr = instance.RecipeMgr; if (recipeMgr == null) { return false; } foreach (PinnedRecipeData cachedPin in recipeMgr.CachedPins) { if (cachedPin != null && !cachedPin.IsGroup) { return true; } } return false; } private static bool IsBuildMenuOpen() { return Hud.InBuildUi(); } private static void SetEntriesActive(GameObject bar, bool pinVisible, bool unpinVisible) { if (!((Object)(object)bar == (Object)null)) { Transform val = bar.transform.Find("Keyboard"); if (!((Object)(object)val == (Object)null)) { SetEntryActive(val.Find("RecipePinnerPin"), pinVisible); SetEntryActive(val.Find("RecipePinnerUnpin"), unpinVisible); } } } private static void SetEntryActive(Transform entry, bool visible) { if (!((Object)(object)entry == (Object)null) && ((Component)entry).gameObject.activeSelf != visible) { ((Component)entry).gameObject.SetActive(visible); } } private static void InjectInto(GameObject bar) { //IL_0056: 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_0083: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bar == (Object)null) { return; } Transform val = bar.transform.Find("Keyboard"); if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Find("RecipePinnerPin") != (Object)null)) { Transform val2 = FindTemplate(val, 1); if ((Object)(object)val2 != (Object)null) { AddEntry(val, val2, "RecipePinnerPin", "hint_pin", GetPinKey(), (KeyCode)0); } Transform val3 = FindTemplate(val, 2); if ((Object)(object)val3 != (Object)null) { AddEntry(val, val3, "RecipePinnerUnpin", "hint_unpin", GetUnpinKey(), GetPinKey()); } } } private static void RefreshBar(GameObject bar) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bar == (Object)null) { return; } Transform val = bar.transform.Find("Keyboard"); if (!((Object)(object)val == (Object)null)) { Transform val2 = val.Find("RecipePinnerPin"); if ((Object)(object)val2 != (Object)null) { ApplyTexts(((Component)val2).gameObject, "hint_pin", GetPinKey(), (KeyCode)0); } Transform val3 = val.Find("RecipePinnerUnpin"); if ((Object)(object)val3 != (Object)null) { ApplyTexts(((Component)val3).gameObject, "hint_unpin", GetUnpinKey(), GetPinKey()); } } } private static Transform FindTemplate(Transform mode, int wantedKeys) { for (int i = 0; i < mode.childCount; i++) { Transform child = mode.GetChild(i); if (((Object)child).name == "RecipePinnerPin" || ((Object)child).name == "RecipePinnerUnpin") { continue; } int num = 0; bool flag = false; TMP_Text[] componentsInChildren = ((Component)child).GetComponentsInChildren<TMP_Text>(true); for (int j = 0; j < componentsInChildren.Length; j++) { string name = ((Object)((Component)componentsInChildren[j]).gameObject).name; if (name == "Key") { num++; } else if (name == "Text") { flag = true; } } if (flag && num == wantedKeys) { return child; } } return null; } private static void AddEntry(Transform parent, Transform template, string entryName, string labelKey, KeyCode firstKey, KeyCode secondKey) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, parent); ((Object)val).name = entryName; if (!val.activeSelf) { val.SetActive(true); } ApplyTexts(val, labelKey, firstKey, secondKey); DebugLogger.Log("Added key hint '" + entryName + "' to '" + ((Object)parent.parent).name + "'"); } private static void ApplyTexts(GameObject entry, string labelKey, KeyCode firstKey, KeyCode secondKey) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) string label = GetLabel(labelKey); int num = 0; TMP_Text[] componentsInChildren = entry.GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val in componentsInChildren) { string name = ((Object)((Component)val).gameObject).name; if (name == "Text") { val.text = label; } else if (name == "Key") { val.text = DescribeKey((num == 0) ? firstKey : secondKey); num++; } } } private unsafe static string DescribeKey(KeyCode code) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) if ((int)code == 0) { return string.Empty; } string text = ZInput.KeyCodeToDisplayName(code); if (string.IsNullOrEmpty(text) || text.Contains("did not have corresponding")) { return ((object)(*(KeyCode*)(&code))/*cast due to .constrained prefix*/).ToString(); } return text; } private static string GetLabel(string labelKey) { RecipePinnerPlugin instance = RecipePinnerPlugin.Instance; LocalizationManager localizationManager = (((Object)(object)instance == (Object)null) ? null : instance.LocalizationMgr); if (localizationManager == null) { return labelKey; } return localizationManager.GetText(labelKey); } private static KeyCode GetPinKey() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (RecipePinnerPlugin.HotkeyPin != null) { return RecipePinnerPlugin.HotkeyPin.Value; } return (KeyCode)325; } private static KeyCode GetUnpinKey() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (RecipePinnerPlugin.HotkeyUnpin != null) { return RecipePinnerPlugin.HotkeyUnpin.Value; } return (KeyCode)304; } } 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" }, { "forge_route", "Idol" }, { "hint_pin", "Pin" }, { "hint_unpin", "Unpin" }, { "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_name_empty", "Group name cannot be empty" }, { "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" }, { "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?" }, { "close_button", "Close" }, { "controls_title", "CONTROLS" }, { "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 raw = array2[1].Trim(',', '"', ' ', '\t', '\r'); raw = UnescapeValue(raw); if (!string.IsNullOrEmpty(text6) && !string.IsNullOrEmpty(raw)) { _localizedText[text6] = raw; num++; } } } if (num < _defaultEnglish.Count / 2) { DebugLogger.Warning($"Only {num} of {_defaultEnglish.Count} translations were read from {text2}.json - the file format may not be supported (one \"key\": \"value\" pair per line is expected). The missing texts fall back to English."); } DebugLogger.Log($"Loaded {num} translations from: {text}.json"); } catch (Exception ex) { DebugLogger.Error("Failed to load language file: " + text3, ex); } } private static string UnescapeValue(string raw) { if (string.IsNullOrEmpty(raw) || raw.IndexOf('\\') < 0) { return raw; } StringBuilder stringBuilder = new StringBuilder(raw.Length); for (int i = 0; i < raw.Length; i++) { char c = raw[i]; if (c != '\\' || i + 1 >= raw.Length) { stringBuilder.Append(c); continue; } switch (raw[i + 1]) { case '"': stringBuilder.Append('"'); i++; break; case 'n': stringBuilder.Append('\n'); i++; break; case 't': stringBuilder.Append('\t'); i++; break; case '\\': stringBuilder.Append('\\'); i++; break; default: stringBuilder.Append(c); break; } } return stringBuilder.ToString(); } 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 PinKey; public string RawName; public string CachedHeader; public Sprite Icon; 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 SingleAmount; 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 class MyPinMaterialUI : MonoBehaviour { public Image Icon; public Text AmountText; } public class MyPinItemUI : MonoBehaviour { public Image Icon; public Transform IconRoot; public Transform MaterialsRoot; public List<MyPinMaterialUI> MaterialCells = new List<MyPinMaterialUI>(); public VerticalLayoutGroup RowLayout; public HorizontalLayoutGroup TopRowLayout; public LayoutElement RowHeight; public Text NameText; public Text CountText; public Button DeleteButton; public Button PlusButton; public Button MinusButton; public Button ExpandButton; public Text ExpandButtonText; public Button DisbandButton; public Toggle SelectToggle; public string RecipeKey; public bool IsGroupItem; public bool IsSubItem; public string ParentGroupName; public static float MaterialsStripHeight => RecipePinnerPlugin.MaterialStripHeight?.Value ?? 24f; public void SetActive(bool active) { ((Component)this).gameObject.SetActive(active); } public void SetMaterialsVisible(bool visible) { if ((Object)(object)MaterialsRoot != (Object)null && ((Component)MaterialsRoot).gameObject.activeSelf != visible) { ((Component)MaterialsRoot).gameObject.SetActive(visible); } if ((Object)(object)RowHeight != (Object)null) { RowHeight.minHeight = (IsSubItem ? 32f : 38f) + (visible ? MaterialsStripHeight : 0f); } } public void SetSubItemStyle(bool isSubItem) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) IsSubItem = isSubItem; if ((Object)(object)RowLayout != (Object)null) { ((LayoutGroup)RowLayout).padding = (isSubItem ? new RectOffset(24, 6, 2, 2) : new RectOffset(6, 6, 4, 4)); } LayoutElement component = ((Component)this).GetComponent<LayoutElement>(); if ((Object)(object)component != (Object)null) { component.minHeight = (isSubItem ? 32 : 38); } if ((Object)(object)NameText != (Object)null) { NameText.fontSize = (isSubItem ? 13 : 15); } if ((Object)(object)CountText != (Object)null) { CountText.fontSize = (isSubItem ? 13 : 15); } Image component2 = ((Component)this).GetComponent<Image>(); if ((Object)(object)component2 != (Object)null) { ((Graphic)component2).color = (isSubItem ? new Color(0.15f, 0.15f, 0.15f, 0.3f) : new Color(0f, 0f, 0f, 0.25f)); } } public void SetSelectionMode(bool selectionMode) { if ((Object)(object)SelectToggle != (Object)null) { ((Component)SelectToggle).gameObject.SetActive(selectionMode && !IsGroupItem && !IsSubItem); if (!selectionMode) { SelectToggle.isOn = false; } } if ((Object)(object)DeleteButton != (Object)null && selectionMode) { ((Component)DeleteButton).gameObject.SetActive(false); } if ((Object)(object)PlusButton != (Object)null && selectionMode) { ((Component)PlusButton).gameObject.SetActive(false); } if ((Object)(object)MinusButton != (Object)null && selectionMode) { ((Component)MinusButton).gameObject.SetActive(false); } if ((Object)(object)ExpandButton != (Object)null && IsGroupItem) { ((Component)ExpandButton).gameObject.SetActive(!selectionMode); } if ((Object)(object)DisbandButton != (Object)null && IsGroupItem) { ((Component)DisbandButton).gameObject.SetActive(!selectionMode); } } } public class MyPinsPanelUI : MonoBehaviour { public RectTransform PanelRect; public Image BgImage; public Transform PinListRoot; public Button GroupButton; public Button ConfirmButton; public Button CancelButton; public Button ClearButton; public Button CloseButton; public ControlsInfoPanel ControlsPanel; public Text EmptyText; public List<MyPinItemUI> PinItems = new List<MyPinItemUI>(); private Coroutine _layoutCoroutine; public void SetActive(bool active) { ((Component)this).gameObject.SetActive(active); } public void RefreshLayout() { if (((Component)this).gameObject.activeInHierarchy) { if (_layoutCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_layoutCoroutine); } _layoutCoroutine = ((MonoBehaviour)this).StartCoroutine(FixLayout()); } } private void OnDisable() { _layoutCoroutine = null; } private IEnumerator FixLayout() { yield return null; if ((Object)(object)PinListRoot != (Object)null) { Transform pinListRoot = PinListRoot; LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((pinListRoot is RectTransform) ? pinListRoot : null)); } if ((Object)(object)PanelRect != (Object)null) { LayoutRebuilder.ForceRebuildLayoutImmediate(PanelRect); } } } 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+)(F)?$", 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(); 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), 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 string BuildRecipeKey(Recipe r) { if ((Object)(object)r == (Object)null) { return null; } ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_recipes == null) { return ((Object)r).name; } int num = 0; int num2 = -1; for (int i = 0; i < instance.m_recipes.Count; i++) { Recipe val = instance.m_recipes[i]; if (!((Object)(object)val == (Object)null) && !(((Object)val).name != ((Object)r).name)) { if ((Object)(object)val == (Object)(object)r) { num2 = num; } num++; } } if (num <= 1 || num2 < 0) { return ((Object)r).name; } return $"{((Object)r).name}#{num2}"; } 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; } int num = name.LastIndexOf('#'); if (num > 0 && int.TryParse(name.Substring(num + 1), out var result)) { string text = name.Substring(0, num); Recipe val = null; int num2 = 0; foreach (Recipe recipe2 in ObjectDB.instance.m_recipes) { if (!((Object)(object)recipe2 == (Object)null) && !(((Object)recipe2).name != text)) { if ((Object)(object)val == (Object)null) { val = recipe2; } if (num2 == result) { DebugLogger.Verbose("Resolved colliding recipe key: " + name); return recipe2; } num2++; } } if ((Object)(object)val != (Object)null) { DebugLogger.Warning($"Recipe key '{name}' is out of range - only {num2} recipe(s) named '{text}'. Falling back to the first."); return val; } } 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 result2)) { DebugLogger.Warning("Invalid upgrade level in recipe key: " + name); return null; } Recipe recipeByName = GetRecipeByName(name2); if ((Object)(object)recipeByName != (Object)null) { if (!IsValidUpgradeTarget(recipeByName, result2, name)) { return null; } Recipe val2 = CreateFakeUpgradeRecipe(recipeByName, result2, name); if ((Object)(object)val2 != (Object)null) { return val2; } } } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name); ItemDrop val3 = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val3 != (Object)null) { Recipe recipe = ObjectDB.instance.GetRecipe(val3.m_itemData); if ((Object)(object)recipe != (Object)null) { DebugLogger.Verbose("Found standard recipe: " + name); return recipe; } } Recipe val4 = null; foreach (Recipe recipe3 in ObjectDB.instance.m_recipes) { if (((Object)recipe3).name == name) { val4 = recipe3; break; } } if ((Object)(object)val4 != (Object)null) { DebugLogger.Verbose("Found recipe in ObjectDB: " + name); return val4; } ZNetScene instance = ZNetScene.instance; GameObject val5 = ((instance != null) ? instance.GetPrefab(name) : null); if ((Object)(object)val5 != (Object)null) { Piece component = val5.GetComponent<Piece>(); if ((Object)(object)component != (Object)null && component.m_resources != null && component.m_resources.Length != 0) { Recipe val6 = ScriptableObject.CreateInstance<Recipe>(); ((Object)val6).hideFlags = (HideFlags)61; ((Object)val6).name = name; val6.m_item = val5.GetComponent<ItemDrop>(); val6.m_resources = (Requirement[])component.m_resources.Clone(); _fakeRecipeCache[name] = val6; DebugLogger.Verbose("Created fake recipe for piece: " + name); return val6; } } DebugLogger.Warning("Recipe not found anywhere: " + name); return null; } public static bool IsForgeUpgradeKey(string recipeKey) { if (string.IsNullOrEmpty(recipeKey)) { return false; } Match match = UpgradeStarRegex.Match(recipeKey); if (match.Success) { return match.Groups[2].Success; } return false; } public static string BuildUpgradeLevelSuffix(string recipeKey) { if (string.IsNullOrEmpty(recipeKey)) { return string.Empty; } Match match = UpgradeStarRegex.Match(recipeKey); if (!match.Success) { return string.Empty; } return " ★" + match.Groups[1].Value; } public static string BuildUpgradeRouteSuffix(string recipeKey) { if (!IsForgeUpgradeKey(recipeKey)) { return string.Empty; } string text = null; if (Localization.instance != null) { string text2 = Localization.instance.Localize("$item_upgrader_name"); if (!string.IsNullOrEmpty(text2) && !text2.Contains("MISSING KEY")) { text = text2; } } if (string.IsNullOrEmpty(text)) { RecipePinnerPlugin instance = RecipePinnerPlugin.Instance; text = (((Object)(object)instance == (Object)null) ? null : instance.LocalizationMgr)?.GetText("forge_route"); } if (string.IsNullOrEmpty(text)) { return string.Empty; } return " (" + text + ")"; } private Recipe CreateFakeUpgradeRecipe(Recipe baseRecipe, int targetLevel, string customName) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: 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; List<Requirement> list = new List<Requirement>(); Requirement[] resources = baseRecipe.m_resources; foreach (Requirement val2 in resources) { if (val2 != null) { int amount = val2.GetAmount(targetLevel); if (amount > 0) { Requirement item = new Requirement { m_resItem = val2.m_resItem, m_amount = amount, m_amountPerLevel = 0, m_recover = val2.m_recover, m_upgraderResource = val2.m_upgraderResource }; 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; bool flag = IsForgeUpgradeKey(customName); if (maxQuality < 2 || (targetLevel > maxQuality && !flag)) { 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; Player localPlayer = Player.m_localPlayer; CraftingStation val4 = (((Object)(object)localPlayer == (Object)null) ? null : localPlayer.GetCurrentCraftingStation()); bool flag3 = (Object)(object)val4 != (Object)null && val4.m_upgrader; if (flag3) { bool flag4 = false; if (recipeFromObject.m_resources != null) { Requirement[] resources = recipeFromObject.m_resources; foreach (Requirement val5 in resources) { if (val5 != null && val5.m_upgraderResource) { flag4 = true; break; } } } if (!flag4) { string text5 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("no_upgrade_cost"); Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, text5, 0, (Sprite)null, false); } return; } } if (quality >= maxQuality && !flag3) { string text6 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("max_level"); Player localPlayer3 = Player.m_localPlayer; if (localPlayer3 != null) { ((Character)localPlayer3).Message((MessageType)2, text6, 0, (Sprite)null, false); } return; } if ((Object)(object)recipeFromObject.m_item == (Object)null) { DebugLogger.Warning("Cannot pin upgrade: recipe '" + ((Object)recipeFromObject).name + "' has no item."); return; } string name = ((Object)recipeFromObject.m_item).name; string text7 = (flag3 ? $"{name} ★{num3}F" : $"{name} ★{num3}"); if (IsUnpinHotkeyHeld() && !PinnedRecipes.ContainsKey(text7)) { return; } DebugLogger.Verbose("Attempting to pin hovered recipe..."); DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})"); DebugLogger.Log("Attempting to pin upgrade: " + text7 + " (Base: " + name + ")"); if ((Object)(object)GetRecipeByName(text7) != (Object)null) { TogglePin(text7); return; } string text8 = RecipePinnerPlugin.Instance.LocalizationMgr.GetText("no_upgrade_cost"); Player localPlayer4 = Player.m_localPlayer; if (localPlayer4 != null) { ((Character)localPlayer4).Message((MessageType)2, text8, 0, (Sprite)null, false); } } else { DebugLogger.Warning("Matched name but could not get ItemData for upgrade."); } } else { string text9 = BuildRecipeKey(recipeFromObject); if (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(text9)) { DebugLogger.Verbose("Attempting to pin hovered recipe..."); DebugLogger.Verbose($"Hovered: '{text3}' (UpgradeTab: {flag})"); DebugLogger.Log("Matched recipe: " + ((Object)recipeFromObject).name); TogglePin(text9); } } return; } } } public void TryPinHoveredPiece() { if ((Object)(object)Hud.instance == (Object)null) { DebugLogger.Verbose("TryPinHoveredPiece: Hud.instance is null"); return; } Piece val = ReflectionHelper.GetBuildMenuPieceUnderPointer(); if ((Object)(object)val == (Object)null) { val = ReflectionHelper.GetHoveredPiece(Hud.instance); } if ((Object)(object)val == (Object)null || val.m_resources == null || val.m_resources.Length == 0) { DebugLogger.Verbose("TryPinHoveredPiece: no build piece under the pointer"); } else if (!IsUnpinHotkeyHeld() || PinnedRecipes.ContainsKey(((Object)val).name)) { DebugLogger.Verbose("Attempting to pin hovered piece..."); DebugLogger.Log("Pinning piece: " + ((Object)val).name); TogglePin(((Object)val).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, false); } 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, false); } 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, false); } 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, false); } } else { Player localPlayer5 = Player.m_localPlayer; if (localPlayer5 != null) { ((Character)localPlayer5).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null, false); } } 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, false); } } 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, false); } } } 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, false); } } DebugLogger.Log($"Increased pin count: {recipeName} = {value}"); } } else { if (flag) { return; } if (GetEffectivePinCount() < RecipePinnerPlugin.MaximumPins.Value) { PinnedRecipes.Add(recipeName, 1); if (!PinnedRecipeOrder.Contains(reci