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 Enigmas Item Salvager v0.1.4
EnigmasItemSalvager.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.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.Bootstrap; 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.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.0.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 EnigmasItemSalvager { [BepInPlugin("EnigmaPrime.EnigmasItemSalvager", "Enigma's Item Salvager", "0.1.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "EnigmaPrime.EnigmasItemSalvager"; public const string PluginName = "Enigma's Item Salvager"; public const string PluginVersion = "0.1.4"; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; if (!((BaseUnityPlugin)this).Config.Bind<bool>("Developer", "ModEnabled", true, "Turns the whole mod on or off (needs a restart). Off: no patches, no skill, no buttons, as if the mod were not installed. For testing conflicts with other mods.").Value) { Log.LogWarning((object)"Enigma's Item Salvager 0.1.4 is switched off (Developer > ModEnabled = false). Nothing was patched."); return; } SalvageConfig.Bind(((BaseUnityPlugin)this).Config); ScrapRules.Bind(((BaseUnityPlugin)this).Config); SortInventory.Bind(((BaseUnityPlugin)this).Config); SalvagingSkill.Register(); _harmony = new Harmony("EnigmaPrime.EnigmasItemSalvager"); _harmony.PatchAll(); foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods()) { Log.LogInfo((object)("Patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name)); } Log.LogInfo((object)"Enigma's Item Salvager 0.1.4 loaded."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class SalvageConfig { public static ConfigEntry<float> Floor; public static ConfigEntry<float> Ceiling; public static ConfigEntry<float> Jitter; public static ConfigEntry<float> SalvagingBonus; public static ConfigEntry<float> SalvagingXp; public static ConfigEntry<string> TestLogFolder; public static ConfigEntry<int> TooltipSize; public static string Big(string text) { if (string.IsNullOrEmpty(text)) { return text; } int num = ((TooltipSize == null) ? 100 : TooltipSize.Value); if (num != 100) { return "<size=" + num + "%>" + text + "</size>"; } return text; } public static void Bind(ConfigFile cfg) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 1f); Floor = cfg.Bind<float>("Return", "Floor", 0.2f, new ConfigDescription("Best-case return fraction for the most recoverable ingredient at Crafting skill 0. Skill 0 must still return something, so keep this above 0.", (AcceptableValueBase)(object)val, Array.Empty<object>())); Ceiling = cfg.Bind<float>("Return", "Ceiling", 0.7f, new ConfigDescription("Best-case return fraction for the most recoverable ingredient at Crafting skill 100, before the Salvaging skill bonus.", (AcceptableValueBase)(object)val, Array.Empty<object>())); Jitter = cfg.Bind<float>("Return", "Jitter", 0.1f, new ConfigDescription("Each salvage rolls somewhere between (best case minus Jitter) and the best case. Raise for more variation, set 0 for none.", (AcceptableValueBase)(object)val, Array.Empty<object>())); SalvagingBonus = cfg.Bind<float>("Return", "SalvagingBonus", 0.1f, new ConfigDescription("Extra return fraction added at Salvaging skill 100 (scales evenly from 0). With the defaults, best case at max skill in both is 0.70 + 0.10 = 0.80.", (AcceptableValueBase)(object)val, Array.Empty<object>())); SalvagingXp = cfg.Bind<float>("Skill", "SalvagingXpPerSalvage", 1f, new ConfigDescription("Salvaging skill experience gained for each item salvaged (crafting gives 1 per item).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>())); TooltipSize = cfg.Bind<int>("Display", "TooltipSizePercent", 110, new ConfigDescription("Size of the hover text on the Salvage tab's button and ingredient squares, in percent. 100 is the game's normal size.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 400), Array.Empty<object>())); TestLogFolder = cfg.Bind<string>("Developer", "TestLogFolder", "", "Folder where the salvagetest command also saves its results as salvagetest-log.txt. Leave empty to use BepInEx\\EnigmasItemSalvager. Developer use only."); } } internal static class IngredientWeights { private struct Tier { public string Label; public float Weight; public string[] Names; public Tier(string label, float weight, params string[] names) { Label = label; Weight = weight; Names = names; } } private const float DefaultWeight = 0.3f; private const string DefaultTier = "Common materials (default)"; private static readonly Tier[] Tiers = new Tier[5] { new Tier("Metal bars", 1f, "Copper", "Tin", "Bronze", "Iron", "Silver", "BlackMetal", "FlametalNew", "Gold"), new Tier("Rare refined materials and gems", 0.85f, "Eitr", "Crystal", "SurtlingCore", "MoltenCore", "ProustitePowder", "Ruby", "AmberPearl", "CrownJewel"), new Tier("Crafted gear used as an ingredient", 0.8f, "AtgeirGold", "AxeBerzerkr", "AxeGold", "BattleaxeGold", "BowAshlands", "BowGold", "CrossbowGold", "CrossbowRipper", "FistGold", "KnifeGold", "MaceEldner", "MaceGold", "SledgeGold", "SpearGold", "SpearSplitner", "SwordGold", "SwordNiedhogg", "THSwordGold", "THSwordSlayer"), new Tier("Rare drops and trophies", 0.7f, "OrbThunderBlood", "OrbFrostFire", "WolfFang", "WolfClaw", "BonemawSerpentTooth", "SerpentScale", "Carapace", "Chitin", "Mandible", "MorgenHeart", "YmirRemains", "FreezeGland", "Bilebag", "GiantBloodSack", "Wisp", "FaderEmber", "CelestialFeather", "DyrnwynHiltFragment", "DyrnwynBladeFragment", "DyrnwynTipFragment", "BjornPaw", "UndeadBjornRibcage", "AskBladder", "MoleClaws", "HardAntler", "Voidplasm", "Ectoplasm", "BlobVial", "AncientSeed", "WrithanRoots", "Ooze", "OozeMork", "Guck", "BarkaBranch", "ScytheHandle", "Needle", "Hook"), new Tier("Fasteners, hides and threads", 0.5f, "Chain", "SulfurStone", "TrollHide", "LoxPelt", "BjornHide", "ScaleHide", "AskHide", "MooseHide", "SealHide", "WolfPelt", "WolfHairBundle", "ElakingHairBundle", "LinenThread", "NornThread", "MooseSinew", "MorgenSinew") }; private static readonly string[] AlwaysNames = new string[0]; private const string AlwaysLabel = "Irreplaceable, cannot be crafted (always returned)"; private static readonly string[][] PrefixRules = new string[2][] { new string[3] { "Trophy", "1.00", "Trophies (single item, chance to keep scales with skill)" }, new string[3] { "Gemstone", "0.85", "Rare refined materials and gems" } }; private static Dictionary<string, float> _defaults; private static Dictionary<string, string> _labels; private static Dictionary<string, float> _overrides; private static HashSet<string> _always; private static bool _loaded; private static string FilePath => Path.Combine(Paths.ConfigPath, "EnigmaPrime.EnigmasItemSalvager.weights.txt"); public static bool IsAlways(string prefab) { EnsureLoaded(); return _always.Contains(prefab); } public static float Get(string prefab) { EnsureLoaded(); if (_overrides.TryGetValue(prefab, out var value)) { return value; } return DefaultFor(prefab); } public static string TierOf(string prefab) { EnsureLoaded(); if (_labels.TryGetValue(prefab, out var value)) { return value; } string[][] prefixRules = PrefixRules; foreach (string[] array in prefixRules) { if (prefab.StartsWith(array[0], StringComparison.Ordinal)) { return array[2]; } } return "Common materials (default)"; } private static float DefaultFor(string prefab) { if (Array.IndexOf(AlwaysNames, prefab) >= 0) { return 1f; } if (_defaults.TryGetValue(prefab, out var value)) { return value; } string[][] prefixRules = PrefixRules; foreach (string[] array in prefixRules) { if (prefab.StartsWith(array[0], StringComparison.Ordinal)) { return float.Parse(array[1], CultureInfo.InvariantCulture); } } return 0.3f; } private static void EnsureLoaded() { if (_loaded) { return; } _loaded = true; _defaults = new Dictionary<string, float>(StringComparer.Ordinal); _labels = new Dictionary<string, string>(StringComparer.Ordinal); _overrides = new Dictionary<string, float>(StringComparer.Ordinal); _always = new HashSet<string>(AlwaysNames, StringComparer.Ordinal); string[] alwaysNames = AlwaysNames; foreach (string key in alwaysNames) { _labels[key] = "Irreplaceable, cannot be crafted (always returned)"; } Tier[] tiers = Tiers; for (int i = 0; i < tiers.Length; i++) { Tier tier = tiers[i]; alwaysNames = tier.Names; foreach (string key2 in alwaysNames) { _defaults[key2] = tier.Weight; _labels[key2] = tier.Label; } } try { if (File.Exists(FilePath)) { ReadOverrides(); } else { WriteTemplate(); } } catch (Exception ex) { Plugin.Log.LogError((object)("Ingredient weights file problem, using built-in defaults: " + ex.Message)); } } private static void ReadOverrides() { string[] array = File.ReadAllLines(FilePath); for (int i = 0; i < array.Length; i++) { string text = array[i]; int num = text.IndexOf('#'); if (num >= 0) { text = text.Substring(0, num); } int num2 = text.IndexOf('='); if (num2 <= 0) { continue; } string text2 = text.Substring(0, num2).Trim(); string text3 = text.Substring(num2 + 1).Trim(); if (text2.Length != 0) { float result; if (string.Equals(text3, "always", StringComparison.OrdinalIgnoreCase)) { _always.Add(text2); } else if (float.TryParse(text3, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { _always.Remove(text2); _overrides[text2] = ((result < 0f) ? 0f : ((result > 1f) ? 1f : result)); } } } Plugin.Log.LogInfo((object)("Loaded " + _overrides.Count + " ingredient weight(s) from " + FilePath)); } private static void WriteTemplate() { if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_recipes == null) { return; } SortedDictionary<string, string> sortedDictionary = new SortedDictionary<string, string>(StringComparer.Ordinal); foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if ((Object)(object)recipe == (Object)null || !SalvageEngine.IsSalvageable(recipe, out var _) || recipe.m_resources == null) { continue; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_recover) { string name = ((Object)((Component)val.m_resItem).gameObject).name; if (!SalvageEngine.IsNeverReturned(name)) { sortedDictionary[name] = null; } } } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Enigma's Item Salvager: ingredient weights."); stringBuilder.AppendLine("# One line per ingredient: PrefabName = weight, where weight is 0 to 1."); stringBuilder.AppendLine("# Higher means it comes back more readily when you salvage. Delete this file to regenerate defaults."); stringBuilder.AppendLine("# Anything not listed uses the built-in default (" + 0.3f.ToString("0.00", CultureInfo.InvariantCulture) + ")."); stringBuilder.AppendLine(); List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); foreach (KeyValuePair<string, string> item in sortedDictionary) { list.Add(new KeyValuePair<string, string>(item.Key, TierOf(item.Key))); } list.Sort(delegate(KeyValuePair<string, string> a, KeyValuePair<string, string> b) { int num = DefaultFor(b.Key).CompareTo(DefaultFor(a.Key)); return (num == 0) ? string.CompareOrdinal(a.Key, b.Key) : num; }); string text = null; foreach (KeyValuePair<string, string> item2 in list) { if (item2.Value != text) { stringBuilder.AppendLine(); stringBuilder.AppendLine("# " + item2.Value); text = item2.Value; } string text2 = (_always.Contains(item2.Key) ? "always" : DefaultFor(item2.Key).ToString("0.00", CultureInfo.InvariantCulture)); stringBuilder.AppendLine(item2.Key + " = " + text2); } File.WriteAllText(FilePath, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); Plugin.Log.LogInfo((object)("Wrote ingredient weights template (" + list.Count + " ingredients) to " + FilePath)); } } internal class SalvageLine { public string Ingredient; public string Name; public float MaxAmount; public float MinAmount; public int Invested; public float Weight; public float Expected; public int Amount; public bool Guaranteed; public bool Always; public string Skipped; } internal class SalvageOutcome { public float Roll; public float BestCase; public readonly List<SalvageLine> Lines = new List<SalvageLine>(); } internal static class SalvageEngine { public const string WorkbenchName = "piece_workbench"; private static readonly HashSet<string> NeverSalvaged = new HashSet<string>(StringComparer.Ordinal) { "AxeEarly" }; private static readonly HashSet<string> FullReturn = new HashSet<string>(StringComparer.Ordinal) { "Torch" }; private static readonly HashSet<ItemType> AllowedTypes = new HashSet<ItemType> { (ItemType)3, (ItemType)14, (ItemType)22, (ItemType)4, (ItemType)5, (ItemType)6, (ItemType)7, (ItemType)11, (ItemType)17, (ItemType)19, (ItemType)15, (ItemType)18, (ItemType)20 }; private static readonly HashSet<string> AllowedStations = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "piece_workbench", "forge", "blackforge", "piece_magetable" }; private static Dictionary<string, Recipe> _byItemName; private static int _recipeCount = -1; public static string NormalizeStation(string name) { if (string.IsNullOrEmpty(name)) { return "piece_workbench"; } int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { name = name.Substring(0, num); } return name.Trim(); } public static bool IsStationAllowed(string stationName) { return AllowedStations.Contains(NormalizeStation(stationName)); } public static Recipe FindRecipe(ItemData item) { if (item == null || item.m_shared == null || (Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_recipes == null) { return null; } if (_byItemName == null || _recipeCount != ObjectDB.instance.m_recipes.Count) { _byItemName = new Dictionary<string, Recipe>(StringComparer.Ordinal); _recipeCount = ObjectDB.instance.m_recipes.Count; foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if (IsSalvageable(recipe, out var _)) { string name = recipe.m_item.m_itemData.m_shared.m_name; if (!_byItemName.ContainsKey(name)) { _byItemName[name] = recipe; } } } } if (!_byItemName.TryGetValue(item.m_shared.m_name, out var value)) { return null; } return value; } public static SalvageOutcome Preview(Recipe r, int quality, float craftingFactor, float salvagingFactor) { SalvageOutcome salvageOutcome = new SalvageOutcome(); float num = BestCase(craftingFactor, salvagingFactor); float num2 = Mathf.Max(0f, num - SalvageConfig.Jitter.Value); salvageOutcome.BestCase = num; salvageOutcome.Roll = (num2 + num) / 2f; int num3 = Math.Max(1, r.m_item.m_itemData.m_shared.m_maxQuality); quality = Mathf.Clamp(quality, 1, num3); if (r.m_resources == null) { return salvageOutcome; } SalvageLine salvageLine = null; Requirement[] resources = r.m_resources; foreach (Requirement val in resources) { if (val == null || (Object)(object)val.m_resItem == (Object)null) { continue; } SalvageLine salvageLine2 = new SalvageLine { Ingredient = ((Object)((Component)val.m_resItem).gameObject).name, Name = val.m_resItem.m_itemData.m_shared.m_name, Invested = Invested(val, quality) }; if (!val.m_recover || IsNeverReturned(salvageLine2.Ingredient) || salvageLine2.Invested <= 0) { continue; } salvageOutcome.Lines.Add(salvageLine2); if (IngredientWeights.IsAlways(salvageLine2.Ingredient) || FullReturn.Contains(((Object)((Component)r.m_item).gameObject).name)) { salvageLine2.Always = true; salvageLine2.Weight = 1f; salvageLine2.Expected = salvageLine2.Invested; salvageLine2.MaxAmount = salvageLine2.Invested; salvageLine2.MinAmount = salvageLine2.Invested; continue; } salvageLine2.Weight = IngredientWeights.Get(salvageLine2.Ingredient); salvageLine2.Expected = Math.Min(salvageLine2.Invested, (float)salvageLine2.Invested * salvageOutcome.Roll * salvageLine2.Weight); salvageLine2.MaxAmount = Math.Min(salvageLine2.Invested, (float)salvageLine2.Invested * num * salvageLine2.Weight); salvageLine2.MinAmount = Math.Min(salvageLine2.Invested, (float)salvageLine2.Invested * num2 * salvageLine2.Weight); if (salvageLine == null || salvageLine2.Weight > salvageLine.Weight || (salvageLine2.Weight == salvageLine.Weight && salvageLine2.Invested > salvageLine.Invested)) { salvageLine = salvageLine2; } } if (salvageLine != null && !salvageLine.Always) { salvageLine.Guaranteed = true; if (salvageLine.Expected < 1f) { salvageLine.Expected = 1f; } if (salvageLine.MaxAmount < 1f) { salvageLine.MaxAmount = 1f; } } return salvageOutcome; } public static string StationOf(Recipe r) { if (!((Object)(object)r.m_craftingStation != (Object)null)) { return "piece_workbench"; } return NormalizeStation(((Object)((Component)r.m_craftingStation).gameObject).name); } public static bool IsNeverReturned(string prefab) { if (!prefab.StartsWith("Mold", StringComparison.Ordinal)) { return prefab.StartsWith("Upgrader", StringComparison.Ordinal); } return true; } public unsafe static bool IsSalvageable(Recipe r, out string reason) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) reason = null; if ((Object)(object)r == (Object)null || (Object)(object)r.m_item == (Object)null || r.m_item.m_itemData == null || r.m_item.m_itemData.m_shared == null) { reason = "no item data"; return false; } if (!r.m_enabled) { reason = "recipe disabled"; return false; } if (NeverSalvaged.Contains(((Object)((Component)r.m_item).gameObject).name)) { reason = "excluded by design (nothing worth recovering)"; return false; } SharedData shared = r.m_item.m_itemData.m_shared; string name = ((Object)((Component)r.m_item).gameObject).name; if (!FullReturn.Contains(name) && ((shared.m_useDurability && shared.m_destroyBroken) || name.StartsWith("Bomb", StringComparison.Ordinal) || name == "Snowball")) { reason = "used up by design (burns out or is thrown)"; return false; } ItemType itemType = r.m_item.m_itemData.m_shared.m_itemType; if (!AllowedTypes.Contains(itemType)) { reason = "item type " + ((object)(*(ItemType*)(&itemType))/*cast due to .constrained prefix*/).ToString() + " is not salvageable"; return false; } string text = StationOf(r); if (!AllowedStations.Contains(text)) { reason = "station " + text + " is not salvageable"; return false; } return true; } public static int Invested(Requirement req, int quality) { int num = 0; for (int i = 1; i <= quality; i++) { num += req.GetAmount(i); } return num; } public static float BestCase(float craftingFactor, float salvagingFactor) { float num = Mathf.Clamp01(craftingFactor); float num2 = Mathf.Clamp01(salvagingFactor); return Mathf.Clamp01(Mathf.Lerp(SalvageConfig.Floor.Value, SalvageConfig.Ceiling.Value, num) + SalvageConfig.SalvagingBonus.Value * num2); } public static float Roll(float craftingFactor, float salvagingFactor, Random rng) { float num = BestCase(craftingFactor, salvagingFactor); return Mathf.Lerp(Mathf.Max(0f, num - SalvageConfig.Jitter.Value), num, (float)rng.NextDouble()); } public static SalvageOutcome Compute(Recipe r, int quality, float craftingFactor, float salvagingFactor, Random rng) { SalvageOutcome salvageOutcome = new SalvageOutcome(); salvageOutcome.BestCase = BestCase(craftingFactor, salvagingFactor); salvageOutcome.Roll = Roll(craftingFactor, salvagingFactor, rng); int num = Math.Max(1, r.m_item.m_itemData.m_shared.m_maxQuality); quality = Mathf.Clamp(quality, 1, num); if (r.m_resources == null) { return salvageOutcome; } Requirement[] resources = r.m_resources; foreach (Requirement val in resources) { if (val == null || (Object)(object)val.m_resItem == (Object)null) { continue; } SalvageLine salvageLine = new SalvageLine { Ingredient = ((Object)((Component)val.m_resItem).gameObject).name, Name = val.m_resItem.m_itemData.m_shared.m_name, Invested = Invested(val, quality) }; salvageOutcome.Lines.Add(salvageLine); if (!val.m_recover) { salvageLine.Skipped = "game marks it not recoverable"; continue; } if (IsNeverReturned(salvageLine.Ingredient)) { salvageLine.Skipped = "never returned (mold or upgrade token)"; continue; } if (salvageLine.Invested <= 0) { salvageLine.Skipped = "none invested"; continue; } if (IngredientWeights.IsAlways(salvageLine.Ingredient) || FullReturn.Contains(((Object)((Component)r.m_item).gameObject).name)) { salvageLine.Always = true; salvageLine.Weight = 1f; salvageLine.Expected = salvageLine.Invested; salvageLine.Amount = salvageLine.Invested; continue; } salvageLine.Weight = IngredientWeights.Get(salvageLine.Ingredient); salvageLine.Expected = (float)salvageLine.Invested * salvageOutcome.Roll * salvageLine.Weight; int num2 = (int)Math.Floor(salvageLine.Expected); float num3 = salvageLine.Expected - (float)num2; if (rng.NextDouble() < (double)num3) { num2++; } salvageLine.Amount = Math.Min(num2, salvageLine.Invested); } SalvageLine salvageLine2 = null; foreach (SalvageLine line in salvageOutcome.Lines) { if (line.Skipped == null && (salvageLine2 == null || line.Weight > salvageLine2.Weight || (line.Weight == salvageLine2.Weight && line.Invested > salvageLine2.Invested))) { salvageLine2 = line; } } if (salvageLine2 != null && salvageLine2.Amount < 1) { salvageLine2.Amount = 1; salvageLine2.Guaranteed = true; } return salvageOutcome; } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class SalvageTestCommandPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; internal void <Postfix>b__0_0(ConsoleEventArgs args) { Run(args); } } private static void Postfix() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { Run(args); }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("salvagetest", "[ItemPrefab] [quality] [craftingSkill 0-100] [salvagingSkill 0-100] [runs]: preview what salvaging an item would return (no changes made)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static void Say(Terminal ctx, string text) { ctx.AddString(text); try { string text2 = SalvageConfig.TestLogFolder.Value; if (string.IsNullOrEmpty(text2)) { text2 = Path.Combine(Paths.BepInExRootPath, "EnigmasItemSalvager"); } Directory.CreateDirectory(text2); File.AppendAllText(Path.Combine(text2, "salvagetest-log.txt"), text + Environment.NewLine, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not write salvagetest-log.txt: " + ex.Message)); } } private static string F(float v, string fmt = "0.00") { return v.ToString(fmt, CultureInfo.InvariantCulture); } private static void Run(ConsoleEventArgs args) { Terminal context = args.Context; if ((Object)(object)context == (Object)null) { return; } Say(context, ""); Say(context, "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + "] " + args.FullLine); if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_recipes == null) { Say(context, "salvagetest: the game's item database is not loaded yet. Load into a world first."); return; } if (args.Length < 2) { Say(context, "usage: salvagetest <ItemPrefab> [quality=1] [craftingSkill=50] [salvagingSkill=0] [runs=1]"); return; } string prefab = args[1]; int num = Math.Max(1, args.TryParameterInt(2, 1)); int num2 = Math.Max(0, Math.Min(100, args.TryParameterInt(3, 50))); int num3 = Math.Max(0, Math.Min(100, args.TryParameterInt(4, 0))); int num4 = Math.Max(1, Math.Min(10000, args.TryParameterInt(5, 1))); Recipe val = ObjectDB.instance.m_recipes.Find((Recipe r) => (Object)(object)r != (Object)null && (Object)(object)r.m_item != (Object)null && string.Equals(((Object)((Component)r.m_item).gameObject).name, prefab, StringComparison.OrdinalIgnoreCase)); if ((Object)(object)val == (Object)null) { Say(context, "salvagetest: no recipe found for item '" + prefab + "'. Use the prefab name, for example SwordBronze."); return; } if (!SalvageEngine.IsSalvageable(val, out var reason)) { Say(context, "salvagetest: " + ((Object)((Component)val.m_item).gameObject).name + " cannot be salvaged (" + reason + ")."); return; } int num5 = Math.Max(1, val.m_item.m_itemData.m_shared.m_maxQuality); if (num > num5) { num = num5; } float craftingFactor = (float)num2 / 100f; float salvagingFactor = (float)num3 / 100f; string text = SalvageEngine.StationOf(val); int requiredStationLevel = val.GetRequiredStationLevel(num); Say(context, "--- " + ((Object)((Component)val.m_item).gameObject).name + " quality " + num + " (" + text + " level " + requiredStationLevel + ") ---"); Say(context, "Crafting " + num2 + ", Salvaging " + num3 + ", best-case return " + F(SalvageEngine.BestCase(craftingFactor, salvagingFactor) * 100f, "0") + "% of the top ingredient, runs: " + num4); Random rng = new Random(); Dictionary<string, long> dictionary = new Dictionary<string, long>(); Dictionary<string, int> dictionary2 = new Dictionary<string, int>(); Dictionary<string, int> dictionary3 = new Dictionary<string, int>(); List<SalvageLine> list = new List<SalvageLine>(); float num6 = 0f; int num7 = 0; for (int num8 = 0; num8 < num4; num8++) { SalvageOutcome salvageOutcome = SalvageEngine.Compute(val, num, craftingFactor, salvagingFactor, rng); num6 += salvageOutcome.Roll; foreach (SalvageLine line in salvageOutcome.Lines) { if (num8 == 0) { list.Add(line); dictionary[line.Ingredient] = 0L; dictionary2[line.Ingredient] = int.MaxValue; dictionary3[line.Ingredient] = 0; } dictionary[line.Ingredient] += line.Amount; if (line.Amount < dictionary2[line.Ingredient]) { dictionary2[line.Ingredient] = line.Amount; } if (line.Amount > dictionary3[line.Ingredient]) { dictionary3[line.Ingredient] = line.Amount; } if (line.Guaranteed) { num7++; } } } Say(context, "Average shared roll: " + F(num6 / (float)num4)); foreach (SalvageLine item in list) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(item.Ingredient).Append(": invested ").Append(item.Invested); if (item.Skipped != null) { stringBuilder.Append(" -> not returned (").Append(item.Skipped).Append(")"); } else { stringBuilder.Append(", weight ").Append(item.Always ? "always" : F(item.Weight)); if (item.Always) { stringBuilder.Append(" -> returns all ").Append(item.Invested); } else if (num4 == 1) { stringBuilder.Append(", expected ").Append(F(item.Expected)).Append(" -> returns ") .Append(item.Amount); if (item.Guaranteed) { stringBuilder.Append(" (guaranteed minimum)"); } } else { float num9 = (float)dictionary[item.Ingredient] / (float)num4; stringBuilder.Append(" -> average ").Append(F(num9)).Append(" (") .Append(F(num9 / (float)item.Invested * 100f, "0")) .Append("% of invested), range ") .Append(dictionary2[item.Ingredient]) .Append(" to ") .Append(dictionary3[item.Ingredient]); } } Say(context, stringBuilder.ToString()); } if (num4 > 1 && num7 > 0) { Say(context, "The guaranteed minimum kicked in on " + num7 + " of " + num4 + " runs."); } } } internal static class SalvageTab { public enum Place { Main, Equipped, Hotbar, Hidden } public static bool Active; public static Button TabButton; private static readonly MethodInfo SetActiveGroupM = AccessTools.Method(typeof(InventoryGui), "SetActiveGroup", new Type[2] { typeof(UIGroupHandler), typeof(bool) }, (Type[])null); private static readonly MethodInfo UpdateCraftingPanelM = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", new Type[1] { typeof(bool) }, (Type[])null); private static readonly MethodInfo AddRecipeToListM = AccessTools.Method(typeof(InventoryGui), "AddRecipeToList", (Type[])null, (Type[])null); private static int? _lastMainRow; public static bool Holding; private static int HoldCount = int.MaxValue; private static ItemData _lastSelected; public static int LastSignature; public static string LastListLog; public static Place PlaceOf(ItemData item) { if (item.m_equipped) { return Place.Equipped; } if (!_lastMainRow.HasValue) { _lastMainRow = (Chainloader.PluginInfos.ContainsKey("shudnal.ExtraSlots") ? 3 : int.MaxValue); } if (item.m_gridPos.y > _lastMainRow.Value) { return Place.Hidden; } if (item.m_gridPos.y <= 0) { return Place.Hotbar; } return Place.Main; } public static bool InMainInventory(ItemData item) { return PlaceOf(item) == Place.Main; } public static bool CanNow(Recipe recipe, ItemData item) { Player localPlayer = Player.m_localPlayer; CraftingStation val = (((Object)(object)localPlayer == (Object)null) ? null : localPlayer.GetCurrentCraftingStation()); if ((Object)(object)val == (Object)null || PlaceOf(item) != Place.Main) { return false; } if (ScrapRules.IsStandIn(recipe)) { return true; } return val.GetLevel(true) >= recipe.GetRequiredStationLevel(item.m_quality); } internal static bool UsesCounter(EntryMode mode, ItemData item) { if (item != null && item.m_stack > 1 && (mode == EntryMode.Scrap || mode == EntryMode.Head)) { return MultiCraftBridge.Present; } return false; } internal static int CountFor(EntryMode mode, ItemData item) { int num = Math.Max(1, item.m_stack); if (!UsesCounter(mode, item)) { return num; } return Mathf.Clamp(MultiCraftBridge.Amount, 1, num); } internal static void SyncMultiCraft(InventoryGui gui) { if (!MultiCraftBridge.Present) { return; } MultiCraftBridge.StopRepeat(); ItemData val = null; EntryMode mode = EntryMode.None; if (GetSelected(gui, out var recipe, out var item, out var _)) { val = item; mode = ScrapRules.ModeOfRecipe(recipe, item); } if (val != _lastSelected) { _lastSelected = val; MultiCraftBridge.Reset(); } int num; if (val != null && !Holding) { num = (UsesCounter(mode, val) ? 1 : 0); if (num != 0) { MultiCraftBridge.ClearStaleRequirements(); if (MultiCraftBridge.Amount > val.m_stack) { MultiCraftBridge.SetAmount(val.m_stack); } } } else { num = 0; } MultiCraftBridge.SetButtonsBlocked(num == 0); } internal static void OnGuiHidden() { _lastSelected = null; if (Active && MultiCraftBridge.Present) { MultiCraftBridge.SetButtonsBlocked(blocked: false); } } internal static bool HeldDown() { if (!ZInput.GetMouseButton(0)) { return ZInput.GetButton("JoyButtonA"); } return true; } internal static void BeginHold(InventoryGui gui) { if (Active && !Holding && GetSelected(gui, out var recipe, out var item, out var _) && ScrapRules.ModeOfRecipe(recipe, item) == EntryMode.Scrap && ((Selectable)gui.m_craftButton).interactable && CanNow(recipe, item)) { Traverse obj = Traverse.Create((object)gui); obj.Field("m_craftRecipe").SetValue((object)recipe); obj.Field("m_craftUpgradeItem").SetValue((object)item); obj.Field("m_multiCrafting").SetValue((object)false); obj.Field("m_craftTimer").SetValue((object)0f); HoldCount = CountFor(EntryMode.Scrap, item); Holding = true; } } public static int PlacementSignature(Player p) { int num = 17; foreach (ItemData allItem in ((Humanoid)p).GetInventory().GetAllItems()) { num = num * 31 + (allItem.m_equipped ? 1 : 0) + allItem.m_gridPos.y * 2 + allItem.m_gridPos.x * 64; } return num; } public static string CurrentStationName() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if (!((Object)(object)currentCraftingStation == (Object)null)) { return SalvageEngine.NormalizeStation(((Object)((Component)currentCraftingStation).gameObject).name); } return null; } internal static void RefreshPanel(InventoryGui gui) { UpdateCraftingPanelM.Invoke(gui, new object[1] { false }); } internal static bool GetSelected(InventoryGui gui, out Recipe recipe, out ItemData item, out bool canSalvage) { recipe = null; item = null; canSalvage = false; object value = Traverse.Create((object)gui).Field("m_selectedRecipe").GetValue(); if (value == null) { return false; } Type type = value.GetType(); object? value2 = type.GetProperty("Recipe").GetValue(value, null); recipe = (Recipe)((value2 is Recipe) ? value2 : null); object? value3 = type.GetProperty("ItemData").GetValue(value, null); item = (ItemData)((value3 is ItemData) ? value3 : null); canSalvage = (bool)type.GetProperty("CanCraft").GetValue(value, null); if ((Object)(object)recipe != (Object)null) { return item != null; } return false; } internal static void OnSalvageTabPressed(InventoryGui gui) { Active = true; Holding = false; MultiCraftBridge.Reset(); SetActiveGroupM.Invoke(gui, new object[2] { gui.m_uiGroups[3], true }); ((Selectable)gui.m_tabCraft).interactable = true; ((Selectable)gui.m_tabUpgrade).interactable = true; ((Selectable)TabButton).interactable = false; RefreshPanel(gui); } private static void StyleTab(InventoryGui gui) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) TMP_Text[] componentsInChildren = ((Component)gui.m_tabUpgrade).GetComponentsInChildren<TMP_Text>(true); TMP_Text[] componentsInChildren2 = ((Component)TabButton).GetComponentsInChildren<TMP_Text>(true); for (int i = 0; i < componentsInChildren2.Length && i < componentsInChildren.Length; i++) { componentsInChildren2[i].font = componentsInChildren[i].font; componentsInChildren2[i].fontSharedMaterial = componentsInChildren[i].fontSharedMaterial; componentsInChildren2[i].fontSize = componentsInChildren[i].fontSize; componentsInChildren2[i].fontStyle = componentsInChildren[i].fontStyle; componentsInChildren2[i].enableAutoSizing = componentsInChildren[i].enableAutoSizing; componentsInChildren2[i].fontSizeMin = componentsInChildren[i].fontSizeMin; componentsInChildren2[i].fontSizeMax = componentsInChildren[i].fontSizeMax; ((Graphic)componentsInChildren2[i]).color = ((Graphic)componentsInChildren[i]).color; componentsInChildren2[i].alignment = componentsInChildren[i].alignment; componentsInChildren2[i].text = "SALVAGE"; } } internal static void ApplyTabStates(InventoryGui gui) { if ((Object)(object)TabButton == (Object)null) { return; } string text = CurrentStationName(); bool flag = text != null && SalvageEngine.IsStationAllowed(text); ((Component)TabButton).gameObject.SetActive(flag); StyleTab(gui); if (Active && !flag) { Active = false; MultiCraftBridge.SetButtonsBlocked(blocked: false); ((Selectable)TabButton).interactable = true; if (((Component)gui.m_tabCraft).gameObject.activeSelf) { ((Selectable)gui.m_tabCraft).interactable = false; } else { ((Selectable)gui.m_tabUpgrade).interactable = false; } } else if (Active) { ((Selectable)gui.m_tabCraft).interactable = true; ((Selectable)gui.m_tabUpgrade).interactable = true; ((Selectable)TabButton).interactable = false; } else { ((Selectable)TabButton).interactable = true; } } private static void AddStacks(Player player, string prefabName, int amount) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); int val = 1; if ((Object)(object)itemPrefab != (Object)null) { ItemDrop component = itemPrefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null) { val = Math.Max(1, component.m_itemData.m_shared.m_maxStackSize); } } while (amount > 0) { int num = Math.Min(amount, val); ((Humanoid)player).GetInventory().AddItem(prefabName, num, 1, 0, player.GetPlayerID(), player.GetPlayerName(), new Vector2i(-1, -1), false, false, true); amount -= num; } } internal static void DoSalvage(InventoryGui gui) { //IL_01eb: 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) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; Traverse obj = Traverse.Create((object)gui); ItemData value = obj.Field("m_craftUpgradeItem").GetValue<ItemData>(); Recipe value2 = obj.Field("m_craftRecipe").GetValue<Recipe>(); if ((Object)(object)localPlayer == (Object)null || value == null || (Object)(object)value2 == (Object)null || !((Humanoid)localPlayer).GetInventory().ContainsItem(value) || !CanNow(value2, value)) { return; } float skillFactor = ((Character)localPlayer).GetSkillFactor((SkillType)107); SalvageOutcome salvageOutcome = SalvageEngine.Compute(value2, value.m_quality, skillFactor, SalvagingSkill.Factor(localPlayer), new Random()); string text = Localization.instance.Localize(value.m_shared.m_name); ((Humanoid)localPlayer).UnequipItem(value, true); ((Humanoid)localPlayer).GetInventory().RemoveItem(value); StringBuilder stringBuilder = new StringBuilder(); foreach (SalvageLine line in salvageOutcome.Lines) { if (line.Skipped == null && line.Amount > 0) { string text2 = MetalScrapHook.Resolve(line.Ingredient); AddStacks(localPlayer, text2, line.Amount); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text2); ItemDrop val = (((Object)(object)itemPrefab == (Object)null) ? null : itemPrefab.GetComponent<ItemDrop>()); string text3 = (((Object)(object)val != (Object)null) ? val.m_itemData.m_shared.m_name : line.Name); stringBuilder.Append("\n+").Append(line.Amount).Append(" ") .Append(Localization.instance.Localize(text3)); } } ((Character)localPlayer).Message((MessageType)2, "Salvaged " + text + ((stringBuilder.Length > 0) ? stringBuilder.ToString() : "\nnothing came back"), 0, (Sprite)null, false); SalvagingSkill.Gain(localPlayer); CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { currentCraftingStation.m_craftItemDoneEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } RefreshPanel(gui); } internal static void DoFinished(InventoryGui gui) { Traverse obj = Traverse.Create((object)gui); ItemData value = obj.Field("m_craftUpgradeItem").GetValue<ItemData>(); Recipe value2 = obj.Field("m_craftRecipe").GetValue<Recipe>(); bool holding = Holding; Holding = false; if (value == null || (Object)(object)value2 == (Object)null) { return; } switch (ScrapRules.ModeOfRecipe(value2, value)) { case EntryMode.Scrap: if (holding) { DoScrap(gui, value2, value); } break; case EntryMode.Head: DoHead(gui, value2, value); break; default: DoSalvage(gui); break; } MultiCraftBridge.Reset(); } private static void DoScrap(InventoryGui gui, Recipe recipe, ItemData item) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ((Humanoid)localPlayer).GetInventory().ContainsItem(item) && CanNow(recipe, item) && !ScrapRules.IsBlacklisted(item)) { string text = Localization.instance.Localize(item.m_shared.m_name); int num = Math.Min(Math.Max(1, HoldCount), item.m_stack); if (num >= item.m_stack) { ((Humanoid)localPlayer).GetInventory().RemoveItem(item); } else { ((Humanoid)localPlayer).GetInventory().RemoveItem(item, num); } ((Character)localPlayer).Message((MessageType)2, "Scrapped " + text + ((num > 1) ? (" x" + num) : ""), 0, (Sprite)null, false); CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { currentCraftingStation.m_craftItemDoneEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } RefreshPanel(gui); } } private static void DoHead(InventoryGui gui, Recipe recipe, ItemData item) { //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !((Humanoid)localPlayer).GetInventory().ContainsItem(item) || !CanNow(recipe, item) || CurrentStationName() != "piece_workbench" || !ScrapRules.TryHead(item, out var givenPrefab, out var amount) || ScrapRules.IsBlacklisted(item)) { return; } int num = CountFor(EntryMode.Head, item); if (!HeadHasRoom(localPlayer, item, givenPrefab, amount, num)) { ((Character)localPlayer).Message((MessageType)2, "$inventory_full", 0, (Sprite)null, false); return; } string text = Localization.instance.Localize(item.m_shared.m_name); int amount2 = amount * num; if (num >= item.m_stack) { ((Humanoid)localPlayer).GetInventory().RemoveItem(item); } else { ((Humanoid)localPlayer).GetInventory().RemoveItem(item, num); } AddStacks(localPlayer, givenPrefab, amount2); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(givenPrefab); ItemDrop val = (((Object)(object)itemPrefab == (Object)null) ? null : itemPrefab.GetComponent<ItemDrop>()); string text2 = (((Object)(object)val != (Object)null) ? Localization.instance.Localize(val.m_itemData.m_shared.m_name) : givenPrefab); ((Character)localPlayer).Message((MessageType)2, "Salvaged " + text + ((num > 1) ? (" x" + num) : "") + "\n+" + amount2 + " " + text2, 0, (Sprite)null, false); CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { currentCraftingStation.m_craftItemDoneEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } RefreshPanel(gui); } internal static bool HeadHasRoom(Player player, ItemData item, string given, int amountPerHead, int count) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(given); ItemDrop val = (((Object)(object)itemPrefab == (Object)null) ? null : itemPrefab.GetComponent<ItemDrop>()); if ((Object)(object)val == (Object)null) { return false; } int num = Math.Max(1, val.m_itemData.m_shared.m_maxStackSize); long num2 = (long)amountPerHead * (long)count; long num3 = ((long)((Humanoid)player).GetInventory().GetEmptySlots() + (long)((count >= item.m_stack) ? 1 : 0)) * num; foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems()) { if (allItem != item && allItem.m_shared.m_name == val.m_itemData.m_shared.m_name && allItem.m_stack < num) { num3 += num - allItem.m_stack; } } return num3 >= num2; } } internal class ScrapHoldInput : MonoBehaviour, IPointerDownHandler, IEventSystemHandler { public InventoryGui Gui; public void OnPointerDown(PointerEventData eventData) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Gui == (Object)null) && !((Object)(object)Gui.m_craftButton == (Object)null) && !((Object)(object)((Component)this).gameObject != (Object)(object)((Component)Gui.m_craftButton).gameObject) && (int)eventData.button == 0) { SalvageTab.BeginHold(Gui); } } } [HarmonyPatch(typeof(InventoryGui), "Awake")] internal static class SalvageTab_Awake { private static void Postfix(InventoryGui __instance) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) try { Button tabUpgrade = __instance.m_tabUpgrade; GameObject val = Object.Instantiate<GameObject>(((Component)tabUpgrade).gameObject, ((Component)tabUpgrade).transform.parent); ((Object)val).name = "SalvageTab"; val.transform.SetSiblingIndex(((Component)tabUpgrade).transform.GetSiblingIndex() + 1); Button component = val.GetComponent<Button>(); component.onClick = new ButtonClickedEvent(); ((UnityEvent)component.onClick).AddListener((UnityAction)delegate { SalvageTab.OnSalvageTabPressed(__instance); }); TMP_Text[] componentsInChildren = val.GetComponentsInChildren<TMP_Text>(true); for (int num = 0; num < componentsInChildren.Length; num++) { componentsInChildren[num].text = "Salvage"; } Transform transform = val.transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); Transform transform2 = ((Component)tabUpgrade).transform; RectTransform val3 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val3 != (Object)null) { Vector2 anchoredPosition = val3.anchoredPosition; Rect rect = val3.rect; val2.anchoredPosition = anchoredPosition + new Vector2(((Rect)(ref rect)).width + 8f, 0f); } SalvageTab.TabButton = component; val.SetActive(false); if ((Object)(object)__instance.m_craftButton != (Object)null) { ((Component)__instance.m_craftButton).gameObject.AddComponent<ScrapHoldInput>().Gui = __instance; } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not create the Salvage tab: " + ex)); } } } [HarmonyPatch(typeof(InventoryGui), "OnTabCraftPressed")] internal static class SalvageTab_CraftTab { private static void Prefix() { SalvageTab.Active = false; } } [HarmonyPatch(typeof(InventoryGui), "OnTabUpgradePressed")] internal static class SalvageTab_UpgradeTab { private static void Prefix() { SalvageTab.Active = false; } } [HarmonyPatch(typeof(InventoryGui), "Hide")] internal static class SalvageTab_Hide { private static void Postfix() { SalvageTab.OnGuiHidden(); } } [HarmonyPatch(typeof(InventoryGui), "UpdateCraftingPanel")] internal static class SalvageTab_UpdatePanel { private static void Postfix(InventoryGui __instance) { SalvageTab.ApplyTabStates(__instance); } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList")] internal static class SalvageTab_List { private static bool Prefix(InventoryGui __instance) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Expected O, but got Unknown if (!SalvageTab.Active) { return true; } Player localPlayer = Player.m_localPlayer; CraftingStation val = (((Object)(object)localPlayer == (Object)null) ? null : localPlayer.GetCurrentCraftingStation()); Traverse val2 = Traverse.Create((object)__instance); IList list = (IList)val2.Field("m_availableRecipes").GetValue(); foreach (object item in list) { Object.Destroy((Object)(GameObject)item.GetType().GetProperty("InterfaceElement").GetValue(item, null)); } list.Clear(); if ((Object)(object)val != (Object)null) { string text = SalvageEngine.NormalizeStation(((Object)((Component)val).gameObject).name); int level = val.GetLevel(true); List<KeyValuePair<Recipe, ItemData>> list2 = new List<KeyValuePair<Recipe, ItemData>>(); StringBuilder stringBuilder = new StringBuilder(); foreach (ItemData allItem in ((Humanoid)localPlayer).GetInventory().GetAllItems()) { string value = ScrapRules.PrefabOf(allItem) ?? allItem.m_shared.m_name; SalvageTab.Place place = SalvageTab.PlaceOf(allItem); Recipe recipe; if (place != SalvageTab.Place.Main) { stringBuilder.Append(value).Append(" (").Append(place) .Append(", row ") .Append(allItem.m_gridPos.y) .Append("); "); } else if (ScrapRules.ModeOf(allItem, text, out recipe) == EntryMode.None || (Object)(object)recipe == (Object)null) { Recipe val3 = SalvageEngine.FindRecipe(allItem); stringBuilder.Append(value).Append(((Object)(object)val3 != (Object)null) ? (" (made at " + SalvageEngine.StationOf(val3) + ")") : " (no entry)").Append("; "); } else { list2.Add(new KeyValuePair<Recipe, ItemData>(recipe, allItem)); } } string text2 = "Salvage list at " + text + " level " + level + ": " + list2.Count + " listed. Left out: " + ((stringBuilder.Length > 0) ? stringBuilder.ToString() : "nothing"); if (text2 != SalvageTab.LastListLog) { SalvageTab.LastListLog = text2; Plugin.Log.LogInfo((object)text2); } list2.Sort(delegate(KeyValuePair<Recipe, ItemData> a, KeyValuePair<Recipe, ItemData> b) { int num2 = string.CompareOrdinal(a.Value.m_shared.m_name, b.Value.m_shared.m_name); return (num2 == 0) ? a.Value.m_quality.CompareTo(b.Value.m_quality) : num2; }); foreach (KeyValuePair<Recipe, ItemData> item2 in list2) { bool flag = ScrapRules.IsStandIn(item2.Key) || level >= item2.Key.GetRequiredStationLevel(item2.Value.m_quality); AccessTools.Method(typeof(InventoryGui), "AddRecipeToList", (Type[])null, (Type[])null).Invoke(__instance, new object[4] { localPlayer, item2.Key, item2.Value, flag }); if (ScrapRules.IsStandIn(item2.Key) && item2.Value.m_stack > 1 && list.Count > 0) { object obj = list[list.Count - 1]; GameObject val4 = (GameObject)obj.GetType().GetProperty("InterfaceElement").GetValue(obj, null); Transform val5 = (((Object)(object)val4 == (Object)null) ? null : val4.transform.Find("name")); TMP_Text val6 = (((Object)(object)val5 == (Object)null) ? null : ((Component)val5).GetComponent<TMP_Text>()); if ((Object)(object)val6 != (Object)null) { val6.text = val6.text + " x" + item2.Value.m_stack; } } } SalvageTab.LastSignature = SalvageTab.PlacementSignature(localPlayer); } float num = Mathf.Max(val2.Field("m_recipeListBaseSize").GetValue<float>(), (float)list.Count * __instance.m_recipeListSpace); __instance.m_recipeListRoot.SetSizeWithCurrentAnchors((Axis)1, num); return false; } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] internal static class SalvageTab_Panel { private static readonly Color Good = new Color(0.55f, 1f, 0.55f); private static bool progressDumped; private static string _lastWhy; private static readonly Dictionary<Object, string> OriginalProgressText = new Dictionary<Object, string>(); private static void SetSlot(Transform root, SalvageLine line) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) Image component = ((Component)root.Find("res_icon")).GetComponent<Image>(); TMP_Text component2 = ((Component)root.Find("res_name")).GetComponent<TMP_Text>(); TMP_Text component3 = ((Component)root.Find("res_amount")).GetComponent<TMP_Text>(); UITooltip component4 = ((Component)root).GetComponent<UITooltip>(); ((Component)component).gameObject.SetActive(true); ((Component)component2).gameObject.SetActive(true); ((Component)component3).gameObject.SetActive(true); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(MetalScrapHook.Resolve(line.Ingredient)); ItemDrop val = (((Object)(object)itemPrefab == (Object)null) ? null : itemPrefab.GetComponent<ItemDrop>()); if ((Object)(object)val != (Object)null) { component.sprite = val.m_itemData.GetIcon(); } ((Graphic)component).color = Color.white; string text = (component2.text = Localization.instance.Localize(((Object)(object)val != (Object)null) ? val.m_itemData.m_shared.m_name : line.Name)); ((Graphic)component2).color = Color.white; if ((Object)(object)component4 != (Object)null) { component4.m_text = SalvageConfig.Big(text); } string text3; if (line.Always) { text3 = line.Invested.ToString(); } else { int num = Mathf.Min(line.Invested, Mathf.CeilToInt(line.MaxAmount - 0.001f)); int num2 = Mathf.Min(num, Mathf.FloorToInt(line.MinAmount + 0.001f)); if (line.Guaranteed) { num2 = Mathf.Max(num2, 1); } text3 = ((num2 == num) ? num.ToString() : (num2 + "-" + num)); } component3.text = text3; ((Graphic)component3).color = Good; } private static void OtherPanel(InventoryGui gui, Player player, EntryMode mode, ItemData item, bool canNow) { ((Behaviour)gui.m_recipeIcon).enabled = true; gui.m_recipeIcon.sprite = item.GetIcon(); ((Behaviour)gui.m_recipeName).enabled = true; string text = Localization.instance.Localize(item.m_shared.m_name); gui.m_recipeName.text = ((item.m_stack > 1) ? (text + " x" + item.m_stack) : text); ((Behaviour)gui.m_recipeDecription).enabled = true; ((Component)gui.m_itemCraftType).gameObject.SetActive(false); ((Component)gui.m_variantButton).gameObject.SetActive(false); ((Component)gui.m_qualityPanel).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); } TMP_Text componentInChildren = ((Component)gui.m_craftButton).GetComponentInChildren<TMP_Text>(); UITooltip component = ((Component)gui.m_craftButton).GetComponent<UITooltip>(); if (mode == EntryMode.Head) { string givenPrefab; int amount; bool flag = ScrapRules.TryHead(item, out givenPrefab, out amount); int num = SalvageTab.CountFor(EntryMode.Head, item); gui.m_recipeDecription.text = (SalvageTab.UsesCounter(EntryMode.Head, item) ? ("Strip " + num + " of the " + item.m_stack + " down to what is usable. Change the number with the + and - buttons.") : ((item.m_stack > 1) ? ("Strip the whole stack of " + item.m_stack + " down to what is usable.") : "Strip it down to what is usable.")); bool flag2 = false; if (flag) { SetSlot(recipeRequirementList[0].transform, new SalvageLine { Ingredient = givenPrefab, Name = givenPrefab, Invested = amount * num, Always = true }); flag2 = SalvageTab.HeadHasRoom(player, item, givenPrefab, amount, num); } if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = "Salvage"; } ((Selectable)gui.m_craftButton).interactable = flag && canNow && flag2; if ((Object)(object)component != (Object)null) { component.m_text = ((flag && canNow && !flag2) ? SalvageConfig.Big("Not enough free inventory slots") : ""); } } else { gui.m_recipeDecription.text = (SalvageTab.UsesCounter(EntryMode.Scrap, item) ? ("Nothing comes back. Hold the button to destroy " + SalvageTab.CountFor(EntryMode.Scrap, item) + " of the " + item.m_stack + ". Change the number with the + and - buttons.") : ((item.m_stack > 1) ? ("Nothing comes back. Hold the button to destroy the whole stack of " + item.m_stack + ".") : "Nothing comes back. Hold the button to destroy it.")); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = "Scrap"; } ((Selectable)gui.m_craftButton).interactable = canNow; if ((Object)(object)component != (Object)null) { component.m_text = (canNow ? SalvageConfig.Big("Hold to scrap") : ""); } } } private static void RememberAndSet(Object key, string current, string word, Action<string> set) { if (!OriginalProgressText.ContainsKey(key)) { OriginalProgressText[key] = current; } set(word); } private static void RestoreProgressText() { if (OriginalProgressText.Count == 0) { return; } foreach (KeyValuePair<Object, string> item in OriginalProgressText) { if (item.Key == (Object)null) { continue; } Object key = item.Key; TMP_Text val = (TMP_Text)(object)((key is TMP_Text) ? key : null); if ((Object)(object)val != (Object)null) { if (val.text != item.Value) { val.text = item.Value; } continue; } Object key2 = item.Key; Text val2 = (Text)(object)((key2 is Text) ? key2 : null); if ((Object)(object)val2 != (Object)null && val2.text != item.Value) { val2.text = item.Value; } } } private static void Postfix(InventoryGui __instance) { //IL_04e5: Unknown result type (might be due to invalid IL or missing references) //IL_04ea: Unknown result type (might be due to invalid IL or missing references) //IL_053f: Unknown result type (might be due to invalid IL or missing references) //IL_0538: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Unknown result type (might be due to invalid IL or missing references) if (!SalvageTab.Active) { RestoreProgressText(); return; } SalvageTab.SyncMultiCraft(__instance); if ((Object)(object)Player.m_localPlayer != (Object)null && SalvageTab.PlacementSignature(Player.m_localPlayer) != SalvageTab.LastSignature) { SalvageTab.LastSignature = SalvageTab.PlacementSignature(Player.m_localPlayer); SalvageTab.RefreshPanel(__instance); } if ((Object)(object)__instance.m_craftProgressPanel != (Object)null && ((Component)__instance.m_craftProgressPanel).gameObject.activeSelf) { TMP_Text[] componentsInChildren = ((Component)__instance.m_craftProgressPanel).GetComponentsInChildren<TMP_Text>(true); Text[] componentsInChildren2 = ((Component)__instance.m_craftProgressPanel).GetComponentsInChildren<Text>(true); string word = (SalvageTab.Holding ? "Scrapping" : "Salvaging"); for (int i = 0; i < componentsInChildren.Length; i++) { string text = componentsInChildren[i].text; if (!string.IsNullOrEmpty(text) && (text.IndexOf("craft", StringComparison.OrdinalIgnoreCase) >= 0 || text == "Salvaging" || text == "Scrapping")) { TMP_Text tt = componentsInChildren[i]; RememberAndSet((Object)(object)tt, text, word, delegate(string v) { tt.text = v; }); } } for (int num = 0; num < componentsInChildren2.Length; num++) { string text2 = componentsInChildren2[num].text; if (!string.IsNullOrEmpty(text2) && (text2.IndexOf("craft", StringComparison.OrdinalIgnoreCase) >= 0 || text2 == "Salvaging" || text2 == "Scrapping")) { Text ot = componentsInChildren2[num]; RememberAndSet((Object)(object)ot, text2, word, delegate(string v) { ot.text = v; }); } } if (!progressDumped) { progressDumped = true; string text3 = ""; for (int num2 = 0; num2 < componentsInChildren.Length; num2++) { text3 = text3 + "[TMP " + ((Object)((Component)componentsInChildren[num2]).gameObject).name + "='" + componentsInChildren[num2].text + "'] "; } for (int num3 = 0; num3 < componentsInChildren2.Length; num3++) { text3 = text3 + "[Text " + ((Object)((Component)componentsInChildren2[num3]).gameObject).name + "='" + componentsInChildren2[num3].text + "'] "; } Plugin.Log.LogInfo((object)("Progress panel labels: " + text3)); } } if (!SalvageTab.GetSelected(__instance, out var recipe, out var item, out var _)) { ((Selectable)__instance.m_craftButton).interactable = false; TMP_Text componentInChildren = ((Component)__instance.m_craftButton).GetComponentInChildren<TMP_Text>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = "Salvage"; } return; } bool flag = SalvageTab.CanNow(recipe, item); SalvageTab.Place place = SalvageTab.PlaceOf(item); Player localPlayer = Player.m_localPlayer; EntryMode entryMode = ScrapRules.ModeOfRecipe(recipe, item); if (entryMode != EntryMode.Salvage) { OtherPanel(__instance, localPlayer, entryMode, item, flag); return; } float skillFactor = ((Character)localPlayer).GetSkillFactor((SkillType)107); SalvageOutcome salvageOutcome = SalvageEngine.Preview(recipe, item.m_quality, skillFactor, SalvagingSkill.Factor(localPlayer)); ((Behaviour)__instance.m_recipeIcon).enabled = true; __instance.m_recipeIcon.sprite = item.GetIcon(); ((Behaviour)__instance.m_recipeName).enabled = true; __instance.m_recipeName.text = Localization.instance.Localize(item.m_shared.m_name); ((Behaviour)__instance.m_recipeDecription).enabled = true; __instance.m_recipeDecription.text = Sayings.For(item); if (item.m_shared.m_maxQuality > 1) { ((Component)__instance.m_itemCraftType).gameObject.SetActive(true); __instance.m_itemCraftType.text = "Quality " + item.m_quality; } else { ((Component)__instance.m_itemCraftType).gameObject.SetActive(false); } ((Component)__instance.m_variantButton).gameObject.SetActive(false); ((Component)__instance.m_qualityPanel).gameObject.SetActive(false); GameObject[] recipeRequirementList = __instance.m_recipeRequirementList; int num4 = recipeRequirementList.Length; int num5 = 0; if (salvageOutcome.Lines.Count > num4) { int num6 = Mathf.CeilToInt((float)salvageOutcome.Lines.Count / (float)num4); num5 = (int)Time.fixedTime % num6 * num4; } for (int num7 = 0; num7 < num4; num7++) { int num8 = num5 + num7; if (num8 < salvageOutcome.Lines.Count) { SetSlot(recipeRequirementList[num7].transform, salvageOutcome.Lines[num8]); } else { InventoryGui.HideRequirement(recipeRequirementList[num7].transform); } } bool flag2 = salvageOutcome.Lines.Count > 0; int requiredStationLevel = recipe.GetRequiredStationLevel(item.m_quality); ((Component)__instance.m_minStationLevelIcon).gameObject.SetActive(true); __instance.m_minStationLevelText.text = requiredStationLevel.ToString(); Color value = Traverse.Create((object)__instance).Field("m_minStationLevelBasecolor").GetValue<Color>(); CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); bool flag3 = (Object)(object)currentCraftingStation != (Object)null && currentCraftingStation.GetLevel(true) >= requiredStationLevel; ((Graphic)__instance.m_minStationLevelText).color = (flag3 ? value : ((Mathf.Sin(Time.time * 10f) > 0f) ? Color.red : value)); bool flag4 = salvageOutcome.Lines.Count - 1 <= ((Humanoid)localPlayer).GetInventory().GetEmptySlots(); TMP_Text componentInChildren2 = ((Component)__instance.m_craftButton).GetComponentInChildren<TMP_Text>(); if ((Object)(object)componentInChildren2 != (Object)null) { componentInChildren2.text = "Salvage"; } ((Selectable)__instance.m_craftButton).interactable = flag && flag4 && flag2; string text4 = ((flag && flag4 && flag2) ? "on" : ((!flag) ? ("off: " + ((place != SalvageTab.Place.Main) ? "item is equipped or on the hotbar" : "station level too low")) : ((!flag4) ? "off: not enough free inventory slots" : "off: nothing would come back"))); if (text4 != _lastWhy) { _lastWhy = text4; Plugin.Log.LogInfo((object)("Salvage button for " + item.m_shared.m_name + " " + text4)); } UITooltip component = ((Component)__instance.m_craftButton).GetComponent<UITooltip>(); if ((Object)(object)component != (Object)null) { component.m_text = ((flag && flag2 && !flag4) ? SalvageConfig.Big("Not enough free inventory slots") : ""); } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] internal static class SalvageTab_Speed { private static void Prefix(InventoryGui __instance, Player player, ref float dt) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (!SalvageTab.Active || (Object)(object)player == (Object)null) { return; } float craftDurationSkillMaxDecrease = __instance.m_craftDurationSkillMaxDecrease; CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); float num = 1f; if ((Object)(object)currentCraftingStation != (Object)null && (int)currentCraftingStation.m_craftingSkill != 0) { num = 1f - ((Character)player).GetSkillFactor(currentCraftingStation.m_craftingSkill) * craftDurationSkillMaxDecrease; } if (SalvageTab.Holding) { Traverse val = Traverse.Create((object)__instance); if (val.Field("m_craftTimer").GetValue<float>() < 0f) { SalvageTab.Holding = false; return; } if (!SalvageTab.HeldDown()) { val.Field("m_craftTimer").SetValue((object)(-1f)); SalvageTab.Holding = false; return; } float num2 = __instance.m_craftDuration; if ((Object)(object)currentCraftingStation != (Object)null && currentCraftingStation.m_upgrader) { Recipe recipe; ItemData item; bool canSalvage; int num3 = ((!SalvageTab.GetSelected(__instance, out recipe, out item, out canSalvage)) ? 1 : (item.m_quality + 1)); num2 = val.Field("m_upgraderDuration").GetValue<float>() + (float)num3 * val.Field("m_upgraderDurationPerLevel").GetValue<float>(); } float num4 = num2 * num; if (num4 > 0.01f) { dt *= num4 / ScrapRules.HoldSeconds.Value; } } else { float num5 = 1f - SalvagingSkill.Factor(player) * craftDurationSkillMaxDecrease; if (!(num5 <= 0.05f) && !(num <= 0.05f)) { dt *= num / num5; } } } } [HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")] internal static class SalvageTab_Press { private static bool Prefix(InventoryGui __instance) { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if (!SalvageTab.Active) { return true; } if (!SalvageTab.GetSelected(__instance, out var recipe, out var item, out var _) || !SalvageTab.CanNow(recipe, item)) { return false; } if (ScrapRules.ModeOfRecipe(recipe, item) == EntryMode.Scrap) { if (ZInput.IsGamepadActive()) { SalvageTab.BeginHold(__instance); } return false; } SalvageTab.Holding = false; Player localPlayer = Player.m_localPlayer; Traverse obj = Traverse.Create((object)__instance); obj.Field("m_craftRecipe").SetValue((object)recipe); obj.Field("m_craftUpgradeItem").SetValue((object)item); obj.Field("m_multiCrafting").SetValue((object)false); obj.Field("m_craftTimer").SetValue((object)0f); CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { currentCraftingStation.m_craftItemEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } return false; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class SalvageTab_Do { private static bool Prefix(InventoryGui __instance) { if (!SalvageTab.Active) { return true; } SalvageTab.DoFinished(__instance); return false; } } internal static class Sayings { private static readonly string[] Weapon = new string[6] { "What the forge joined, the hammer now parts.", "Every blow it struck is forgiven. Back to the bars it goes.", "Its edge dulls to ore and timber once more.", "A warrior's last service: to be many things again.", "Unmade, it asks nothing more of the smith.", "Sharp no longer, but never wasted." }; private static readonly string[] Sword = new string[4] { "The blade has sung its last. Let it be quiet.", "Hilt from blade, blade from legend. All of it comes apart.", "A sword is only a promise made of steel. The promise is kept.", "It will not be drawn again. It will be melted down, and drawn out new." }; private static readonly string[] Axe = new string[4] { "Head from haft, as the first axe was parted from its tree.", "It took down forests. Now it is taken down.", "The edge lifts away from the handle with no complaint.", "Timber's old enemy, finally at rest." }; private static readonly string[] Hammer = new string[3] { "The head that broke so much is itself broken down.", "Heavy to swing, lighter to dismantle.", "All that weight returns to the bench." }; private static readonly string[] Spear = new string[3] { "Point from shaft. The throw is over.", "It flew far and true. Now it rests in pieces.", "A long reach, shortened at last." }; private static readonly string[] Mace = new string[3] { "Many heads have rung against it. None will again.", "The flanges come off and the haft goes quiet.", "A blunt end for a blunt tool." }; private static readonly string[] Knife = new string[3] { "A small blade, a small farewell.", "It cut what needed cutting. Now it is cut apart.", "The edge folds back into the metal." }; private static readonly string[] Fist = new string[3] { "The claws that struck come loose, one by one.", "Bare hands again. The rest comes apart in your lap.", "Its fury goes out of the grip and back into the material." }; private static readonly string[] Pick = new string[3] { "It broke the earth. Now the earth keeps nothing of it.", "The head slips off. The mountain breathes easy.", "Ore gave it its edge. Ore gets some of it back." }; private static readonly string[] Staff = new string[3] { "The magic drains out and leaves only wood and stone.", "A spell unspoken, a staff unbound.", "Whatever it held in its crown slips quietly away." }; private static readonly string[] Bow = new string[3] { "The string goes slack and the wood remembers the tree.", "No arrow will leave it now. Take it apart.", "Bent for the hunt, straightened for the bench." }; private static readonly string[] Shield = new string[3] { "No blow lands on it now. Loosen the planks.", "It held the line. Let it rest in pieces.", "The boss goes cold and the rim comes off." }; private static readonly string[] Armor = new string[4] { "Shed the weight of war and take back what it was made of.", "It guarded you well. Let it rest in pieces.", "Stitch by stitch and rivet by rivet, undone.", "Armor laid down is only metal and cloth again." }; private static readonly string[] Helmet = new string[3] { "A head leaves it empty and it is only a shell.", "It kept thoughts safe. Now the thoughts are free of it.", "The brim, the plate, the strap: all of it lets go." }; private static readonly string[] Cape = new string[3] { "A cloak laid down is only thread again.", "It kept the cold off, and the cold does not mind.", "Fold it, unpick it, and let the fibres go back to work." }; private static readonly string[] Tool = new string[3] { "A tool laid down is only wood and metal.", "Its work is done. The parts have more to give.", "Once it built, now it unbuilds." }; private static readonly string[] Torch = new string[2] { "The flame is out. The wood remains.", "Cold ash, loose pitch, a stick." }; private static readonly string[] Utility = new string[2] { "Taken apart, it is only what it was made of.", "Everything that was bound together comes loose." }; private static readonly string[][] ThemeLines = new string[20][] { new string[4] { "Wood", "Timber goes back to being timber.", "Plain wood, plainly parted.", "The grain remembers the forest." }, new string[3] { "Flint", "Flint sparked once. It can spark again somewhere else.", "Knapped by hand, unknapped by hand." }, new string[3] { "Bone", "Bone to bone. What was cut returns to the pile.", "The dead lend their bones a second time." }, new string[3] { "Chitin", "Chitin lets go of the wood without a fight.", "Shell and shaft, cleanly parted." }, new string[3] { "Bronze", "Bronze remembers the crucible and goes back to it gladly.", "Copper and tin, parted for now, joined again later." }, new string[3] { "Iron", "Iron has been through the fire before. It will go again.", "Bog iron, hammer-hard. Soft in the end." }, new string[3] { "Silver", "Silver gleams even in pieces.", "The moonlit metal comes away clean." }, new string[3] { "BlackMetal", "Black metal is stubborn. It lets go anyway.", "Dark metal, darker work. All of it comes apart." }, new string[3] { "Flametal", "Flametal cools and keeps what it kept.", "Embers settle and the glow goes out of it." }, new string[3] { "Carapace", "Carapace plates part along the old seams.", "The hive's armor is only plates once more." }, new string[3] { "Troll", "Troll hide is tough to wear and tougher to cut. It yields.", "Troll leather, stitched by someone who was not asked." }, new string[3] { "Wolf", "Wolf pelt, wolf fang, wolf ghosts. Let them go.", "The pack scatters again." }, new string[3] { "Fenring", "Fenring fur sheds the last of the moon.", "A werewolf's coat, hung up for good." }, new string[3] { "Lox", "Lox hide carries the plains in its weave.", "Heavy as a lox, and just as hard to move." }, new string[3] { "Leather", "Leather unlaces back to hide.", "Hide, thread, and a bit of sweat." }, new string[3] { "Root", "Roots uncoil from the frame.", "Living wood, no longer living. Let go of it." }, new string[3] { "Padded", "Quilting comes undone one stitch at a time.", "Padding, plate, and patience." }, new string[3] { "Linen", "Linen unravels the way it was woven.", "Thread by thread, back to flax." }, new string[3] { "Rags", "Rags to rags, and not a stitch lost.", "Barely held together, now not at all." }, new string[3] { "Mage", "The enchantment fades from the cloth.", "Spell-threads loosen and let go." } }; private static readonly Dictionary<string, string> Specific = new Dictionary<string, string>(StringComparer.Ordinal) { { "SwordMistwalker", "The mist lets go of a blade that never wanted to be held." }, { "SwordDyrnwyn", "The flame sleeps, and the steel goes quiet." }, { "THSwordKrom", "Krom's weight is lifted, one hard piece at a time." }, { "SwordNiedhogg", "Even the root-gnawer's fang comes apart at the bench." }, { "THSwordSlayer", "Slayer stays its hand. Only the pieces fall." }, { "AxeJotunBane", "The giants' bane is unmade, and the giants do not thank you." }, { "SledgeDemolisher", "It broke walls. Now it breaks into parts." }, { "SledgeStagbreaker", "The stag is long gone. Antler and iron come back." }, { "KnifeButcher", "It has cut enough. Time to be cut apart." }, { "KnifeSkollAndHati", "The two wolves part ways at last." }, { "FistBjornClaw", "The bear's claws come off, one by one." }, { "FistFenrirClaw", "Fenrir's claws are only claws again." }, { "BowSpineSnap", "The spine snaps for the last time." }, { "BowHuntsman", "The hunt is over. The bow rests in parts." }, { "BowDraugrFang", "Old fang, old string. Let it go." }, { "BowFineWood", "Fine wood, finely taken apart." }, { "SpearWolfFang", "The fang loosens from the shaft." }, { "SpearElderbark", "Elderbark, unbound from its point." }, { "SpearChitin", "Chitin splits from wood easily enough." }, { "ShieldSerpentscale", "The serpent's scales fall away from the rim." }, { "Lantern", "The light goes out. The glass and iron stay." }, { "Hoe", "Soil forgets the hoe quickly. Take it apart." }, { "Cultivator", "No more furrows. Only metal and wood." }, { "Demister", "The wisplight fades and the frame comes apart." }, { "Scythe", "Harvest is over. The blade comes off the handle." }, { "GrapplingHook", "It held a climber's weight. Now it holds nothing." }, { "Tankard", "Empty, as all good tankards end up." }, { "TankardOdin", "Odin's cup, drained. The pieces are only metal." }, { "TankardAnniversary", "A toast to the years. Now a toast to the pieces." }, { "CapeOdin", "Even a god's mantle is only thread and stitch." }, { "HelmetOdin", "A god's helm sets down its pride." }, { "HelmetFishingHat", "The fish win after all." }, { "HelmetPointyHat", "A pointed hat unpoints." }, { "HelmetCelebration", "The party is over. The scraps return." }, { "HelmetMidsummerCrown", "Midsummer ends. The petals and thread go home." }, { "StaffFireball", "The fire gutters out of the staff." }, { "StaffIceShards", "Frost cracks and falls away." }, { "StaffLightning", "The last spark crawls out of it." }, { "StaffSkeleton", "The dead are released from service." }, { "StaffGreenRoots", "Roots let go of the wood." }, { "StaffShield", "The ward fades and the wood goes plain." }, { "StaffClusterbomb", "Nothing left to scatter. Only the staff." } }; private static readonly string[] KindOrder = new string[15] { "Staff", "Cape", "Helmet", "Knife", "Fist", "Pickaxe", "Battleaxe", "Axe", "Sledge", "Spear", "Atgeir", "Mace", "THSword", "Sword", "Crossbow" }; private static Dictionary<string, string> _custom; private static string FilePath => Path.Combine(Paths.ConfigPath, "EnigmaPrime.EnigmasItemSalvager.sayings.txt"); private static void LoadCustom() { _custom = new Dictionary<string, string>(StringComparer.Ordinal); try { if (!File.Exists(FilePath)) { return; } string[] array = File.ReadAllLines(FilePath); foreach (string text in array) { if (text.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal)) { continue; } int num = text.IndexOf('='); if (num > 0) { string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); if (text2.Length > 0 && text3.Length > 0) { _custom[text2] = text3; } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not read the sayings file: " + ex.Message)); } } private static string[] PoolForName(string prefab) { string[] kindOrder = KindOrder; foreach (string text in kindOrder) { if (prefab.StartsWith(text, StringComparison.Ordinal)) { switch (text) { case "Staff": return Staff; case "Cape": return Cape; case "Helmet": return Helmet; case "Knife": return Knife; case "Fist": return Fist; case "Pickaxe": return Pick; case "Battleaxe": case "Axe": return Axe; case "Sledge": return Hammer; case "Spear": case "Atgeir": return Spear; case "Mace": return Mace; case "Sword": case "THSword": return Sword; case "Crossbow": return Bow; } } } return null; } private static string[] ThemeFor(string prefab) { string[][] themeLines = ThemeLines; foreach (string[] array in themeLines) { if (prefab.IndexOf(array[0], StringComparison.OrdinalIgnoreCase) >= 0) { string[] array2 = new string[array.Length - 1]; Array.Copy(array, 1, array2, 0, array2.Length); return array2; } } return null; } public static string For(ItemData item) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected I4, but got Unknown if (_custom == null) { LoadCustom(); } string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name); if (_custom.TryGetValue(text, out var value)) { return value; } if (Specific.TryGetValue(text, out value)) { return value; } string[] array = PoolForName(text); if (array == null) { ItemType itemType = item.m_shared.m_itemType; switch (itemType - 4) { case 0: array = Bow; break; case 1: array = Shield; break; case 2: array = Helmet; break; case 3: case 7: array = Armor; break; case 13: array = Cape; break; case 15: array = Tool; break; case 11: array = Torch; break; case 14: array = Utility; break; default: array = Weapon; break; } } int num = 17; string text2 = text; foreach (char c in text2) { num = num * 31 + c; } num &= 0x7FFFFFFF; if (array != Staff && (num & 1) == 0) { string[] array2 = ThemeFor(text); if (array2 != null) { return array2[(num >> 1) % array2.Length]; } } return array[(num >> 1) % array.Length]; } } internal static class SalvagingSkill { public const string Identifier = "EnigmaPrime.EnigmasItemSalvager.Salvaging"; public static SkillType Type; public static bool Registered; public static void Register() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0043: 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) try { SkillConfig val = new SkillConfig { Identifier = "EnigmaPrime.EnigmasItemSalvager.Salvaging", Name = "Salvaging", Description = "Taking things apart. The better you are, the more comes back and the faster you work.", IncreaseStep = 1f, Icon = LoadIcon() }; Type = SkillManager.Instance.AddSkill(val); Registered = true; Plugin.Log.LogInfo((object)"Salvaging skill registered."); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not register the Salvaging skill. Salvaging will work, but without the skill: " + ex)); } } private static Sprite LoadIcon() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) try { Assembly assembly = typeof(SalvagingSkill).Assembly; string text = assembly.GetManifestResourceNames().FirstOrDefault((string n) => n.EndsWith("salvaging_icon.png", StringComparison.OrdinalIgnoreCase)); if (text == null) { return null; } using Stream stream = assembly.GetManifestResourceStream(text); using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); Type type = System.Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule"); MethodInfo methodInfo = ((type == null) ? null : type.GetMethod("LoadImage", new Type[2] { typeof(Texture2D), typeof(byte[]) })); if (methodInfo == null || !(bool)methodInfo.Invoke(null, new object[2] { val, memoryStream.ToArray() })) { return null; } return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not load the Salvaging skill icon, using the default: " + ex.Message)); return null; } } public static float Factor(Player p) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!Registered || !((Object)(object)p != (Object)null)) { return 0f; } return ((Character)p).GetSkillFactor(Type); } public static void Gain(Player p) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (Registered && !((Object)(object)p == (Object)null)) { float value = SalvageConfig.SalvagingXp.Value; if (value > 0f) { ((Character)p).RaiseSkill(Type, value); } } } public static bool SetLevel(Player p, float level) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!Registered || (Object)(object)p == (Object)null) { return false; } try { object value = Traverse.Create((object)((Character)p).GetSkills()).Method("GetSkill", new Type[1] { typeof(SkillType) }, new object[1] { Type }).GetValue(); if (value == null) { return false; } Traverse.Create(value).Field("m_level").SetValue((object)level); Traverse.Create(value).Field("m_accumulator").SetValue((object)0f); return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not set the Salvaging skill level: " + ex.Message)); return false; } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class SalvageSkillCommandPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; internal void <Postfix>b__0_0(ConsoleEventArgs args) { Run(args); } } private static void Postfix() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { Run(args); }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("salvageskill", "[0-100]: show or set your Salvaging skill level (for testing)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static void Run(ConsoleEventArgs args) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) Terminal context = args.Context; Player localPlayer = Player.m_localPlayer; if ((Object)(object)context == (Object)null) { return; } if ((Object)(object)localPlayer == (Object)null) { context.AddString("salvageskill: load into a world first."); return; } if (!SalvagingSkill.Registered) { context.AddString("salvageskill: the Salvaging skill is not registered (see the log)."); return; } if (args.Length < 2) { context.AddString("Salvaging skill level: " + ((Character)localPlayer).GetSkillLevel(SalvagingSkill.Type).ToString("0.0")); return; } float level = Mathf.Clamp(args.TryParameterInt(1, 0), 0, 100); if (SalvagingSkill.SetLevel(localPlayer, level)) { context.AddString("Salvaging skill set to " + level.ToString("0") + ". Open the skills page once to see it."); } else { context.AddString("salvageskill: could not set it. Try the game's own 'raiseskill' command instead."); } } } internal static class MetalScrapHook { internal const string ForgeGuid = "EnigmaPrime.EnigmasEnhancedForging"; private static readonly Dictionary<string, string> ScrapFor = new Dictionary<string, string> { { "Iron", "IronScrap" }, { "BlackMetal", "BlackMetalScrap" }, { "Copper", "CopperScrap" }, { "Tin", "EP_TinScrap" }, { "Bronze", "BronzeScrap" }, { "Silver", "EP_SilverScrap" }, { "FlametalNew", "EP_FlametalScrap" } }; private static bool? _forgeLoaded; private static bool ForgeLoaded { get { if (!_forgeLoaded.HasValue) { _forgeLoaded = !string.IsNullOrEmpty("EnigmaPrime.EnigmasEnhancedForging") && Chainloader.PluginInfos.ContainsKey("EnigmaPrime.EnigmasEnhancedForging"); if (_forgeLoaded == true) { Plugin.Log.LogInfo((object)"Enhanced Forging found: metal bars will come back as scrap."); } } return _forgeLoaded.Value; } } internal static string Resolve(string ingredientPrefab) { if (!ForgeLoaded || !ScrapFor.TryGetValue(ingredientPrefab, out var value)) { return ingredientPrefab; } if ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ObjectDB.instance.GetItemPrefab(value) == (Object)null) { return ingredientPrefab; } return value; } } internal enum EntryMode { None, Salvage, Head, Scrap } internal static class ScrapRules { public static ConfigEntry<bool> Enabled; public static ConfigEntry<float> HoldSeconds; public static ConfigEntry<string> Blacklist; public static ConfigEntry<string> ScrapTypes; public static ConfigEntry<string> HeadTable; public static ConfigEntry<string> LogTable; private const string DefaultBlacklist = "TrophyEikthyr,TrophyTheElder,TrophyBonemass,TrophyDragonQueen,TrophyGoblinKing,TrophySeekerQueen,TrophyFader,TrophyCultist_Hildir,TrophySkeletonHildir,TrophyGoblinBruteBrosBrute,TrophyGoblinBruteBrosShaman,TrophySerpent,TrophyBonemawSerpent,TrophyAbomination,TrophyFallenValkyrie,TrophyGjall,TrophyBarka,TrophyElaking,CryptKey,DvergrKey,BloodGoldKey,HildirKey_*,chest_hildir*,AxeEarly,TorchMist"; private const string DefaultTypes = "Trophy,Consumable,Fish,Material,Misc,Ammo,AmmoNonEquipable,OneHandedWeapon,TwoHandedWeapon,TwoHandedWeaponLeft,Bow,Shield,Helmet,Chest,Legs,Shoulder,Utility,Tool,Torch"; private const string DefaultHeads = "TrophyHare=BoneFragments:2,TrophyVolture=BoneFragments:2,TrophySkeleton=BoneFragments:2,TrophySkeletonPoison=BoneFragments:2,TrophyGoblinShaman=BoneFragments:2,TrophySeal=BoneFragments:2,TrophyMole=BoneFragments:2,TrophyHatchling=BoneFragments:2,TrophyGoblin=BoneFragments:3,TrophyDvergr=BoneFragments:3,TrophyBoar=BoneFragments:3,TrophyDeer=BoneFragments:3,TrophyWolf=BoneFragments:3,TrophyUlv=BoneFragments:3,TrophyFenring=BoneFragments:3,TrophyAsksvin=BoneFragments:3,TrophyGoblinBrute=BoneFragments:4,TrophyMoose=BoneFragments:4,TrophyLox=BoneFragments:5,TrophyBjorn=BoneFragments:5,TrophyForestTroll=BoneFragments:6,TrophyFrostTroll=BoneFragments:6,TrophyDraugr=Entrails:2,TrophyDraugrFem=Entrails:2,TrophyDraugrElite=Entrails:4,TrophyCharredMelee=CharredBone:3,TrophyCharredArcher=CharredBone:2,TrophyCharredMage=CharredBone:2,TrophyGreydwarf=GreydwarfEye:2,TrophyGreydwarfShaman=GreydwarfEye:3,TrophyGreydwarfBrute=GreydwarfEye:4,TrophyBlob=Ooze:2,TrophyBlob_Frost=Ooze:2,TrophyBlob_Lava=Ooze:2,TrophyBlob_Morkhalla=OozeMork:3,TrophyGrowth=Tar:3,TrophyLeech=Bloodbag:2,TrophyGhost=Ectoplasm:2"; private const string DefaultLogs = "RoundLog=Wood:4"; private static bool _loaded; private static HashSet<string> _black; private static List<string> _blackPrefixes; private static HashSet<ItemType> _types; private static Dictionary<string, KeyValuePair<string, int>> _heads; private static readonly Dictionary<string, Recipe> StandIns = new Dictionary<string, Recipe>(StringComparer.Ordinal); private static readonly HashSet<Recipe> StandInSet = new HashSet<Recipe>(); public static void Bind(ConfigFile cfg) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown Enabled = cfg.Bind<bool>("Scrap", "Enabled", true, "Lets the Salvage tab also list items with no recipe (trophies, food, materials, spare gear) so they can be scrapped, and turns creature heads into materials at the basic workbench."); HoldSeconds = cfg.Bind<float>("Scrap", "HoldSeconds", 1.5f, new ConfigDescription("How long the Scrap button must be held to delete the item. Letting go early cancels it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 5f), Array.Empty<object>())); Blacklist = cfg.Bind<string>("Scrap", "Blacklist", "TrophyEikthyr,TrophyTheElder,TrophyBonemass,TrophyDragonQueen,TrophyGoblinKing,TrophySeekerQueen,TrophyFader,TrophyCultist_Hildir,TrophySkeletonHildir,TrophyGoblinBruteBrosBrute,TrophyGoblinBruteBrosShaman,TrophySerpent,TrophyBonemawSerpent,TrophyAbomination,TrophyFallenValkyrie,TrophyGjall,TrophyBarka,TrophyElaking,CryptKey,DvergrKey,BloodGoldKey,HildirKey_*,chest_hildir*,AxeEarly,TorchMist", "Item prefab names that can never be scrapped or converted, separated by commas. A trailing * matches any ending (HildirKey_* covers all three keys). Quest items are always protected on top of this."); ScrapTypes = cfg.Bind<string>("Scrap", "ScrapTypes", "Trophy,Consumable,Fish,Material,Misc,Ammo,AmmoNonEquipable,OneHandedWeapon,TwoHandedWeapon,TwoHandedWeaponLeft,Bow,Shield,Helmet,Chest,Legs,Shoulder,Utility,Tool,Torch", "Item types the Scrap button may delete. Remove a type to hide those items from the list (remove OneHandedWeapon, Chest and so on to keep spare gear out of it)."); HeadTable = cfg.Bind<string>("Scrap", "HeadTable", "TrophyHare=BoneFragments:2,TrophyVolture=BoneFragments:2,TrophySkeleton=BoneFragments:2,TrophySkeletonPoison=BoneFragments:2,TrophyGoblinShaman=BoneFragments:2,TrophySeal=BoneFragments:2,TrophyMole=BoneFragments:2,TrophyHatchling=BoneFragments:2,TrophyGoblin=BoneFragments:3,TrophyDvergr=BoneFragments:3,TrophyBoar=BoneFragments:3,TrophyDeer=BoneFragments:3,TrophyWolf=BoneFragments:3,TrophyUlv=BoneFragments:3,TrophyFenring=BoneFragments:3,TrophyAsksvin=BoneFragments:3,TrophyGoblinBrute=BoneFragments:4,TrophyMoose=BoneFragments:4,TrophyLox=BoneFragments:5,TrophyBjorn=BoneFragments:5,TrophyForestTroll=BoneFragments:6,TrophyFrostTroll=BoneFragments:6,TrophyDraugr=Entrails:2,TrophyDraugrFem=Entrails:2,TrophyDraugrElite=Entrails:4,TrophyCharredMelee=CharredBone:3,TrophyCharredArcher=CharredBone:2,TrophyCharredMage=CharredBone:2,TrophyGreydwarf=GreydwarfEye:2,TrophyGreydwarfShaman=GreydwarfEye:3,TrophyGreydwarfBrute=GreydwarfEye:4,TrophyBlob=Ooze:2,TrophyBlob_Frost=Ooze:2,TrophyBlob_Lava=Ooze:2,TrophyBlob_Morkhalla=OozeMork:3,TrophyGrowth=Tar:3,TrophyLeech=Bloodbag:2,TrophyGhost=Ectoplasm:2", "Creature heads that can be salvaged at the basic workbench. Format: TrophyPrefab=ItemPrefab:Amount, separated by commas. One head gives that amount. A trophy not listed here can only be scrapped."); LogTable = cfg.Bind<string>("Scrap", "LogTable", "RoundLog=Wood:4", "Logs that can be chopped into smaller wood at the basic workbench. Format: ItemPrefab=ItemPrefab:Amount, separated by commas. One log gives that amount, and the whole stack is chopped at once. Core Wood is RoundLog."); } private static void Load() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) if (_loaded) { return; } _loaded = true; _black = new HashSet<string>(StringComparer.OrdinalIgnoreCase); _blackPrefixes = new List<string>(); _types =