using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[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.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ValheimRecipePinner;
public class ContainerScanner
{
public static List<Container> AllContainers = new List<Container>();
internal static readonly object ContainerLock = new object();
public Dictionary<string, int> ContainerCache = new Dictionary<string, int>();
private static HashSet<int> _processedIDs = new HashSet<int>();
private Vector3 _lastScanPos;
private int _lastItemCount = 0;
private float _scanTimer = 0f;
private const float MovementThresholdSqr = 4f;
public void InitializeContainers()
{
DebugLogger.Log("Initializing container tracking...");
lock (ContainerLock)
{
if (AllContainers.Count != 0)
{
return;
}
Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
Container[] array2 = array;
foreach (Container val in array2)
{
if ((Object)(object)val != (Object)null && !AllContainers.Contains(val))
{
AllContainers.Add(val);
if ((Object)(object)((Component)val).GetComponent<ContainerTracker>() == (Object)null)
{
ContainerTracker containerTracker = ((Component)val).gameObject.AddComponent<ContainerTracker>();
containerTracker.MyContainer = val;
}
}
}
DebugLogger.Log($"Initialized tracking for {AllContainers.Count} existing containers");
}
}
public void UpdateScanning()
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Player.m_localPlayer == (Object)null)
{
return;
}
_scanTimer += Time.deltaTime;
float num = Vector3.SqrMagnitude(((Component)Player.m_localPlayer).transform.position - _lastScanPos);
bool flag = num > 4f;
int num2 = 0;
foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems())
{
num2 += allItem.m_stack;
}
bool flag2 = num2 != _lastItemCount;
bool flag3 = false;
if ((Object)(object)InventoryGui.instance != (Object)null)
{
flag3 = (Object)(object)ReflectionHelper.GetCurrentContainer(InventoryGui.instance) != (Object)null;
}
float num3 = (flag3 ? 0.5f : RecipePinnerPlugin.ChestScanInterval.Value);
if (flag || flag2 || _scanTimer >= num3)
{
_scanTimer = 0f;
_lastItemCount = num2;
DebugLogger.Verbose($"Scanning containers - Moved: {flag}, InvChanged: {flag2}, Interval: {_scanTimer >= num3}");
UpdateContainerCache();
}
}
private void UpdateContainerCache()
{
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0246: Unknown result type (might be due to invalid IL or missing references)
//IL_0247: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Unknown result type (might be due to invalid IL or missing references)
//IL_0116: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
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;
List<Container> list;
lock (ContainerLock)
{
list = new List<Container>(AllContainers);
}
_processedIDs.Clear();
int num2 = 0;
int num3 = 0;
int num4 = 0;
foreach (Container item in list)
{
if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).transform == (Object)null)
{
num3++;
continue;
}
int instanceID = ((Object)item).GetInstanceID();
if (_processedIDs.Contains(instanceID))
{
num3++;
continue;
}
_processedIDs.Add(instanceID);
float num5 = Vector3.SqrMagnitude(((Component)item).transform.position - position);
if (num5 > 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))
{
return;
}
lock (ContainerLock)
{
if (!AllContainers.Contains(__instance))
{
AllContainers.Add(__instance);
ContainerTracker containerTracker = ((Component)__instance).gameObject.GetComponent<ContainerTracker>();
if ((Object)(object)containerTracker == (Object)null)
{
containerTracker = ((Component)__instance).gameObject.AddComponent<ContainerTracker>();
}
containerTracker.MyContainer = __instance;
DebugLogger.Verbose($"New container tracked: {((Object)__instance).name} (Total: {AllContainers.Count})");
}
}
}
}
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);
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 recipeMgr = RecipePinnerPlugin.Instance.RecipeMgr;
List<string> list = new List<string>();
foreach (KeyValuePair<string, int> pinnedRecipe in recipeMgr.PinnedRecipes)
{
list.Add($"{pinnedRecipe.Key}:{pinnedRecipe.Value}");
}
File.WriteAllLines(savePath, list);
DebugLogger.Log($"Saved {list.Count} pinned recipes 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 recipeMgr = RecipePinnerPlugin.Instance.RecipeMgr;
if (!File.Exists(savePath))
{
DebugLogger.Log("No save file found at: " + savePath);
return;
}
try
{
recipeMgr.PinnedRecipes.Clear();
string[] array = File.ReadAllLines(savePath);
int num = 0;
int num2 = 0;
string[] array2 = array;
foreach (string text in array2)
{
if (string.IsNullOrWhiteSpace(text))
{
continue;
}
if (text.Contains(":"))
{
string[] array3 = text.Split(new char[1] { ':' });
if (array3.Length == 2)
{
string key = array3[0].Trim();
string s = array3[1].Trim();
if (int.TryParse(s, out var result))
{
recipeMgr.PinnedRecipes[key] = result;
num++;
}
else
{
DebugLogger.Warning("Invalid count value in save file: " + text);
num2++;
}
}
}
else if (!recipeMgr.PinnedRecipes.ContainsKey(text))
{
recipeMgr.PinnedRecipes[text] = 1;
num++;
}
}
if (recipeMgr.PinnedRecipes.Count > RecipePinnerPlugin.MaximumPins.Value)
{
int count = recipeMgr.PinnedRecipes.Count;
recipeMgr.PinnedRecipes = recipeMgr.PinnedRecipes.Take(RecipePinnerPlugin.MaximumPins.Value).ToDictionary((KeyValuePair<string, int> k) => k.Key, (KeyValuePair<string, int> v) => v.Value);
DebugLogger.Warning($"Exceeded max pins limit - trimmed from {count} to {recipeMgr.PinnedRecipes.Count}");
}
DebugLogger.Log($"Loaded {num} recipes from: {savePath} (Errors: {num2})");
}
catch (Exception ex)
{
DebugLogger.Error("Failed to load pins", ex);
}
}
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()
{
return (Object)(object)RecipePinnerPlugin.Instance != (Object)null && RecipePinnerPlugin.EnableDebugLogging != null && RecipePinnerPlugin.EnableDebugLogging.Value;
}
}
public class LocalizationManager
{
private RecipePinnerPlugin _plugin;
private 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" }
};
public LocalizationManager(RecipePinnerPlugin plugin)
{
_plugin = plugin;
DebugLogger.Log("LocalizationManager initialized");
}
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 directoryName = Path.GetDirectoryName(((BaseUnityPlugin)_plugin).Info.Location);
string text2 = Path.Combine(directoryName, "RecipePinner_languages", text + ".json");
if (!File.Exists(text2))
{
DebugLogger.Log("Language file not found: " + text2 + " - Using default English");
return;
}
try
{
string text3 = File.ReadAllText(text2);
int num = 0;
string[] array = text3.Split(new char[1] { '\n' });
foreach (string text4 in array)
{
if (!text4.Contains(":"))
{
continue;
}
string[] array2 = text4.Split(new char[1] { ':' }, 2);
if (array2.Length == 2)
{
string text5 = array2[0].Trim().Trim(',', '"', ' ', '\t', '\r');
string value = array2[1].Trim().Trim(',', '"', ' ', '\t', '\r');
if (!string.IsNullOrEmpty(text5) && !string.IsNullOrEmpty(value))
{
_localizedText[text5] = value;
num++;
}
}
}
DebugLogger.Log($"Loaded {num} translations from: {text}.json");
}
catch (Exception ex)
{
DebugLogger.Error("Failed to load language file: " + text2, 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 string CachedShadowHeader;
public Sprite Icon;
public int StackCount;
public List<PinnedResData> Resources = new List<PinnedResData>();
public bool IsDirty = true;
}
public class PinnedResData
{
public string ItemName;
public string CachedName;
public string CachedShadowName;
public Sprite Icon;
public int RequiredAmount;
public int LastKnownAmount;
public int LastKnownInvAmount;
public string CachedAmountString;
}
public class RecipeManager
{
public Dictionary<string, int> PinnedRecipes = new Dictionary<string, int>();
public List<PinnedRecipeData> CachedPins = new List<PinnedRecipeData>();
private Dictionary<string, Recipe> _fakeRecipeCache = new Dictionary<string, Recipe>();
private static readonly Regex CleanNameRegex = new Regex("<.*?>", RegexOptions.Compiled);
private static readonly Regex ShadowCleanRegex = new Regex("<color=.*?>|</color>", RegexOptions.Compiled);
private static readonly Regex AmountSuffixRegex = new Regex("\\s*[xX]?\\s*\\d+$", RegexOptions.Compiled);
public void Cleanup()
{
DebugLogger.Log("RecipeManager cleanup started");
if (_fakeRecipeCache == null)
{
return;
}
int count = _fakeRecipeCache.Count;
foreach (Recipe value in _fakeRecipeCache.Values)
{
Object val = (Object)(object)value;
if (val != null && val != (Object)null)
{
Object.Destroy(val);
}
}
_fakeRecipeCache.Clear();
DebugLogger.Log($"Cleaned up {count} fake recipes");
}
public void RefreshRecipeCache()
{
DebugLogger.Verbose("Refreshing recipe cache...");
CachedPins.Clear();
if ((Object)(object)ObjectDB.instance == (Object)null)
{
DebugLogger.Warning("Cannot refresh recipe cache - ObjectDB.instance is null");
return;
}
int num = 0;
int num2 = 0;
foreach (KeyValuePair<string, int> pinnedRecipe in PinnedRecipes)
{
string key = pinnedRecipe.Key;
int value = pinnedRecipe.Value;
Recipe recipeByName = GetRecipeByName(key);
if ((Object)(object)recipeByName != (Object)null)
{
PinnedRecipeData pinnedRecipeData = new PinnedRecipeData
{
IsDirty = true,
RecipeRef = recipeByName,
StackCount = value
};
if ((Object)(object)recipeByName.m_item != (Object)null)
{
pinnedRecipeData.Icon = recipeByName.m_item.m_itemData.GetIcon();
pinnedRecipeData.RawName = recipeByName.m_item.m_itemData.m_shared.m_name;
}
else
{
ZNetScene instance = ZNetScene.instance;
GameObject val = ((instance != null) ? instance.GetPrefab(((Object)recipeByName).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)recipeByName).name;
}
string text = pinnedRecipeData.RawName;
if (Localization.instance != null)
{
text = Localization.instance.Localize(pinnedRecipeData.RawName);
}
text = text.Replace("\r", "").Replace("\n", "");
if (recipeByName.m_amount > 1)
{
text += $" (x{recipeByName.m_amount})";
}
if (value > 1)
{
text = $"{value}x {text}";
}
pinnedRecipeData.CachedHeader = text;
pinnedRecipeData.CachedShadowHeader = text;
Requirement[] resources = recipeByName.m_resources;
foreach (Requirement val2 in resources)
{
if (val2 != null && !((Object)(object)val2.m_resItem == (Object)null) && val2.m_amount > 0)
{
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 * value,
LastKnownAmount = -1,
LastKnownInvAmount = -1
};
string text2 = pinnedResData.ItemName;
if (Localization.instance != null)
{
text2 = Localization.instance.Localize(pinnedResData.ItemName);
}
text2 = (pinnedResData.CachedName = text2.Replace("\r", "").Replace("\n", ""));
pinnedResData.CachedShadowName = ShadowCleanRegex.Replace(text2, string.Empty);
pinnedRecipeData.Resources.Add(pinnedResData);
}
}
CachedPins.Add(pinnedRecipeData);
num++;
}
else
{
DebugLogger.Warning("Recipe not found: " + key);
num2++;
}
}
DebugLogger.Log($"Recipe cache refreshed: {num} successful, {num2} failed");
if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)RecipePinnerPlugin.Instance != (Object)null)
{
RecipePinnerPlugin.Instance.UIMgr.UpdateUI(isVisible: true);
}
}
public Recipe GetRecipeByName(string name)
{
if ((Object)(object)ObjectDB.instance == (Object)null)
{
return null;
}
GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name);
ItemDrop val = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null);
if ((Object)(object)val != (Object)null)
{
Recipe recipe = ObjectDB.instance.GetRecipe(val.m_itemData);
if ((Object)(object)recipe != (Object)null)
{
DebugLogger.Verbose("Found standard recipe: " + name);
return recipe;
}
}
Recipe val2 = ((IEnumerable<Recipe>)ObjectDB.instance.m_recipes).FirstOrDefault((Func<Recipe, bool>)((Recipe r) => ((Object)r).name == name));
if ((Object)(object)val2 != (Object)null)
{
DebugLogger.Verbose("Found recipe in ObjectDB: " + name);
return val2;
}
if (_fakeRecipeCache.TryGetValue(name, out var value))
{
DebugLogger.Verbose("Found cached fake recipe: " + name);
return value;
}
ZNetScene instance = ZNetScene.instance;
GameObject val3 = ((instance != null) ? instance.GetPrefab(name) : null);
if ((Object)(object)val3 != (Object)null)
{
Piece component = val3.GetComponent<Piece>();
if ((Object)(object)component != (Object)null && component.m_resources != null && component.m_resources.Length != 0)
{
Recipe val4 = ScriptableObject.CreateInstance<Recipe>();
((Object)val4).hideFlags = (HideFlags)61;
((Object)val4).name = name;
val4.m_item = val3.GetComponent<ItemDrop>();
List<Requirement> list = component.m_resources.ToList();
val4.m_resources = (Requirement[])(object)new Requirement[list.Count];
for (int i = 0; i < list.Count; i++)
{
val4.m_resources[i] = list[i];
}
_fakeRecipeCache[name] = val4;
DebugLogger.Verbose("Created fake recipe for piece: " + name);
return val4;
}
}
DebugLogger.Warning("Recipe not found anywhere: " + name);
return null;
}
public void ValidateAndCleanPins()
{
if ((Object)(object)ObjectDB.instance == (Object)null)
{
DebugLogger.Warning("Cannot validate pins - ObjectDB.instance is null");
return;
}
DebugLogger.Log("Validating pinned recipes...");
List<string> list = new List<string>();
foreach (string key in PinnedRecipes.Keys)
{
Recipe recipeByName = GetRecipeByName(key);
if ((Object)(object)recipeByName == (Object)null)
{
list.Add(key);
}
}
if (list.Count > 0)
{
foreach (string item in list)
{
PinnedRecipes.Remove(item);
DebugLogger.Warning("Removed invalid recipe: " + item);
}
if ((Object)(object)RecipePinnerPlugin.Instance != (Object)null)
{
RecipePinnerPlugin.Instance.DataMgr.SavePins();
}
DebugLogger.Log($"Validation complete: {list.Count} invalid recipes removed");
}
else
{
DebugLogger.Log("All pinned recipes are valid");
}
}
public void TryPinHoveredRecipe(InventoryGui gui)
{
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Expected O, but got Unknown
DebugLogger.Verbose("Attempting to pin hovered recipe...");
Transform recipeListRoot = ReflectionHelper.GetRecipeListRoot(gui);
object availableRecipes = ReflectionHelper.GetAvailableRecipes(gui);
IList list = availableRecipes as IList;
if ((Object)(object)recipeListRoot == (Object)null || list == null)
{
DebugLogger.Verbose("Cannot pin - listRoot or availableRecipes is null");
return;
}
ScrollRect componentInParent = ((Component)recipeListRoot).GetComponentInParent<ScrollRect>();
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))
{
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();
DebugLogger.Verbose("Hovered text: '" + text3 + "'");
foreach (object item2 in list)
{
Recipe recipeFromObject = GetRecipeFromObject(item2);
if (!((Object)(object)recipeFromObject != (Object)null))
{
continue;
}
string rawRecipeName = GetRawRecipeName(recipeFromObject);
if (!string.IsNullOrEmpty(rawRecipeName))
{
string text4 = rawRecipeName;
if (Localization.instance != null)
{
text4 = Localization.instance.Localize(rawRecipeName);
}
text4 = text4.Replace("\r", "").Replace("\n", "");
if (text4.Equals(text3, StringComparison.OrdinalIgnoreCase) || text4.Equals(text2, StringComparison.OrdinalIgnoreCase))
{
DebugLogger.Log("Matched recipe: " + ((Object)recipeFromObject).name);
TogglePin(((Object)recipeFromObject).name);
return;
}
}
}
}
}
public void TryPinHoveredPiece()
{
DebugLogger.Verbose("Attempting to pin hovered piece...");
if (!((Object)(object)Hud.instance == (Object)null))
{
Piece val = ReflectionHelper.GetHoveredPiece(Hud.instance);
if ((Object)(object)val == (Object)null && (Object)(object)Player.m_localPlayer != (Object)null)
{
val = Player.m_localPlayer.GetSelectedPiece();
}
if ((Object)(object)val != (Object)null && val.m_resources != null && val.m_resources.Length != 0)
{
DebugLogger.Log("Pinning piece: " + ((Object)val).name);
TogglePin(((Object)val).name);
}
else
{
DebugLogger.Verbose("No valid piece to pin");
}
}
}
private void TogglePin(string recipeName)
{
bool flag = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303);
LocalizationManager localizationMgr = RecipePinnerPlugin.Instance.LocalizationMgr;
if (PinnedRecipes.ContainsKey(recipeName))
{
if (flag)
{
PinnedRecipes[recipeName]--;
if (PinnedRecipes[recipeName] <= 0)
{
PinnedRecipes.Remove(recipeName);
Player localPlayer = Player.m_localPlayer;
if (localPlayer != null)
{
((Character)localPlayer).Message((MessageType)2, localizationMgr.GetText("unpinned"), 0, (Sprite)null);
}
DebugLogger.Log("Unpinned: " + recipeName);
}
else
{
string text = string.Format(localizationMgr.GetText("decreased"), PinnedRecipes[recipeName]);
Player localPlayer2 = Player.m_localPlayer;
if (localPlayer2 != null)
{
((Character)localPlayer2).Message((MessageType)2, text, 0, (Sprite)null);
}
DebugLogger.Log($"Decreased pin count: {recipeName} = {PinnedRecipes[recipeName]}");
}
}
else
{
PinnedRecipes[recipeName]++;
string text2 = string.Format(localizationMgr.GetText("added_more"), PinnedRecipes[recipeName]);
Player localPlayer3 = Player.m_localPlayer;
if (localPlayer3 != null)
{
((Character)localPlayer3).Message((MessageType)2, text2, 0, (Sprite)null);
}
DebugLogger.Log($"Increased pin count: {recipeName} = {PinnedRecipes[recipeName]}");
}
}
else
{
if (flag)
{
return;
}
if (PinnedRecipes.Count < RecipePinnerPlugin.MaximumPins.Value)
{
PinnedRecipes.Add(recipeName, 1);
Player localPlayer4 = Player.m_localPlayer;
if (localPlayer4 != null)
{
((Character)localPlayer4).Message((MessageType)2, localizationMgr.GetText("pinned"), 0, (Sprite)null);
}
DebugLogger.Log("Pinned new recipe: " + recipeName);
}
else
{
Player localPlayer5 = Player.m_localPlayer;
if (localPlayer5 != null)
{
((Character)localPlayer5).Message((MessageType)2, localizationMgr.GetText("list_full"), 0, (Sprite)null);
}
DebugLogger.Warning($"Cannot pin {recipeName} - max pins reached ({RecipePinnerPlugin.MaximumPins.Value})");
}
}
RefreshRecipeCache();
}
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);
Component[] array = componentsInChildren;
foreach (Component val in array)
{
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 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 (type.Name.Contains("KeyValuePair"))
{
PropertyInfo property = type.GetProperty("Key");
if (property != null)
{
object? value = property.GetValue(data, null);
Recipe val2 = (Recipe)((value is Recipe) ? value : null);
if (val2 != null)
{
return val2;
}
}
}
FieldInfo field = type.GetField("m_recipe", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
object? value2 = field.GetValue(data);
return (Recipe)((value2 is Recipe) ? value2 : null);
}
PropertyInfo property2 = type.GetProperty("Recipe");
if (property2 != null)
{
object? value3 = property2.GetValue(data, null);
return (Recipe)((value3 is Recipe) ? value3 : null);
}
return null;
}
private bool IsVisibleInScroll(RectTransform item, ScrollRect scrollRect)
{
//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
//IL_00db: Unknown result type (might be due to invalid IL or missing references)
//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
//IL_00e3: 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);
}
}
[BepInPlugin("com.Kadrio.RecipePinner", "Recipe Pinner", "1.0.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 static ConfigEntry<bool> EnableMod;
public static ConfigEntry<string> LanguageOverride;
public static ConfigEntry<PinLayoutMode> LayoutModeConfig;
public static ConfigEntry<int> MaximumPins;
public static ConfigEntry<bool> AutoUnpinAfterCrafting;
public static ConfigEntry<KeyCode> HotkeyPin;
public static ConfigEntry<KeyCode> HotkeyClearAll;
public static ConfigEntry<KeyCode> HotkeyToggleVisibility;
public static ConfigEntry<bool> EnableChestScanning;
public static ConfigEntry<float> ChestScanRange;
public static ConfigEntry<float> ChestScanInterval;
public static ConfigEntry<float> UIScale;
public static ConfigEntry<int> FontSizeRecipeName;
public static ConfigEntry<int> FontSizeMaterials;
public static ConfigEntry<float> BackgroundOpacity;
public static ConfigEntry<Color> ColorHeader;
public static ConfigEntry<Color> ColorEnoughInInventory;
public static ConfigEntry<Color> ColorEnoughWithChests;
public static ConfigEntry<Color> ColorMissing;
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 LocalizationManager LocalizationMgr;
public RecipeManager RecipeMgr;
public ContainerScanner ContainerMgr;
public UIManager UIMgr;
public DataPersistence DataMgr;
internal bool _mluiMapListEnabled = false;
internal bool _mluiNoMapListEnabled = false;
internal bool _mluiInstalled = false;
private bool _startupInitialized = false;
private string _lastLanguage = "";
private string _currentSessionPlayer = null;
private static bool _isUiVisible = true;
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_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Expected O, but got Unknown
Instance = this;
BindConfigs();
DebugLogger.Log("RecipePinner plugin initializing...");
LocalizationMgr = new LocalizationManager(this);
RecipeMgr = new RecipeManager();
ContainerMgr = new ContainerScanner();
UIMgr = new UIManager();
DataMgr = new DataPersistence();
DebugLogger.Log("All managers initialized successfully");
Harmony val = new Harmony("com.Kadrio.RecipePinner");
val.PatchAll(typeof(RecipePinnerPlugin));
val.PatchAll(typeof(ContainerScanner));
DebugLogger.Log("Harmony patches applied successfully");
}
private void BindConfigs()
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Expected O, but got Unknown
//IL_008f: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Expected O, but got Unknown
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Expected O, but got Unknown
//IL_0146: Unknown result type (might be due to invalid IL or missing references)
//IL_0150: Expected O, but got Unknown
//IL_019e: Unknown result type (might be due to invalid IL or missing references)
//IL_01a8: Expected O, but got Unknown
//IL_0248: Unknown result type (might be due to invalid IL or missing references)
//IL_0252: Expected O, but got Unknown
//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
//IL_02bc: Expected O, but got Unknown
//IL_0305: Unknown result type (might be due to invalid IL or missing references)
//IL_030f: Expected O, but got Unknown
//IL_0342: Unknown result type (might be due to invalid IL or missing references)
//IL_034c: Expected O, but got Unknown
//IL_0396: Unknown result type (might be due to invalid IL or missing references)
//IL_03a0: Expected O, but got Unknown
//IL_0439: Unknown result type (might be due to invalid IL or missing references)
//IL_0488: Unknown result type (might be due to invalid IL or missing references)
//IL_04d7: Unknown result type (might be due to invalid IL or missing references)
//IL_0526: Unknown result type (might be due to invalid IL or missing references)
//IL_05b3: Unknown result type (might be due to invalid IL or missing references)
//IL_0629: Unknown result type (might be due to invalid IL or missing references)
//IL_069f: Unknown result type (might be due to invalid IL or missing references)
EnableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "EnableMod", true, new ConfigDescription("Enable or disable the mod completely.", (AcceptableValueBase)null, new object[1]
{
new ConfigurationManagerAttributes
{
Order = 99
}
}));
EnableMod.SettingChanged += delegate
{
if (!EnableMod.Value)
{
UIMgr?.DestroyUI();
}
};
LanguageOverride = ((BaseUnityPlugin)this).Config.Bind<string>("1 - General", "LanguageOverride", "Auto", new ConfigDescription("Force a specific language (e.g., 'German', 'Turkish').", (AcceptableValueBase)null, new object[1]
{
new ConfigurationManagerAttributes
{
Order = 98
}
}));
LanguageOverride.SettingChanged += delegate
{
LocalizationMgr?.LoadTranslations();
RecipeMgr?.RefreshRecipeCache();
};
LayoutModeConfig = ((BaseUnityPlugin)this).Config.Bind<PinLayoutMode>("1 - General", "LayoutMode", PinLayoutMode.AutoDetect, new ConfigDescription("Choose layout position.", (AcceptableValueBase)null, new object[1]
{
new ConfigurationManagerAttributes
{
Order = 97
}
}));
LayoutModeConfig.SettingChanged += delegate
{
UIMgr?.DestroyUI();
};
MaximumPins = ((BaseUnityPlugin)this).Config.Bind<int>("1 - General", "MaximumPins", 5, new ConfigDescription("Max pins allowed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), new object[1]
{
new ConfigurationManagerAttributes
{
Order = 96
}
}));
MaximumPins.SettingChanged += delegate
{
UIMgr?.DestroyUI();
};
AutoUnpinAfterCrafting = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "AutoUnpinAfterCrafting", true, new ConfigDescription("Unpin after crafting.", (AcceptableValueBase)null, new object[1]
{
new ConfigurationManagerAttributes
{
Order = 95
}
}));
HotkeyPin = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("2 - Controls", "HotkeyPin", (KeyCode)325, "Key to pin recipe.");
HotkeyClearAll = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("2 - Controls", "HotkeyClearAll", (KeyCode)112, "Key to clear all pins.");
HotkeyToggleVisibility = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("2 - Controls", "HotkeyToggleVisibility", (KeyCode)288, "Key to toggle overlay.");
EnableChestScanning = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Chest Scanner", "EnableChestScanning", false, new ConfigDescription("Count materials in nearby chests.", (AcceptableValueBase)null, new object[1]
{
new ConfigurationManagerAttributes
{
Order = 99
}
}));
EnableChestScanning.SettingChanged += delegate
{
RecipeMgr?.RefreshRecipeCache();
};
ChestScanRange = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Chest Scanner", "ChestScanRange", 20f, new ConfigDescription("Scan radius.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f), new object[1]
{
new ConfigurationManagerAttributes
{
Order = 98
}
}));
ChestScanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Chest Scanner", "ChestScanInterval", 3f, new ConfigDescription("Scan frequency.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), new object[1]
{
new ConfigurationManagerAttributes
{
Order = 97
}
}));
UIScale = ((BaseUnityPlugin)this).Config.Bind<float>("4 - Visual Settings", "UIScale", 0.75f, new ConfigDescription("Global UI scale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 3f), Array.Empty<object>()));
UIScale.SettingChanged += delegate
{
UIMgr?.DestroyUI();
};
BackgroundOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("4 - Visual Settings", "BackgroundOpacity", 0.45f, new ConfigDescription("Background opacity.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
FontSizeRecipeName = ((BaseUnityPlugin)this).Config.Bind<int>("4 - Visual Settings", "FontSizeRecipeName", 15, "Recipe name font size.");
FontSizeRecipeName.SettingChanged += delegate
{
RecipeMgr?.RefreshRecipeCache();
};
FontSizeMaterials = ((BaseUnityPlugin)this).Config.Bind<int>("4 - Visual Settings", "FontSizeMaterials", 15, "Material font size.");
FontSizeMaterials.SettingChanged += delegate
{
RecipeMgr?.RefreshRecipeCache();
};
ColorHeader = ((BaseUnityPlugin)this).Config.Bind<Color>("4 - Visual Settings", "ColorHeader", new Color(1f, 0.77f, 0.31f, 1f), "Recipe title color.");
ColorHeader.SettingChanged += delegate
{
RecipeMgr?.RefreshRecipeCache();
};
ColorEnoughInInventory = ((BaseUnityPlugin)this).Config.Bind<Color>("4 - Visual Settings", "ColorEnoughInInventory", new Color(0f, 1f, 0f, 1f), "Color: Enough in inventory.");
ColorEnoughInInventory.SettingChanged += delegate
{
RecipeMgr?.RefreshRecipeCache();
};
ColorEnoughWithChests = ((BaseUnityPlugin)this).Config.Bind<Color>("4 - Visual Settings", "ColorEnoughWithChests", new Color(1f, 1f, 0f, 1f), "Color: Enough with chests.");
ColorEnoughWithChests.SettingChanged += delegate
{
RecipeMgr?.RefreshRecipeCache();
};
ColorMissing = ((BaseUnityPlugin)this).Config.Bind<Color>("4 - Visual Settings", "ColorMissing", new Color(1f, 0.33f, 0.33f, 1f), "Color: Missing materials.");
ColorMissing.SettingChanged += delegate
{
RecipeMgr?.RefreshRecipeCache();
};
VerticalListWidth = ((BaseUnityPlugin)this).Config.Bind<float>("5 - Layout (Vertical Mode)", "ListWidth", 265f, "List width.");
VerticalPinSpacing = ((BaseUnityPlugin)this).Config.Bind<float>("5 - Layout (Vertical Mode)", "PinSpacing", 10f, "Spacing between pins.");
VerticalPosition = ((BaseUnityPlugin)this).Config.Bind<Vector2>("5 - Layout (Vertical Mode)", "Position", new Vector2(-40f, -250f), "Position (X, Y).");
HorizontalColumnWidth = ((BaseUnityPlugin)this).Config.Bind<float>("6 - Layout (Horizontal - Map Side)", "ColumnWidth", 250f, "Column width.");
HorizontalPinSpacing = ((BaseUnityPlugin)this).Config.Bind<float>("6 - Layout (Horizontal - Map Side)", "PinSpacing", 10f, "Spacing between pins.");
HorizontalPosition = ((BaseUnityPlugin)this).Config.Bind<Vector2>("6 - Layout (Horizontal - Map Side)", "Position", new Vector2(-250f, -40f), "Position (X, Y).");
BottomRightColumnWidth = ((BaseUnityPlugin)this).Config.Bind<float>("7 - Layout (Horizontal - Bottom Right)", "ColumnWidth", 250f, "Column width.");
BottomRightPinSpacing = ((BaseUnityPlugin)this).Config.Bind<float>("7 - Layout (Horizontal - Bottom Right)", "PinSpacing", 10f, "Spacing between pins.");
BottomRightPosition = ((BaseUnityPlugin)this).Config.Bind<Vector2>("7 - Layout (Horizontal - Bottom Right)", "Position", new Vector2(-40f, 40f), "Position (X, Y).");
EnableDebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("8 - Debug", "EnableDebugLogging", false, "Enable debug logs.");
DebugLogger.Log("Configuration loaded successfully");
}
private void Start()
{
DebugLogger.Log("Start() called - Loading translations and initializing containers");
LocalizationMgr.LoadTranslations();
ReadMyLittleUIConfig();
ContainerMgr.InitializeContainers();
DebugLogger.Log("Start() completed successfully");
}
private void OnDestroy()
{
DebugLogger.Log("Plugin destroyed - Saving data and cleaning up");
DataMgr.SavePins();
RecipeMgr.Cleanup();
}
private void Update()
{
//IL_010b: Unknown result type (might be due to invalid IL or missing references)
//IL_016f: 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)
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-time initialization triggered");
_lastLanguage = Localization.instance.GetSelectedLanguage();
DataMgr.LoadPins();
RecipeMgr.ValidateAndCleanPins();
RecipeMgr.RefreshRecipeCache();
_startupInitialized = true;
DebugLogger.Log($"Initialization complete - {RecipeMgr.PinnedRecipes.Count} recipes loaded");
}
if (EnableChestScanning.Value && (Object)(object)Player.m_localPlayer != (Object)null && RecipeMgr.CachedPins.Count > 0)
{
ContainerMgr.UpdateScanning();
}
if (Input.GetKeyDown(HotkeyToggleVisibility.Value) && !InputHelper.IsInputBlocked())
{
_isUiVisible = !_isUiVisible;
DebugLogger.Log($"UI visibility toggled: {_isUiVisible}");
}
if ((Object)(object)Player.m_localPlayer != (Object)null)
{
UpdatePlayerSession();
}
if (Input.GetKeyDown(HotkeyPin.Value))
{
if ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible())
{
RecipeMgr.TryPinHoveredRecipe(InventoryGui.instance);
}
else if ((Object)(object)Hud.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null && ((Character)Player.m_localPlayer).InPlaceMode())
{
RecipeMgr.TryPinHoveredPiece();
}
}
if (Input.GetKeyDown(HotkeyClearAll.Value))
{
if (InputHelper.IsInputBlocked())
{
return;
}
if (RecipeMgr.PinnedRecipes.Count > 0)
{
int count = RecipeMgr.PinnedRecipes.Count;
RecipeMgr.PinnedRecipes.Clear();
RecipeMgr.RefreshRecipeCache();
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");
}
}
if (Localization.instance != null)
{
string selectedLanguage = Localization.instance.GetSelectedLanguage();
if (_lastLanguage != selectedLanguage)
{
DebugLogger.Log("Language changed from " + _lastLanguage + " to " + selectedLanguage);
_lastLanguage = selectedLanguage;
LocalizationMgr.LoadTranslations();
RecipeMgr.RefreshRecipeCache();
}
}
}
private void UpdatePlayerSession()
{
if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Character)Player.m_localPlayer).IsDead())
{
string playerName = Player.m_localPlayer.GetPlayerName();
if (_currentSessionPlayer != playerName)
{
DebugLogger.Log("Player session changed from '" + _currentSessionPlayer + "' to '" + playerName + "'");
RecipeMgr.PinnedRecipes.Clear();
RecipeMgr.CachedPins.Clear();
UIMgr.DestroyUI();
_currentSessionPlayer = playerName;
DataMgr.LoadPins();
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;
foreach (string text2 in array2)
{
string text3 = text2.Trim();
if (text3.StartsWith("[") && text3.EndsWith("]"))
{
text = text3;
}
else if (text3.StartsWith("Enable"))
{
bool flag = text3.ToLower().Contains("true");
if (text == "[Status effects - Map - List]")
{
_mluiMapListEnabled = flag;
}
else if (text == "[Status effects - Nomap - List]")
{
_mluiNoMapListEnabled = flag;
}
}
}
DebugLogger.Log($"MyLittleUI Config: MapList={_mluiMapListEnabled}, NoMapList={_mluiNoMapListEnabled}");
}
catch (Exception ex)
{
Debug.LogWarning((object)("[RecipePinner] Error reading MyLittleUI config: " + ex.Message));
}
}
[HarmonyPatch(typeof(Game), "SavePlayerProfile")]
[HarmonyPostfix]
public static void AutoSavePinsHook()
{
if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Instance != (Object)null)
{
DebugLogger.Log("Auto-saving pins on profile save");
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 && Instance.RecipeMgr.PinnedRecipes.ContainsKey(((Object)craftRecipe).name))
{
Instance.RecipeMgr.PinnedRecipes[((Object)craftRecipe).name]--;
DebugLogger.Log($"Auto-unpin: {((Object)craftRecipe).name}, remaining count: {Instance.RecipeMgr.PinnedRecipes[((Object)craftRecipe).name]}");
if (Instance.RecipeMgr.PinnedRecipes[((Object)craftRecipe).name] <= 0)
{
Instance.RecipeMgr.PinnedRecipes.Remove(((Object)craftRecipe).name);
DebugLogger.Log("Recipe " + ((Object)craftRecipe).name + " fully unpinned");
}
Instance.RecipeMgr.RefreshRecipeCache();
}
}
}
public static class ReflectionHelper
{
private static Func<float> _getGuiScale;
private static Func<InventoryGui, Transform> _getRecipeListRoot;
private static Func<InventoryGui, object> _getAvailableRecipes;
private static Func<InventoryGui, Container> _getCurrentContainer;
private static Func<InventoryGui, Recipe> _getCraftRecipe;
private static Func<Hud, Piece> _getHoveredPiece;
public static Func<Container, long, bool> CheckContainerAccess;
public static float currentGuiScaleValue;
static ReflectionHelper()
{
currentGuiScaleValue = 1f;
InitializeReflection();
}
public static void InitializeReflection()
{
DebugLogger.Log("Initializing reflection helpers...");
int num = 0;
int num2 = 0;
try
{
FieldInfo fieldInfo = AccessTools.Field(typeof(GuiScaler), "m_largeGuiScale");
if (fieldInfo != null && fieldInfo.IsStatic)
{
_getGuiScale = Expression.Lambda<Func<float>>(Expression.Field(null, fieldInfo), Array.Empty<ParameterExpression>()).Compile();
num++;
DebugLogger.Verbose("✓ GuiScaler.m_largeGuiScale");
}
else
{
num2++;
DebugLogger.Warning("✗ GuiScaler.m_largeGuiScale not found");
}
FieldInfo fieldInfo2 = AccessTools.Field(typeof(InventoryGui), "m_recipeListRoot");
if (fieldInfo2 != null)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(InventoryGui), "arg");
_getRecipeListRoot = Expression.Lambda<Func<InventoryGui, Transform>>(Expression.Field(parameterExpression, fieldInfo2), new ParameterExpression[1] { parameterExpression }).Compile();
num++;
DebugLogger.Verbose("✓ InventoryGui.m_recipeListRoot");
}
else
{
num2++;
DebugLogger.Warning("✗ InventoryGui.m_recipeListRoot not found");
}
FieldInfo fieldInfo3 = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes");
if (fieldInfo3 != null)
{
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(InventoryGui), "arg");
_getAvailableRecipes = Expression.Lambda<Func<InventoryGui, object>>(Expression.Field(parameterExpression2, fieldInfo3), new ParameterExpression[1] { parameterExpression2 }).Compile();
num++;
DebugLogger.Verbose("✓ InventoryGui.m_availableRecipes");
}
else
{
num2++;
DebugLogger.Warning("✗ InventoryGui.m_availableRecipes not found");
}
FieldInfo fieldInfo4 = AccessTools.Field(typeof(InventoryGui), "m_currentContainer");
if (fieldInfo4 != null)
{
ParameterExpression parameterExpression3 = Expression.Parameter(typeof(InventoryGui), "arg");
_getCurrentContainer = Expression.Lambda<Func<InventoryGui, Container>>(Expression.Field(parameterExpression3, fieldInfo4), new ParameterExpression[1] { parameterExpression3 }).Compile();
num++;
DebugLogger.Verbose("✓ InventoryGui.m_currentContainer");
}
else
{
num2++;
DebugLogger.Warning("✗ InventoryGui.m_currentContainer not found");
}
FieldInfo fieldInfo5 = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe");
if (fieldInfo5 != null)
{
ParameterExpression parameterExpression4 = Expression.Parameter(typeof(InventoryGui), "arg");
_getCraftRecipe = Expression.Lambda<Func<InventoryGui, Recipe>>(Expression.Field(parameterExpression4, fieldInfo5), new ParameterExpression[1] { parameterExpression4 }).Compile();
num++;
DebugLogger.Verbose("✓ InventoryGui.m_craftRecipe");
}
else
{
num2++;
DebugLogger.Warning("✗ InventoryGui.m_craftRecipe not found");
}
FieldInfo fieldInfo6 = AccessTools.Field(typeof(Hud), "m_hoveredPiece");
if (fieldInfo6 != null)
{
ParameterExpression parameterExpression5 = Expression.Parameter(typeof(Hud), "arg");
_getHoveredPiece = Expression.Lambda<Func<Hud, Piece>>(Expression.Field(parameterExpression5, fieldInfo6), new ParameterExpression[1] { parameterExpression5 }).Compile();
num++;
DebugLogger.Verbose("✓ Hud.m_hoveredPiece");
}
else
{
num2++;
DebugLogger.Warning("✗ Hud.m_hoveredPiece not found");
}
MethodInfo methodInfo = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null);
if (methodInfo != null)
{
CheckContainerAccess = AccessTools.MethodDelegate<Func<Container, long, bool>>(methodInfo, (object)null, true);
num++;
DebugLogger.Verbose("✓ Container.CheckAccess");
}
else
{
num2++;
DebugLogger.Warning("✗ Container.CheckAccess not found");
}
DebugLogger.Log($"Reflection initialization complete: {num} successful, {num2} failed");
if (num2 > 0)
{
DebugLogger.Warning("Some reflection targets failed - mod may not work correctly!");
}
}
catch (Exception ex)
{
DebugLogger.Error("Critical error during reflection initialization", ex);
}
}
public static void UpdateGuiScale()
{
if (_getGuiScale != null)
{
currentGuiScaleValue = _getGuiScale();
}
else
{
currentGuiScaleValue = 1f;
}
}
public static Transform GetRecipeListRoot(InventoryGui gui)
{
if (_getRecipeListRoot == null)
{
DebugLogger.Warning("GetRecipeListRoot delegate is null");
return null;
}
return _getRecipeListRoot(gui);
}
public static object GetAvailableRecipes(InventoryGui gui)
{
if (_getAvailableRecipes == null)
{
DebugLogger.Warning("GetAvailableRecipes delegate is null");
return null;
}
return _getAvailableRecipes(gui);
}
public static Container GetCurrentContainer(InventoryGui gui)
{
if (_getCurrentContainer == null)
{
DebugLogger.Verbose("GetCurrentContainer delegate is null");
return null;
}
return _getCurrentContainer(gui);
}
public static Recipe GetCraftRecipe(InventoryGui gui)
{
if (_getCraftRecipe == null)
{
DebugLogger.Warning("GetCraftRecipe delegate is null");
return null;
}
return _getCraftRecipe(gui);
}
public static Piece GetHoveredPiece(Hud hud)
{
if (_getHoveredPiece == null)
{
DebugLogger.Verbose("GetHoveredPiece delegate is null");
return null;
}
return _getHoveredPiece(hud);
}
}
public static class InputHelper
{
public static bool IsInputBlocked()
{
if (Console.IsVisible())
{
return true;
}
if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
{
return true;
}
if (TextInput.IsVisible())
{
DebugLogger.Verbose("Input blocked: TextInput is visible");
return true;
}
return false;
}
public static bool IsMouseOverRect(RectTransform rect)
{
//IL_001d: 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)
if ((Object)(object)rect == (Object)null)
{
DebugLogger.Verbose("IsMouseOverRect: rect is null");
return false;
}
bool flag = RectTransformUtility.RectangleContainsScreenPoint(rect, Vector2.op_Implicit(Input.mousePosition));
if (flag)
{
DebugLogger.Verbose("Mouse over rect: " + ((Object)((Component)rect).gameObject).name);
}
return flag;
}
}
public static class UIBuilder
{
private static Color ValheimOrange = new Color(1f, 0.77f, 0.31f, 1f);
private static Color DividerColor = new Color(1f, 1f, 1f, 0.1f);
private static Sprite _cachedUiSprite;
private static bool _spriteSearchDone = false;
private static Sprite GetBackgroundSprite()
{
if ((Object)(object)_cachedUiSprite != (Object)null)
{
return _cachedUiSprite;
}
if (_spriteSearchDone)
{
return null;
}
Sprite[] source = Resources.FindObjectsOfTypeAll<Sprite>();
_cachedUiSprite = ((IEnumerable<Sprite>)source).FirstOrDefault((Func<Sprite, bool>)((Sprite x) => ((Object)x).name == "UISprite")) ?? ((IEnumerable<Sprite>)source).FirstOrDefault((Func<Sprite, bool>)((Sprite x) => ((Object)x).name == "Knob"));
_spriteSearchDone = true;
if ((Object)(object)_cachedUiSprite != (Object)null)
{
DebugLogger.Verbose("Found background sprite: " + ((Object)_cachedUiSprite).name);
}
else
{
DebugLogger.Warning("No suitable background sprite found");
}
return _cachedUiSprite;
}
public static PinSlotUI CreatePinSlot(Transform parent, Font font)
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Expected O, but got Unknown
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
//IL_0106: Unknown result type (might be due to invalid IL or missing references)
//IL_0110: Expected O, but got Unknown
//IL_0143: Unknown result type (might be due to invalid IL or missing references)
//IL_014a: Expected O, but got Unknown
//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
//IL_01f0: Expected O, but got Unknown
//IL_0293: Unknown result type (might be due to invalid IL or missing references)
//IL_029a: Expected O, but got Unknown
//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
//IL_0348: Unknown result type (might be due to invalid IL or missing references)
//IL_034f: Expected O, but got Unknown
//IL_038c: Unknown result type (might be due to invalid IL or missing references)
//IL_0391: Unknown result type (might be due to invalid IL or missing references)
//IL_03ba: Unknown result type (might be due to invalid IL or missing references)
//IL_0416: Unknown result type (might be due to invalid IL or missing references)
//IL_041d: Expected O, but got Unknown
GameObject val = new GameObject("PinSlot", new Type[1] { typeof(RectTransform) });
val.layer = 5;
val.transform.SetParent(parent, false);
PinSlotUI pinSlotUI = val.AddComponent<PinSlotUI>();
pinSlotUI.Rect = val.GetComponent<RectTransform>();
Image val2 = val.AddComponent<Image>();
Sprite val3 = (val2.sprite = GetBackgroundSprite());
if ((Object)(object)val3 != (Object)null && val3.border != Vector4.zero)
{
val2.type = (Type)1;
}
else
{
val2.type = (Type)0;
}
float num = ((RecipePinnerPlugin.BackgroundOpacity != null) ? RecipePinnerPlugin.BackgroundOpacity.Value : 0.45f);
((Graphic)val2).color = new Color(0f, 0f, 0f, num);
((Graphic)val2).raycastTarget = false;
VerticalLayoutGroup val4 = val.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val4).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)val4).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)val4).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)val4).spacing = 5f;
((LayoutGroup)val4).padding = new RectOffset(8, 8, 8, 8);
ContentSizeFitter val5 = val.AddComponent<ContentSizeFitter>();
val5.horizontalFit = (FitMode)0;
val5.verticalFit = (FitMode)2;
GameObject val6 = new GameObject("HeaderRow", new Type[1] { typeof(RectTransform) });
val6.layer = 5;
val6.transform.SetParent(val.transform, false);
HorizontalLayoutGroup val7 = val6.AddComponent<HorizontalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val7).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)val7).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)val7).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)val7).childForceExpandWidth = false;
((HorizontalOrVerticalLayoutGroup)val7).spacing = 8f;
LayoutElement val8 = val6.AddComponent<LayoutElement>();
val8.minHeight = 30f;
val8.flexibleHeight = 0f;
val8.flexibleWidth = 1f;
GameObject val9 = new GameObject("Icon", new Type[1] { typeof(RectTransform) });
val9.layer = 5;
val9.transform.SetParent(val6.transform, false);
Image val10 = val9.AddComponent<Image>();
((Graphic)val10).raycastTarget = false;
val10.preserveAspect = true;
pinSlotUI.IconImage = val10;
LayoutElement val11 = val9.AddComponent<LayoutElement>();
val11.minWidth = 28f;
val11.minHeight = 28f;
val11.preferredWidth = 28f;
val11.preferredHeight = 28f;
val11.flexibleWidth = 0f;
GameObject val12 = new GameObject("Title", new Type[1] { typeof(RectTransform) });
val12.layer = 5;
val12.transform.SetParent(val6.transform, false);
Text val13 = val12.AddComponent<Text>();
((Graphic)val13).raycastTarget = false;
val13.font = font;
val13.fontSize = 18;
val13.alignment = (TextAnchor)3;
val13.horizontalOverflow = (HorizontalWrapMode)0;
val13.verticalOverflow = (VerticalWrapMode)1;
((Graphic)val13).color = ValheimOrange;
pinSlotUI.HeaderText = val13;
LayoutElement val14 = val12.AddComponent<LayoutElement>();
val14.minHeight = 24f;
val14.flexibleWidth = 1f;
GameObject val15 = new GameObject("Divider", new Type[1] { typeof(RectTransform) });
val15.layer = 5;
val15.transform.SetParent(val.transform, false);
Image val16 = val15.AddComponent<Image>();
val16.sprite = GetBackgroundSprite();
if ((Object)(object)val3 != (Object)null && val3.border != Vector4.zero)
{
val16.type = (Type)1;
}
else
{
val16.type = (Type)0;
}
((Graphic)val16).color = DividerColor;
((Graphic)val16).raycastTarget = false;
LayoutElement val17 = val15.AddComponent<LayoutElement>();
val17.minHeight = 2f;
val17.preferredHeight = 2f;
val17.flexibleWidth = 1f;
GameObject val18 = new GameObject("ResourceList", new Type[1] { typeof(RectTransform) });
val18.layer = 5;
val18.transform.SetParent(val.transform, false);
VerticalLayoutGroup val19 = val18.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val19).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)val19).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)val19).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)val19).spacing = 3f;
ContentSizeFitter val20 = val18.AddComponent<ContentSizeFitter>();
val20.verticalFit = (FitMode)2;
pinSlotUI.ResourceListRoot = val18.transform;
DebugLogger.Verbose("Created pin slot UI");
return pinSlotUI;
}
public static ResourceSlotUI CreateResourceSlot(Transform parent, Font font)
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Expected O, but got Unknown
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Expected O, but got Unknown
//IL_0153: Unknown result type (might be due to invalid IL or missing references)
//IL_015a: Expected O, but got Unknown
//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
//IL_0219: Unknown result type (might be due to invalid IL or missing references)
//IL_0220: Expected O, but got Unknown
GameObject val = new GameObject("ResSlot", new Type[1] { typeof(RectTransform) });
val.layer = 5;
val.transform.SetParent(parent, false);
ResourceSlotUI resourceSlotUI = val.AddComponent<ResourceSlotUI>();
HorizontalLayoutGroup val2 = val.AddComponent<HorizontalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val2).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)val2).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)val2).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)val2).childForceExpandWidth = false;
((HorizontalOrVerticalLayoutGroup)val2).spacing = 6f;
LayoutElement val3 = val.AddComponent<LayoutElement>();
val3.minHeight = 22f;
val3.flexibleHeight = 0f;
GameObject val4 = new GameObject("Icon", new Type[1] { typeof(RectTransform) });
val4.layer = 5;
val4.transform.SetParent(val.transform, false);
resourceSlotUI.ResIcon = val4.AddComponent<Image>();
((Graphic)resourceSlotUI.ResIcon).raycastTarget = false;
resourceSlotUI.ResIcon.preserveAspect = true;
LayoutElement val5 = val4.AddComponent<LayoutElement>();
val5.minWidth = 20f;
val5.minHeight = 20f;
val5.preferredWidth = 20f;
val5.preferredHeight = 20f;
val5.flexibleWidth = 0f;
GameObject val6 = new GameObject("Name", new Type[1] { typeof(RectTransform) });
val6.layer = 5;
val6.transform.SetParent(val.transform, false);
resourceSlotUI.ResName = val6.AddComponent<Text>();
((Graphic)resourceSlotUI.ResName).raycastTarget = false;
resourceSlotUI.ResName.font = font;
resourceSlotUI.ResName.fontSize = 15;
resourceSlotUI.ResName.alignment = (TextAnchor)3;
resourceSlotUI.ResName.horizontalOverflow = (HorizontalWrapMode)0;
((Graphic)resourceSlotUI.ResName).color = new Color(0.9f, 0.9f, 0.9f, 1f);
LayoutElement val7 = val6.AddComponent<LayoutElement>();
val7.flexibleWidth = 1f;
GameObject val8 = new GameObject("Amount", new Type[1] { typeof(RectTransform) });
val8.layer = 5;
val8.transform.SetParent(val.transform, false);
resourceSlotUI.ResAmount = val8.AddComponent<Text>();
((Graphic)resourceSlotUI.ResAmount).raycastTarget = false;
resourceSlotUI.ResAmount.font = font;
resourceSlotUI.ResAmount.fontSize = 15;
resourceSlotUI.ResAmount.alignment = (TextAnchor)5;
LayoutElement val9 = val8.AddComponent<LayoutElement>();
val9.minWidth = 40f;
DebugLogger.Verbose("Created resource slot UI");
return resourceSlotUI;
}
}
public class PinSlotUI : MonoBehaviour
{
[CompilerGenerated]
private sealed class <FixLayout>d__15 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public PinSlotUI <>4__this;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <FixLayout>d__15(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = null;
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if ((Object)(object)<>4__this.ResourceListRoot != (Object)null)
{
Transform resourceListRoot = <>4__this.ResourceListRoot;
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((resourceListRoot is RectTransform) ? resourceListRoot : null));
}
if ((Object)(object)<>4__this.Rect != (Object)null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(<>4__this.Rect);
}
if ((Object)(object)((Component)<>4__this).transform.parent != (Object)null)
{
Transform parent = ((Component)<>4__this).transform.parent;
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((parent is RectTransform) ? parent : null));
}
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
public RectTransform Rect;
public Image IconImage;
public Text HeaderText;
public Transform ResourceListRoot;
private Coroutine _layoutCoroutine;
private Image _cachedBg;
private ContentSizeFitter _cachedCsf;
public List<ResourceSlotUI> ResourceSlots = new List<ResourceSlotUI>();
public Image BgImage => Object.op_Implicit((Object)(object)_cachedBg) ? _cachedBg : (_cachedBg = ((Component)this).GetComponent<Image>());
public ContentSizeFitter Csf => Object.op_Implicit((Object)(object)_cachedCsf) ? _cachedCsf : (_cachedCsf = ((Component)this).GetComponent<ContentSizeFitter>());
public void SetActive(bool active)
{
((Component)this).gameObject.SetActive(active);
}
public void UpdateData(PinnedRecipeData data, Font font)
{
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
IconImage.sprite = data.Icon;
HeaderText.text = data.CachedHeader;
HeaderText.font = font;
HeaderText.fontSize = RecipePinnerPlugin.FontSizeRecipeName.Value;
((Graphic)HeaderText).color = RecipePinnerPlugin.ColorHeader.Value;
int count = data.Resources.Count;
while (ResourceSlots.Count < count)
{
ResourceSlots.Add(UIBuilder.CreateResourceSlot(ResourceListRoot, font));
}
for (int i = 0; i < ResourceSlots.Count; i++)
{
if (i < count)
{
ResourceSlots[i].SetActive(active: true);
ResourceSlots[i].UpdateResource(data.Resources[i]);
}
else
{
ResourceSlots[i].SetActive(active: false);
}
}
if (((Component)this).gameObject.activeInHierarchy)
{
if (_layoutCoroutine != null)
{
((MonoBehaviour)this).StopCoroutine(_layoutCoroutine);
}
_layoutCoroutine = ((MonoBehaviour)this).StartCoroutine(FixLayout());
}
}
private void OnDisable()
{
_layoutCoroutine = null;
}
[IteratorStateMachine(typeof(<FixLayout>d__15))]
private IEnumerator FixLayout()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <FixLayout>d__15(0)
{
<>4__this = this
};
}
}
public class ResourceSlotUI : MonoBehaviour
{
public Image ResIcon;
public Text ResName;
public Text ResAmount;
public void SetActive(bool active)
{
((Component)this).gameObject.SetActive(active);
}
public void UpdateResource(PinnedResData res)
{
ResIcon.sprite = res.Icon;
ResName.text = res.CachedName;
ResAmount.text = res.CachedAmountString;
if (RecipePinnerPlugin.FontSizeMaterials != null)
{
int value = RecipePinnerPlugin.FontSizeMaterials.Value;
ResName.fontSize = value;
ResAmount.fontSize = value;
}
}
}
public class UIManager
{
private Transform _pinListRoot;
private List<PinSlotUI> _pinPool = new List<PinSlotUI>();
private Font _cachedFont;
private static Dictionary<string, int> _reusableInvCounts = new Dictionary<string, int>();
public void DestroyUI()
{
DebugLogger.Verbose("Destroying UI...");
if ((Object)(object)_pinListRoot != (Object)null)
{
Object.Destroy((Object)(object)((Component)_pinListRoot).gameObject);
_pinListRoot = null;
}
if (_pinPool != null)
{
_pinPool.Clear();
}
DebugLogger.Log("UI destroyed successfully");
}
public void UpdateUI(bool isVisible)
{
if ((Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsDead())
{
return;
}
Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory();
if (inventory == null)
{
return;
}
RecipePinnerPlugin instance = RecipePinnerPlugin.Instance;
RecipeManager recipeMgr = instance.RecipeMgr;
ContainerScanner containerMgr = instance.ContainerMgr;
if (_pinPool.Count < RecipePinnerPlugin.MaximumPins.Value)
{
DebugLogger.Log($"Pin limit changed (Pool: {_pinPool.Count}, Config: {RecipePinnerPlugin.MaximumPins.Value}). Rebuilding UI...");
DestroyUI();
}
if ((Object)(object)_pinListRoot == (Object)null)
{
_pinPool.Clear();
CreateCanvasUI();
if ((Object)(object)_pinListRoot == (Object)null)
{
return;
}
foreach (PinnedRecipeData cachedPin in recipeMgr.CachedPins)
{
cachedPin.IsDirty = true;
}
}
UpdateLayout();
if ((Object)(object)_pinListRoot == (Object)null)
{
return;
}
bool flag = isVisible && !InputHelper.IsInputBlocked() && recipeMgr.CachedPins.Count > 0;
if (((Component)_pinListRoot).gameObject.activeSelf != flag)
{
((Component)_pinListRoot).gameObject.SetActive(flag);
DebugLogger.Verbose($"UI visibility changed: {flag}");
}
if (!flag)
{
return;
}
_reusableInvCounts.Clear();
foreach (ItemData allItem in inventory.GetAllItems())
{
string name = allItem.m_shared.m_name;
if (_reusableInvCounts.ContainsKey(name))
{
_reusableInvCounts[name] += allItem.m_stack;
}
else
{
_reusableInvCounts[name] = allItem.m_stack;
}
}
int count = recipeMgr.CachedPins.Count;
for (int i = 0; i < _pinPool.Count; i++)
{
if (!((Object)(object)_pinPool[i] == (Object)null))
{
if (i < count)
{
UpdatePinSlot(i, recipeMgr.CachedPins[i], containerMgr);
}
else if ((Object)(object)_pinPool[i] != (Object)null && (Object)(object)((Component)_pinPool[i]).gameObject != (Object)null && ((Component)_pinPool[i]).gameObject.activeSelf)
{
_pinPool[i].SetActive(active: false);
}
}
}
}
private void UpdatePinSlot(int index, PinnedRecipeData pinData, ContainerScanner containerMgr)
{
//IL_0066: 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_0121: 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_0150: Unknown result type (might be due to invalid IL or missing references)
//IL_0247: Unknown result type (might be due to invalid IL or missing references)
//IL_024c: Unknown result type (might be due to invalid IL or missing references)
//IL_0253: Unknown result type (might be due to invalid IL or missing references)
//IL_023b: Unknown result type (might be due to invalid IL or missing references)
//IL_022f: Unknown result type (might be due to invalid IL or missing references)
PinSlotUI pinSlotUI = _pinPool[index];
if ((Object)(object)pinSlotUI == (Object)null || (Object)(object)((Component)pinSlotUI).gameObject == (Object)null)
{
return;
}
if (!((Component)pinSlotUI).gameObject.activeSelf)
{
pinSlotUI.SetActive(active: true);
}
if ((Object)(object)pinSlotUI.BgImage != (Object)null)
{
float a = ((Graphic)pinSlotUI.BgImage).color.a;
if (Mathf.Abs(a - RecipePinnerPlugin.BackgroundOpacity.Value) > 0.01f)
{
((Graphic)pinSlotUI.BgImage).color = new Color(0f, 0f, 0f, RecipePinnerPlugin.BackgroundOpacity.Value);
}
}
bool flag = RecipePinnerPlugin.LayoutModeConfig.Value == RecipePinnerPlugin.PinLayoutMode.ForceBottomRightHorizontal;
bool flag2 = (Object)(object)Player.m_localPlayer.GetControlledShip() != (Object)null;
if (RecipePinnerPlugin.Instance.IsHorizontalMode || flag2)
{
RectTransform val = pinSlotUI.Rect ?? ((Component)pinSlotUI).GetComponent<RectTransform>();
float num = ((flag || flag2) ? RecipePinnerPlugin.BottomRightColumnWidth.Value : RecipePinnerPlugin.HorizontalColumnWidth.Value);
if (Mathf.Abs(val.sizeDelta.x - num) > 1f)
{
val.sizeDelta = new Vector2(num, val.sizeDelta.y);
}
}
foreach (PinnedResData resource in pinData.Resources)
{
int value = 0;
_reusableInvCounts.TryGetValue(resource.ItemName, out value);
int num2 = 0;
if (RecipePinnerPlugin.EnableChestScanning.Value && containerMgr.ContainerCache.ContainsKey(resource.ItemName))
{
num2 = containerMgr.ContainerCache[resource.ItemName];
}
int num3 = value + num2;
if (num3 != resource.LastKnownAmount || value != resource.LastKnownInvAmount || resource.CachedAmountString == null)
{
resource.LastKnownAmount = num3;
resource.LastKnownInvAmount = value;
Color val2 = ((value >= resource.RequiredAmount) ? RecipePinnerPlugin.ColorEnoughInInventory.Value : ((num3 >= resource.RequiredAmount) ? RecipePinnerPlugin.ColorEnoughWithChests.Value : RecipePinnerPlugin.ColorMissing.Value));
string arg = "#" + ColorUtility.ToHtmlStringRGBA(val2);
string text = $"<color={arg}>{num3}/{resource.RequiredAmount}</color>";
if (resource.CachedAmountString != text)
{
resource.CachedAmountString = text;
pinData.IsDirty = true;
}
}
}
if (pinData.IsDirty)
{
pinSlotUI.UpdateData(pinData, _cachedFont);
pinData.IsDirty = false;
}
}
private void CreateCanvasUI()
{
//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
//IL_00c9: Expected O, but got Unknown
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_0100: Unknown result type (might be due to invalid IL or missing references)
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_0133: Unknown result type (might be due to invalid IL or missing references)
//IL_0149: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)_pinListRoot != (Object)null)
{
return;
}
if ((Object)(object)Hud.instance == (Object)null || (Object)(object)Hud.instance.m_rootObject == (Object)null)
{
DebugLogger.Warning("Cannot create canvas - Hud.instance is null");
return;
}
if ((Object)(object)_cachedFont == (Object)null)
{
_cachedFont = GetGameFont();
}
if ((Object)(object)_cachedFont == (Object)null)
{
DebugLogger.Error("Cannot create UI - no valid font found");
return;
}
DebugLogger.Log("Creating canvas UI...");
Transform transform = Hud.instance.m_rootObject.transform;
GameObject val = new GameObject("PinListRoot", new Type[1] { typeof(RectTransform) });
val.layer = 5;
val.transform.SetParent(transform, false);
_pinListRoot = val.transform;
_pinListRoot.localScale = Vector3.one * RecipePinnerPlugin.UIScale.Value;
RectTransform component = val.GetComponent<RectTransform>();
component.anchorMin = new Vector2(1f, 1f);
component.anchorMax = new Vector2(1f, 1f);
component.pivot = new Vector2(1f, 1f);
bool flag = (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Player.m_localPlayer.GetControlledShip() != (Object)null;
bool flag2 = RecipePinnerPlugin.LayoutModeConfig.Value == RecipePinnerPlugin.PinLayoutMode.ForceBottomRightHorizontal;
bool flag3 = flag || flag2;
bool flag4 = RecipePinnerPlugin.Instance.IsHorizontalMode || flag;
DebugLogger.Verbose($"UI Layout - Horizontal: {flag4}, BottomRight: {flag3}, Sailing: {flag}");
if (flag4)
{
HorizontalLayoutGroup val2 = val.AddComponent<HorizontalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val2).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)val2).childControlWidth = false;
((HorizontalOrVerticalLayoutGroup)val2).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)val2).childForceExpandWidth = false;
((LayoutGroup)val2).childAlignment = (TextAnchor)(flag3 ? 8 : 2);
((HorizontalOrVerticalLayoutGroup)val2).spacing = RecipePinnerPlugin.HorizontalPinSpacing.Value;
ContentSizeFitter val3 = val.AddComponent<ContentSizeFitter>();
val3.horizontalFit = (FitMode)2;
val3.verticalFit = (FitMode)2;
}
else
{
VerticalLayoutGroup val4 = val.AddComponent<VerticalLayoutGroup>();
((HorizontalOrVerticalLayoutGroup)val4).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)val4).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)val4).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)val4).spacing = 8f;
ContentSizeFitter val5 = val.AddComponent<ContentSizeFitter>();
val5.verticalFit = (FitMode)2;
}
for (int i = 0; i < RecipePinnerPlugin.MaximumPins.Value; i++)
{
PinSlotUI pinSlotUI = UIBuilder.CreatePinSlot(_pinListRoot, _cachedFont);
if ((Object)(object)pinSlotUI != (Object)null)
{
pinSlotUI.SetActive(active: false);
_pinPool.Add(pinSlotUI);
}
}
DebugLogger.Log($"Canvas UI created with {_pinPool.Count} pin slots");
}
private void UpdateLayout()
{
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_00b8: 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)
if ((Object)(object)_pinListRoot == (Object)null)
{
return;
}
RecipePinnerPlugin instance = RecipePinnerPlugin.Instance;
bool flag = RecipePinnerPlugin.LayoutModeConfig.Value == RecipePinnerPlugin.PinLayoutMode.ForceBottomRightHorizontal;
bool flag2 = !flag && (Object)(object)Player.m_localPlayer.GetControlledShip() != (Object)null;
bool isBottomRightMode = flag || flag2;
bool flag3 = instance.IsHorizontalMode || flag2;
HorizontalLayoutGroup component = ((Component)_pinListRoot).GetComponent<HorizontalLayoutGroup>();
bool flag4 = (Object)(object)component != (Object)null;
if (flag3 != flag4)
{
DebugLogger.Log("Layout mode changed - rebuilding UI");
DestroyUI();
return;
}
if (_pinListRoot.localScale.x != RecipePinnerPlugin.UIScale.Value)
{
_pinListRoot.localScale = Vector3.one * RecipePinnerPlugin.UIScale.Value;
}
RectTransform component2 = ((Component)_pinListRoot).GetComponent<RectTransform>();
if (flag3)
{
UpdateHorizontalLayout(component2, isBottomRightMode);
}
else
{
UpdateVerticalLayout(component2);
}
}
private void UpdateHorizontalLayout(RectTransform rootRect, bool isBottomRightMode)
{
//IL_010b: Unknown result type (might be due to invalid IL or missing references)
//IL_011a: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0136: Unknown result type (might be due to invalid IL or missing references)
//IL_014c: Unknown result type (might be due to invalid IL or missing references)
//IL_0162: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0178: Unknown result type (might be due to invalid IL or missing references)
//IL_017e: Invalid comparison between Unknown and I4
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_0088: Invalid comparison between Unknown and I4
//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
//IL_01de: 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_00e8: Unknown result type (might be due to invalid IL or missing references)
//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
//IL_00ef: 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)
//IL_025f: 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_0207: Unknown result type (might be due to invalid IL or missing references)
//IL_0272: Unknown result type (might be due to invalid IL or missing references)
//IL_0232: Unknown result type (might be due to invalid IL or missing references)
HorizontalLayoutGroup component = ((Component)_pinListRoot).GetComponent<HorizontalLayoutGroup>();
if (isBottomRightMode)
{
if (rootRect.anchorMin != new Vector2(1f, 0f))
{
rootRect.anchorMin = new Vector2(1f, 0f);
rootRect.anchorMax = new Vector2(1f, 0f);
rootRect.pivot = new Vector2(1f, 0f);
}
if ((Object)(object)component != (Object)null && (int)((LayoutGroup)component).childAlignment != 8)
{
((LayoutGroup)component).childAlignment = (TextAnchor)8;
}
if ((Object)(object)component != (Object)null && Mathf.Abs(((HorizontalOrVerticalLayoutGroup)component).spacing - RecipePinnerPlugin.BottomRightPinSpacing.Value) > 0.01f)
{
((HorizontalOrVerticalLayoutGroup)component).spacing = RecipePinnerPlugin.BottomRightPinSpacing.Value;
}
Vector2 value = RecipePinnerPlugin.BottomRightPosition.Value;
if (rootRect.anchoredPosition != value)
{
rootRect.anchoredPosition = value;
}
return;
}
if (rootRect.anchorMin != new Vector2(1f, 1f))
{
rootRect.anchorMin = new Vector2(1f, 1f);
rootRect.anchorMax = new Vector2(1f, 1f);
rootRect.pivot = new Vector2(1f, 1f);
}
if ((Object)(object)component != (Object)null && (int)((LayoutGroup)component).childAlignment != 2)
{
((LayoutGroup)component).childAlignment = (TextAnchor)2;
}
if ((Object)(object)component != (Object)null && Mathf.Abs(((HorizontalOrVerticalLayoutGroup)component).spacing - RecipePinnerPlugin.HorizontalPinSpacing.Value) > 0.01f)
{
((HorizontalOrVerticalLayoutGroup)component).spacing = RecipePinnerPlugin.HorizontalPinSpacing.Value;
}
Vector2 value2 = RecipePinnerPlugin.HorizontalPosition.Value;
if (Game.m_noMap && RecipePinnerPlugin.Instance._mluiInstalled && RecipePinnerPlugin.Instance._mluiNoMapListEnabled)
{
if (Mathf.Abs(value2.x - -250f) < 1f)
{
value2.x = -270f;
}
if (Mathf.Abs(value2.y - -40f) < 1f)
{
value2.y = -15f;
}
}
if (rootRect.anchoredPosition != value2)
{
rootRect.anchoredPosition = value2;
}
}
private void UpdateVerticalLayout(RectTransform rootRect)
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
//IL_010d: Unknown result type (might be due to invalid IL or missing references)
if (rootRect.anchorMin != new Vector2(1f, 1f))
{
rootRect.anchorMin = new Vector2(1f, 1f);
rootRect.anchorMax = new Vector2(1f, 1f);
rootRect.pivot = new Vector2(1f, 1f);
}
VerticalLayoutGroup component = ((Component)_pinListRoot).GetComponent<VerticalLayoutGroup>();
if ((Object)(object)component != (Object)null && Mathf.Abs(((HorizontalOrVerticalLayoutGroup)component).spacing - RecipePinnerPlugin.VerticalPinSpacing.Value) > 0.01f)
{
((HorizontalOrVerticalLayoutGroup)component).spacing = RecipePinnerPlugin.VerticalPinSpacing.Value;
}
Vector2 value = RecipePinnerPlugin.VerticalPosition.Value;
if (rootRect.anchoredPosition != value)
{
rootRect.anchoredPosition = value;
}
if (Mathf.Abs(rootRect.sizeDelta.x - RecipePinnerPlugin.VerticalListWidth.Value) > 1f)
{
rootRect.sizeDelta = new Vector2(RecipePinnerPlugin.VerticalListWidth.Value, 0f);
}
}
private Font GetGameFont()
{
Font[] array = Resources.FindObjectsOfTypeAll<Font>();
Font[] array2 = array;
foreach (Font val in array2)
{
if ((Object)(object)val != (Object)null && ((Object)val).name == "AveriaSerifLibre-Bold")
{
DebugLogger.Log("Found game font: AveriaSerifLibre-Bold");
return val;
}
}
Font[] array3 = array;
foreach (Font val2 in array3)
{
if ((Object)(object)val2 != (Object)null && ((Object)val2).name == "Arial")
{
DebugLogger.Log("Using fallback font: Arial");
return val2;
}
}
try
{
DebugLogger.Log("Creating dynamic font from OS: Arial");
return Font.CreateDynamicFontFromOSFont("Arial", 14);
}
catch
{
DebugLogger.Warning("Failed to create dynamic font, using first available font");
return (array.Length != 0) ? array[0] : null;
}
}
}