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 SmartRepair v1.0.1
SmartRepair.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; 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: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SmartRepair")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Auto-repair on crafting station use, with higher-tier stations covering lower-tier gear.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.1+e85340794c811b3a1544efb38885d0eba40b6fb3")] [assembly: AssemblyProduct("SmartRepair")] [assembly: AssemblyTitle("SmartRepair")] [assembly: AssemblyVersion("1.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 SmartRepair { internal static class ModConfig { internal static ConfigEntry<bool> AutoRepair; internal static ConfigEntry<bool> ShowMessage; internal static ConfigEntry<string> MessageText; internal static ConfigEntry<bool> CrossStationRepair; internal static ConfigEntry<string> StationOrder; internal static ConfigEntry<bool> RequireStationLevel; internal static ConfigEntry<bool> VerboseLogging; internal static void Bind(ConfigFile config) { AutoRepair = config.Bind<bool>("Auto Repair", "Enabled", true, "Repair everything the station can handle the moment you start using it."); ShowMessage = config.Bind<bool>("Auto Repair", "ShowMessage", true, "Show a centre-screen message saying how many items were repaired."); MessageText = config.Bind<string>("Auto Repair", "MessageText", "Repaired {0} item(s)", "Text for that message. {0} is the number of items repaired."); CrossStationRepair = config.Bind<bool>("Cross-Station Repair", "Enabled", true, "Let a station repair anything an earlier station in the progression could repair. A Black Forge then covers Forge and Workbench gear as well as its own. This also applies to the manual repair button, not just the automatic pass."); StationOrder = config.Bind<string>("Cross-Station Repair", "StationOrder", "$piece_workbench, $piece_forge, $piece_artisanstation, $piece_blackforge, $piece_magetable", "Crafting stations in progression order, earliest first, comma separated. These are the station's internal names (the localisation token, with or without the $). Stations left off this list keep vanilla behaviour, so add modded stations here to slot them into the progression."); RequireStationLevel = config.Bind<bool>("Cross-Station Repair", "RequireStationLevel", false, "Also require the station's upgrade level to meet the recipe's minimum when repairing gear from an earlier station. Off by default: station levels are not comparable across station types, so a level 1 Black Forge is further along than a level 4 Workbench."); VerboseLogging = config.Bind<bool>("Advanced", "VerboseLogging", false, "Log a line per repaired item to the BepInEx console."); } } [BepInPlugin("com.ljindustries.valheim.smartrepair", "SmartRepair", "1.0.1")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.ljindustries.valheim.smartrepair"; public const string ModName = "SmartRepair"; public const string ModVersion = "1.0.1"; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("com.ljindustries.valheim.smartrepair"); Type[] types = typeof(Plugin).Assembly.GetTypes(); foreach (Type type in types) { try { _harmony.CreateClassProcessor(type).Patch(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Harmony skip " + type.FullName + ": " + ex.GetType().Name + ": " + ex.Message)); } } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class RecipeCache { private static readonly Dictionary<string, Recipe> ByItemName = new Dictionary<string, Recipe>(); private static ObjectDB _builtFor; private static int _builtForRecipeCount = -1; internal static Recipe GetRecipe(ItemData item) { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || item == null || item.m_shared == null) { return null; } int num = ((instance.m_recipes != null) ? instance.m_recipes.Count : 0); if (instance != _builtFor || num != _builtForRecipeCount) { ByItemName.Clear(); _builtFor = instance; _builtForRecipeCount = num; } string name = item.m_shared.m_name; if (string.IsNullOrEmpty(name)) { return null; } if (ByItemName.TryGetValue(name, out var value)) { return value; } Recipe recipe = instance.GetRecipe(item); ByItemName[name] = recipe; return recipe; } } internal static class RepairService { private static readonly List<ItemData> WornBuffer = new List<ItemData>(); private static Func<InventoryGui, ItemData, bool> _vanillaCanRepair; private static bool _canRepairResolved; internal static void RepairAllAt(CraftingStation station) { //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.AutoRepair.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)station == (Object)null || (Object)(object)localPlayer.GetCurrentCraftingStation() != (Object)(object)station || !station.m_canRepair || !station.CheckUsable(localPlayer, false)) { return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory == null) { return; } int num = 0; WornBuffer.Clear(); try { inventory.GetWornItems(WornBuffer); foreach (ItemData item in WornBuffer) { if (item == null || item.m_shared == null || !CanRepair(item)) { continue; } float maxDurability = item.GetMaxDurability(); if (!(maxDurability <= 0f) && !(item.m_durability >= maxDurability)) { ((Character)localPlayer).RaiseSkill((SkillType)107, 1f - item.m_durability / maxDurability); item.m_durability = maxDurability; num++; if (ModConfig.VerboseLogging.Value) { Plugin.Log.LogInfo((object)("Repaired " + item.m_shared.m_name + " at " + station.m_name)); } } } } catch (Exception arg) { Plugin.Log.LogError((object)$"Auto repair failed: {arg}"); } finally { WornBuffer.Clear(); } if (num == 0) { return; } if (station.m_repairItemDoneEffects != null) { station.m_repairItemDoneEffects.Create(((Component)station).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } if (ModConfig.ShowMessage.Value) { string text = ModConfig.MessageText.Value; if (string.IsNullOrEmpty(text)) { text = "Repaired {0} item(s)"; } ((Character)localPlayer).Message((MessageType)2, SafeFormat(text, num), 0, (Sprite)null, false); } } private static string SafeFormat(string format, int count) { try { return string.Format(format, count); } catch (FormatException) { return format; } } private static bool CanRepair(ItemData item) { Func<InventoryGui, ItemData, bool> func = ResolveVanillaCanRepair(); InventoryGui instance = InventoryGui.instance; if (func != null && (Object)(object)instance != (Object)null) { return func(instance, item); } return FallbackCanRepair(item); } private static Func<InventoryGui, ItemData, bool> ResolveVanillaCanRepair() { if (_canRepairResolved) { return _vanillaCanRepair; } _canRepairResolved = true; MethodInfo method = AccessTools.Method(typeof(InventoryGui), "CanRepair", new Type[1] { typeof(ItemData) }, (Type[])null); if (method == null) { Plugin.Log.LogWarning((object)"InventoryGui.CanRepair not found - falling back to SmartRepair's own repair rules. A Valheim update most likely renamed it; please report this."); return null; } try { _vanillaCanRepair = AccessTools.MethodDelegate<Func<InventoryGui, ItemData, bool>>(method, (object)null, false); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Binding InventoryGui.CanRepair as a delegate failed (" + ex.GetType().Name + ": " + ex.Message + "); using plain reflection instead.")); _vanillaCanRepair = (InventoryGui gui, ItemData item) => (bool)method.Invoke(gui, new object[1] { item }); } return _vanillaCanRepair; } private static bool FallbackCanRepair(ItemData item) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || item == null || item.m_shared == null) { return false; } if (!item.m_shared.m_canBeReparied) { return false; } if (localPlayer.NoCostCheat()) { return true; } CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation == (Object)null) { return false; } Recipe recipe = RecipeCache.GetRecipe(item); if ((Object)(object)recipe == (Object)null) { return false; } if ((Object)(object)recipe.m_craftingStation == (Object)null && (Object)(object)recipe.m_repairStation == (Object)null) { return false; } if (((Object)(object)recipe.m_repairStation != (Object)null && recipe.m_repairStation.m_name == currentCraftingStation.m_name) || ((Object)(object)recipe.m_craftingStation != (Object)null && recipe.m_craftingStation.m_name == currentCraftingStation.m_name) || item.m_worldLevel < Game.m_worldLevel) { return Mathf.Min(currentCraftingStation.GetLevel(true), 4) >= recipe.m_minStationLevel; } return StationLadder.AllowsCrossTierRepair(currentCraftingStation, item); } } internal static class StationLadder { internal const string DefaultOrder = "$piece_workbench, $piece_forge, $piece_artisanstation, $piece_blackforge, $piece_magetable"; private static readonly Dictionary<string, int> Tiers = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private static string _parsedFrom; internal static bool AllowsCrossTierRepair(CraftingStation station, ItemData item) { if (!ModConfig.CrossStationRepair.Value) { return false; } if ((Object)(object)station == (Object)null || item == null || item.m_shared == null) { return false; } if (!item.m_shared.m_canBeReparied) { return false; } int num = TierOf(station.m_name); if (num < 0) { return false; } Recipe recipe = RecipeCache.GetRecipe(item); if ((Object)(object)recipe == (Object)null) { return false; } int num2 = RequiredTier(recipe); if (num2 < 0) { return false; } if (num <= num2) { return false; } if (ModConfig.RequireStationLevel.Value && Mathf.Min(station.GetLevel(true), 4) < recipe.m_minStationLevel) { return false; } return true; } private static int RequiredTier(Recipe recipe) { return Lowest(Lowest(-1, recipe.m_repairStation), recipe.m_craftingStation); } private static int Lowest(int best, CraftingStation station) { if ((Object)(object)station == (Object)null) { return best; } int num = TierOf(station.m_name); if (num < 0) { return best; } if (best >= 0 && num >= best) { return best; } return num; } internal static int TierOf(string stationName) { if (string.IsNullOrEmpty(stationName)) { return -1; } EnsureParsed(); if (!Tiers.TryGetValue(Normalize(stationName), out var value)) { return -1; } return value; } private static void EnsureParsed() { string text = ((ModConfig.StationOrder == null) ? "$piece_workbench, $piece_forge, $piece_artisanstation, $piece_blackforge, $piece_magetable" : ModConfig.StationOrder.Value); if (text == null) { text = "$piece_workbench, $piece_forge, $piece_artisanstation, $piece_blackforge, $piece_magetable"; } if (text == _parsedFrom) { return; } Tiers.Clear(); int num = 0; string[] array = text.Split(','); for (int i = 0; i < array.Length; i++) { string text2 = Normalize(array[i]); if (text2.Length != 0 && !Tiers.ContainsKey(text2)) { Tiers[text2] = num++; } } _parsedFrom = text; } private static string Normalize(string name) { return name.Trim().TrimStart('$').Trim(); } } } namespace SmartRepair.Patches { [HarmonyPatch(typeof(CraftingStation), "Interact")] internal static class CraftingStationInteractPatch { private static void Postfix(CraftingStation __instance, Humanoid user, bool repeat) { if (!repeat) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)user != (Object)(object)localPlayer)) { RepairService.RepairAllAt(__instance); } } } } [HarmonyPatch(typeof(InventoryGui), "CanRepair")] internal static class InventoryGuiCanRepairPatch { private static void Postfix(ItemData item, ref bool __result) { if (__result) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if (!((Object)(object)currentCraftingStation == (Object)null)) { __result = StationLadder.AllowsCrossTierRepair(currentCraftingStation, item); } } } } }