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 VanillaRecycler v0.1.0
despr0grammer.VanillaRecycler.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [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 despr0grammer.VanillaRecycler { internal static class ContainerAccess { internal static bool TryAdd(Inventory inventory, string prefab, int amount) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) return inventory.AddItem(prefab, amount, 1, 0, 0L, string.Empty, new Vector2i(-1, -1), false, false, false) != null; } internal static bool OpenElsewhere(ZNetView view) { if ((Object)(object)view != (Object)null && view.IsValid()) { return view.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1; } return false; } internal static void Own(ZNetView view) { if ((Object)(object)view != (Object)null && view.IsValid() && !view.IsOwner()) { view.ClaimOwnership(); } } } internal static class GameAccess { private const string CoroutineName = "Incinerate"; private static readonly FieldInfo NviewField = AccessTools.Field(typeof(Incinerator), "m_nview"); internal static readonly MethodBase CoroutineStep = FindStep(); private static readonly FieldInfo StateField = ((CoroutineStep?.DeclaringType != null) ? AccessTools.Field(CoroutineStep.DeclaringType, "<>1__state") : null); private static readonly FieldInfo OwnerField = ((CoroutineStep?.DeclaringType != null) ? AccessTools.Field(CoroutineStep.DeclaringType, "<>4__this") : null); internal static bool Ready { get { if (NviewField != null && NviewField.FieldType == typeof(ZNetView) && CoroutineStep != null && StateField != null && StateField.FieldType == typeof(int) && OwnerField != null) { return OwnerField.FieldType == typeof(Incinerator); } return false; } } private static MethodBase FindStep() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Incinerator), "Incinerate", new Type[1] { typeof(long) }, (Type[])null); return (methodInfo != null && methodInfo.ReturnType == typeof(IEnumerator)) ? AccessTools.EnumeratorMoveNext((MethodBase)methodInfo) : null; } catch (Exception) { return null; } } internal static ZNetView NetViewOf(Incinerator incinerator) { object? obj = NviewField?.GetValue(incinerator); return (ZNetView)((obj is ZNetView) ? obj : null); } internal static int StepOf(object enumerator) { if (!(StateField != null) || enumerator == null) { return -1; } return (int)StateField.GetValue(enumerator); } internal static Incinerator IncineratorOf(object enumerator) { object? obj = OwnerField?.GetValue(enumerator); return (Incinerator)((obj is Incinerator) ? obj : null); } } internal static class IncineratePatch { private static bool _reported; private static void Prefix(object __instance, out RecyclePlan __state) { __state = null; if (!Plugin.Enabled.Value) { return; } try { if (GameAccess.StepOf(__instance) == 1) { Incinerator val = GameAccess.IncineratorOf(__instance); ZNetView val2 = (((Object)(object)val != (Object)null) ? GameAccess.NetViewOf(val) : null); if (!((Object)(object)val2 == (Object)null) && val2.IsValid() && val2.IsOwner() && !((Object)(object)val.m_container == (Object)null) && !val.m_container.IsInUse()) { __state = Recycler.Plan(val); } } } catch (Exception e) { Report("reading the Obliterator", e); } } private static void Postfix(object __instance, bool __result, RecyclePlan __state) { if (__state == null) { return; } try { Incinerator val = GameAccess.IncineratorOf(__instance); if (!((Object)(object)val == (Object)null) && !Recycler.Apply(val, __state) && !__result) { Plugin.Log.LogWarning((object)"The Obliterator did not empty its container as expected; nothing was given back (did the game change?)."); } } catch (Exception e) { Report("giving the resources back", e); } } private static void Report(string what, Exception e) { if (!_reported) { _reported = true; Plugin.Log.LogError((object)$"Error while {what} (further errors are not logged): {e}"); } } } [HarmonyPatch(typeof(Incinerator), "OnIncinerate")] internal static class LeverPatch { private static bool _reported; private static void Prefix(Incinerator __instance, ZNetView ___m_nview) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Enabled.Value) { return; } try { if (!((Object)(object)___m_nview == (Object)null) && ___m_nview.IsValid() && !___m_nview.IsOwner() && !ContainerAccess.OpenElsewhere(___m_nview) && PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false)) { ContainerAccess.Own(___m_nview); } } catch (Exception arg) { if (!_reported) { _reported = true; Plugin.Log.LogError((object)$"Could not take ownership of the Obliterator: {arg}"); } } } } [HarmonyPatch(typeof(Incinerator), "GetLeverHoverText")] internal static class LeverHoverPatch { private static void Postfix(ref string __result) { if (Plugin.Enabled.Value && Plugin.ShowOnHover.Value && Plugin.RefundPercent.Value > 0) { __result = __result + "\n" + Text.HoverLine(RefundMath.ClampPercent(Plugin.RefundPercent.Value)); } } } [BepInPlugin("com.despr0grammer.vanillarecycler", "VanillaRecycler", "0.1.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.despr0grammer.vanillarecycler"; public const string PluginName = "VanillaRecycler"; public const string PluginVersion = "0.1.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<int> RefundPercent; internal static ConfigEntry<RoundingMode> Rounding; internal static ConfigEntry<bool> ScaleByDurability; internal static ConfigEntry<bool> IncludeConsumables; internal static ConfigEntry<string> ExcludedItems; internal static ConfigEntry<bool> ShowMessage; internal static ConfigEntry<bool> ShowOnHover; private Harmony _harmony; private static readonly Type[] OptionalPatches = new Type[2] { typeof(LeverPatch), typeof(LeverHoverPatch) }; private void Awake() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BindConfig(); if (!GameAccess.Ready) { ((BaseUnityPlugin)this).Logger.LogError((object)"Valheim internals changed (the Obliterator's lever coroutine or network view not found); mod disabled."); ((Behaviour)this).enabled = false; return; } _harmony = new Harmony("com.despr0grammer.vanillarecycler"); if (!ApplyPatches()) { ((Behaviour)this).enabled = false; } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("{0} {1} loaded (the Obliterator gives back {2}% of the resources, ", "VanillaRecycler", "0.1.0", RefundMath.ClampPercent(RefundPercent.Value)) + "rounding " + Rounding.Value.ToString().ToLowerInvariant() + ")")); } } private void BindConfig() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch. Off, the Obliterator works as it does in the vanilla game (it just destroys what you put in)."); RefundPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Recycling", "RefundPercent", 60, new ConfigDescription("Percentage of the resources an item cost to make that the Obliterator gives back. It uses the item's recipe, upgrades included (a quality 3 axe gives back its upgrades too), and the fractions of everything you put in are added up before rounding. 0 gives nothing back.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); Rounding = ((BaseUnityPlugin)this).Config.Bind<RoundingMode>("Recycling", "Rounding", RoundingMode.Down, "What to do with a fraction of a resource: Down never gives more than the percentage (60% of 1 wood is 0, but ten such items give 6), Nearest rounds to the closest whole number, Up gives at least one for any fraction."); ScaleByDurability = ((BaseUnityPlugin)this).Config.Bind<bool>("Recycling", "ScaleByDurability", false, "A worn tool or weapon gives back less: in proportion to the durability it has left."); IncludeConsumables = ((BaseUnityPlugin)this).Config.Bind<bool>("Recycling", "IncludeConsumables", false, "Also give back the ingredients of food and potions (cooked meals, meads). Off by default, so the Obliterator is for gear and ammunition and cannot be used to get cheap ingredients back."); ExcludedItems = ((BaseUnityPlugin)this).Config.Bind<string>("Recycling", "ExcludedItems", string.Empty, "Items that never give anything back, by prefab name (the names the spawn console command uses), separated by commas. For example: ArrowFlint, Torch"); ShowMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("Feedback", "ShowMessage", true, "Show what was given back at the top left after pulling the lever."); ShowOnHover = ((BaseUnityPlugin)this).Config.Bind<bool>("Feedback", "ShowOnHover", true, "Show the percentage under the lever's name when you look at it."); } private bool ApplyPatches() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_003d: Expected O, but got Unknown try { _harmony.Patch(GameAccess.CoroutineStep, new HarmonyMethod(typeof(IncineratePatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(IncineratePatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Required patch on the Obliterator's lever failed, mod disabled: " + ex.Message)); _harmony.UnpatchSelf(); return false; } Type[] optionalPatches = OptionalPatches; foreach (Type type in optionalPatches) { try { _harmony.PatchAll(type); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Optional patch " + type.Name + " failed (" + ex2.Message + "); that feature is off.")); } } return true; } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class RecyclePlan { internal readonly List<ItemData> Before; internal readonly List<KeyValuePair<ItemDrop, int>> Refunds = new List<KeyValuePair<ItemDrop, int>>(); internal RecyclePlan(List<ItemData> before) { Before = before; } } internal static class Recycler { private static readonly HashSet<string> NoNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static RecyclePlan Plan(Incinerator incinerator) { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || (Object)(object)incinerator.m_container == (Object)null) { return null; } int num = RefundMath.ClampPercent(Plugin.RefundPercent.Value); List<ItemData> list = new List<ItemData>(incinerator.m_container.GetInventory().GetAllItems()); if (num <= 0 || list.Count == 0) { return null; } HashSet<string> converted = ConversionInputs(incinerator); HashSet<string> excluded = RefundMath.ParseNames(Plugin.ExcludedItems.Value); Dictionary<string, ItemDrop> dictionary = new Dictionary<string, ItemDrop>(StringComparer.Ordinal); RefundBag refundBag = new RefundBag(); foreach (ItemData item in list) { Salvage salvage = SalvageOf(instance, item, converted, excluded, dictionary); if (salvage != null) { refundBag.Add(salvage, num); } } RecyclePlan recyclePlan = new RecyclePlan(list); foreach (KeyValuePair<string, int> item2 in refundBag.Results(Plugin.Rounding.Value)) { if (dictionary.TryGetValue(item2.Key, out var value)) { recyclePlan.Refunds.Add(new KeyValuePair<ItemDrop, int>(value, item2.Value)); } } if (recyclePlan.Refunds.Count <= 0) { return null; } return recyclePlan; } private static Salvage SalvageOf(ObjectDB db, ItemData item, HashSet<string> converted, HashSet<string> excluded, Dictionary<string, ItemDrop> prefabs) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 SharedData shared = item.m_shared; if (shared == null || item.m_stack <= 0) { return null; } if (converted.Contains(shared.m_name)) { return null; } if (!Plugin.IncludeConsumables.Value && (int)shared.m_itemType == 2) { return null; } if ((Object)(object)item.m_dropPrefab != (Object)null && excluded.Contains(((Object)item.m_dropPrefab).name)) { return null; } Recipe recipe = db.GetRecipe(item); if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || recipe.m_requireOnlyOneIngredient || recipe.m_resources == null) { return null; } Salvage salvage = new Salvage { CraftAmount = recipe.m_amount, Stack = item.m_stack, Condition = (Plugin.ScaleByDurability.Value ? RefundMath.Condition(shared.m_useDurability, item.m_durability, item.GetMaxDurability()) : 1m) }; Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_recover) { Requirement cost = val; int num = RefundMath.TotalCost(item.m_quality, (int level) => cost.GetAmount(level)); if (num > 0) { string name = ((Object)((Component)val.m_resItem).gameObject).name; prefabs[name] = val.m_resItem; salvage.Costs.Add(new ResourceCost { Key = name, Amount = num }); } } } if (salvage.Costs.Count <= 0) { return null; } return salvage; } private static HashSet<string> ConversionInputs(Incinerator incinerator) { if (incinerator.m_conversions == null || incinerator.m_conversions.Count == 0) { return NoNames; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (IncineratorConversion conversion in incinerator.m_conversions) { if (conversion?.m_requirements == null) { continue; } foreach (Requirement requirement in conversion.m_requirements) { string text = requirement?.m_resItem?.m_itemData?.m_shared?.m_name; if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); } } } return hashSet; } internal static bool Apply(Incinerator incinerator, RecyclePlan plan) { Inventory inventory = incinerator.m_container.GetInventory(); List<ItemData> allItems = inventory.GetAllItems(); foreach (ItemData item in plan.Before) { if (allItems.Contains(item)) { return false; } } List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(plan.Refunds.Count); foreach (KeyValuePair<ItemDrop, int> refund in plan.Refunds) { Give(incinerator, inventory, refund.Key, refund.Value); list.Add(new KeyValuePair<string, int>(Localization.instance.Localize(refund.Key.m_itemData.m_shared.m_name), refund.Value)); } Plugin.Log.LogInfo((object)$"Recycled {plan.Before.Count} stack(s) in an Obliterator: {Text.Recycled(Plugin.RefundPercent.Value, list)}"); Notify(incinerator, list); return true; } private static void Give(Incinerator incinerator, Inventory inventory, ItemDrop prefab, int amount) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) int val = Math.Max(1, prefab.m_itemData.m_shared.m_maxStackSize); string name = ((Object)((Component)prefab).gameObject).name; int num = amount; while (num > 0) { int num2 = Math.Min(num, val); if (!ContainerAccess.TryAdd(inventory, name, num2)) { break; } num -= num2; } Transform transform = ((Component)incinerator).transform; while (num > 0) { int num3 = Math.Min(num, val); Vector3 val2 = transform.position + transform.forward * 1.2f + Vector3.up * 1.2f; ItemDrop component = Object.Instantiate<GameObject>(((Component)prefab).gameObject, val2, Quaternion.identity).GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null) { component.SetStack(num3); } num -= num3; } } private static void Notify(Incinerator incinerator, List<KeyValuePair<string, int>> given) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.ShowMessage.Value) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { Vector3 val = ((Component)localPlayer).transform.position - ((Component)incinerator).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > 900f)) { ((Character)localPlayer).Message((MessageType)1, Text.Recycled(Plugin.RefundPercent.Value, given), 0, (Sprite)null, false); } } } } internal enum RoundingMode { Down, Nearest, Up } internal struct ResourceCost { internal string Key; internal int Amount; } internal sealed class Salvage { internal readonly List<ResourceCost> Costs = new List<ResourceCost>(); internal int CraftAmount = 1; internal int Stack = 1; internal decimal Condition = 1m; } internal sealed class RefundBag { private const decimal Epsilon = 0.000001m; private readonly List<string> _order = new List<string>(); private readonly Dictionary<string, decimal> _units = new Dictionary<string, decimal>(StringComparer.Ordinal); internal bool IsEmpty => _order.Count == 0; internal void Add(Salvage item, int percent) { int num = RefundMath.ClampPercent(percent); if (item == null || num <= 0) { return; } decimal num2 = Math.Max(1, item.CraftAmount); decimal num3 = Math.Max(0, item.Stack); decimal num4 = Math.Max(0m, Math.Min(1m, item.Condition)); foreach (ResourceCost cost in item.Costs) { if (!string.IsNullOrEmpty(cost.Key) && cost.Amount > 0) { decimal num5 = (decimal)cost.Amount * num3 * (decimal)num / 100m * num4 / num2; if (!_units.TryGetValue(cost.Key, out var value)) { _order.Add(cost.Key); } _units[cost.Key] = value + num5; } } } internal List<KeyValuePair<string, int>> Results(RoundingMode mode) { List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(); foreach (string item in _order) { int num = RefundMath.Round(_units[item], mode); if (num > 0) { list.Add(new KeyValuePair<string, int>(item, num)); } } return list; } } internal static class RefundMath { internal const int MaxPercent = 100; internal static int ClampPercent(int percent) { return Math.Max(0, Math.Min(100, percent)); } internal static int Round(decimal units, RoundingMode mode) { decimal num = mode switch { RoundingMode.Nearest => Math.Floor(units + 0.5m + 0.000001m), RoundingMode.Up => Math.Ceiling(units - 0.000001m), _ => Math.Floor(units + 0.000001m), }; if (!(num > 2147483647m)) { return (int)Math.Max(0m, num); } return int.MaxValue; } internal static HashSet<string> ParseNames(string text) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(text)) { return hashSet; } string[] array = text.Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length > 0) { hashSet.Add(text2); } } return hashSet; } internal static int TotalCost(int quality, Func<int, int> amountForQuality) { long num = 0L; for (int i = 1; i <= Math.Max(1, quality); i++) { num += Math.Max(0, amountForQuality(i)); } if (num <= int.MaxValue) { return (int)num; } return int.MaxValue; } internal static decimal Condition(bool wearsOut, float durability, float maxDurability) { if (!wearsOut || maxDurability <= 0f) { return 1m; } decimal val = (decimal)(durability / maxDurability); return Math.Max(0m, Math.Min(1m, val)); } } internal static class Text { internal static bool Spanish { get { if (Localization.instance != null) { return Localization.instance.GetSelectedLanguage() == "Spanish"; } return false; } } internal static string HoverLine(int percent) { return T($"Recicla el {percent}% de los recursos", $"Recycles {percent}% of the resources"); } internal static string Recycled(int percent, IEnumerable<KeyValuePair<string, int>> items) { StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, int> item in items) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(item.Value).Append("x ").Append(item.Key); } return T($"Reciclado ({percent}%): {stringBuilder}", $"Recycled ({percent}%): {stringBuilder}"); } private static string T(string spanish, string english) { if (!Spanish) { return english; } return spanish; } } }