Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Shack Recycler v1.1.0
ShackRecycler.dll
Decompiled a day agousing 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; } } }