Decompiled source of RainproofBuildings v1.0.0

BepInEx/plugins/NegroniForge-RainproofBuildings/RainWearProtection.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("RainWearProtection.Tests")]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: AssemblyCompany("RainWearProtection")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Protect your buildings from rain and water wear.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Rainproof Buildings")]
[assembly: AssemblyTitle("RainWearProtection")]
[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 NegroniForge.RainWearProtection
{
	[BepInPlugin("negroniforge.valheim.rainwearprotection", "Rainproof Buildings", "1.0.0")]
	[BepInIncompatibility("uk.co.oliapps.valheim.noraindamage")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "negroniforge.valheim.rainwearprotection";

		public const string Version = "1.0.0";

		internal static volatile WearPolicy Policy = new WearPolicy(enabled: true, DamageFilter.ALL, "", 0f, 5f);

		private ConfigEntry<bool> enabledSetting;

		private ConfigEntry<DamageFilter> filter;

		private ConfigEntry<string> list;

		private ConfigEntry<float> maximum;

		private ConfigEntry<float> rate;

		private Harmony harmony;

		private void Awake()
		{
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Expected O, but got Unknown
			enabledSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable protection. False uses vanilla rain and water wear.");
			filter = ((BaseUnityPlugin)this).Config.Bind<DamageFilter>("Damage Filter", "Filter Type", DamageFilter.ALL, "ALL, INCLUSIVE or EXCLUSIVE. Unselected pieces use vanilla wear.");
			list = ((BaseUnityPlugin)this).Config.Bind<string>("Damage Filter", "Filter List", "", "Comma-separated, case-sensitive substrings of GameObject names. Whitespace and empty entries are ignored.");
			maximum = ((BaseUnityPlugin)this).Config.Bind<float>("Rain Damage", "Max Damage", 0f, "Maximum fraction of maximum HP lost to rain/water (0..1). 0 fully protects; 1 allows destruction.");
			rate = ((BaseUnityPlugin)this).Config.Bind<float>("Rain Damage", "Damage Per Minute", 5f, "Percentage points of maximum HP per vanilla rain tick (>60 seconds). Negative or non-finite values become zero.");
			RefreshSettings(null, null);
			((BaseUnityPlugin)this).Config.SettingChanged += RefreshSettings;
			harmony = new Harmony("negroniforge.valheim.rainwearprotection");
			try
			{
				harmony.Patch((MethodBase)AccessTools.Method(typeof(WearNTear), "UpdateWear", (Type[])null, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RainTranspiler), "Transpile", (Type[])null)
				{
					priority = 0
				}, (HarmonyMethod)null, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Rain/water wear patch applied (two validated edits). Other UpdateWear logic remains original.");
			}
			catch (Exception ex)
			{
				harmony.UnpatchSelf();
				((BaseUnityPlugin)this).Logger.LogError((object)("Rain/water protection NOT active: UpdateWear was not recognized or patch installation failed. Original wear remains. " + ex.GetBaseException().Message));
			}
		}

		private void RefreshSettings(object sender, SettingChangedEventArgs args)
		{
			Policy = new WearPolicy(enabledSetting.Value, filter.Value, list.Value, maximum.Value, rate.Value);
		}

		private void OnDestroy()
		{
			((BaseUnityPlugin)this).Config.SettingChanged -= RefreshSettings;
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}

		private static bool Selected(WearPolicy policy, WearNTear piece)
		{
			if (policy.Enabled)
			{
				if (policy.Filter != DamageFilter.ALL)
				{
					return policy.Selects(((Object)((Component)piece).gameObject).name);
				}
				return true;
			}
			return false;
		}

		public static bool ShouldRunRain(WearNTear piece)
		{
			WearPolicy policy = Policy;
			if (!Selected(policy, piece))
			{
				return piece.GetHealthPercentage() > 0.5f;
			}
			if (policy.MaxDamage > 0f)
			{
				return policy.Rate > 0f;
			}
			return false;
		}

		public static float RainContribution(WearNTear piece, ZNetView view)
		{
			WearPolicy policy = Policy;
			if (!Selected(policy, piece))
			{
				return 5f;
			}
			if (policy.MaxDamage <= 0f || policy.Rate <= 0f)
			{
				return 0f;
			}
			ZDO zDO = view.GetZDO();
			if (zDO != null)
			{
				return policy.Contribution(zDO.GetFloat(ZDOVars.s_health, piece.m_health), piece.m_health);
			}
			return 0f;
		}
	}
	internal static class RainTranspiler
	{
		private static FieldInfo Field(string name)
		{
			return AccessTools.Field(typeof(WearNTear), name);
		}

		private static MethodInfo Method(string name)
		{
			return AccessTools.Method(typeof(WearNTear), name, (Type[])null, (Type[])null);
		}

		public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Expected O, but got Unknown
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Expected O, but got Unknown
			List<CodeInstruction> source = instructions.ToList();
			List<int> list = (from i in Enumerable.Range(0, Math.Max(0, source.Count - 38))
				where Matches(source, i)
				select i).ToList();
			if (list.Count != 1)
			{
				throw new InvalidOperationException("Expected exactly one intact rain region; found " + list.Count + ".");
			}
			int num = list[0];
			List<CodeInstruction> list2 = ((IEnumerable<CodeInstruction>)source).Select((Func<CodeInstruction, CodeInstruction>)((CodeInstruction c) => new CodeInstruction(c))).ToList();
			list2[num + 6].operand = AccessTools.Method(typeof(Plugin), "ShouldRunRain", (Type[])null, (Type[])null);
			list2[num + 7].opcode = OpCodes.Nop;
			list2[num + 7].operand = null;
			list2[num + 8].opcode = OpCodes.Brfalse;
			list2[num + 30].opcode = OpCodes.Ldarg_0;
			list2[num + 30].operand = null;
			list2.InsertRange(num + 31, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Ldfld, (object)Field("m_nview")),
				new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Plugin), "RainContribution", (Type[])null, (Type[])null))
			});
			return list2;
		}

		private static bool Matches(List<CodeInstruction> code, int s)
		{
			string[] array = new string[39]
			{
				"ldarg.0", "ldfld", "brfalse", "ldloc.0", "brtrue", "ldarg.0", "call", "ldc.r4", "ble.un", "ldarg.0",
				"call", "brfalse", "ldarg.0", "ldfld", "ldc.r4", "bne.un", "ldarg.0", "ldarg.1", "stfld", "br",
				"ldarg.1", "ldarg.0", "ldfld", "sub", "ldc.r4", "ble.un", "ldarg.0", "ldarg.1", "stfld", "ldloc.1",
				"ldc.r4", "add", "stloc.1", "br", "ldarg.0", "ldc.r4", "stfld", "ldarg.0", "ldfld"
			};
			for (int i = 0; i < array.Length; i++)
			{
				string text = code[s + i].opcode.Name;
				if (text.EndsWith(".s"))
				{
					text = text.Substring(0, text.Length - 2);
				}
				if (text != array[i] || code[s + i].blocks.Count != 0)
				{
					return false;
				}
			}
			if (!object.Equals(code[s + 1].operand, Field("m_noRoofWear")) || !object.Equals(code[s + 6].operand, Method("GetHealthPercentage")) || !object.Equals(code[s + 10].operand, Method("IsWet")) || !object.Equals(code[s + 38].operand, Field("m_noSupportWear")))
			{
				return false;
			}
			int[] array2 = new int[5] { 13, 18, 22, 28, 36 };
			foreach (int num in array2)
			{
				if (!object.Equals(code[s + num].operand, Field("m_rainTimer")))
				{
					return false;
				}
			}
			(int, float)[] array3 = new(int, float)[5]
			{
				(7, 0.5f),
				(14, 0f),
				(24, 60f),
				(30, 5f),
				(35, 0f)
			};
			for (int j = 0; j < array3.Length; j++)
			{
				(int, float) tuple = array3[j];
				if (!object.Equals(code[s + tuple.Item1].operand, tuple.Item2))
				{
					return false;
				}
			}
			(int, int)[] array4 = new(int, int)[8]
			{
				(2, 37),
				(4, 37),
				(8, 37),
				(11, 34),
				(15, 20),
				(19, 37),
				(25, 37),
				(33, 37)
			};
			for (int j = 0; j < array4.Length; j++)
			{
				(int, int) tuple2 = array4[j];
				if (!(code[s + tuple2.Item1].operand is Label item) || !code[s + tuple2.Item2].labels.Contains(item))
				{
					return false;
				}
			}
			HashSet<Label> hashSet = new HashSet<Label>(code.Skip(s + 1).Take(36).SelectMany((CodeInstruction c) => c.labels));
			for (int num2 = 0; num2 < code.Count; num2++)
			{
				if (num2 < s || num2 > s + 36)
				{
					if (code[num2].operand is Label item2 && hashSet.Contains(item2))
					{
						return false;
					}
					if (code[num2].operand is Label[] source && source.Any(hashSet.Contains))
					{
						return false;
					}
				}
			}
			for (int num3 = 1; num3 <= 36; num3++)
			{
				if (num3 != 20 && num3 != 34 && code[s + num3].labels.Count != 0)
				{
					return false;
				}
			}
			return true;
		}
	}
	public enum DamageFilter
	{
		ALL,
		INCLUSIVE,
		EXCLUSIVE
	}
	internal sealed class WearPolicy
	{
		internal readonly bool Enabled;

		internal readonly DamageFilter Filter;

		internal readonly float MaxDamage;

		internal readonly float Rate;

		private readonly string[] tokens;

		internal WearPolicy(bool enabled, DamageFilter filter, string list, float maxDamage, float rate)
		{
			Enabled = enabled;
			Filter = filter;
			MaxDamage = (Finite(maxDamage) ? Math.Max(0f, Math.Min(1f, maxDamage)) : 0f);
			Rate = (Finite(rate) ? Math.Max(0f, rate) : 0f);
			tokens = (from s in (list ?? "").Split(new char[1] { ',' })
				select s.Trim() into s
				where s.Length > 0
				select s).Distinct().ToArray();
		}

		internal bool Selects(string name)
		{
			if (!Enabled)
			{
				return false;
			}
			if (Filter == DamageFilter.ALL)
			{
				return true;
			}
			bool flag = false;
			for (int i = 0; i < tokens.Length; i++)
			{
				if (name.IndexOf(tokens[i], StringComparison.Ordinal) >= 0)
				{
					flag = true;
					break;
				}
			}
			if (Filter != DamageFilter.INCLUSIVE)
			{
				if (Filter == DamageFilter.EXCLUSIVE)
				{
					return !flag;
				}
				return false;
			}
			return flag;
		}

		internal float Contribution(float currentHealth, float maxHealth)
		{
			if (MaxDamage <= 0f || Rate <= 0f || !Finite(currentHealth) || !Finite(maxHealth) || maxHealth <= 0f)
			{
				return 0f;
			}
			double val = ((double)currentHealth - (double)maxHealth * (1.0 - (double)MaxDamage)) / (double)maxHealth * 100.0;
			return (float)Math.Max(0.0, Math.Min(Rate, val));
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
}