Decompiled source of SmartRepair v1.0.1

SmartRepair.dll

Decompiled 2 weeks ago
using 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);
				}
			}
		}
	}
}