Decompiled source of Shack Recycler v1.1.0

ShackRecycler.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ShackRecycler")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("ShackRecycler")]
[assembly: AssemblyTitle("ShackRecycler")]
[assembly: AssemblyVersion("1.1.0.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 TheShack.Recycler
{
	[BepInPlugin("theshack.recycler", "Shack Recycler", "1.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	[SynchronizationMode(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "theshack.recycler";

		public const string PluginName = "Shack Recycler";

		public const string PluginVersion = "1.1.0";

		internal static ManualLogSource Log;

		internal static SkillType RecyclingSkill;

		private void Awake()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Settings.Bind(((BaseUnityPlugin)this).Config);
			RegisterSkill();
			new Harmony("theshack.recycler").PatchAll(Assembly.GetExecutingAssembly());
			Log.LogInfo((object)string.Format("{0} {1} ready (returns: {2}).", "Shack Recycler", "1.1.0", Settings.Mode.Value));
		}

		private static void RegisterSkill()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: 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_0020: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				RecyclingSkill = SkillManager.Instance.AddSkill(new SkillConfig
				{
					Identifier = "theshack.recycler.recycling",
					Name = "Recycling",
					Description = "Improves your luck when recycling with luck rolls: fewer items crumble and more jackpots.",
					Icon = LoadSprite("skill-icon.png"),
					IncreaseStep = 1f
				});
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Could not register the Recycling skill; luck rolls will ignore it. " + ex));
			}
		}

		private static Sprite LoadSprite(string resource)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource);
			if (stream == null)
			{
				return null;
			}
			MemoryStream memoryStream = new MemoryStream();
			stream.CopyTo(memoryStream);
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			ImageConversion.LoadImage(val, memoryStream.ToArray());
			return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
		}
	}
	public enum ReturnMode
	{
		Flat,
		Luck
	}
	public enum ConfirmMode
	{
		Never,
		Gear,
		Everything
	}
	internal static class Settings
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<string> Stations;

		public static ConfigEntry<bool> StationFreeItems;

		public static ConfigEntry<bool> RequireStationLevel;

		public static ConfigEntry<bool> OnlyKnownRecipes;

		public static ConfigEntry<string> ExcludedItemTypes;

		public static ConfigEntry<string> BlockedItems;

		public static ConfigEntry<string> BlockedMaterials;

		public static ConfigEntry<ReturnMode> Mode;

		public static ConfigEntry<int> FlatPercent;

		public static ConfigEntry<bool> IncludeUpgrades;

		public static ConfigEntry<bool> DurabilityAffectsReturn;

		public static ConfigEntry<ConfirmMode> Confirm;

		public static ConfigEntry<float> CrumbleWeight;

		public static ConfigEntry<float> PoorWeight;

		public static ConfigEntry<float> NormalWeight;

		public static ConfigEntry<float> GoodWeight;

		public static ConfigEntry<float> JackpotWeight;

		public static ConfigEntry<string> PoorRange;

		public static ConfigEntry<string> NormalRange;

		public static ConfigEntry<string> GoodRange;

		public static ConfigEntry<bool> SkillEnabled;

		public static ConfigEntry<float> SkillXp;

		public static ConfigEntry<float> SkillCrumbleReduction;

		public static ConfigEntry<float> SkillJackpotBonus;

		public static ConfigEntry<bool> ResearchEnabled;

		public static ConfigEntry<string> ResearchStations;

		public static ConfigEntry<int> ResearchCount;

		public static ConfigEntry<string> ResearchExcludedTypes;

		public static int Version;

		private static int parsedVersion = -1;

		private static HashSet<string> stations;

		private static HashSet<string> researchStations;

		private static HashSet<ItemType> excludedTypes;

		private static HashSet<ItemType> researchExcludedTypes;

		private static HashSet<string> blockedItems;

		private static HashSet<string> blockedMaterials;

		private static readonly HashSet<string> warnedRanges = new HashSet<string>();

		public static void Bind(ConfigFile config)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			int order = 100;
			Confirm = config.Bind<ConfirmMode>("0 - This PC", "Ask before recycling", ConfirmMode.Gear, new ConfigDescription("Ask Yes/No before recycling. Gear = anything that doesn't stack (tools, weapons, armour). Everything = stacks too. Never = recycle on click.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				Order = order--
			} }));
			Enabled = Bind<bool>("1 - General", "Enabled", value: true, "Show the Recycle tab at the stations below.");
			Stations = Bind<string>("1 - General", "Stations", "$piece_workbench, $piece_forge, $piece_stonecutter", "Crafting stations that get a Recycle tab, comma separated. Use the station's name token or prefab name, e.g. $piece_workbench, $piece_forge, $piece_stonecutter, $piece_artisanstation, $piece_blackforge, $piece_magetable. Each station recycles what it crafts.");
			StationFreeItems = Bind<bool>("1 - General", "Station-free items", value: true, "Items that need no crafting station (hammer, torch, club...) can be recycled at any of the stations above.");
			RequireStationLevel = Bind<bool>("1 - General", "Require station level", value: true, "Recycling needs the station level that crafting (or upgrading to) the item's level needs.");
			OnlyKnownRecipes = Bind<bool>("1 - General", "Only known recipes", value: true, "Only things you know how to craft can be recycled. Others show greyed out; research them to learn them. Keeps recycling from handing out materials from tiers you haven't reached.");
			ExcludedItemTypes = Bind<string>("1 - General", "Excluded item types", "Consumable", "Item types that are never recycled, comma separated (Consumable = food, meads and potions). Other types: Material, Ammo, AmmoNonEquipable, OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield, Helmet, Chest, Legs, Shoulder, Utility, Tool, Torch, Trinket, Misc.");
			BlockedItems = Bind<string>("1 - General", "Blocked items", "", "Item prefab names that are never recycled, comma separated (e.g. SwordIron, ArrowWood).");
			BlockedMaterials = Bind<string>("1 - General", "Blocked materials", "", "Material prefab names that are never given back, comma separated (e.g. DragonTear). The item can still be recycled.");
			Mode = Bind<ReturnMode>("2 - Returns", "Mode", ReturnMode.Flat, "Flat: always the percentage below. Luck: each material is rolled (crumbled, poor, normal, good, jackpot), and the Recycling skill improves the odds.");
			FlatPercent = Bind<int>("2 - Returns", "Flat percent", 100, "Flat mode: share of the crafting cost given back. 100 = everything it cost to make.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100));
			IncludeUpgrades = Bind<bool>("2 - Returns", "Include upgrades", value: true, "Upgraded items also give back what each upgrade cost. Off: only what the item cost to craft at level 1.");
			DurabilityAffectsReturn = Bind<bool>("2 - Returns", "Durability affects return", value: false, "Worn items give back less: 50% plus 50% times the remaining durability. Applies to both modes (not to a Luck jackpot).");
			CrumbleWeight = Bind<float>("3 - Luck", "Crumble weight", 20f, "Luck mode: weight of 'crumbled' (nothing back).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
			PoorWeight = Bind<float>("3 - Luck", "Poor weight", 30f, "Luck mode: weight of 'poor'.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
			NormalWeight = Bind<float>("3 - Luck", "Normal weight", 30f, "Luck mode: weight of 'normal'.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
			GoodWeight = Bind<float>("3 - Luck", "Good weight", 15f, "Luck mode: weight of 'good'.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
			JackpotWeight = Bind<float>("3 - Luck", "Jackpot weight", 5f, "Luck mode: weight of 'jackpot' (everything back).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f));
			PoorRange = Bind<string>("3 - Luck", "Poor range", "0.25-0.5", "Luck mode: share of the cost a 'poor' roll gives back (min-max, 0-1).");
			NormalRange = Bind<string>("3 - Luck", "Normal range", "0.5-0.75", "Luck mode: share of the cost a 'normal' roll gives back (min-max, 0-1).");
			GoodRange = Bind<string>("3 - Luck", "Good range", "0.75-0.99", "Luck mode: share of the cost a 'good' roll gives back (min-max, 0-1).");
			SkillEnabled = Bind<bool>("4 - Skill", "Enabled", value: true, "Luck mode: recycling trains the Recycling skill, and the skill improves the odds. Levels are kept when this is off.");
			SkillXp = Bind<float>("4 - Skill", "XP per recycle", 10f, "Luck mode: skill XP per recycled item (times its level) or per craft's worth of a stack.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f));
			SkillCrumbleReduction = Bind<float>("4 - Skill", "Crumble reduction at 100", 0.5f, "Luck mode: at skill 100 the crumble weight shrinks by this share (scales with level); the removed weight goes to 'good'.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f));
			SkillJackpotBonus = Bind<float>("4 - Skill", "Jackpot bonus at 100", 1f, "Luck mode: at skill 100 the jackpot weight grows by this share (1 = doubled; scales with level).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f));
			ResearchEnabled = Bind<bool>("5 - Research", "Enabled", value: true, "A Research tab at the stations below. Researching uses up an item you can't craft yet (nothing comes back); after enough researches you learn its recipe. Stacks use one craft's worth per research (20 arrows).");
			ResearchStations = Bind<string>("5 - Research", "Stations", "$piece_workbench", "Crafting stations that get a Research tab, comma separated (name token or prefab name). Any item can be researched there, whichever station crafts it.");
			ResearchCount = Bind<int>("5 - Research", "Researches needed", 5, "How many times an item must be researched to learn its recipe.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50));
			ResearchExcludedTypes = Bind<string>("5 - Research", "Excluded item types", "", "Item types that can't be researched, comma separated (same names as the recycling list). Empty = anything with a recipe, including food and meads. Meads and baked food teach the recipe they're made from (mead base, dough). 'Blocked items' applies to research too.");
			config.SettingChanged += delegate
			{
				Version++;
			};
			ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase range = null)
			{
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_0040: Expected O, but got Unknown
				//IL_0040: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Expected O, but got Unknown
				return config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { (object)new ConfigurationManagerAttributes
				{
					IsAdminOnly = true,
					Order = order--
				} }));
			}
		}

		public static bool IsStationAllowed(CraftingStation station)
		{
			if (!Enabled.Value || (Object)(object)station == (Object)null || !station.m_hasCraftTab || station.m_upgrader)
			{
				return false;
			}
			Parse();
			if (!stations.Contains(Normalize(station.m_name)))
			{
				return stations.Contains(Normalize(Utils.GetPrefabName(((Component)station).gameObject)));
			}
			return true;
		}

		public static bool IsResearchStation(CraftingStation station)
		{
			if (!ResearchEnabled.Value || (Object)(object)station == (Object)null || !station.m_hasCraftTab || station.m_upgrader)
			{
				return false;
			}
			Parse();
			if (!researchStations.Contains(Normalize(station.m_name)))
			{
				return researchStations.Contains(Normalize(Utils.GetPrefabName(((Component)station).gameObject)));
			}
			return true;
		}

		public static bool IsExcludedType(ItemType type)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			Parse();
			return excludedTypes.Contains(type);
		}

		public static bool IsResearchExcluded(ItemType type)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			Parse();
			return researchExcludedTypes.Contains(type);
		}

		public static bool IsBlockedItem(string prefab)
		{
			Parse();
			if (prefab != null)
			{
				return blockedItems.Contains(Normalize(prefab));
			}
			return false;
		}

		public static bool IsBlockedMaterial(string prefab)
		{
			Parse();
			if (prefab != null)
			{
				return blockedMaterials.Contains(Normalize(prefab));
			}
			return false;
		}

		public static (float min, float max) Range(ConfigEntry<string> entry)
		{
			string[] array = (entry.Value ?? "").Split('-');
			if (array.Length != 2 || !TryFraction(array[0], out var value) || !TryFraction(array[1], out var value2))
			{
				array = ((string)((ConfigEntryBase)entry).DefaultValue).Split('-');
				TryFraction(array[0], out value);
				TryFraction(array[1], out value2);
				if (warnedRanges.Add(entry.Value ?? ""))
				{
					Plugin.Log.LogWarning((object)$"{((ConfigEntryBase)entry).Definition.Key} '{entry.Value}' is not 'min-max' with 0-1 fractions; using {((ConfigEntryBase)entry).DefaultValue}.");
				}
			}
			if (!(value <= value2))
			{
				return (min: value2, max: value);
			}
			return (min: value, max: value2);
		}

		private static bool TryFraction(string text, out float value)
		{
			bool result = float.TryParse(text.Trim().Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
			value = Mathf.Clamp01(value);
			return result;
		}

		private static string Normalize(string name)
		{
			return (name ?? "").Trim().TrimStart('$').ToLowerInvariant();
		}

		private static void Parse()
		{
			if (parsedVersion != Version)
			{
				parsedVersion = Version;
				stations = NameSet(Stations.Value);
				researchStations = NameSet(ResearchStations.Value);
				blockedItems = NameSet(BlockedItems.Value);
				blockedMaterials = NameSet(BlockedMaterials.Value);
				excludedTypes = TypeSet(ExcludedItemTypes);
				researchExcludedTypes = TypeSet(ResearchExcludedTypes);
			}
		}

		private static HashSet<ItemType> TypeSet(ConfigEntry<string> entry)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			HashSet<ItemType> hashSet = new HashSet<ItemType>();
			foreach (string item in Split(entry.Value))
			{
				if (Enum.TryParse<ItemType>(item, ignoreCase: true, out ItemType result))
				{
					hashSet.Add(result);
					continue;
				}
				Plugin.Log.LogWarning((object)(((ConfigEntryBase)entry).Definition.Section + " / " + ((ConfigEntryBase)entry).Definition.Key + ": '" + item + "' is not an item type; ignored."));
			}
			return hashSet;
		}

		private static HashSet<string> NameSet(string list)
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (string item in Split(list))
			{
				hashSet.Add(Normalize(item));
			}
			return hashSet;
		}

		private static IEnumerable<string> Split(string list)
		{
			return from s in (list ?? "").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim() into s
				where s.Length > 0
				select s;
		}
	}
	internal enum Mode
	{
		None,
		Recycle,
		Research
	}
	[HarmonyPatch(typeof(InventoryGui))]
	internal static class RecycleTab
	{
		private struct Entry
		{
			public ItemData Item;

			public Recipe Recipe;

			public bool Can;

			public int Rank;

			public string Name;

			public string Note;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Comparison<Entry> <>9__43_0;

			public static PopupButtonCallback <>9__59_2;

			internal int <FillList>b__43_0(Entry a, Entry b)
			{
				int num = a.Rank.CompareTo(b.Rank);
				if (num == 0)
				{
					num = string.Compare(a.Name, b.Name, StringComparison.CurrentCultureIgnoreCase);
				}
				if (num == 0)
				{
					num = b.Item.m_quality.CompareTo(a.Item.m_quality);
				}
				if (num == 0)
				{
					num = b.Item.m_stack.CompareTo(a.Item.m_stack);
				}
				return num;
			}

			internal void <Run>b__59_2()
			{
				UnifiedPopup.Pop();
			}
		}

		private static Mode mode = Mode.None;

		private static Button recycleTab;

		private static Button researchTab;

		private static Vector2 firstSlot;

		private static Vector2 slotStep;

		private static Button button;

		private static TMP_Text buttonText;

		private static UITooltip buttonTooltip;

		private static readonly Type pairType = AccessTools.Inner(typeof(InventoryGui), "RecipeDataPair");

		private static readonly PropertyInfo pairItem = AccessTools.Property(pairType, "ItemData");

		private static readonly PropertyInfo pairElement = AccessTools.Property(pairType, "InterfaceElement");

		private static readonly FieldInfo availableRecipes = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes");

		private static readonly FieldInfo selectedRecipe = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static readonly FieldRef<InventoryGui, float> craftTimer = AccessTools.FieldRefAccess<InventoryGui, float>("m_craftTimer");

		private static readonly FieldRef<InventoryGui, float> listBaseSize = AccessTools.FieldRefAccess<InventoryGui, float>("m_recipeListBaseSize");

		private static readonly FieldRef<InventoryGui, Color> levelBaseColor = AccessTools.FieldRefAccess<InventoryGui, Color>("m_minStationLevelBasecolor");

		private static readonly MethodInfo updateCraftingPanel = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", (Type[])null, (Type[])null);

		private static readonly MethodInfo addRecipeToList = AccessTools.Method(typeof(InventoryGui), "AddRecipeToList", (Type[])null, (Type[])null);

		private static readonly MethodInfo setActiveGroup = AccessTools.Method(typeof(InventoryGui), "SetActiveGroup", new Type[2]
		{
			typeof(UIGroupHandler),
			typeof(bool)
		}, (Type[])null);

		private static ItemData cachedItem;

		private static CraftingStation cachedStation;

		private static int cachedQuality;

		private static int cachedVersion = -1;

		private static RecycleInfo cachedInfo;

		private static ItemData pendingItem;

		private static float pendingSince;

		private static readonly HashSet<string> loggedErrors = new HashSet<string>();

		[HarmonyPostfix]
		[HarmonyPatch("Awake")]
		private static void CreateUi(InventoryGui __instance)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				RectTransform val = (RectTransform)((Component)__instance.m_tabCraft).transform;
				RectTransform val2 = (RectTransform)((Component)__instance.m_tabUpgrade).transform;
				slotStep = val2.anchoredPosition - val.anchoredPosition;
				firstSlot = val2.anchoredPosition + slotStep;
				recycleTab = CreateTab(__instance, "ShackRecyclerTab", "Recycle", Mode.Recycle);
				researchTab = CreateTab(__instance, "ShackResearchTab", "Research", Mode.Research);
				CreateButton(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not add the Recycle and Research tabs: " + ex));
				recycleTab = (researchTab = null);
				button = null;
			}
		}

		private static Button CreateTab(InventoryGui gui, string objectName, string label, Mode opens)
		{
			//IL_0052: 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_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			GameObject obj = Object.Instantiate<GameObject>(((Component)gui.m_tabUpgrade).gameObject, ((Component)gui.m_tabUpgrade).transform.parent);
			((Object)obj).name = objectName;
			StripCopy(obj, keepGamepad: false);
			((RectTransform)obj.transform).anchoredPosition = firstSlot;
			Button component = obj.GetComponent<Button>();
			component.onClick = new ButtonClickedEvent();
			((UnityEvent)component.onClick).AddListener((UnityAction)delegate
			{
				Open(gui, opens);
			});
			((Selectable)component).interactable = true;
			TMP_Text componentInChildren = obj.GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				componentInChildren.text = label;
			}
			obj.SetActive(false);
			return component;
		}

		private static void CreateButton(InventoryGui gui)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			GameObject obj = Object.Instantiate<GameObject>(((Component)gui.m_craftButton).gameObject, ((Component)gui.m_craftButton).transform.parent);
			((Object)obj).name = "ShackRecyclerButton";
			obj.transform.SetSiblingIndex(((Component)gui.m_craftButton).transform.GetSiblingIndex() + 1);
			StripCopy(obj, keepGamepad: true);
			button = obj.GetComponent<Button>();
			button.onClick = new ButtonClickedEvent();
			((UnityEvent)button.onClick).AddListener((UnityAction)delegate
			{
				OnButtonClicked(gui);
			});
			buttonText = obj.GetComponentInChildren<TMP_Text>(true);
			buttonTooltip = obj.GetComponent<UITooltip>();
			obj.SetActive(false);
		}

		private static void StripCopy(GameObject copy, bool keepGamepad)
		{
			Localize[] componentsInChildren = copy.GetComponentsInChildren<Localize>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren[i]);
			}
			if (!keepGamepad)
			{
				UIGamePad[] componentsInChildren2 = copy.GetComponentsInChildren<UIGamePad>(true);
				for (int i = 0; i < componentsInChildren2.Length; i++)
				{
					Object.DestroyImmediate((Object)(object)componentsInChildren2[i]);
				}
			}
		}

		private static bool Allowed(Mode m, CraftingStation station)
		{
			return m switch
			{
				Mode.Research => Settings.IsResearchStation(station), 
				Mode.Recycle => Settings.IsStationAllowed(station), 
				_ => false, 
			};
		}

		private static void Open(InventoryGui gui, Mode opening)
		{
			if (mode != opening)
			{
				Player localPlayer = Player.m_localPlayer;
				if (Allowed(opening, (localPlayer != null) ? localPlayer.GetCurrentCraftingStation() : null))
				{
					mode = opening;
					setActiveGroup.Invoke(gui, new object[2]
					{
						gui.m_uiGroups[3],
						true
					});
					((Selectable)gui.m_tabCraft).interactable = true;
					((Selectable)gui.m_tabUpgrade).interactable = true;
					SetTabStates();
					cachedVersion = -1;
					updateCraftingPanel.Invoke(gui, new object[1] { true });
				}
			}
		}

		private static void Close(InventoryGui gui, bool selectCraftTab)
		{
			mode = Mode.None;
			if (selectCraftTab)
			{
				((Selectable)gui.m_tabCraft).interactable = false;
				((Selectable)gui.m_tabUpgrade).interactable = true;
			}
			SetTabStates();
			if ((Object)(object)button != (Object)null)
			{
				((Component)button).gameObject.SetActive(false);
			}
		}

		private static void SetTabStates()
		{
			if ((Object)(object)recycleTab != (Object)null)
			{
				((Selectable)recycleTab).interactable = mode != Mode.Recycle;
			}
			if ((Object)(object)researchTab != (Object)null)
			{
				((Selectable)researchTab).interactable = mode != Mode.Research;
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("OnTabCraftPressed")]
		private static void OnCraftTab(InventoryGui __instance)
		{
			if (mode != Mode.None)
			{
				Close(__instance, selectCraftTab: false);
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("OnTabUpgradePressed")]
		private static void OnUpgradeTab(InventoryGui __instance)
		{
			if (mode != Mode.None)
			{
				Close(__instance, selectCraftTab: false);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("Hide")]
		private static void OnHide(InventoryGui __instance)
		{
			if (mode != Mode.None)
			{
				Close(__instance, selectCraftTab: true);
			}
		}

		[HarmonyPrefix]
		[HarmonyPriority(800)]
		[HarmonyPatch("OnCraftPressed")]
		private static bool BlockCraftWhileOpen()
		{
			return mode == Mode.None;
		}

		[HarmonyPrefix]
		[HarmonyPatch("UpdateCraftingPanel")]
		private static void LeaveIfNotAllowed(InventoryGui __instance)
		{
			if (mode != Mode.None)
			{
				Mode m = mode;
				Player localPlayer = Player.m_localPlayer;
				if (!Allowed(m, (localPlayer != null) ? localPlayer.GetCurrentCraftingStation() : null))
				{
					Close(__instance, selectCraftTab: true);
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("UpdateCraftingPanel")]
		private static void ShowTabs()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)recycleTab == (Object)null) && !((Object)(object)researchTab == (Object)null))
			{
				Player localPlayer = Player.m_localPlayer;
				CraftingStation station = ((localPlayer != null) ? localPlayer.GetCurrentCraftingStation() : null);
				bool flag = Settings.IsStationAllowed(station);
				bool active = Settings.IsResearchStation(station);
				((Component)recycleTab).gameObject.SetActive(flag);
				((Component)researchTab).gameObject.SetActive(active);
				((RectTransform)((Component)recycleTab).transform).anchoredPosition = firstSlot;
				((RectTransform)((Component)researchTab).transform).anchoredPosition = (flag ? (firstSlot + slotStep) : firstSlot);
				SetTabStates();
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("UpdateRecipeList")]
		private static bool BuildList(InventoryGui __instance)
		{
			if (mode == Mode.None)
			{
				return true;
			}
			try
			{
				FillList(__instance);
			}
			catch (Exception e)
			{
				LogOnce("list", e);
			}
			return false;
		}

		private static void FillList(InventoryGui gui)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			IList list = (IList)availableRecipes.GetValue(gui);
			foreach (object item in list)
			{
				GameObject val = (GameObject)pairElement.GetValue(item);
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
			list.Clear();
			Player localPlayer = Player.m_localPlayer;
			CraftingStation val2 = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetCurrentCraftingStation() : null);
			List<Entry> obj = (((Object)(object)val2 == (Object)null) ? new List<Entry>() : ((mode == Mode.Recycle) ? RecycleEntries(localPlayer, val2) : ResearchEntries(localPlayer)));
			obj.Sort(delegate(Entry a, Entry b)
			{
				int num = a.Rank.CompareTo(b.Rank);
				if (num == 0)
				{
					num = string.Compare(a.Name, b.Name, StringComparison.CurrentCultureIgnoreCase);
				}
				if (num == 0)
				{
					num = b.Item.m_quality.CompareTo(a.Item.m_quality);
				}
				if (num == 0)
				{
					num = b.Item.m_stack.CompareTo(a.Item.m_stack);
				}
				return num;
			});
			foreach (Entry item2 in obj)
			{
				addRecipeToList.Invoke(gui, new object[4] { localPlayer, item2.Recipe, item2.Item, item2.Can });
				Dress((GameObject)pairElement.GetValue(list[list.Count - 1]), item2);
			}
			gui.m_recipeListRoot.SetSizeWithCurrentAnchors((Axis)1, Mathf.Max(listBaseSize.Invoke(gui), (float)list.Count * gui.m_recipeListSpace));
		}

		private static List<Entry> RecycleEntries(Player player, CraftingStation station)
		{
			List<Entry> list = new List<Entry>();
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				RecycleInfo recycleInfo = Recycling.EvaluateAnywhere(allItem, station);
				if (recycleInfo != null)
				{
					bool flag = Recycling.RecipeAllowed(player, allItem);
					bool flag2 = (Object)(object)recycleInfo.Elsewhere == (Object)null && flag && !Recycling.IsInUse(player, allItem) && LevelOk(station, recycleInfo);
					list.Add(new Entry
					{
						Item = allItem,
						Recipe = recycleInfo.Recipe,
						Can = flag2,
						Rank = ((!flag2) ? (((Object)(object)recycleInfo.Elsewhere == (Object)null) ? 1 : 2) : 0),
						Name = Localization.instance.Localize(allItem.m_shared.m_name),
						Note = ((!flag) ? "not known" : (((Object)(object)recycleInfo.Elsewhere != (Object)null) ? Localization.instance.Localize(recycleInfo.Elsewhere.m_name) : null))
					});
				}
			}
			return list;
		}

		private static List<Entry> ResearchEntries(Player player)
		{
			List<Entry> list = new List<Entry>();
			HashSet<string> hashSet = new HashSet<string>();
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				if (allItem.m_shared.m_maxStackSize <= 1 || (!Recycling.IsInUse(player, allItem) && hashSet.Add(allItem.m_shared.m_name)))
				{
					ResearchInfo researchInfo = Research.Evaluate(player, allItem);
					if (researchInfo != null)
					{
						bool flag = Research.Available(player, researchInfo) >= researchInfo.Batch;
						list.Add(new Entry
						{
							Item = allItem,
							Recipe = researchInfo.Recipe,
							Can = flag,
							Rank = ((!flag) ? 1 : 0),
							Name = Localization.instance.Localize(allItem.m_shared.m_name),
							Note = $"{researchInfo.Done}/{researchInfo.Needed}"
						});
					}
				}
			}
			return list;
		}

		private static void Dress(GameObject element, Entry entry)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			Transform obj = element.transform.Find("icon");
			Image val = ((obj != null) ? ((Component)obj).GetComponent<Image>() : null);
			if ((Object)(object)val != (Object)null)
			{
				val.sprite = entry.Item.GetIcon();
				((Graphic)val).color = (Color)(entry.Can ? Color.white : new Color(1f, 1f, 1f, 0.4f));
			}
			Transform obj2 = element.transform.Find("name");
			TMP_Text val2 = ((obj2 != null) ? ((Component)obj2).GetComponent<TMP_Text>() : null);
			if ((Object)(object)val2 != (Object)null)
			{
				val2.text = ((entry.Item.m_stack > 1) ? $"{entry.Name} x{entry.Item.m_stack}" : entry.Name);
				if (entry.Note != null)
				{
					val2.text = val2.text + " <size=80%>(" + entry.Note + ")</size>";
				}
			}
			Transform val3 = element.transform.Find("QualityLevel");
			if ((Object)(object)val3 != (Object)null)
			{
				((Component)val3).gameObject.SetActive(entry.Item.m_shared.m_maxQuality > 1);
			}
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		[HarmonyPatch("UpdateRecipe")]
		private static void ShowDetails(InventoryGui __instance, Player player)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			if ((Object)(object)button == (Object)null)
			{
				return;
			}
			if (mode == Mode.None)
			{
				if (((Component)button).gameObject.activeSelf)
				{
					((Component)button).gameObject.SetActive(false);
				}
				return;
			}
			try
			{
				bool flag = craftTimer.Invoke(__instance) >= 0f;
				((Component)button).gameObject.SetActive(!flag);
				((Selectable)__instance.m_craftButton).interactable = false;
				((Component)__instance.m_variantButton).gameObject.SetActive(false);
				((Component)__instance.m_qualityPanel).gameObject.SetActive(false);
				ItemData val = (ItemData)pairItem.GetValue(selectedRecipe.GetValue(__instance));
				CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
				if (val == null || (Object)(object)currentCraftingStation == (Object)null || !((Humanoid)player).GetInventory().ContainsItem(val))
				{
					DescribeNothing(__instance);
				}
				else if (mode == Mode.Recycle)
				{
					DescribeRecycle(__instance, player, currentCraftingStation, val, flag);
				}
				else
				{
					DescribeResearch(__instance, player, currentCraftingStation, val, flag);
				}
			}
			catch (Exception e)
			{
				LogOnce("describe", e);
			}
		}

		private static void DescribeRecycle(InventoryGui gui, Player player, CraftingStation station, ItemData item, bool crafting)
		{
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			RecycleInfo recycleInfo = Info(item, station);
			if (recycleInfo == null)
			{
				DescribeNothing(gui);
				return;
			}
			string text = Localization.instance.Localize(item.m_shared.m_name);
			int num = Count(item, recycleInfo.Batch);
			double durability = Recycling.DurabilityFactor(item);
			bool flag = Settings.Mode.Value == ReturnMode.Luck;
			bool flag2 = Recycling.IsInUse(player, item);
			bool flag3 = Recycling.RecipeAllowed(player, item);
			bool flag4 = LevelOk(station, recycleInfo);
			bool flag5 = station.CheckUsable(player, false);
			bool flag6 = (Object)(object)recycleInfo.Elsewhere != (Object)null;
			ShowItem(gui, item, text, "Recycle " + text, num);
			gui.m_recipeDecription.text = Tooltip(item) + "\n\n" + RecycleSummary(player, item, recycleInfo, durability, flag, flag2, flag3);
			GameObject[] recipeRequirementList = gui.m_recipeRequirementList;
			for (int i = 0; i < recipeRequirementList.Length; i++)
			{
				if (i < recycleInfo.Yields.Count)
				{
					Yield yield = recycleInfo.Yields[i];
					int num2 = yield.Max(num);
					ShowSlot(recipeRequirementList[i].transform, yield.Resource.m_itemData, (!flag) ? Recycling.FlatAmount(yield, num, durability).ToString() : ((num2 > 0) ? $"0-{num2}" : "0"));
				}
				else
				{
					InventoryGui.HideRequirement(recipeRequirementList[i].transform);
				}
			}
			((Component)gui.m_minStationLevelIcon).gameObject.SetActive(!flag6 && recycleInfo.RequiredLevel > 0);
			if (!flag6 && recycleInfo.RequiredLevel > 0)
			{
				gui.m_minStationLevelText.text = recycleInfo.RequiredLevel.ToString();
				((Graphic)gui.m_minStationLevelText).color = (flag4 ? levelBaseColor.Invoke(gui) : Color.red);
			}
			((Selectable)button).interactable = !flag6 && flag3 && !crafting && !flag2 && flag4 && flag5;
			buttonText.text = ((num > 1) ? $"Recycle x{num}" : "Recycle");
			SetTooltip(flag6 ? ("Recycle this at the " + Localization.instance.Localize(recycleInfo.Elsewhere.m_name) + ".") : ((!flag3) ? "You don't know how to craft this yet." : (flag2 ? "Unequip it first." : ((!flag4) ? Localization.instance.Localize("$msg_missingrequirement") : ((!flag5) ? Localization.instance.Localize("$msg_missingstation") : "")))));
		}

		private static void DescribeResearch(InventoryGui gui, Player player, CraftingStation station, ItemData item, bool crafting)
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			ResearchInfo researchInfo = Research.Evaluate(player, item);
			if (researchInfo == null)
			{
				DescribeNothing(gui);
				return;
			}
			string text = Localization.instance.Localize(item.m_shared.m_name);
			int num = Research.Available(player, researchInfo);
			bool flag = num >= researchInfo.Batch;
			bool flag2 = station.CheckUsable(player, false);
			ShowItem(gui, item, text, $"Research {text} ({researchInfo.Done}/{researchInfo.Needed})", 1);
			gui.m_recipeDecription.text = Tooltip(item) + "\n\n" + ResearchSummary(researchInfo, text, num);
			GameObject[] recipeRequirementList = gui.m_recipeRequirementList;
			for (int i = 0; i < recipeRequirementList.Length; i++)
			{
				if (i == 0)
				{
					ShowSlot(recipeRequirementList[i].transform, item, researchInfo.Batch.ToString(), flag ? Color.white : Color.red);
				}
				else
				{
					InventoryGui.HideRequirement(recipeRequirementList[i].transform);
				}
			}
			((Component)gui.m_minStationLevelIcon).gameObject.SetActive(false);
			((Selectable)button).interactable = flag && !crafting && flag2;
			buttonText.text = "Research";
			SetTooltip(flag ? ((!flag2) ? Localization.instance.Localize("$msg_missingstation") : "") : ((researchInfo.Batch > 1) ? $"One research needs {researchInfo.Batch} (unequipped)." : "Unequip it first."));
		}

		private static void DescribeNothing(InventoryGui gui)
		{
			((Behaviour)gui.m_recipeIcon).enabled = false;
			((Behaviour)gui.m_recipeName).enabled = false;
			((Behaviour)gui.m_recipeDecription).enabled = true;
			gui.m_recipeDecription.text = ((mode == Mode.Research) ? ("Nothing in your inventory to research.\n\nResearch things you can't craft yet. Each research uses one up " + $"(nothing comes back); after {Math.Max(1, Settings.ResearchCount.Value)} you learn to craft it.") : ("Nothing in your inventory can be recycled here.\n\nThis station recycles what it crafts" + (Settings.StationFreeItems.Value ? ", and things that need no station (hammer, torch...)." : ".")));
			((Component)gui.m_itemCraftType).gameObject.SetActive(false);
			((Component)gui.m_minStationLevelIcon).gameObject.SetActive(false);
			GameObject[] recipeRequirementList = gui.m_recipeRequirementList;
			for (int i = 0; i < recipeRequirementList.Length; i++)
			{
				InventoryGui.HideRequirement(recipeRequirementList[i].transform);
			}
			((Selectable)button).interactable = false;
			buttonText.text = ((mode == Mode.Research) ? "Research" : "Recycle");
			SetTooltip("");
		}

		private static void ShowItem(InventoryGui gui, ItemData item, string name, string craftType, int count)
		{
			((Behaviour)gui.m_recipeIcon).enabled = true;
			gui.m_recipeIcon.sprite = item.GetIcon();
			((Behaviour)gui.m_recipeName).enabled = true;
			gui.m_recipeName.text = ((item.m_stack > 1) ? $"{name} x{item.m_stack}" : name);
			((Behaviour)gui.m_recipeDecription).enabled = true;
			((Component)gui.m_itemCraftType).gameObject.SetActive(true);
			gui.m_itemCraftType.text = ((item.m_shared.m_maxQuality > 1) ? $"{craftType} (level {item.m_quality})" : ((count > 1) ? $"{craftType} x{count}" : craftType));
		}

		private static string Tooltip(ItemData item)
		{
			return Localization.instance.Localize(ItemData.GetTooltip(item, item.m_quality, false, (float)Game.m_worldLevel, -1, false));
		}

		private static void ShowSlot(Transform slot, ItemData shown, string amountText, Color? amountColor = null)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			Image component = ((Component)slot.Find("res_icon")).GetComponent<Image>();
			TMP_Text component2 = ((Component)slot.Find("res_name")).GetComponent<TMP_Text>();
			TMP_Text component3 = ((Component)slot.Find("res_amount")).GetComponent<TMP_Text>();
			((Component)component).gameObject.SetActive(true);
			((Component)component2).gameObject.SetActive(true);
			((Component)component3).gameObject.SetActive(true);
			component.sprite = shown.GetIcon();
			((Graphic)component).color = Color.white;
			component2.text = Localization.instance.Localize(shown.m_shared.m_name);
			component3.text = amountText;
			((Graphic)component3).color = (Color)(((??)amountColor) ?? Color.white);
			UITooltip component4 = ((Component)slot).GetComponent<UITooltip>();
			if ((Object)(object)component4 != (Object)null)
			{
				component4.m_text = component2.text;
			}
		}

		private static void SetTooltip(string text)
		{
			if ((Object)(object)buttonTooltip != (Object)null)
			{
				buttonTooltip.m_text = text;
			}
		}

		private static string RecycleSummary(Player player, ItemData item, RecycleInfo info, double durability, bool luck, bool inUse, bool known)
		{
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			if ((Object)(object)info.Elsewhere != (Object)null)
			{
				stringBuilder.Append("<color=yellow>Recycle this at the " + Localization.instance.Localize(info.Elsewhere.m_name) + ".</color>\n");
			}
			if (!known)
			{
				stringBuilder.Append("<color=yellow>You don't know how to craft this yet, so it can't be recycled.");
				if (Settings.ResearchEnabled.Value)
				{
					ResearchInfo researchInfo = Research.Evaluate(player, item);
					if (researchInfo != null)
					{
						stringBuilder.Append($" Research it to learn it ({researchInfo.Done}/{researchInfo.Needed}).");
					}
				}
				stringBuilder.Append("</color>\n");
			}
			float[] weights;
			float total;
			if (luck)
			{
				weights = Recycling.LuckWeights(player);
				total = Mathf.Max(0.0001f, weights[0] + weights[1] + weights[2] + weights[3] + weights[4]);
				stringBuilder.Append("<color=orange>Recycling (luck):</color> each material rolls " + Pct(0) + " crumbled, " + Pct(1) + " poor, " + Pct(2) + " normal, " + Pct(3) + " good, " + Pct(4) + " jackpot.");
				if (Settings.SkillEnabled.Value && (int)Plugin.RecyclingSkill != 0)
				{
					stringBuilder.Append($" Recycling skill: {Mathf.FloorToInt(((Character)player).GetSkillLevel(Plugin.RecyclingSkill))}.");
				}
			}
			else
			{
				stringBuilder.Append($"<color=orange>Recycling:</color> gives back {Mathf.Clamp(Settings.FlatPercent.Value, 0, 100)}% of what it cost to make.");
			}
			if (item.m_quality > 1)
			{
				stringBuilder.Append(Settings.IncludeUpgrades.Value ? " Upgrades included." : " Upgrade materials are not given back.");
			}
			if (durability < 1.0)
			{
				stringBuilder.Append($" Worn: {Mathf.RoundToInt((float)durability * 100f)}% of that.");
			}
			if (item.m_shared.m_maxStackSize > 1)
			{
				stringBuilder.Append($"\nClick recycles {Math.Min(item.m_stack, info.Batch)} (one craft makes {info.Batch}); hold Shift to recycle all {item.m_stack}.");
				if (info.Batch > 1)
				{
					stringBuilder.Append(" Less than a full craft gives back its share, rounded down.");
				}
			}
			if (inUse)
			{
				stringBuilder.Append("\n<color=red>Equipped: unequip it first.</color>");
			}
			return stringBuilder.ToString();
			string Pct(int i)
			{
				return Mathf.RoundToInt(100f * weights[i] / total) + "%";
			}
		}

		private static string ResearchSummary(ResearchInfo info, string name, int available)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"<color=orange>Research:</color> {info.Done} of {info.Needed} done. Each research uses up ");
			stringBuilder.Append((info.Batch > 1) ? $"{info.Batch} (one craft's worth)" : "the item");
			stringBuilder.Append("; nothing comes back.");
			stringBuilder.Append($" After {info.Needed} you can craft {info.LearnsName}");
			stringBuilder.Append(((Object)(object)info.CraftedAt != (Object)null) ? (" at the " + Localization.instance.Localize(info.CraftedAt.m_name)) : "");
			stringBuilder.Append((info.MadeIn != null) ? ("; it becomes " + name + " in the " + info.MadeIn + ".") : ".");
			if (available < info.Batch)
			{
				stringBuilder.Append((info.Batch > 1) ? $"\n<color=red>You have {available} unequipped; one research needs {info.Batch}.</color>" : "\n<color=red>Equipped: unequip it first.</color>");
			}
			return stringBuilder.ToString();
		}

		private static void OnButtonClicked(InventoryGui gui)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			Player player = Player.m_localPlayer;
			if (mode == Mode.None || (Object)(object)player == (Object)null || craftTimer.Invoke(gui) >= 0f)
			{
				return;
			}
			ItemData item = (ItemData)pairItem.GetValue(selectedRecipe.GetValue(gui));
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			if (item == null || (Object)(object)currentCraftingStation == (Object)null)
			{
				return;
			}
			string text = Localization.instance.Localize(item.m_shared.m_name);
			bool flag = item.m_shared.m_maxStackSize <= 1;
			ConfirmMode value = Settings.Confirm.Value;
			bool ask = value == ConfirmMode.Everything || (value == ConfirmMode.Gear && flag);
			Mode clickedIn = mode;
			if (mode == Mode.Recycle)
			{
				RecycleInfo recycleInfo = Info(item, currentCraftingStation);
				if (recycleInfo != null && !((Object)(object)recycleInfo.Elsewhere != (Object)null))
				{
					int count = Count(item, recycleInfo.Batch);
					string text2 = ((item.m_shared.m_maxQuality > 1) ? $"{text} (level {item.m_quality})" : ((count > 1) ? $"{text} x{count}" : text));
					Run(gui, ask, "Recycle?", "Recycle " + text2 + "?\n\n" + RecycleBack(recycleInfo, item, count), text2, clickedIn, () => Recycling.Recycle(player, player.GetCurrentCraftingStation(), item, count));
				}
				return;
			}
			ResearchInfo researchInfo = Research.Evaluate(player, item);
			if (researchInfo != null)
			{
				string text3 = ((item.m_shared.m_maxQuality > 1) ? $"{text} (level {item.m_quality})" : ((researchInfo.Batch > 1) ? $"{text} x{researchInfo.Batch}" : text));
				Run(gui, ask, "Research?", "Research " + text3 + "? It will be used up, and nothing comes back.\n\n" + $"Research {researchInfo.Done + 1} of {researchInfo.Needed}.", text3, clickedIn, () => Research.DoResearch(player, player.GetCurrentCraftingStation(), item));
			}
		}

		private static string RecycleBack(RecycleInfo info, ItemData item, int count)
		{
			bool flag = Settings.Mode.Value == ReturnMode.Luck;
			double durability = Recycling.DurabilityFactor(item);
			List<string> list = new List<string>();
			foreach (Yield yield in info.Yields)
			{
				int num = (flag ? yield.Max(count) : Recycling.FlatAmount(yield, count, durability));
				if (num > 0)
				{
					list.Add($"{num} {Localization.instance.Localize(yield.Resource.m_itemData.m_shared.m_name)}");
				}
			}
			if (list.Count != 0)
			{
				return (flag ? "You get back up to: " : "You get back: ") + string.Join(", ", list);
			}
			return "You get nothing back.";
		}

		private static void Run(InventoryGui gui, bool ask, string header, string text, string what, Mode clickedIn, Func<bool> action)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_00d9: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Expected O, but got Unknown
			//IL_00c7: 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_00d2: Expected O, but got Unknown
			if (!ask)
			{
				Act();
				return;
			}
			if (!UnifiedPopup.IsAvailable())
			{
				ItemData val = (ItemData)pairItem.GetValue(selectedRecipe.GetValue(gui));
				if (pendingItem == val && Time.time - pendingSince < 4f)
				{
					pendingItem = null;
					Act();
					return;
				}
				pendingItem = val;
				pendingSince = Time.time;
				Player localPlayer = Player.m_localPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, "Click again to confirm: " + what, 0, (Sprite)null, false);
				}
				return;
			}
			PopupButtonCallback val2 = delegate
			{
				UnifiedPopup.Pop();
				Act();
			};
			object obj = <>c.<>9__59_2;
			if (obj == null)
			{
				PopupButtonCallback val3 = delegate
				{
					UnifiedPopup.Pop();
				};
				<>c.<>9__59_2 = val3;
				obj = (object)val3;
			}
			UnifiedPopup.Push((PopupBase)new YesNoPopup(header, text, val2, (PopupButtonCallback)obj, false, true));
			void Act()
			{
				if (mode != clickedIn || (Object)(object)Player.m_localPlayer == (Object)null || !InventoryGui.IsVisible() || craftTimer.Invoke(gui) >= 0f)
				{
					return;
				}
				try
				{
					if (action())
					{
						cachedVersion = -1;
						updateCraftingPanel.Invoke(gui, new object[1] { false });
					}
				}
				catch (Exception e)
				{
					LogOnce("action", e);
				}
			}
		}

		private static int Count(ItemData item, int batch)
		{
			if (!UnityInput.Current.GetKey((KeyCode)304) && !UnityInput.Current.GetKey((KeyCode)303) && !ZInput.GetButton("JoyLStick"))
			{
				return Math.Min(item.m_stack, batch);
			}
			return item.m_stack;
		}

		private static bool LevelOk(CraftingStation station, RecycleInfo info)
		{
			if (Settings.RequireStationLevel.Value)
			{
				return station.GetLevel(true) >= info.RequiredLevel;
			}
			return true;
		}

		private static RecycleInfo Info(ItemData item, CraftingStation station)
		{
			if (item != cachedItem || (Object)(object)station != (Object)(object)cachedStation || item.m_quality != cachedQuality || cachedVersion != Settings.Version)
			{
				cachedItem = item;
				cachedStation = station;
				cachedQuality = item.m_quality;
				cachedVersion = Settings.Version;
				cachedInfo = Recycling.EvaluateAnywhere(item, station);
			}
			return cachedInfo;
		}

		private static void LogOnce(string where, Exception e)
		{
			if (loggedErrors.Add(where + e.GetType().Name + e.Message))
			{
				Plugin.Log.LogError((object)$"[{where}] {e}");
			}
		}
	}
	internal sealed class Yield
	{
		public ItemDrop Resource;

		public string Prefab;

		public double PerUnit;

		public int Max(int count)
		{
			return (int)Math.Floor(PerUnit * (double)count + 1E-06);
		}
	}
	internal sealed class RecycleInfo
	{
		public ItemData Item;

		public Recipe Recipe;

		public int Batch;

		public int RequiredLevel;

		public CraftingStation Elsewhere;

		public readonly List<Yield> Yields = new List<Yield>();
	}
	internal enum Outcome
	{
		Crumbled,
		Poor,
		Normal,
		Good,
		Jackpot
	}
	internal static class Recycling
	{
		private static readonly FieldRef<Humanoid, ItemData> hiddenLeft = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_hiddenLeftItem");

		private static readonly FieldRef<Humanoid, ItemData> hiddenRight = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_hiddenRightItem");

		private static readonly Dictionary<string, List<Recipe>> recipesByItem = new Dictionary<string, List<Recipe>>();

		private static ObjectDB recipesFrom;

		private static int recipesCount = -1;

		public static bool IsInUse(Player player, ItemData item)
		{
			if (!item.m_equipped && item != hiddenLeft.Invoke((Humanoid)(object)player))
			{
				return item == hiddenRight.Invoke((Humanoid)(object)player);
			}
			return true;
		}

		public static bool RecipeAllowed(Player player, ItemData item)
		{
			if (Settings.OnlyKnownRecipes.Value)
			{
				return player.IsRecipeKnown(item.m_shared.m_name);
			}
			return true;
		}

		public static RecycleInfo Evaluate(ItemData item, CraftingStation station)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (item == null || (Object)(object)station == (Object)null || Settings.IsExcludedType(item.m_shared.m_itemType))
			{
				return null;
			}
			List<Recipe> list = RecipesFor(item);
			if (list == null)
			{
				return null;
			}
			int num = Math.Max(1, item.m_quality);
			Recipe val = null;
			Recipe val2 = null;
			foreach (Recipe item2 in list)
			{
				if (item2.m_requireOnlyOneIngredient)
				{
					return null;
				}
				CraftingStation requiredStation = item2.GetRequiredStation(num);
				if ((Object)(object)requiredStation == (Object)null)
				{
					val2 = val2 ?? item2;
				}
				else if (requiredStation.m_name == station.m_name && ((Object)(object)val == (Object)null || item2.m_amount < val.m_amount))
				{
					val = item2;
				}
			}
			Recipe val3 = val ?? (Settings.StationFreeItems.Value ? val2 : null);
			if ((Object)(object)val3 == (Object)null || Settings.IsBlockedItem(((Object)val3.m_item).name))
			{
				return null;
			}
			RecycleInfo recycleInfo = new RecycleInfo
			{
				Item = item,
				Recipe = val3,
				Batch = ((item.m_shared.m_maxStackSize <= 1) ? 1 : Math.Max(1, val3.m_amount)),
				RequiredLevel = (((Object)(object)val3.GetRequiredStation(num) != (Object)null) ? val3.GetRequiredStationLevel(num) : 0)
			};
			AddYields(recycleInfo, list, num);
			if (recycleInfo.Yields.Count <= 0)
			{
				return null;
			}
			return recycleInfo;
		}

		public static RecycleInfo EvaluateAnywhere(ItemData item, CraftingStation station)
		{
			RecycleInfo recycleInfo = Evaluate(item, station);
			if (recycleInfo != null || item == null || (Object)(object)station == (Object)null)
			{
				return recycleInfo;
			}
			List<Recipe> list = RecipesFor(item);
			if (list == null)
			{
				return null;
			}
			foreach (Recipe item2 in list)
			{
				CraftingStation requiredStation = item2.GetRequiredStation(Math.Max(1, item.m_quality));
				if ((Object)(object)requiredStation != (Object)null && requiredStation.m_name != station.m_name && Settings.IsStationAllowed(requiredStation))
				{
					recycleInfo = Evaluate(item, requiredStation);
					if (recycleInfo != null)
					{
						recycleInfo.Elsewhere = requiredStation;
						return recycleInfo;
					}
				}
			}
			return null;
		}

		private static void AddYields(RecycleInfo info, List<Recipe> recipes, int quality)
		{
			Dictionary<string, Yield> dictionary = null;
			Requirement[] array;
			foreach (Recipe recipe in recipes)
			{
				Dictionary<string, Yield> dictionary2 = new Dictionary<string, Yield>();
				array = recipe.m_resources ?? Array.Empty<Requirement>();
				foreach (Requirement val in array)
				{
					if (val == null || (Object)(object)val.m_resItem == (Object)null || !val.m_recover || val.m_upgraderResource)
					{
						continue;
					}
					int num = 0;
					int num2 = ((!Settings.IncludeUpgrades.Value) ? 1 : quality);
					for (int j = 1; j <= num2; j++)
					{
						num += val.GetAmount(j);
					}
					if (num > 0)
					{
						string name = ((Object)val.m_resItem).name;
						double num3 = (double)num / (double)Math.Max(1, recipe.m_amount);
						if (dictionary2.TryGetValue(name, out var value))
						{
							value.PerUnit += num3;
							continue;
						}
						dictionary2[name] = new Yield
						{
							Resource = val.m_resItem,
							Prefab = name,
							PerUnit = num3
						};
					}
				}
				if (dictionary == null)
				{
					dictionary = dictionary2;
					continue;
				}
				foreach (string item in new List<string>(dictionary.Keys))
				{
					if (dictionary2.TryGetValue(item, out var value2))
					{
						dictionary[item].PerUnit = Math.Min(dictionary[item].PerUnit, value2.PerUnit);
					}
					else
					{
						dictionary.Remove(item);
					}
				}
			}
			array = info.Recipe.m_resources ?? Array.Empty<Requirement>();
			foreach (Requirement val2 in array)
			{
				if (!((Object)(object)val2?.m_resItem == (Object)null) && dictionary != null && dictionary.TryGetValue(((Object)val2.m_resItem).name, out var value3))
				{
					dictionary.Remove(((Object)val2.m_resItem).name);
					if (value3.PerUnit > 0.0 && !Settings.IsBlockedMaterial(value3.Prefab))
					{
						info.Yields.Add(value3);
					}
				}
			}
		}

		internal static List<Recipe> RecipesFor(ItemData item)
		{
			return RecipesFor(item.m_shared.m_name);
		}

		internal static List<Recipe> RecipesFor(string sharedName)
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			if ((Object)(object)instance != (Object)(object)recipesFrom || instance.m_recipes.Count != recipesCount)
			{
				recipesByItem.Clear();
				foreach (Recipe recipe in instance.m_recipes)
				{
					if (!((Object)(object)recipe == (Object)null) && recipe.m_enabled && !((Object)(object)recipe.m_item == (Object)null))
					{
						string name = recipe.m_item.m_itemData.m_shared.m_name;
						if (!recipesByItem.TryGetValue(name, out var value))
						{
							value = (recipesByItem[name] = new List<Recipe>());
						}
						value.Add(recipe);
					}
				}
				recipesFrom = instance;
				recipesCount = instance.m_recipes.Count;
			}
			if (!recipesByItem.TryGetValue(sharedName, out var value2))
			{
				return null;
			}
			return value2;
		}

		public static double DurabilityFactor(ItemData item)
		{
			if (!Settings.DurabilityAffectsReturn.Value || !item.m_shared.m_useDurability)
			{
				return 1.0;
			}
			float durabilityPercentage = item.GetDurabilityPercentage();
			if (!float.IsNaN(durabilityPercentage) && !float.IsInfinity(durabilityPercentage))
			{
				return 0.5 + 0.5 * (double)Mathf.Clamp01(durabilityPercentage);
			}
			return 1.0;
		}

		public static int FlatAmount(Yield yield, int count, double durability)
		{
			double num = (double)Mathf.Clamp(Settings.FlatPercent.Value, 0, 100) / 100.0 * durability;
			return Math.Min(yield.Max(count), (int)Math.Floor(yield.PerUnit * (double)count * num + 1E-06));
		}

		public static float[] LuckWeights(Player player)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			float num = ((Settings.SkillEnabled.Value && (int)Plugin.RecyclingSkill != 0 && (Object)(object)player != (Object)null) ? ((Character)player).GetSkillFactor(Plugin.RecyclingSkill) : 0f);
			float num2 = Settings.CrumbleWeight.Value * num * Settings.SkillCrumbleReduction.Value;
			float[] array = new float[5]
			{
				Settings.CrumbleWeight.Value - num2,
				Settings.PoorWeight.Value,
				Settings.NormalWeight.Value,
				Settings.GoodWeight.Value + num2,
				Settings.JackpotWeight.Value * (1f + num * Settings.SkillJackpotBonus.Value)
			};
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = Mathf.Max(0f, array[i]);
			}
			return array;
		}

		private static Outcome Roll(float[] weights)
		{
			float num = 0f;
			foreach (float num2 in weights)
			{
				num += num2;
			}
			if (num <= 0f)
			{
				return Outcome.Crumbled;
			}
			float num3 = Random.Range(0f, num);
			for (int j = 0; j < weights.Length; j++)
			{
				if (num3 < weights[j])
				{
					return (Outcome)j;
				}
				num3 -= weights[j];
			}
			return Outcome.Normal;
		}

		private static int LuckAmount(Yield yield, int count, double durability, Outcome outcome)
		{
			int num = yield.Max(count);
			(float, float) tuple;
			switch (outcome)
			{
			case Outcome.Jackpot:
				return num;
			case Outcome.Poor:
				tuple = Settings.Range(Settings.PoorRange);
				break;
			case Outcome.Normal:
				tuple = Settings.Range(Settings.NormalRange);
				break;
			case Outcome.Good:
				tuple = Settings.Range(Settings.GoodRange);
				break;
			default:
				return 0;
			}
			double num2 = yield.PerUnit * (double)count * (double)Random.Range(tuple.Item1, tuple.Item2) * durability;
			int num3 = (int)Math.Floor(num2);
			if ((double)Random.value < num2 - (double)num3)
			{
				num3++;
			}
			return Mathf.Clamp(num3, 0, num);
		}

		public static bool Recycle(Player player, CraftingStation station, ItemData item, int count)
		{
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Unknown result type (might be due to invalid IL or missing references)
			//IL_031a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (!inventory.ContainsItem(item) || !Settings.IsStationAllowed(station))
			{
				return false;
			}
			RecycleInfo recycleInfo = Evaluate(item, station);
			if (recycleInfo == null)
			{
				return false;
			}
			if (IsInUse(player, item))
			{
				((Character)player).Message((MessageType)2, "Unequip it before recycling it.", 0, (Sprite)null, false);
				return false;
			}
			if (!RecipeAllowed(player, item))
			{
				((Character)player).Message((MessageType)2, "You don't know how to craft this yet. Research it first.", 0, (Sprite)null, false);
				return false;
			}
			if (Settings.RequireStationLevel.Value && station.GetLevel(true) < recycleInfo.RequiredLevel)
			{
				((Character)player).Message((MessageType)2, "$msg_missingrequirement", 0, (Sprite)null, false);
				return false;
			}
			if (!station.CheckUsable(player, true))
			{
				return false;
			}
			count = Mathf.Clamp(count, 1, item.m_stack);
			double durability = DurabilityFactor(item);
			bool flag = Settings.Mode.Value == ReturnMode.Luck;
			float[] weights = (flag ? LuckWeights(player) : null);
			List<KeyValuePair<ItemDrop, int>> list = new List<KeyValuePair<ItemDrop, int>>();
			bool flag2 = false;
			foreach (Yield yield in recycleInfo.Yields)
			{
				int num;
				if (flag)
				{
					Outcome outcome = Roll(weights);
					flag2 = flag2 || outcome == Outcome.Jackpot;
					num = LuckAmount(yield, count, durability, outcome);
				}
				else
				{
					num = FlatAmount(yield, count, durability);
				}
				if (num > 0)
				{
					list.Add(new KeyValuePair<ItemDrop, int>(yield.Resource, num));
				}
			}
			string text = Localization.instance.Localize(item.m_shared.m_name);
			item.GetIcon();
			int quality = item.m_quality;
			if (!((count >= item.m_stack) ? inventory.RemoveItem(item) : inventory.RemoveItem(item, count)))
			{
				return false;
			}
			foreach (KeyValuePair<ItemDrop, int> item2 in list)
			{
				Give(player, item2.Key, item2.Value);
				((Character)player).Message((MessageType)1, "$msg_added " + item2.Key.m_itemData.m_shared.m_name, item2.Value, item2.Key.m_itemData.GetIcon(), false);
			}
			string text2 = ((list.Count == 0) ? " - it crumbled to nothing" : (flag2 ? " - jackpot!" : ""));
			((Character)player).Message((MessageType)2, "Recycled " + text + ((count > 1) ? (" x" + count) : "") + text2, 0, (Sprite)null, false);
			Plugin.Log.LogDebug((object)($"Recycled {item.m_shared.m_name} x{count} (q{quality}) at {station.m_name}: " + string.Join(", ", list.ConvertAll((KeyValuePair<ItemDrop, int> g) => ((Object)g.Key).name + " x" + g.Value))));
			station.m_craftItemEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
			if (flag && Settings.SkillEnabled.Value && (int)Plugin.RecyclingSkill != 0)
			{
				float num2 = ((item.m_shared.m_maxStackSize > 1) ? ((float)count / (float)recycleInfo.Batch) : ((float)quality));
				((Character)player).RaiseSkill(Plugin.RecyclingSkill, Settings.SkillXp.Value * Mathf.Max(0.1f, num2));
			}
			return true;
		}

		private static void Give(Player player, ItemDrop resource, int amount)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			Inventory inventory = ((Humanoid)player).GetInventory();
			int val = Math.Max(1, resource.m_itemData.m_shared.m_maxStackSize);
			while (amount > 0)
			{
				ItemData val2 = resource.m_itemData.Clone();
				val2.m_dropPrefab = ((Component)resource).gameObject;
				val2.m_stack = Math.Min(amount, val);
				val2.m_worldLevel = Game.m_worldLevel;
				val2.m_durability = val2.GetMaxDurability();
				amount -= val2.m_stack;
				if (!inventory.AddItem(val2) && val2.m_stack > 0)
				{
					Transform transform = ((Component)player).transform;
					ItemDrop.DropItem(val2, val2.m_stack, transform.position + transform.forward * 0.7f + Vector3.up, transform.rotation);
				}
			}
		}
	}
	internal sealed class ResearchInfo
	{
		public ItemData Item;

		public Recipe Recipe;

		public string Prefab;

		public int Batch;

		public int Done;

		public int Needed;

		public CraftingStation CraftedAt;

		public string LearnsName;

		public string MadeIn;
	}
	internal static class Research
	{
		private const string KeyPrefix = "theshack.recycler.research.";

		private static readonly MethodInfo addKnownRecipe = AccessTools.Method(typeof(Player), "AddKnownRecipe", new Type[1] { typeof(Recipe) }, (Type[])null);

		private static readonly Dictionary<string, List<(ItemDrop from, int produced, string station)>> madeFrom = new Dictionary<string, List<(ItemDrop, int, string)>>();

		private static ZNetScene madeFromScene;

		public static ResearchInfo Evaluate(Player player, ItemData item)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || item == null || Settings.IsResearchExcluded(item.m_shared.m_itemType))
			{
				return null;
			}
			List<Recipe> list = Recycling.RecipesFor(item);
			int produced = 1;
			string station = null;
			if (list == null && TryMadeFrom(item.m_shared.m_name, out var source, out produced, out station))
			{
				list = Recycling.RecipesFor(source.m_itemData.m_shared.m_name);
			}
			if (list == null)
			{
				return null;
			}
			Recipe val = null;
			foreach (Recipe item2 in list)
			{
				if ((Object)(object)val == (Object)null || item2.m_amount < val.m_amount)
				{
					val = item2;
				}
			}
			string name = val.m_item.m_itemData.m_shared.m_name;
			if (player.IsRecipeKnown(name) || Settings.IsBlockedItem(((Object)val.m_item).name) || ((Object)(object)item.m_dropPrefab != (Object)null && Settings.IsBlockedItem(((Object)item.m_dropPrefab).name)))
			{
				return null;
			}
			return new ResearchInfo
			{
				Item = item,
				Recipe = val,
				Prefab = ((Object)val.m_item).name,
				Batch = ((item.m_shared.m_maxStackSize <= 1) ? 1 : (Math.Max(1, val.m_amount) * Math.Max(1, produced))),
				Done = Progress(player, ((Object)val.m_item).name),
				Needed = Math.Max(1, Settings.ResearchCount.Value),
				CraftedAt = val.GetRequiredStation(1),
				LearnsName = Localization.instance.Localize(name),
				MadeIn = station
			};
		}

		private static bool TryMadeFrom(string sharedName, out ItemDrop source, out int produced, out string station)
		{
			source = null;
			produced = 1;
			station = null;
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			if ((Object)(object)instance != (Object)(object)madeFromScene)
			{
				madeFromScene = instance;
				madeFrom.Clear();
				foreach (GameObject prefab in instance.m_prefabs)
				{
					if ((Object)(object)prefab == (Object)null)
					{
						continue;
					}
					Piece component = prefab.GetComponent<Piece>();
					string station2 = Localization.instance.Localize(((Object)(object)component != (Object)null) ? component.m_name : ((Object)prefab).name);
					Fermenter component2 = prefab.GetComponent<Fermenter>();
					if ((Object)(object)component2 != (Object)null)
					{
						foreach (ItemConversion item3 in component2.m_conversion)
						{
							AddMadeFrom(item3.m_from, item3.m_to, item3.m_producedItems, station2);
						}
					}
					CookingStation component3 = prefab.GetComponent<CookingStation>();
					if (!((Object)(object)component3 != (Object)null))
					{
						continue;
					}
					foreach (ItemConversion item4 in component3.m_conversion)
					{
						AddMadeFrom(item4.m_from, item4.m_to, 1, station2);
					}
				}
			}
			if (!madeFrom.TryGetValue(sharedName, out List<(ItemDrop, int, string)> value))
			{
				return false;
			}
			foreach (var item5 in value)
			{
				if (Recycling.RecipesFor(item5.Item1.m_itemData.m_shared.m_name) != null)
				{
					ItemDrop item = item5.Item1;
					int num = Math.Max(1, item5.Item2);
					string item2 = item5.Item3;
					source = item;
					produced = num;
					station = item2;
					return true;
				}
			}
			return false;
		}

		private static void AddMadeFrom(ItemDrop from, ItemDrop to, int produced, string station)
		{
			if (!((Object)(object)from == (Object)null) && !((Object)(object)to == (Object)null))
			{
				string name = to.m_itemData.m_shared.m_name;
				if (!madeFrom.TryGetValue(name, out List<(ItemDrop, int, string)> value))
				{
					value = (madeFrom[name] = new List<(ItemDrop, int, string)>());
				}
				value.Add((from, produced, station));
			}
		}

		public static int Progress(Player player, string prefab)
		{
			if (!player.m_customData.TryGetValue("theshack.recycler.research." + prefab, out var value) || !int.TryParse(value, out var result))
			{
				return 0;
			}
			return result;
		}

		public static int Available(Player player, ResearchInfo info)
		{
			if (info.Item.m_shared.m_maxStackSize <= 1)
			{
				return (!Recycling.IsInUse(player, info.Item)) ? 1 : 0;
			}
			int num = 0;
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				if (allItem.m_shared.m_name == info.Item.m_shared.m_name && !Recycling.IsInUse(player, allItem))
				{
					num += allItem.m_stack;
				}
			}
			return num;
		}

		public static bool DoResearch(Player player, CraftingStation station, ItemData item)
		{
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
			if (!((Humanoid)player).GetInventory().ContainsItem(item) || !Settings.IsResearchStation(station))
			{
				return false;
			}
			ResearchInfo researchInfo = Evaluate(player, item);
			if (researchInfo == null)
			{
				return false;
			}
			if (Available(player, researchInfo) < researchInfo.Batch)
			{
				((Character)player).Message((MessageType)2, (researchInfo.Batch > 1) ? $"One research needs {researchInfo.Batch} (unequipped)." : "Unequip it before researching it.", 0, (Sprite)null, false);
				return false;
			}
			if (!station.CheckUsable(player, true))
			{
				return false;
			}
			string arg = Localization.instance.Localize(item.m_shared.m_name);
			if (!UseUp(player, item, researchInfo.Batch))
			{
				return false;
			}
			int num = researchInfo.Done + 1;
			if (num >= researchInfo.Needed)
			{
				player.m_customData.Remove("theshack.recycler.research." + researchInfo.Prefab);
				addKnownRecipe.Invoke(player, new object[1] { researchInfo.Recipe });
				string text = (((Object)(object)researchInfo.CraftedAt != (Object)null) ? (" at the " + Localization.instance.Localize(researchInfo.CraftedAt.m_name)) : "");
				((Character)player).Message((MessageType)2, "Research complete: you can now craft " + researchInfo.LearnsName + text + "!", 0, (Sprite)null, false);
			}
			else
			{
				player.m_customData["theshack.recycler.research." + researchInfo.Prefab] = num.ToString();
				((Character)player).Message((MessageType)2, $"Researched {arg}: {num} / {researchInfo.Needed}", 0, (Sprite)null, false);
			}
			Plugin.Log.LogDebug((object)$"Researched {researchInfo.Prefab} x{researchInfo.Batch}: {num}/{researchInfo.Needed}");
			station.m_craftItemEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
			return true;
		}

		private static bool UseUp(Player player, ItemData selected, int amount)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (selected.m_shared.m_maxStackSize <= 1)
			{
				return inventory.RemoveItem(selected);
			}
			List<ItemData> list = new List<ItemData>();
			if (!Recycling.IsInUse(player, selected))
			{
				list.Add(selected);
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != selected && allItem.m_shared.m_name == selected.m_shared.m_name && !Recycling.IsInUse(player, allItem))
				{
					list.Add(allItem);
				}
			}
			foreach (ItemData item in list)
			{
				int num = Math.Min(amount, item.m_stack);
				if (num > 0 && ((num >= item.m_stack) ? inventory.RemoveItem(item) : inventory.RemoveItem(item, num)))
				{
					amount -= num;
				}
				if (amount <= 0)
				{
					return true;
				}
			}
			return amount <= 0;
		}
	}
}