using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Derpability.Patches;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Wagnarok")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Valheim mod to reduce maximum durability on equipment after repairs.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Derpability")]
[assembly: AssemblyTitle("Derpability")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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 Derpability
{
[BepInPlugin("wagnarok.valeim.derpability", "Derpability", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
public const string PluginGUID = "wagnarok.valeim.derpability";
public const string PluginName = "Derpability";
public const string PluginVersion = "1.0.0";
internal static ManualLogSource Log;
private readonly Harmony _harmony = new Harmony("wagnarok.valeim.derpability");
public static ConfigEntry<float> MaxDurabilityLossPerRepair;
public static ConfigEntry<float> BrokenThresholdPercent;
private void Awake()
{
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
MaxDurabilityLossPerRepair = ((BaseUnityPlugin)this).Config.Bind<float>("Repair", "MaxDurabilityLossPerRepair", 0.1f, "Maximum fraction of the item's ORIGINAL max durability lost per repair (when item is at 0 durability). Actual loss is scaled by how damaged the item was. Default 0.10 = up to 10% loss per repair.");
BrokenThresholdPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Repair", "BrokenThresholdPercent", 0.3f, new ConfigDescription("Once an item's current max durability reaches this fraction of its original max, it can no longer be repaired but still functions until it hits 0. Default 0.30 = unrepairable at 30%. Maximum 0.37 ensures wear stage colors remain within 0-100%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.37f), Array.Empty<object>()));
_harmony.PatchAll(Assembly.GetExecutingAssembly());
TryPatchTooltip();
TryPatchCanUpgrade();
Log.LogInfo((object)"Derpability v1.0.0 loaded successfully.");
}
private void OnDestroy()
{
_harmony.UnpatchAll("wagnarok.valeim.derpability");
}
private void TryPatchCanUpgrade()
{
//IL_0089: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Expected O, but got Unknown
MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(InventoryGui), "AddRecipeToList", new Type[4]
{
typeof(Player),
typeof(Recipe),
typeof(ItemData),
typeof(bool)
}, (Type[])null);
if (methodInfo == null)
{
Log.LogWarning((object)"AddRecipeToList patch skipped — method not found.");
return;
}
MethodInfo methodInfo2 = AccessTools.Method(typeof(BlockUpgradeIfBrokenPatch), "Postfix", (Type[])null, (Type[])null);
_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null);
Log.LogInfo((object)"Upgrade block patch applied to AddRecipeToList.");
}
private void TryPatchTooltip()
{
//IL_0178: Unknown result type (might be due to invalid IL or missing references)
//IL_0184: Expected O, but got Unknown
MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ItemData), "GetTooltip", new Type[5]
{
typeof(ItemData),
typeof(int),
typeof(bool),
typeof(float),
typeof(int)
}, (Type[])null);
if (methodInfo == null)
{
Log.LogWarning((object)"Tooltip patch skipped — dumping all ItemData methods with 'Tooltip' in name:");
MethodInfo[] methods = typeof(ItemData).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methodInfo2 in methods)
{
if (methodInfo2.Name.Contains("ooltip"))
{
Log.LogWarning((object)(" " + (methodInfo2.IsStatic ? "static " : "") + methodInfo2.ReturnType.Name + " " + methodInfo2.Name + "(" + string.Join(", ", Array.ConvertAll(methodInfo2.GetParameters(), (ParameterInfo p) => p.ParameterType.Name)) + ")"));
}
}
}
else
{
MethodInfo methodInfo3 = AccessTools.Method(typeof(BrokenItemTooltipPatch), "Postfix", (Type[])null, (Type[])null);
_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null);
Log.LogInfo((object)"Tooltip patch applied successfully.");
}
}
}
[HarmonyPatch(typeof(ZNet), "SendPeerInfo")]
public static class ZNet_SendPeerInfo_Patch
{
[HarmonyPostfix]
public static void Postfix(ZRpc rpc)
{
rpc.Invoke("Derpability_VersionCheck", new object[1] { "1.0.0" });
Plugin.Log.LogInfo((object)"Sent Derpability version 1.0.0 to peer.");
}
}
[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
public static class ZNet_OnNewConnection_Patch
{
[HarmonyPostfix]
public static void Postfix(ZNetPeer peer)
{
peer.m_rpc.Register<string>("Derpability_VersionCheck", (Action<ZRpc, string>)delegate(ZRpc rpc, string version)
{
if (version != "1.0.0")
{
Plugin.Log.LogWarning((object)("Kicking peer: Derpability version mismatch. Server=1.0.0, Client=" + version));
ZNet.instance.Disconnect(peer);
}
else
{
Plugin.Log.LogInfo((object)("Peer passed Derpability version check (v" + version + ")."));
}
});
}
}
}
namespace Derpability.Patches
{
internal static class ItemKeys
{
public const string OriginalMaxDurability = "ee_original_max";
public const string CurrentMaxDurability = "ee_current_max";
}
internal static class DurabilityHelper
{
internal static bool BypassPatch;
private static float ParseOrDefault(ItemData item, string key, float fallback)
{
if (item.m_customData.TryGetValue(key, out var value) && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
return result;
}
return fallback;
}
private static void Store(ItemData item, string key, float value)
{
item.m_customData[key] = value.ToString("F4", CultureInfo.InvariantCulture);
}
public static float CalcOriginalMax(ItemData item)
{
return item.m_shared.m_maxDurability + (float)Mathf.Max(0, item.m_quality - 1) * item.m_shared.m_durabilityPerLevel;
}
public static float GetOriginalMax(ItemData item)
{
float num = ParseOrDefault(item, "ee_original_max", -1f);
if (!(num > 0f))
{
return CalcOriginalMax(item);
}
return num;
}
public static float GetCurrentMax(ItemData item)
{
float num = ParseOrDefault(item, "ee_current_max", -1f);
if (!(num > 0f))
{
return GetOriginalMax(item);
}
return num;
}
public static bool HasBeenDegraded(ItemData item)
{
return item.m_customData.ContainsKey("ee_current_max");
}
public static void ApplyRepairDegradation(ItemData item)
{
BypassPatch = true;
float maxDurability = item.GetMaxDurability();
BypassPatch = false;
if (!item.m_customData.ContainsKey("ee_original_max"))
{
Store(item, "ee_original_max", maxDurability);
}
float originalMax = GetOriginalMax(item);
float currentMax = GetCurrentMax(item);
if (!(maxDurability - item.m_durability <= 0f))
{
float num = 1f - Mathf.Clamp01(item.m_durability / currentMax);
float num2 = originalMax * Plugin.MaxDurabilityLossPerRepair.Value * num;
float num3 = Mathf.Max(currentMax - num2, 0f);
Store(item, "ee_current_max", num3);
Plugin.Log.LogDebug((object)("Repaired " + item.m_shared.m_name + ": " + $"damage was {num * 100f:F1}%, " + $"degraded max {currentMax:F1} -> {num3:F1} (loss: {num2:F1}) " + $"(original: {originalMax:F1}, threshold: {originalMax * Plugin.BrokenThresholdPercent.Value:F1})"));
}
}
public static bool IsUnrepairable(ItemData item)
{
if (!HasBeenDegraded(item))
{
return false;
}
float originalMax = GetOriginalMax(item);
return GetCurrentMax(item) <= originalMax * Plugin.BrokenThresholdPercent.Value;
}
public static float GetWearRatio(ItemData item)
{
if (!HasBeenDegraded(item))
{
return 1f;
}
float originalMax = GetOriginalMax(item);
if (originalMax <= 0f)
{
return 1f;
}
return GetCurrentMax(item) / originalMax;
}
}
internal static class WearStage
{
public static float Red => Plugin.BrokenThresholdPercent.Value;
public static float Orange => Plugin.BrokenThresholdPercent.Value * 2f;
public static float Yellow => Plugin.BrokenThresholdPercent.Value * 2.67f;
}
[HarmonyPatch(typeof(ItemData), "GetMaxDurability", new Type[] { typeof(int) })]
public static class GetMaxDurabilityPatch
{
[HarmonyPostfix]
public static void Postfix(ItemData __instance, ref float __result)
{
if (!DurabilityHelper.BypassPatch && DurabilityHelper.HasBeenDegraded(__instance))
{
float currentMax = DurabilityHelper.GetCurrentMax(__instance);
if (currentMax < __result)
{
__result = currentMax;
}
}
}
}
[HarmonyPatch(typeof(Humanoid), "EquipItem")]
public static class EquipItemClampPatch
{
[HarmonyPrefix]
public static void Prefix(ItemData item)
{
if (item != null && item.m_shared.m_useDurability && DurabilityHelper.HasBeenDegraded(item))
{
float currentMax = DurabilityHelper.GetCurrentMax(item);
if (item.m_durability > currentMax)
{
item.m_durability = currentMax;
}
}
}
}
[HarmonyPatch(typeof(Player), "SetCraftingStation")]
public static class RestoreHandItemOnStationExitPatch
{
private static ItemData _savedRightItem;
private static MethodInfo _getRightItem;
private static MethodInfo _queueEquipItem;
private static bool _reflected;
private static bool TryReflect()
{
if (_reflected)
{
if (_getRightItem != null)
{
return _queueEquipItem != null;
}
return false;
}
_reflected = true;
BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
_getRightItem = typeof(Humanoid).GetMethod("GetRightItem", bindingAttr);
_queueEquipItem = typeof(Player).GetMethod("QueueEquipAction", bindingAttr) ?? typeof(Humanoid).GetMethod("QueueEquipAction", bindingAttr);
if (_getRightItem == null)
{
Plugin.Log.LogWarning((object)"RestoreHandItem: GetRightItem not found");
}
if (_queueEquipItem == null)
{
Plugin.Log.LogWarning((object)"RestoreHandItem: QueueEquipAction not found");
}
if (_getRightItem != null)
{
return _queueEquipItem != null;
}
return false;
}
[HarmonyPrefix]
public static void Prefix(Player __instance, CraftingStation station)
{
if (!((Object)(object)station == (Object)null) && TryReflect())
{
object? obj = _getRightItem.Invoke(__instance, null);
_savedRightItem = (ItemData)((obj is ItemData) ? obj : null);
}
}
[HarmonyPostfix]
public static void Postfix(Player __instance, CraftingStation station)
{
if (!((Object)(object)station != (Object)null) && _savedRightItem != null && TryReflect())
{
if (!((Humanoid)__instance).IsItemEquiped(_savedRightItem))
{
_queueEquipItem.Invoke(__instance, new object[1] { _savedRightItem });
Plugin.Log.LogDebug((object)("Re-queued hand item after leaving crafting station: " + _savedRightItem.m_shared.m_name));
}
_savedRightItem = null;
}
}
}
public static class BlockRepairIfBrokenPatch
{
[HarmonyPostfix]
public static void Postfix(ItemData item, ref bool __result)
{
if (item != null && __result && DurabilityHelper.IsUnrepairable(item))
{
__result = false;
Plugin.Log.LogDebug((object)("Repair blocked: " + item.m_shared.m_name + " is too worn to repair."));
}
}
}
public static class BlockUpgradeIfBrokenPatch
{
public static void Postfix(Player player, Recipe recipe, ItemData item, ref bool canCraft)
{
if (item != null && canCraft && DurabilityHelper.IsUnrepairable(item))
{
canCraft = false;
Plugin.Log.LogDebug((object)("Upgrade blocked: " + item.m_shared.m_name + " is too worn to upgrade."));
}
}
}
[HarmonyPatch(typeof(InventoryGui), "RepairOneItem")]
public static class ReduceMaxDurabilityOnRepairPatch
{
[HarmonyPrefix]
public static void Prefix()
{
if ((Object)(object)Player.m_localPlayer == (Object)null)
{
return;
}
List<ItemData> list = new List<ItemData>();
((Humanoid)Player.m_localPlayer).GetInventory().GetWornItems(list);
foreach (ItemData item in list)
{
if (item.m_shared.m_canBeReparied && item.m_durability < item.GetMaxDurability() && !DurabilityHelper.IsUnrepairable(item))
{
float durability = item.m_durability;
DurabilityHelper.ApplyRepairDegradation(item);
item.m_durability = durability;
break;
}
}
}
}
[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
public static class PreserveWearRatioOnUpgradePatch
{
private static readonly Dictionary<Vector2i, (float ratio, int quality)> _snapshot = new Dictionary<Vector2i, (float, int)>();
[HarmonyPrefix]
public static void Prefix(InventoryGui __instance, Player player)
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
_snapshot.Clear();
if ((Object)(object)player == (Object)null)
{
return;
}
foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
{
if (allItem.m_shared.m_useDurability && DurabilityHelper.HasBeenDegraded(allItem))
{
float wearRatio = DurabilityHelper.GetWearRatio(allItem);
if (wearRatio < 1f)
{
_snapshot[allItem.m_gridPos] = (wearRatio, allItem.m_quality);
}
}
}
}
[HarmonyPostfix]
public static void Postfix(InventoryGui __instance, Player player)
{
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)player == (Object)null || _snapshot.Count == 0)
{
return;
}
foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
{
if (allItem.m_shared.m_useDurability && _snapshot.TryGetValue(allItem.m_gridPos, out (float, int) value) && allItem.m_quality > value.Item2 && !DurabilityHelper.IsUnrepairable(allItem))
{
DurabilityHelper.BypassPatch = true;
float maxDurability = allItem.GetMaxDurability();
DurabilityHelper.BypassPatch = false;
float num = maxDurability * value.Item1;
float durability = allItem.m_durability;
allItem.m_customData["ee_original_max"] = maxDurability.ToString("F4", CultureInfo.InvariantCulture);
allItem.m_customData["ee_current_max"] = num.ToString("F4", CultureInfo.InvariantCulture);
allItem.m_durability = Mathf.Min(durability, num);
Plugin.Log.LogDebug((object)($"Upgraded {allItem.m_shared.m_name} q{allItem.m_quality}: " + $"new original max={maxDurability:F1}, " + $"preserved wear ratio={value.Item1:P0}, " + $"new current max={num:F1}"));
}
}
_snapshot.Clear();
}
}
public static class BrokenItemTooltipPatch
{
private const string Tag = "Too worn to repair";
public static void Postfix(ItemData item, ref string __result)
{
if (item != null && __result != null && !__result.Contains("Too worn to repair") && item.m_shared.m_useDurability)
{
float wearRatio = DurabilityHelper.GetWearRatio(item);
if (DurabilityHelper.IsUnrepairable(item))
{
__result += "\n<color=red>⚠ Too worn to repair — use until it breaks</color>";
}
else if (wearRatio < WearStage.Orange)
{
__result += "\n<color=orange>Heavily worn — limited repairs remaining</color>";
}
else if (wearRatio < WearStage.Yellow)
{
__result += "\n<color=yellow>Worn — showing signs of wear</color>";
}
}
}
}
[HarmonyPatch(typeof(HotkeyBar), "UpdateIcons")]
public static class HotbarDurabilityColorPatch
{
private static FieldInfo _elementsField;
private static FieldInfo _elementDurabilityField;
private static FieldInfo _elementUsedField;
private static bool _reflected;
private static bool TryReflect()
{
if (_reflected)
{
return _elementsField != null;
}
_reflected = true;
BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
_elementsField = typeof(HotkeyBar).GetField("m_elements", bindingAttr);
Type type = typeof(HotkeyBar).GetNestedType("ElementData", BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(HotkeyBar).GetNestedType("Element", BindingFlags.Public | BindingFlags.NonPublic);
if (type != null)
{
_elementDurabilityField = type.GetField("m_durability", bindingAttr);
_elementUsedField = type.GetField("m_used", bindingAttr);
}
if (_elementsField == null || _elementDurabilityField == null)
{
Plugin.Log.LogWarning((object)"HotbarDurabilityColorPatch: reflection incomplete — hotbar colors disabled.");
return false;
}
Plugin.Log.LogInfo((object)"HotbarDurabilityColorPatch: reflection succeeded.");
return true;
}
[HarmonyPostfix]
public static void Postfix(HotkeyBar __instance, Player player)
{
//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
//IL_0123: Unknown result type (might be due to invalid IL or missing references)
if (!TryReflect() || (Object)(object)player == (Object)null || !(_elementsField.GetValue(__instance) is IList list))
{
return;
}
for (int i = 0; i < list.Count; i++)
{
object obj = list[i];
if (_elementUsedField != null && !(bool)_elementUsedField.GetValue(obj))
{
continue;
}
ItemData itemAt = ((Humanoid)player).GetInventory().GetItemAt(i, 0);
if (itemAt == null || !itemAt.m_shared.m_useDurability)
{
continue;
}
object? value = _elementDurabilityField.GetValue(obj);
GuiBar val = (GuiBar)((value is GuiBar) ? value : null);
if (!((Object)(object)val == (Object)null))
{
((Component)val).gameObject.SetActive(true);
val.SetValue(itemAt.GetDurabilityPercentage());
float wearRatio = DurabilityHelper.GetWearRatio(itemAt);
if (wearRatio <= WearStage.Red)
{
val.SetColor(new Color(1f, 0.2f, 0.2f));
}
else if (wearRatio <= WearStage.Orange)
{
val.SetColor(new Color(1f, 0.5f, 0f));
}
else if (wearRatio <= WearStage.Yellow)
{
val.SetColor(new Color(1f, 1f, 0f));
}
else
{
val.ResetColor();
}
}
}
}
}
[HarmonyPatch(typeof(InventoryGrid), "UpdateGui")]
public static class DurabilityBarColorPatch
{
private static FieldInfo _inventoryField;
private static FieldInfo _elementsField;
private static FieldInfo _elementDurabilityField;
private static FieldInfo _elementPosField;
private static bool _reflected;
private static bool TryReflect()
{
if (_reflected)
{
return _inventoryField != null;
}
_reflected = true;
BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
_inventoryField = typeof(InventoryGrid).GetField("m_inventory", bindingAttr);
_elementsField = typeof(InventoryGrid).GetField("m_elements", bindingAttr);
Type nestedType = typeof(InventoryGrid).GetNestedType("Element", BindingFlags.Public | BindingFlags.NonPublic);
if (nestedType != null)
{
_elementDurabilityField = nestedType.GetField("m_durability", bindingAttr);
_elementPosField = nestedType.GetField("m_pos", bindingAttr);
}
if (_inventoryField == null || _elementsField == null || _elementDurabilityField == null || _elementPosField == null)
{
Plugin.Log.LogWarning((object)"DurabilityBarColorPatch: failed to reflect InventoryGrid fields — bar colors disabled.");
return false;
}
Plugin.Log.LogInfo((object)"DurabilityBarColorPatch: reflection succeeded.");
return true;
}
[HarmonyPostfix]
public static void Postfix(InventoryGrid __instance)
{
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: 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_00b3: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
//IL_0148: Unknown result type (might be due to invalid IL or missing references)
//IL_016e: Unknown result type (might be due to invalid IL or missing references)
if (!TryReflect())
{
return;
}
object? value = _inventoryField.GetValue(__instance);
Inventory val = (Inventory)((value is Inventory) ? value : null);
if (val == null || !(_elementsField.GetValue(__instance) is IList list))
{
return;
}
Dictionary<Vector2i, object> dictionary = new Dictionary<Vector2i, object>();
foreach (object item in list)
{
Vector2i key = (Vector2i)_elementPosField.GetValue(item);
dictionary[key] = item;
}
foreach (ItemData allItem in val.GetAllItems())
{
if (!allItem.m_shared.m_useDurability || !dictionary.TryGetValue(allItem.m_gridPos, out var value2))
{
continue;
}
object? value3 = _elementDurabilityField.GetValue(value2);
GuiBar val2 = (GuiBar)((value3 is GuiBar) ? value3 : null);
if (!((Object)(object)val2 == (Object)null))
{
((Component)val2).gameObject.SetActive(true);
val2.SetValue(allItem.GetDurabilityPercentage());
float wearRatio = DurabilityHelper.GetWearRatio(allItem);
if (wearRatio <= WearStage.Red)
{
val2.SetColor(new Color(1f, 0.2f, 0.2f));
}
else if (wearRatio <= WearStage.Orange)
{
val2.SetColor(new Color(1f, 0.5f, 0f));
}
else if (wearRatio <= WearStage.Yellow)
{
val2.SetColor(new Color(1f, 1f, 0f));
}
else
{
val2.ResetColor();
}
}
}
}
}
}