Index: FiniteItemRepairs/7dtd-binaries/README.txt
===================================================================
--- FiniteItemRepairs/7dtd-binaries/README.txt	(revision 509)
+++ FiniteItemRepairs/7dtd-binaries/README.txt	(revision 509)
@@ -0,0 +1,8 @@
+Put the following files from your dedicated server in this folder:
+- 0Harmony.dll
+- Assembly-CSharp.dll
+- LogLibrary.dll
+- mscorlib.dll
+- Newtonsoft.Json.dll
+- System.dll
+- UnityEngine.CoreModule.dll
Index: FiniteItemRepairs/ConsoleCmdDegradationPercent.cs
===================================================================
--- FiniteItemRepairs/ConsoleCmdDegradationPercent.cs	(revision 509)
+++ FiniteItemRepairs/ConsoleCmdDegradationPercent.cs	(revision 509)
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace FiniteItemRepairs {
+	[UsedImplicitly]
+	public class ConsoleCmdDegradationPercent : ConsoleCmdAbstract {
+		public override string[] getCommands () {
+			return new[] { "degradationpercent", "degperc" };
+		}
+
+		public override bool IsExecuteOnClient => true;
+		public override bool AllowedInMainMenu => true;
+
+		public override void Execute (List<string> parms, CommandSenderInfo senderInfo) {
+			if (parms.Count == 0)
+			{
+				SdtdConsole.Instance.Output($"Current DegradationPercentage: {Settings.Data.DegradationPercent:P0} (== {Settings.Data.DegradationPercent})");
+				return;
+			}
+
+			if (!StringParsers.TryParseFloat(parms[0], out float value))
+			{
+				SdtdConsole.Instance.Output("Can not parse input value as float");
+				return;
+			}
+
+			Settings.Data.DegradationPercent = value;
+		}
+
+		public override string getDescription () {
+			return "Get / set the degradation percentage per item repair";
+		}
+	}
+}
Index: FiniteItemRepairs/FiniteItemRepairs.csproj
===================================================================
--- FiniteItemRepairs/FiniteItemRepairs.csproj	(revision 509)
+++ FiniteItemRepairs/FiniteItemRepairs.csproj	(revision 509)
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
+            Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
+    <PropertyGroup>
+        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+        <ProjectGuid>{04FD5F18-9C29-4CE8-9BD9-6C72EE214D5B}</ProjectGuid>
+        <OutputType>Library</OutputType>
+        <AppDesignerFolder>Properties</AppDesignerFolder>
+        <RootNamespace>FiniteItemRepairs</RootNamespace>
+        <AssemblyName>FiniteItemRepairs</AssemblyName>
+        <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
+        <FileAlignment>512</FileAlignment>
+        <LangVersion>9</LangVersion>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugSymbols>true</DebugSymbols>
+        <DebugType>full</DebugType>
+        <Optimize>false</Optimize>
+        <OutputPath>bin\Debug\</OutputPath>
+        <DefineConstants>DEBUG;TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugType>embedded</DebugType>
+        <Optimize>true</Optimize>
+        <OutputPath>Mods\FiniteItemRepairs</OutputPath>
+        <DefineConstants>TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+        <DebugSymbols>true</DebugSymbols>
+    </PropertyGroup>
+    <ItemGroup>
+      <Reference Include="0Harmony">
+        <HintPath>7dtd-binaries\0Harmony.dll</HintPath>
+        <Private>False</Private>
+      </Reference>
+      <Reference Include="Assembly-CSharp">
+        <HintPath>7dtd-binaries\Assembly-CSharp.dll</HintPath>
+          <Private>False</Private>
+      </Reference>
+      <Reference Include="LogLibrary">
+        <HintPath>7dtd-binaries\LogLibrary.dll</HintPath>
+          <Private>False</Private>
+      </Reference>
+      <Reference Include="mscorlib">
+        <HintPath>7dtd-binaries\mscorlib.dll</HintPath>
+          <Private>False</Private>
+      </Reference>
+      <Reference Include="Newtonsoft.Json">
+        <HintPath>7dtd-binaries\Newtonsoft.Json.dll</HintPath>
+          <Private>False</Private>
+      </Reference>
+      <Reference Include="System">
+        <HintPath>7dtd-binaries\System.dll</HintPath>
+          <Private>False</Private>
+      </Reference>
+      <Reference Include="UnityEngine.CoreModule">
+        <HintPath>7dtd-binaries\UnityEngine.CoreModule.dll</HintPath>
+          <Private>False</Private>
+      </Reference>
+    </ItemGroup>
+    <ItemGroup>
+      <Compile Include="ConsoleCmdDegradationPercent.cs" />
+      <Compile Include="FiniteItemRepairsPatch.cs" />
+      <Compile Include="ModMain.cs" />
+      <Compile Include="Settings.cs" />
+    </ItemGroup>
+    <ItemGroup>
+      <Content Include="Licht.puml" />
+      <Content Include="ModInfo.xml">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </Content>
+    </ItemGroup>
+    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
+    <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+         Other similar extension points exist, see Microsoft.Common.targets.
+    <Target Name="BeforeBuild">
+    </Target>
+    <Target Name="AfterBuild">
+    </Target>
+    -->
+</Project>
Index: FiniteItemRepairs/FiniteItemRepairs.sln
===================================================================
--- FiniteItemRepairs/FiniteItemRepairs.sln	(revision 509)
+++ FiniteItemRepairs/FiniteItemRepairs.sln	(revision 509)
@@ -0,0 +1,16 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FiniteItemRepairs", "FiniteItemRepairs.csproj", "{04FD5F18-9C29-4CE8-9BD9-6C72EE214D5B}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{04FD5F18-9C29-4CE8-9BD9-6C72EE214D5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{04FD5F18-9C29-4CE8-9BD9-6C72EE214D5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{04FD5F18-9C29-4CE8-9BD9-6C72EE214D5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{04FD5F18-9C29-4CE8-9BD9-6C72EE214D5B}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal
Index: FiniteItemRepairs/FiniteItemRepairsPatch.cs
===================================================================
--- FiniteItemRepairs/FiniteItemRepairsPatch.cs	(revision 509)
+++ FiniteItemRepairs/FiniteItemRepairsPatch.cs	(revision 509)
@@ -0,0 +1,163 @@
+﻿using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+using HarmonyLib;
+using JetBrains.Annotations;
+using UnityEngine;
+
+namespace FiniteItemRepairs
+{
+    [UsedImplicitly]
+    public class FiniteItemRepairsPatch
+    {
+        private const string MetaDataName = "DurabilityModifier";
+
+        private static readonly PassiveEffects EnumDegradationMax = EnumUtils.Parse<PassiveEffects> (nameof (PassiveEffects.DegradationMax));
+        private const string CustomNameDegradationMax = "DegradationMax";
+
+
+        [HarmonyPatch(typeof(XUiC_RecipeStack))]
+        [HarmonyPatch(nameof(XUiC_RecipeStack.outputStack))]
+        public static class XUiC_RecipeStack_outputStack
+        {
+            private static readonly MethodInfo VanillaItemValueCloneMethod = AccessTools.Method(typeof(ItemValue), "Clone");
+            private static readonly MethodInfo GetHandleDegradationMethod = SymbolExtensions.GetMethodInfo(() => HandleDegradation(null));
+
+            [UsedImplicitly]
+            private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
+            {
+                return new CodeMatcher(instructions)
+                    .MatchForward(true,
+                        new CodeMatch(OpCodes.Ldarg_0),
+                        new CodeMatch(OpCodes.Ldfld),
+                        new CodeMatch(OpCodes.Callvirt, VanillaItemValueCloneMethod)
+                    ).InsertAndAdvance(
+                        new CodeInstruction(OpCodes.Dup),
+                        new CodeInstruction(OpCodes.Call, GetHandleDegradationMethod)
+                    ).InstructionEnumeration();
+            }
+
+            private static void HandleDegradation(ItemValue outItem)
+            {
+                float currentDurabilityModifier = 1;
+                if (outItem.HasMetadata(MetaDataName, TypedMetadataValue.TypeTag.Float))
+                {
+                    object metadata = outItem.GetMetadata(MetaDataName);
+                    currentDurabilityModifier = (float)metadata;
+                }
+
+                currentDurabilityModifier = (1 - (1 - outItem.PercentUsesLeft) * Settings.Data.DegradationPercent) * currentDurabilityModifier;
+                currentDurabilityModifier = Mathf.Max(currentDurabilityModifier, 0.01f);
+
+                outItem.SetMetadata(MetaDataName, currentDurabilityModifier, TypedMetadataValue.TypeTag.Float);
+            }
+        }
+
+
+        [HarmonyPatch(typeof(ItemValue))]
+        [HarmonyPatch(nameof(ItemValue.MaxUseTimes))]
+        [HarmonyPatch(MethodType.Getter)]
+        public static class ItemValue_MaxUseTimes
+        {
+            [UsedImplicitly]
+            private static void Postfix(ref int __result, ItemValue __instance)
+            {
+                __result = ModMaxUseTimes(__result, __instance);
+            }
+        }
+
+        [HarmonyPatch(typeof(XUiM_ItemStack))]
+        [HarmonyPatch(nameof(XUiM_ItemStack.GetStatItemValueTextWithCompareInfo))]
+        public static class XUiM_ItemStack_GetStatItemValueTextWithCompareInfo
+        {
+            [UsedImplicitly]
+            private static void Prefix (DisplayInfoEntry infoEntry)
+            {
+                if (infoEntry.StatType != EnumDegradationMax)
+                {
+                    return;
+                }
+
+                infoEntry.CustomName = CustomNameDegradationMax;
+            }
+        }
+
+        [HarmonyPatch(typeof(XUiM_ItemStack))]
+        [HarmonyPatch(nameof(XUiM_ItemStack.GetStatItemValueTextWithModColoring))]
+        public static class XUiM_ItemStack_GetStatItemValueTextWithModColoring
+        {
+            [UsedImplicitly]
+            private static void Prefix (DisplayInfoEntry infoEntry)
+            {
+                if (infoEntry.StatType != EnumDegradationMax)
+                {
+                    return;
+                }
+
+                infoEntry.CustomName = CustomNameDegradationMax;
+            }
+        }
+
+        [HarmonyPatch(typeof(XUiM_ItemStack))]
+        [HarmonyPatch(nameof(XUiM_ItemStack.GetStatItemValueTextWithModInfo))]
+        public static class XUiM_ItemStack_GetStatItemValueTextWithModInfo
+        {
+            [UsedImplicitly]
+            private static void Prefix (DisplayInfoEntry infoEntry)
+            {
+                if (infoEntry.StatType != EnumDegradationMax)
+                {
+                    return;
+                }
+
+                infoEntry.CustomName = CustomNameDegradationMax;
+            }
+        }
+
+        [HarmonyPatch(typeof(XUiM_ItemStack))]
+        [HarmonyPatch(nameof(XUiM_ItemStack.GetCustomValue))]
+        public static class XUiM_ItemStack_GetCustomValue
+        {
+            [UsedImplicitly]
+            private static bool Prefix (ref float __result, DisplayInfoEntry entry, ItemValue itemValue, bool useMods)
+            {
+                if (entry.StatType != EnumDegradationMax)
+                {
+                    return true;
+                }
+
+                __result = DegradationMaxMod(entry.StatType, itemValue, null, entry.tags, useMods, 0);
+                return false;
+            }
+        }
+
+        private static float DegradationMaxMod (PassiveEffects statType, ItemValue itemValue, EntityPlayer player, FastTags<TagGroup.Global> tags, bool useMods, float value) {
+            if (statType != EnumDegradationMax) {
+                return value;
+            }
+        
+            value = EffectManager.GetValue(PassiveEffects.DegradationMax, itemValue, 0, player, tags: tags, calcEquipment: false, calcHoldingItem: false, calcProgression: false, calcBuffs: false, useMods: useMods);
+            value = ModMaxUseTimes ((int)value, itemValue);
+            return value;
+        }
+        
+        private static int ModMaxUseTimes(int value, ItemValue iv)
+        {
+            if (value <= 0)
+            {
+                return value;
+            }
+
+            if (!iv.HasMetadata(MetaDataName, TypedMetadataValue.TypeTag.Float))
+            {
+                return value;
+            }
+
+            object metadata = iv.GetMetadata(MetaDataName);
+            float currentDurabilityModifier = (float)metadata;
+            value = Mathf.RoundToInt(value * currentDurabilityModifier);
+            value = Mathf.Max(value, 1);
+            return value;
+        }
+    }
+}
Index: FiniteItemRepairs/ModInfo.xml
===================================================================
--- FiniteItemRepairs/ModInfo.xml	(revision 509)
+++ FiniteItemRepairs/ModInfo.xml	(revision 509)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<xml>
+	<Name value="FiniteItemRepairs" />
+	<DisplayName value="Finite Item Repairs" />
+	<Description value="Makes items degrade on every repair to keep incentive to go looting" />
+	<Author value="Alloc" />
+	<Version value="1.2.0.288" />
+	<Website value="" />
+</xml>
Index: FiniteItemRepairs/ModMain.cs
===================================================================
--- FiniteItemRepairs/ModMain.cs	(revision 509)
+++ FiniteItemRepairs/ModMain.cs	(revision 509)
@@ -0,0 +1,29 @@
+﻿using System.Reflection;
+using HarmonyLib;
+using JetBrains.Annotations;
+
+namespace FiniteItemRepairs
+{
+    [UsedImplicitly]
+    public class ModMain : IModApi
+    {
+        public static Mod Mod;
+
+        public void InitMod(Mod modInstance)
+        {
+            Mod = modInstance;
+            Settings.Init();
+            
+            ModEvents.GameShutdown.RegisterHandler(OnGameShutdown);
+            
+            Harmony harmony = new(GetType().ToString());
+            harmony.PatchAll(Assembly.GetExecutingAssembly());
+        }
+
+        private static void OnGameShutdown(ref ModEvents.SGameShutdownData data)
+        {
+            Settings.Save();
+            Settings.DestroyFileWatcher();
+        }
+    }
+}
Index: FiniteItemRepairs/Settings.cs
===================================================================
--- FiniteItemRepairs/Settings.cs	(revision 509)
+++ FiniteItemRepairs/Settings.cs	(revision 509)
@@ -0,0 +1,137 @@
+﻿using System;
+using System.IO;
+using Newtonsoft.Json;
+using UnityEngine;
+
+namespace FiniteItemRepairs
+{
+    public static class Settings
+    {
+        [JsonObject (MemberSerialization.Fields)]
+        public class SettingsData
+        {
+            private float degradationPercent = 0.5f;
+            public float DegradationPercent
+            {
+                get => degradationPercent;
+                set
+                {
+                    if (Mathf.Approximately(value, degradationPercent))
+                    {
+                        return;
+                    }
+
+                    degradationPercent = Mathf.Clamp01(value);
+                    Save();
+                }
+            }
+        }
+
+        public static SettingsData Data { get; private set; }
+
+        private static bool initDone;
+
+
+        public static void Init()
+        {
+            if (initDone)
+            {
+                return;
+            }
+
+            initDone = true;
+            InitFileWatcher();
+            Load();
+        }
+
+#region IO
+
+        private static readonly string ConfigFileName = $"{ModMain.Mod.Name}_Settings.json";
+
+        private static FileSystemWatcher fileWatcher;
+
+        private static void InitFileWatcher()
+        {
+            fileWatcher = new FileSystemWatcher(GetFilePath(), ConfigFileName);
+            fileWatcher.Changed += OnFileChanged;
+            fileWatcher.Created += OnFileChanged;
+            fileWatcher.Deleted += OnFileChanged;
+            fileWatcher.EnableRaisingEvents = true;
+        }
+
+        public static void DestroyFileWatcher()
+        {
+            if (fileWatcher == null)
+            {
+                return;
+            }
+
+            fileWatcher.Dispose();
+            fileWatcher = null;
+        }
+
+        private static void OnFileChanged(object source, FileSystemEventArgs e)
+        {
+            Log.Out($"[MOD] [{ModMain.Mod.DisplayName}] Reloading {ConfigFileName}");
+            Load();
+        }
+
+        private static string GetFilePath()
+        {
+            return GameIO.GetUserGameDataDir();
+        }
+
+        private static string GetFullPath()
+        {
+            return $"{GetFilePath()}/{ConfigFileName}";
+        }
+
+        private static void Load()
+        {
+            if (!SdFile.Exists(GetFullPath()))
+            {
+                Log.Out($"[MOD] [{ModMain.Mod.DisplayName}] config file '{ConfigFileName}' not found, creating.");
+                Data = new SettingsData();
+                Save();
+                return;
+            }
+
+            Log.Out($"[MOD] [{ModMain.Mod.DisplayName}] Loading config file from '{GetFullPath()}'");
+
+            try
+            {
+                string jsonText = SdFile.ReadAllText(GetFullPath());
+                Data = JsonConvert.DeserializeObject<SettingsData>(jsonText);
+            }
+            catch (JsonException e)
+            {
+                Log.Error($"[MOD] [{ModMain.Mod.DisplayName}] Exception while trying to load config file:");
+                Log.Exception(e);
+
+                Data = new SettingsData();
+                Save();
+            }
+        }
+
+        public static void Save()
+        {
+            try
+            {
+                string jsonText = JsonConvert.SerializeObject(Data);
+
+                fileWatcher.EnableRaisingEvents = false;
+                SdFile.WriteAllText(GetFullPath(), jsonText);
+                fileWatcher.EnableRaisingEvents = true;
+
+                Log.Out($"[MOD] [{ModMain.Mod.DisplayName}] Saved config file to '{GetFullPath()}'.");
+            }
+            catch (Exception e)
+            {
+                Log.Error($"[MOD] [{ModMain.Mod.DisplayName}] Exception while trying to save config file to '{GetFullPath()}':");
+                Log.Exception(e);
+            }
+        }
+
+#endregion
+    }
+}
