Decompiled source of ValheimCommunityPatch v0.38.0

plugins/ValheimCommunityPatch.dll

Decompiled 6 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Utils;
using SoftReferenceableAssets;
using Unity.Collections;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using Valheim.SettingsGui;
using ValheimCommunityPatch.Patches.Correctness;
using ValheimCommunityPatch.Patches.Performance;
using ValheimCommunityPatch.Patches.Terrain;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ValheimCommunityPatch")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ValheimCommunityPatch")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")]
[assembly: AssemblyFileVersion("0.38.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.38.0.0")]
namespace ValheimCommunityPatch
{
	public static class APIReceiver
	{
		internal const int ApiVersion = 1;

		private const string ApiNamespace = "ValheimCommunityPatch.API";

		public static int GetApiVersion()
		{
			return 1;
		}

		public static List<string> GetFixIds()
		{
			return FixRegistry.GetFixIds();
		}

		public static int GetFixState(string fixId)
		{
			return FixRegistry.GetState(fixId);
		}

		public static bool CanDisableFix(string fixId)
		{
			return FixRegistry.CanDisable(fixId);
		}

		public static bool DisableFix(string fixId, string reason)
		{
			return FixRegistry.Disable(fixId, reason, DescribeCaller());
		}

		public static bool IsTerrainBuildLoopActive()
		{
			return HeightmapBuilderThroughputPatch.LoopRunning;
		}

		public static int GetTerrainReadyCap()
		{
			return HeightmapBuilderThroughputPatch.ReadyCapInForce;
		}

		public static bool SetTerrainPriorityZones(IList<Vector2s> zones)
		{
			HeightmapBuilderThroughputPatch.SetPriorityZones(zones);
			return HeightmapBuilderThroughputPatch.LoopRunning;
		}

		public static void ClearTerrainPriorityZones()
		{
			HeightmapBuilderThroughputPatch.ClearPriorityZones();
		}

		public static void AddTerrainBuildFinished(Action<HMBuildData> handler)
		{
			HeightmapBuilderThroughputPatch.AddBuildFinishedHandler(handler);
		}

		public static void RemoveTerrainBuildFinished(Action<HMBuildData> handler)
		{
			HeightmapBuilderThroughputPatch.RemoveBuildFinishedHandler(handler);
		}

		public static bool SubmitFinishedTerrainBuild(HMBuildData data)
		{
			return HeightmapBuilderThroughputPatch.SubmitFinishedBuild(data);
		}

		public static bool GetSkipGhostZones()
		{
			return ZoneGenPacingPatch.SkipGhostZones;
		}

		public static void SetSkipGhostZones(bool skip)
		{
			ZoneGenPacingPatch.SkipGhostZones = skip;
		}

		public static bool IsSkipGhostZonesSupported()
		{
			return ZoneGenPacingPatch.SkipGhostZonesSupported;
		}

		private static string DescribeCaller()
		{
			StackTrace stackTrace = new StackTrace(1, fNeedFileInfo: false);
			for (int i = 0; i < stackTrace.FrameCount; i++)
			{
				MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod();
				Type type = methodBase?.DeclaringType;
				if (!(type == null))
				{
					Assembly assembly = type.Assembly;
					if (!(assembly == typeof(APIReceiver).Assembly) && !(assembly == typeof(object).Assembly) && !(type.Namespace == "ValheimCommunityPatch.API"))
					{
						return PluginOf(assembly) + ", from " + type.FullName + "." + methodBase.Name;
					}
				}
			}
			return "an unknown mod";
		}

		private static string PluginOf(Assembly assembly)
		{
			string location = assembly.Location;
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				if ((!string.IsNullOrEmpty(location) && string.Equals(value.Location, location, StringComparison.OrdinalIgnoreCase)) || ((Object)(object)value.Instance != (Object)null && ((object)value.Instance).GetType().Assembly == assembly))
				{
					return $"{value.Metadata.Name} {value.Metadata.Version} ({value.Metadata.GUID})";
				}
			}
			return "assembly " + assembly.GetName().Name;
		}
	}
	internal static class FixRegistry
	{
		private enum Outcome
		{
			Pending,
			Applied,
			NotApplied,
			Failed
		}

		private sealed class Entry
		{
			internal Type Fix;

			internal Outcome Outcome;

			internal ConfigEntry<bool> Toggle;

			internal volatile bool StoodDown;

			internal readonly FixSwitch Switch = new FixSwitch();
		}

		internal const int StateUnknown = 0;

		internal const int StateActive = 1;

		internal const int StateDisabled = 2;

		internal const int StateStoodDown = 3;

		internal const int StateNotApplied = 4;

		internal const int StateFailed = 5;

		private static readonly object Gate = new object();

		private static readonly Dictionary<string, Entry> ById = new Dictionary<string, Entry>(StringComparer.Ordinal);

		private static readonly HashSet<string> Warned = new HashSet<string>(StringComparer.Ordinal);

		internal static Type FixOf(Type type)
		{
			while (type?.DeclaringType != null)
			{
				type = type.DeclaringType;
			}
			if ((object)type == null || type.Namespace == null || !type.Namespace.StartsWith("ValheimCommunityPatch.Patches", StringComparison.Ordinal))
			{
				return null;
			}
			return type;
		}

		internal static void RecordPatched(Type patchClass, bool patchedAnything)
		{
			Entry entry = Get(patchClass);
			if (entry == null)
			{
				return;
			}
			lock (Gate)
			{
				if (entry.Outcome != Outcome.Failed)
				{
					if (patchedAnything)
					{
						entry.Outcome = Outcome.Applied;
					}
					else if (entry.Outcome == Outcome.Pending)
					{
						entry.Outcome = Outcome.NotApplied;
					}
				}
			}
		}

		internal static void RecordFailed(Type patchClass)
		{
			Entry entry = Get(patchClass);
			if (entry == null)
			{
				return;
			}
			lock (Gate)
			{
				entry.Outcome = Outcome.Failed;
			}
		}

		internal static void RecordSkipped(Type patchClass)
		{
			RecordPatched(patchClass, patchedAnything: false);
		}

		internal static void RecordToggle(Type patchClass, ConfigEntry<bool> toggle)
		{
			Entry entry = Get(patchClass);
			if (entry == null)
			{
				return;
			}
			lock (Gate)
			{
				entry.Toggle = toggle;
			}
		}

		internal static void MarkStoodDown(Type patchClass)
		{
			Entry entry = Get(patchClass);
			if (entry != null)
			{
				entry.StoodDown = true;
			}
		}

		internal static FixSwitch SwitchOf(Type patchClass)
		{
			return Get(patchClass)?.Switch ?? FixSwitch.Inert;
		}

		internal static List<string> GetFixIds()
		{
			lock (Gate)
			{
				List<string> list = new List<string>(ById.Keys);
				list.Sort(StringComparer.Ordinal);
				return list;
			}
		}

		internal static int GetState(string fixId)
		{
			if (string.IsNullOrEmpty(fixId))
			{
				return 0;
			}
			lock (Gate)
			{
				if (!ById.TryGetValue(fixId, out var value))
				{
					return 0;
				}
				switch (value.Outcome)
				{
				case Outcome.Failed:
					return 5;
				case Outcome.Pending:
				case Outcome.NotApplied:
					return 4;
				default:
					if (value.Toggle != null && !value.Toggle.Value)
					{
						return 2;
					}
					return (!value.StoodDown && !value.Switch.Off) ? 1 : 3;
				}
			}
		}

		internal static bool CanDisable(string fixId)
		{
			if (string.IsNullOrEmpty(fixId))
			{
				return false;
			}
			lock (Gate)
			{
				Entry value;
				return ById.TryGetValue(fixId, out value) && IsDisableable(value.Fix);
			}
		}

		internal static bool Disable(string fixId, string reason, string caller)
		{
			string text = (string.IsNullOrEmpty(reason) ? "" : (" Reason given: \"" + reason + "\"."));
			if (Thread.CurrentThread.ManagedThreadId != ValheimCommunityPatch.MainThreadId)
			{
				WarnOnce(fixId + "|" + caller + "|thread", caller + " asked to turn off fix '" + fixId + "' from a background thread, so nothing changed. Ask from the main thread." + text);
				return false;
			}
			Entry value = null;
			if (!string.IsNullOrEmpty(fixId))
			{
				lock (Gate)
				{
					ById.TryGetValue(fixId, out value);
				}
			}
			if (value == null)
			{
				WarnOnce(fixId + "|" + caller, caller + " asked to turn off fix '" + fixId + "', but no fix has that id, so nothing changed." + text);
				return false;
			}
			string text2 = Describe(fixId, value);
			int state = GetState(fixId);
			bool flag = state == 4 || state == 5;
			if (!IsDisableable(value.Fix))
			{
				bool flag2 = flag || state == 3;
				WarnOnce(fixId + "|" + caller, caller + " asked to turn off " + text2 + ", but that fix cannot be turned off while the game is running" + (flag2 ? "; it is not running this session anyway." : ", so it stays as it is.") + text);
				return flag2;
			}
			if (value.Switch.Off)
			{
				WarnOnce(fixId + "|" + caller, caller + " asked to turn off " + text2 + ", which was already turned off." + text);
				return true;
			}
			WarnOnce(fixId + "|" + caller, flag ? (caller + " turned off " + text2 + " for the rest of this session; it was not running this session anyway." + text) : (text2 + " was turned off for the rest of this session by " + caller + ". The game's own behaviour applies in its place; restart the game to have this fix back." + text));
			value.Switch.TurnOff();
			return true;
		}

		private static bool IsDisableable(Type fix)
		{
			if (fix != null)
			{
				return fix.IsDefined(typeof(ModDisableableAttribute), inherit: false);
			}
			return false;
		}

		private static string Describe(string fixId, Entry entry)
		{
			ConfigEntry<bool> toggle = entry.Toggle;
			if (toggle == null)
			{
				return "fix " + fixId;
			}
			return "'" + ((ConfigEntryBase)toggle).Definition.Key + "' (" + fixId + ")";
		}

		private static void WarnOnce(string key, string message)
		{
			bool flag;
			lock (Gate)
			{
				flag = Warned.Add(key);
			}
			if (flag)
			{
				Logger.LogWarning(message);
			}
			else
			{
				Logger.LogDebug(message);
			}
		}

		private static Entry Get(Type patchClass)
		{
			Type type = FixOf(patchClass);
			if (type == null)
			{
				return null;
			}
			lock (Gate)
			{
				if (!ById.TryGetValue(type.Name, out var value))
				{
					value = new Entry
					{
						Fix = type
					};
					ById.Add(type.Name, value);
				}
				return value;
			}
		}
	}
	internal sealed class FixSwitch
	{
		internal static readonly FixSwitch Inert = new FixSwitch();

		private volatile bool _off;

		internal bool Off => _off;

		internal event Action TurnedOff;

		internal void TurnOff()
		{
			if (_off)
			{
				return;
			}
			_off = true;
			Action action = this.TurnedOff;
			if (action == null)
			{
				return;
			}
			Delegate[] invocationList = action.GetInvocationList();
			for (int i = 0; i < invocationList.Length; i++)
			{
				Action action2 = (Action)invocationList[i];
				try
				{
					action2();
				}
				catch (Exception arg)
				{
					Logger.LogError($"Turning a fix off did not finish tidying up: {arg}");
				}
			}
		}
	}
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	internal sealed class ModDisableableAttribute : Attribute
	{
	}
	internal sealed class FixToggle
	{
		private readonly ConfigEntry<bool> _entry;

		private readonly FixSwitch _switch;

		internal bool Value
		{
			get
			{
				if (!_switch.Off)
				{
					return _entry.Value;
				}
				return false;
			}
		}

		internal ConfigEntry<bool> Entry => _entry;

		internal event EventHandler SettingChanged;

		internal FixToggle(ConfigEntry<bool> entry, FixSwitch fixSwitch)
		{
			_entry = entry;
			_switch = fixSwitch;
			_entry.SettingChanged += delegate(object sender, EventArgs args)
			{
				this.SettingChanged?.Invoke(this, args);
			};
			_switch.TurnedOff += delegate
			{
				this.SettingChanged?.Invoke(this, EventArgs.Empty);
			};
		}
	}
	internal static class HearthBelowCompat
	{
		internal const string Guid = "Azumatt.HearthBelow";

		internal static bool Loaded => Chainloader.PluginInfos.ContainsKey("Azumatt.HearthBelow");
	}
	internal static class HeightmapSampling
	{
		internal static bool TryGetHeight(Heightmap hmap, Vector3 position, out float height)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			Vector3 normal;
			return Sample(hmap, ((Component)hmap).transform.position, position, out height, out normal, wantNormal: false);
		}

		internal static bool TryGetHeight(Heightmap hmap, Vector3 origin, Vector3 position, out float height)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			Vector3 normal;
			return Sample(hmap, origin, position, out height, out normal, wantNormal: false);
		}

		internal static bool TryGetSurface(Heightmap hmap, Vector3 position, out float height, out Vector3 normal)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			return Sample(hmap, ((Component)hmap).transform.position, position, out height, out normal, wantNormal: true);
		}

		internal static bool TryGetSurface(Heightmap hmap, Vector3 origin, Vector3 position, out float height, out Vector3 normal)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			return Sample(hmap, origin, position, out height, out normal, wantNormal: true);
		}

		private static bool Sample(Heightmap hmap, Vector3 origin, Vector3 position, out float height, out Vector3 normal, bool wantNormal)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			normal = Vector3.up;
			int width = hmap.m_width;
			float scale = hmap.m_scale;
			float num = (position.x - origin.x) / scale + (float)width * 0.5f;
			float num2 = (position.z - origin.z) / scale + (float)width * 0.5f;
			if (num < 0f || num > (float)width || num2 < 0f || num2 > (float)width)
			{
				height = 0f;
				return false;
			}
			int num3 = Mathf.Min((int)num, width - 1);
			int num4 = Mathf.Min((int)num2, width - 1);
			float num5 = num - (float)num3;
			float num6 = num2 - (float)num4;
			float height2 = hmap.GetHeight(num3, num4);
			float height3 = hmap.GetHeight(num3 + 1, num4);
			float height4 = hmap.GetHeight(num3, num4 + 1);
			float height5 = hmap.GetHeight(num3 + 1, num4 + 1);
			Vector3 val;
			if (num5 + num6 <= 1f)
			{
				height = height2 + (height3 - height2) * num5 + (height4 - height2) * num6 + origin.y;
				if (wantNormal)
				{
					val = new Vector3(height2 - height3, scale, height2 - height4);
					normal = ((Vector3)(ref val)).normalized;
				}
			}
			else
			{
				height = height5 + (height4 - height5) * (1f - num5) + (height3 - height5) * (1f - num6) + origin.y;
				if (wantNormal)
				{
					val = new Vector3(height4 - height5, scale, height3 - height5);
					normal = ((Vector3)(ref val)).normalized;
				}
			}
			return true;
		}
	}
	internal static class Logger
	{
		private static bool _debug;

		internal static bool DebugEnabled => _debug;

		internal static void SetDebug(bool enabled)
		{
			_debug = enabled;
		}

		internal static void LogDebug(string message)
		{
			if (_debug)
			{
				ValheimCommunityPatch.Log.LogInfo((object)("[DEBUG]" + message));
			}
		}

		internal static void LogInfo(string message)
		{
			ValheimCommunityPatch.Log.LogInfo((object)message);
		}

		internal static void LogWarning(string message)
		{
			ValheimCommunityPatch.Log.LogWarning((object)message);
		}

		internal static void LogError(string message)
		{
			ValheimCommunityPatch.Log.LogError((object)message);
		}

		internal static void DebugSink(object message)
		{
			if (_debug)
			{
				LogDebug(message?.ToString() ?? string.Empty);
			}
		}
	}
	internal static class PatchHelper
	{
		internal const int AnyCount = -1;

		internal static List<CodeInstruction> Copy(IEnumerable<CodeInstruction> instructions)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			List<CodeInstruction> list = ((instructions is ICollection<CodeInstruction> collection) ? new List<CodeInstruction>(collection.Count) : new List<CodeInstruction>());
			foreach (CodeInstruction instruction in instructions)
			{
				list.Add(new CodeInstruction(instruction));
			}
			return list;
		}

		internal static IEnumerable<CodeInstruction> ReplaceCalls(IEnumerable<CodeInstruction> instructions, MethodInfo original, MethodInfo replacement, string site, int expected = -1)
		{
			if (original == null || replacement == null)
			{
				Logger.LogWarning(site + ": a method this fix needs could not be resolved, so it is inactive here.");
				return instructions;
			}
			List<CodeInstruction> list = Copy(instructions);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (CodeInstructionExtensions.Calls(list[i], original))
				{
					list[i].opcode = OpCodes.Call;
					list[i].operand = replacement;
					num++;
				}
			}
			if ((expected == -1) ? (num > 0) : (num == expected))
			{
				return list;
			}
			string text = ((expected == -1) ? "at least 1" : expected.ToString());
			Logger.LogWarning(site + ": expected " + text + " call(s) to " + original.DeclaringType?.Name + "." + original.Name + ", " + $"found {num}, so this fix is inactive here. Another mod has most likely already " + "rewritten the method - if so, nothing is wrong.");
			return instructions;
		}

		internal static IEnumerable<CodeInstruction> ReplaceAllMatches(IEnumerable<CodeInstruction> instructions, CodeMatch[] pattern, CodeInstruction[] replacement, string site, int expected = -1)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null);
			int num = 0;
			while (val.MatchStartForward(pattern).IsValid)
			{
				for (int i = 1; i < pattern.Length; i++)
				{
					CodeInstruction val2 = val.InstructionAt(i);
					if (val2.labels.Count != 0 || val2.blocks.Count != 0)
					{
						Logger.LogWarning(site + ": a jump or exception block lands inside the code this fix rewrites, so it is inactive here. Another mod has most likely already rewritten the method - if so, nothing is wrong.");
						return instructions;
					}
				}
				CodeInstruction[] array = (CodeInstruction[])(object)new CodeInstruction[replacement.Length];
				for (int j = 0; j < array.Length; j++)
				{
					array[j] = replacement[j].Clone();
				}
				CodeInstructionExtensions.MoveBlocksFrom(CodeInstructionExtensions.MoveLabelsFrom(array[0], val.Instruction), val.Instruction);
				val.RemoveInstructions(pattern.Length).InsertAndAdvance(array);
				num++;
			}
			if ((expected == -1) ? (num > 0) : (num == expected))
			{
				return val.InstructionEnumeration();
			}
			string arg = ((expected == -1) ? "at least 1" : expected.ToString());
			Logger.LogWarning($"{site}: expected {arg} match(es) of the code this fix rewrites, found {num}, " + "so this fix is inactive here. Another mod has most likely already rewritten the method - if so, nothing is wrong.");
			return instructions;
		}

		internal static bool HasHook(MethodBase target, Type hookClass)
		{
			Patches val = ((target == null) ? null : Harmony.GetPatchInfo(target));
			if (val == null)
			{
				return false;
			}
			if (!DeclaredBy(val.Prefixes, hookClass) && !DeclaredBy(val.Postfixes, hookClass))
			{
				return DeclaredBy(val.Finalizers, hookClass);
			}
			return true;
		}

		private static bool DeclaredBy(IReadOnlyList<Patch> patches, Type hookClass)
		{
			foreach (Patch patch in patches)
			{
				if (!(patch.owner != "MidnightsFX.ValheimCommunityPatch") && patch.PatchMethod?.DeclaringType == hookClass)
				{
					return true;
				}
			}
			return false;
		}

		public static bool TryMatchStartForward(this CodeMatcher matcher, string failureMessage, params CodeMatch[] matches)
		{
			matcher.MatchStartForward(matches);
			if (matcher.IsInvalid)
			{
				Logger.LogWarning(failureMessage + " Anchor instruction not found, it was likely consumed by another mod. Skipping this patch.");
				return false;
			}
			return true;
		}

		public static bool TryMatchStartForward(this CodeMatcher matcher, params CodeMatch[] matches)
		{
			matcher.MatchStartForward(matches);
			if (matcher.IsInvalid)
			{
				return false;
			}
			return true;
		}

		public static bool TryMatchStartBackwards(this CodeMatcher matcher, string failureMessage, params CodeMatch[] matches)
		{
			matcher.MatchStartBackwards(matches);
			if (matcher.IsInvalid)
			{
				Logger.LogWarning(failureMessage + " Anchor instruction not found, it was likely consumed by another mod. Skipping this patch.");
				return false;
			}
			return true;
		}

		public static bool TryMatchStartBackwards(this CodeMatcher matcher, params CodeMatch[] matches)
		{
			matcher.MatchStartBackwards(matches);
			if (matcher.IsInvalid)
			{
				return false;
			}
			return true;
		}

		public static bool TryMatchEndForward(this CodeMatcher matcher, string failureMessage, params CodeMatch[] matches)
		{
			matcher.MatchEndForward(matches);
			if (matcher.IsInvalid)
			{
				Logger.LogWarning(failureMessage + " Anchor instruction not found, it was likely consumed by another mod. Skipping this patch.");
				return false;
			}
			return true;
		}

		public static bool TryMatchEndForward(this CodeMatcher matcher, params CodeMatch[] matches)
		{
			matcher.MatchEndForward(matches);
			if (matcher.IsInvalid)
			{
				return false;
			}
			return true;
		}
	}
	internal sealed class HookHealth
	{
		private readonly Type _fix;

		private readonly string _fixName;

		private readonly Func<bool> _allAttached;

		private bool _checked;

		private bool _healthy;

		internal bool Healthy
		{
			get
			{
				if (_checked)
				{
					return _healthy;
				}
				_checked = true;
				_healthy = _allAttached();
				if (!_healthy)
				{
					FixRegistry.MarkStoodDown(_fix);
					Logger.LogError(_fixName + ": a maintenance hook is not attached, so this fix stands down to vanilla for this session. A Valheim update has most likely changed one of the patched methods - look for the patch failure logged at startup.");
				}
				return _healthy;
			}
		}

		internal HookHealth(Type fix, string fixName, Func<bool> allAttached)
		{
			_fix = fix;
			_fixName = fixName;
			_allAttached = allAttached;
		}
	}
	[Flags]
	internal enum HookKinds
	{
		BoolPrefixes = 1,
		Prefixes = 2,
		Postfixes = 4,
		Transpilers = 8,
		Finalizers = 0x10,
		Any = 0x1E
	}
	internal sealed class TakeoverCheck
	{
		private readonly Type _fix;

		private readonly MethodBase _target;

		private readonly HookKinds _kinds;

		private readonly Func<string, string> _message;

		private bool _checked;

		private bool _takenOver;

		internal bool TakenOver
		{
			get
			{
				if (_checked)
				{
					return _takenOver;
				}
				_checked = true;
				SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.Ordinal);
				Patches val = ((_target == null) ? null : Harmony.GetPatchInfo(_target));
				if (val != null)
				{
					bool flag = (_kinds & HookKinds.Prefixes) != 0;
					bool flag2 = (_kinds & HookKinds.BoolPrefixes) != 0;
					foreach (Patch prefix in val.Prefixes)
					{
						if (flag || (flag2 && prefix.PatchMethod?.ReturnType == typeof(bool)))
						{
							AddForeign(sortedSet, prefix);
						}
					}
					if ((_kinds & HookKinds.Postfixes) != 0)
					{
						AddForeign(sortedSet, (IReadOnlyList<Patch>)val.Postfixes);
					}
					if ((_kinds & HookKinds.Transpilers) != 0)
					{
						AddForeign(sortedSet, (IReadOnlyList<Patch>)val.Transpilers);
					}
					if ((_kinds & HookKinds.Finalizers) != 0)
					{
						AddForeign(sortedSet, (IReadOnlyList<Patch>)val.Finalizers);
					}
				}
				_takenOver = sortedSet.Count > 0;
				if (_takenOver)
				{
					FixRegistry.MarkStoodDown(_fix);
					Logger.LogInfo(_message(string.Join(", ", sortedSet)));
				}
				return _takenOver;
			}
		}

		internal TakeoverCheck(Type fix, MethodBase target, bool transpilers, Func<string, string> message)
			: this(fix, target, (HookKinds)(1 | (transpilers ? 8 : 0)), message)
		{
		}

		internal TakeoverCheck(Type fix, MethodBase target, HookKinds kinds, Func<string, string> message)
		{
			_fix = fix;
			_target = target;
			_kinds = kinds;
			_message = message;
		}

		private static void AddForeign(SortedSet<string> owners, IReadOnlyList<Patch> patches)
		{
			foreach (Patch patch in patches)
			{
				AddForeign(owners, patch);
			}
		}

		private static void AddForeign(SortedSet<string> owners, Patch patch)
		{
			if (patch.owner != "MidnightsFX.ValheimCommunityPatch")
			{
				owners.Add(patch.owner);
			}
		}
	}
	internal enum Side
	{
		Client,
		Server,
		Both
	}
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	internal sealed class PatchSideAttribute : Attribute
	{
		internal Side Side { get; }

		internal PatchSideAttribute(Side side)
		{
			Side = side;
		}

		internal static Side Of(Type type)
		{
			Type type2 = type;
			while (type2 != null)
			{
				object[] customAttributes = type2.GetCustomAttributes(typeof(PatchSideAttribute), inherit: false);
				if (customAttributes.Length != 0)
				{
					return ((PatchSideAttribute)customAttributes[0]).Side;
				}
				type2 = type2.DeclaringType;
			}
			return Side.Both;
		}

		internal static string Tag(Side side)
		{
			return side switch
			{
				Side.Client => "(client)", 
				Side.Server => "(server)", 
				_ => "(both)", 
			};
		}
	}
	internal static class RunMode
	{
		private static readonly bool _headless = (int)SystemInfo.graphicsDeviceType == 4;

		private static ZNet _resolvedFor;

		private static bool _isServer;

		private static bool _isDedicated;

		internal static bool IsHeadless => _headless;

		internal static bool IsServer
		{
			get
			{
				Resolve();
				return _isServer;
			}
		}

		internal static bool IsDedicated
		{
			get
			{
				Resolve();
				return _isDedicated;
			}
		}

		internal static bool InLoadingScreen()
		{
			if (!((Object)(object)Player.m_localPlayer == (Object)null))
			{
				return ((Character)Player.m_localPlayer).IsTeleporting();
			}
			return true;
		}

		private static void Resolve()
		{
			ZNet instance = ZNet.instance;
			if (instance != _resolvedFor)
			{
				if (instance == null)
				{
					_resolvedFor = null;
					_isServer = false;
					_isDedicated = false;
				}
				else
				{
					_isServer = instance.IsServer();
					_isDedicated = instance.IsDedicated();
					_resolvedFor = instance;
				}
			}
		}
	}
	[PatchSide(Side.Both)]
	internal static class TeardownHooks
	{
		[HarmonyPatch(typeof(ZNetView))]
		internal static class ViewHook
		{
			[HarmonyPostfix]
			[HarmonyPatch("OnDestroy")]
			private static void OnDestroyPostfix(ZNetView __instance)
			{
				if (_statsOn)
				{
					_viewDestroys++;
				}
				if (__instance.m_ghost)
				{
					if (_statsOn)
					{
						_ghostDestroys++;
					}
				}
				else
				{
					SectorInstanceIndexPatch.OnViewDestroyed(__instance);
					DeadInstancePatch.OnViewDestroyed(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(WearNTear))]
		internal static class PieceHook
		{
			[HarmonyPostfix]
			[HarmonyPatch("OnDestroy")]
			private static void OnDestroyPostfix(WearNTear __instance)
			{
				int instanceID = ((Object)__instance).GetInstanceID();
				SupportSleepPatch.OnPieceDestroyed(__instance, instanceID);
				WearSupportLookupPatch.OnPieceDestroyed(instanceID);
				WearCacheEventPatch.OnPieceDestroyed(__instance, instanceID);
			}
		}

		[HarmonyPatch(typeof(ZNetScene))]
		internal static class FrameHook
		{
			[HarmonyPrefix]
			[HarmonyPatch("Update")]
			private static void UpdatePrefix()
			{
				if (LogStormStats == null || !LogStormStats.Value)
				{
					if (_statsOn)
					{
						LogSummary("final");
						Clear();
						_statsOn = false;
					}
					return;
				}
				if (!_statsOn)
				{
					_statsOn = true;
					Clear();
					_statsSince = Time.unscaledTime;
					Logger.LogInfo("Destroy storm stats: on. Bucketing frames by teardown count; a summary follows every 30 s.");
					return;
				}
				RecordFrame(_viewDestroys, Time.unscaledDeltaTime * 1000f);
				_viewDestroys = 0;
				if (Time.unscaledTime - _statsSince >= 30f)
				{
					LogSummary("periodic");
					Clear();
					_statsSince = Time.unscaledTime;
				}
			}
		}

		internal static ConfigEntry<bool> LogStormStats;

		private static bool _statsOn;

		private static float _statsSince;

		private static int _viewDestroys;

		private static int _ghostDestroys;

		private static int _unloadPasses;

		private static int _unloadObjects;

		private static int _unloadWorstObjects;

		private static double _unloadMs;

		private static double _unloadWorstMs;

		private static int _unloadWorstBacklog;

		private static int _unloadDeferredPasses;

		private static readonly int[] BucketFloor = new int[5] { 0, 1, 25, 100, 500 };

		private static readonly string[] BucketName = new string[5] { "0", "1-24", "25-99", "100-499", "500+" };

		private static readonly long[] BucketFrames = new long[5];

		private static readonly double[] BucketMs = new double[5];

		private static readonly double[] BucketWorstMs = new double[5];

		private const float ReportIntervalSeconds = 30f;

		internal static bool StatsOn => _statsOn;

		internal static void BindConfig()
		{
			LogStormStats = ValConfig.BindServerConfig("Debug", "Log Destroy Storm Stats", value: false, "Diagnostic. Buckets every frame by how many networked objects were torn down in it and reports the frame time each bucket ran at, so a teardown burst can be told apart from a steady drain. Also reports the unload pass's own object count and wall-clock. Costs a counter increment per destroyed object; leave it off unless you are measuring.", null, advanced: true);
		}

		internal static void NoteUnloadPass(int processed, int backlog, double milliseconds)
		{
			if (_statsOn)
			{
				_unloadPasses++;
				_unloadObjects += processed;
				_unloadMs += milliseconds;
				if (processed > _unloadWorstObjects)
				{
					_unloadWorstObjects = processed;
				}
				if (milliseconds > _unloadWorstMs)
				{
					_unloadWorstMs = milliseconds;
				}
				if (backlog > _unloadWorstBacklog)
				{
					_unloadWorstBacklog = backlog;
				}
				if (backlog > processed)
				{
					_unloadDeferredPasses++;
				}
			}
		}

		private static void RecordFrame(int destroys, float frameMs)
		{
			int num = 0;
			for (int num2 = BucketFloor.Length - 1; num2 >= 0; num2--)
			{
				if (destroys >= BucketFloor[num2])
				{
					num = num2;
					break;
				}
			}
			BucketFrames[num]++;
			BucketMs[num] += frameMs;
			if ((double)frameMs > BucketWorstMs[num])
			{
				BucketWorstMs[num] = frameMs;
			}
		}

		private static void Clear()
		{
			_viewDestroys = 0;
			_ghostDestroys = 0;
			_unloadPasses = 0;
			_unloadObjects = 0;
			_unloadWorstObjects = 0;
			_unloadMs = 0.0;
			_unloadWorstMs = 0.0;
			_unloadWorstBacklog = 0;
			_unloadDeferredPasses = 0;
			for (int i = 0; i < BucketFrames.Length; i++)
			{
				BucketFrames[i] = 0L;
				BucketMs[i] = 0.0;
				BucketWorstMs[i] = 0.0;
			}
		}

		private static void LogSummary(string kind)
		{
			long num = 0L;
			for (int i = 0; i < BucketFrames.Length; i++)
			{
				num += BucketFrames[i];
			}
			if (num == 0L)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"Destroy storm stats ({kind}), {num} frame(s) over ").Append($"{Time.unscaledTime - _statsSince:F0} s:");
			for (int j = 0; j < BucketFrames.Length; j++)
			{
				if (BucketFrames[j] != 0L)
				{
					stringBuilder.Append($" | {BucketName[j]} destroys: {BucketFrames[j]} frame(s), ").Append($"mean {BucketMs[j] / (double)BucketFrames[j]:F1} ms, worst {BucketWorstMs[j]:F0} ms");
				}
			}
			stringBuilder.Append($" || ghost destroys {_ghostDestroys}");
			if (_unloadPasses > 0)
			{
				stringBuilder.Append($" || unload pass: {_unloadPasses} pass(es), {_unloadObjects} object(s), ").Append($"{_unloadMs:F0} ms managed, worst pass {_unloadWorstObjects} object(s) / ").Append($"{_unloadWorstMs:F1} ms")
					.Append($", worst backlog {_unloadWorstBacklog}, {_unloadDeferredPasses} pass(es) capped");
			}
			Logger.LogInfo(stringBuilder.ToString());
		}
	}
	internal static class ValConfig
	{
		public static ConfigFile cfg;

		public static ConfigEntry<bool> EnableDebugMode;

		public static ConfigEntry<bool> PatchEverySide;

		public const string SectionPerformance = "Fixes - Performance";

		public const string SectionCorrectness = "Fixes - Correctness";

		public const string SectionTerrain = "Fixes - Terrain";

		public const string SectionServerMemory = "Fixes - Server Memory";

		public const string SectionDebug = "Debug";

		internal static void Bind(ConfigFile file)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Expected O, but got Unknown
			cfg = file;
			cfg.SaveOnConfigSet = false;
			EnableDebugMode = cfg.Bind<bool>("Client config", "EnableDebugMode", false, new ConfigDescription("Enables Debug logging.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdvanced = true
			} }));
			Logger.SetDebug(EnableDebugMode.Value);
			EnableDebugMode.SettingChanged += delegate
			{
				Logger.SetDebug(EnableDebugMode.Value);
			};
			PatchEverySide = cfg.Bind<bool>("Client config", "Patch Every Side", false, new ConfigDescription("Applies every fix regardless of which side it is for. Normally the client-only fixes are not applied on a dedicated server, because nothing there could ever reach them. Turn this on only if this machine has a display but was detected as headless. Requires a game restart.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdvanced = true
			} }));
			BindFixConfig();
			cfg.SaveOnConfigSet = true;
			cfg.Save();
		}

		private static void BindFixConfig()
		{
			TeardownHooks.BindConfig();
			OrphanZdoIndexPatch.BindConfig();
			ZdoPrefabIndexPatch.BindConfig();
			HeightmapLookupPatch.BindConfig();
			StaticPhysicsCachePatch.BindConfig();
			ClutterRebuildCapPatch.BindConfig();
			HeightmapBuilderThroughputPatch.BindConfig();
			ZoneGenPacingPatch.BindConfig();
			LoadingZoneCadencePatch.BindConfig();
			LocationSpawnPacingPatch.BindConfig();
			DungeonRoomSpreadPatch.BindConfig();
			GhostDungeonStandInPatch.BindConfig();
			LocationRoomPreloadPatch.BindConfig();
			ModelTemplatePatch.BindConfig();
			MinimapFogUploadPatch.BindConfig();
			TerrainLodSpreadPatch.BindConfig();
			WearSupportLookupPatch.BindConfig();
			SceneIdleSkipPatch.BindConfig();
			LightCostPatch.BindConfig();
			RemoveSweepPacingPatch.BindConfig();
			SpawnQueueCachePatch.BindConfig();
			SectorInstanceIndexPatch.BindConfig();
			SupportSleepPatch.BindConfig();
			ZoneDiffRemovalPatch.BindConfig();
			DeadZdoEvictPatch.BindConfig();
			ZdoDataPoolTrimPatch.BindConfig();
			ZdoSerializeAllocPatch.BindConfig();
			ZdoMemoryStats.BindConfig();
			WorldGenTimingStats.BindConfig();
			CreatureSyncDeadbandPatch.BindConfig();
			PortalPrefetchPatch.BindConfig();
			HourlyAssetUnloadPatch.BindConfig();
			RecipeGetAmountNrePatch.BindConfig();
			ProjectileZeroVelocityPatch.BindConfig();
			SpawnAreaNullPrefabPatch.BindConfig();
			ZdoLoadDuplicatePatch.BindConfig();
			RemoveObjectsNrePatch.BindConfig();
			EffectAreaPatch.BindConfig();
			FuelLossPatch.BindConfig();
			StationRefundPatch.BindConfig();
			BossKeySharePatch.BindConfig();
			ItemIconVariantPatch.BindConfig();
			SendFailureLogSpamPatch.BindConfig();
			ContainerLogSpamPatch.BindConfig();
			ContainerGridAlignmentPatch.BindConfig();
			NegativeStaminaPatch.BindConfig();
			SectorJumpInvalidatePatch.BindConfig();
			ReceivedZdoDirtyPatch.BindConfig();
			WaterColorSeamPatch.BindConfig();
			ObjectDbNonItemPatch.BindConfig();
			TamedPatrolPointPatch.BindConfig();
			MapAutoClosePatch.BindConfig();
			BiomeSectorLookupPatch.BindConfig();
			DeadInstancePatch.BindConfig();
			EquipmentTextureErrorPatch.BindConfig();
			UnkillableCreaturePatch.BindConfig();
			ClothWindShelterPatch.BindConfig();
			LoadingWaitPatch.BindConfig();
			LostBedSpawnPointPatch.BindConfig();
			LovePotionSpawnPatch.BindConfig();
			PathfindingLinkLimitPatch.BindConfig();
			GraphicsSettingsEventPatch.BindConfig();
			LoopingSoundConcurrencyPatch.BindConfig();
			GetAttackerGetZDOIDPatch.BindConfig();
			DamageReactionOrderPatch.BindConfig();
			StatusEffectRegenApplicationOrderPatch.BindConfig();
			SeamlessNormalsPatch.BindConfig();
			PaintSeamReconcilePatch.BindConfig();
			TerrainOpPaintFanoutPatch.BindConfig();
			PaintSpreadOncePatch.BindConfig();
			BiomeColorAliasPatch.BindConfig();
			PaintMaskStridePatch.BindConfig();
			TerrainCompNullHmapPatch.BindConfig();
		}

		public static FixToggle BindFixToggle(Type patchClass, string category, string key, bool value, string description, bool advanced = false)
		{
			ConfigEntry<bool> val = BindServerConfig(category, key, value, PatchSideAttribute.Tag(PatchSideAttribute.Of(patchClass)) + " " + description, null, advanced);
			FixRegistry.RecordToggle(patchClass, val);
			return new FixToggle(val, FixRegistry.SwitchOf(patchClass));
		}

		public static ConfigEntry<bool> BindServerConfig(string category, string key, bool value, string description, AcceptableValueBase acceptableValues = null, bool advanced = false)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			return cfg.Bind<bool>(category, key, value, new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<int> BindServerConfig(string category, string key, int value, string description, bool advanced = false, int valMin = 0, int valMax = 150)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<int>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<float> BindServerConfig(string category, string key, float value, string description, bool advanced = false, float valMin = 0f, float valMax = 150f)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			return cfg.Bind<float>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(valMin, valMax), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}

		public static ConfigEntry<T> BindServerConfig<T>(string category, string key, T value, string description, bool advanced = false) where T : Enum
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			return cfg.Bind<T>(category, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true,
				IsAdvanced = advanced
			} }));
		}
	}
	[BepInPlugin("MidnightsFX.ValheimCommunityPatch", "ValheimCommunityPatch", "0.38.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	internal class ValheimCommunityPatch : BaseUnityPlugin
	{
		public const string PluginGUID = "MidnightsFX.ValheimCommunityPatch";

		public const string PluginName = "ValheimCommunityPatch";

		public const string PluginVersion = "0.38.0";

		internal static ManualLogSource Log;

		internal static int MainThreadId;

		private readonly Harmony harmony = new Harmony("MidnightsFX.ValheimCommunityPatch");

		private static bool _quitting;

		public void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			MainThreadId = Thread.CurrentThread.ManagedThreadId;
			Application.quitting += OnQuitting;
			ValConfig.Bind(((BaseUnityPlugin)this).Config);
			CollisionCallbackReusePatch.Apply();
			FixRegistry.RecordPatched(typeof(CollisionCallbackReusePatch), patchedAnything: true);
			ApplyPatches();
		}

		private void ApplyPatches()
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			bool flag = ValConfig.PatchEverySide != null && ValConfig.PatchEverySide.Value;
			Type[] types = Assembly.GetExecutingAssembly().GetTypes();
			foreach (Type type in types)
			{
				if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length == 0)
				{
					continue;
				}
				Side side = PatchSideAttribute.Of(type);
				bool flag2 = type.DeclaringType == null;
				if (side == Side.Client && RunMode.IsHeadless && !flag)
				{
					if (flag2)
					{
						num3++;
					}
					FixRegistry.RecordSkipped(type);
					Log.LogDebug((object)("Skipped " + type.Name + ": client-only, and this process is headless."));
					continue;
				}
				try
				{
					List<MethodInfo> list = harmony.CreateClassProcessor(type).Patch();
					FixRegistry.RecordPatched(type, list != null && list.Count > 0);
					if (flag2)
					{
						num++;
						switch (side)
						{
						case Side.Client:
							num4++;
							break;
						case Side.Server:
							num5++;
							break;
						default:
							num6++;
							break;
						}
					}
					Log.LogDebug((object)("Applied " + type.Name + " " + PatchSideAttribute.Tag(side) + "."));
				}
				catch (Exception arg)
				{
					num2++;
					FixRegistry.RecordFailed(type);
					Log.LogError((object)$"Could not apply {type.Name}: {arg}");
				}
			}
			Log.LogInfo((object)$"{num} fix(es) applied: {num5} server, {num6} both, {num4} client.");
			if (num3 > 0)
			{
				Log.LogInfo((object)($"{num3} client-only fix(es) not applied: this process has no graphics device, " + "so it is a dedicated server and nothing would ever reach them. If that is wrong, set 'Patch Every Side' in the config."));
			}
			if (num2 > 0)
			{
				Log.LogWarning((object)($"{num2} fix(es) failed. The failures above are usually caused by a Valheim " + "update changing a patched method; the remaining fixes are unaffected."));
			}
		}

		private static void OnQuitting()
		{
			_quitting = true;
		}

		public void OnDestroy()
		{
			if (!_quitting)
			{
				Application.quitting -= OnQuitting;
				ZsfxIdleDormancyPatch.RestoreAll();
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
			}
		}
	}
}
namespace ValheimCommunityPatch.Patches.Terrain
{
	[PatchSide(Side.Client)]
	[ModDisableable]
	[HarmonyPatch(typeof(Heightmap))]
	internal static class BiomeColorAliasPatch
	{
		internal enum BridgeGround
		{
			BlackForest,
			Ashlands
		}

		private const string FixName = "Fix Swamp Plains Shore Seams";

		internal static FixToggle Enabled;

		internal static ConfigEntry<float> Sharpness;

		internal static ConfigEntry<BridgeGround> Bridge;

		private const float DeadZone = 0.1f;

		private const float AshlandsCap = 0.85f;

		private static readonly TakeoverCheck Takeover = new TakeoverCheck(typeof(BiomeColorAliasPatch), AccessTools.DeclaredMethod(typeof(Heightmap), "RebuildRenderMesh", (Type[])null, (Type[])null), transpilers: false, (string owners) => "Terrain mesh building is changed by " + owners + ", so 'Fix Swamp Plains Shore Seams' stands down and that mod's terrain colors apply.");

		private static Heightmap _cachedMap;

		private static BiomeSector[] _cachedCorners;

		private static int _swampCorners;

		private static int _plainsCorners;

		private static int _protectedCorners;

		internal static void BindConfig()
		{
			Enabled = ValConfig.BindFixToggle(typeof(BiomeColorAliasPatch), "Fixes - Terrain", "Fix Swamp Plains Shore Seams", value: true, "Stops shores where swamp blends into plains from being drawn with the Ashlands shoreline, which ends in hard straight lines along the 64m zone grid. The game's terrain colors cannot express a swamp and plains blend without also meaning Ashlands, so this bridges the two with a strip of ground where they meet instead (see Swamp Plains Bridge Ground).");
			Sharpness = ValConfig.BindServerConfig("Fixes - Terrain", "Swamp Plains Blend Sharpness", 3f, "With Fix Swamp Plains Shore Seams on, how quickly swamp and plains ground give way to each other. 1 blends across the whole 64m tile with a wide bridge strip in the middle; higher values keep swamp and plains ground closer to the crossover and narrow the strip (about 11m at 3, 9m at 4). A black forest strip never gets narrower than about 5m.", advanced: true, 1f, 8f);
			Bridge = ValConfig.BindServerConfig("Fixes - Terrain", "Swamp Plains Bridge Ground", BridgeGround.BlackForest, "With Fix Swamp Plains Shore Seams on, the ground drawn in the strip where swamp meets plains. BlackForest keeps the Ashlands look out entirely. Ashlands is closest to how the game blends them: near water the Ashlands shoreline fills the strip and ends at its edge, following where swamp and plains meet rather than the zone grid.", advanced: true);
			Enabled.SettingChanged += delegate
			{
				RebuildLoadedTerrain();
			};
			Sharpness.SettingChanged += delegate
			{
				RebuildLoadedTerrain();
			};
			Bridge.SettingChanged += delegate
			{
				RebuildLoadedTerrain();
			};
		}

		[HarmonyPostfix]
		[HarmonyPatch("GetBiomeColor", new Type[]
		{
			typeof(float),
			typeof(float)
		})]
		private static void GetBiomeColorPostfix(Heightmap __instance, float ix, float iy, ref Color __result)
		{
			if (Enabled == null || !Enabled.Value || (__result.r <= 0f && __result.a <= 0f) || __instance.IsDistantLod || Takeover.TakenOver)
			{
				return;
			}
			ReadCorners(__instance);
			int num = ((ix < 1f && iy < 1f) ? 1 : 0) | ((ix > 0f && iy < 1f) ? 2 : 0) | ((ix < 1f && iy > 0f) ? 4 : 0) | ((ix > 0f && iy > 0f) ? 8 : 0);
			if ((num & _protectedCorners) == 0 && (num & _swampCorners) != 0 && (num & _plainsCorners) != 0)
			{
				float num2 = __result.r + __result.a;
				float num3 = __result.r - __result.a;
				float num4 = ((Sharpness != null) ? Mathf.Max(1f, Sharpness.Value) : 1f);
				float num5 = 0.9f / num4;
				float num6 = Mathf.Clamp((num3 - 0.1f) / num5, 0f, num2);
				float num7 = Mathf.Clamp((0f - num3 - 0.1f) / num5, 0f, num2);
				float num8 = num2 - num6 - num7;
				if (Bridge != null && Bridge.Value == BridgeGround.Ashlands)
				{
					float num9 = Mathf.Min(num8, 0.85f);
					__result.r = num6 + num9;
					__result.a = num7 + num9;
				}
				else
				{
					__result.r = num6;
					__result.a = num7;
					__result.b += num8;
				}
			}
		}

		private static void ReadCorners(Heightmap hmap)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			BiomeSector[] cornerBiomes = hmap.m_cornerBiomes;
			if (hmap == _cachedMap && cornerBiomes == _cachedCorners)
			{
				return;
			}
			_cachedMap = hmap;
			_cachedCorners = cornerBiomes;
			_swampCorners = 0;
			_plainsCorners = 0;
			_protectedCorners = 0;
			for (int i = 0; i < cornerBiomes.Length && i < 4; i++)
			{
				if (cornerBiomes[i] == null)
				{
					continue;
				}
				Color32 biomeColor = Heightmap.GetBiomeColor(cornerBiomes[i]);
				int num = 1 << i;
				if (biomeColor.a > 0 && (biomeColor.r > 0 || biomeColor.b > 0))
				{
					_protectedCorners |= num;
					continue;
				}
				if (biomeColor.r > 0)
				{
					_swampCorners |= num;
				}
				if (biomeColor.a > 0)
				{
					_plainsCorners |= num;
				}
			}
		}

		private static void RebuildLoadedTerrain()
		{
			if (RunMode.IsDedicated)
			{
				return;
			}
			_cachedMap = null;
			_cachedCorners = null;
			foreach (Heightmap s_heightmap in Heightmap.s_heightmaps)
			{
				if ((Object)(object)s_heightmap != (Object)null)
				{
					s_heightmap.Poke(1, false);
				}
			}
		}
	}
	[PatchSide(Side.Client)]
	[ModDisableable]
	[HarmonyPatch]
	internal static class PaintMaskStridePatch
	{
		internal static FixToggle Enabled;

		internal static void BindConfig()
		{
			Enabled = ValConfig.BindFixToggle(typeof(PaintMaskStridePatch), "Fixes - Terrain", "Fix Terrain Paint Mask Indexing", value: true, "Corrects the array stride and bounds used when reading and writing terrain paint. Vanilla indexes a 33-wide paint array with a stride of 32, which skews the paint diagonally, and refuses to write the row and column each zone shares with its neighbour.");
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Heightmap), "SetPaintMask")]
		private static bool SetPaintMaskPrefix(Heightmap __instance, int x, int y, Color paint)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled == null || !Enabled.Value)
			{
				return true;
			}
			int num = __instance.m_width + 1;
			if (x < 0 || y < 0 || x >= num || y >= num)
			{
				return false;
			}
			__instance.m_paintMask.SetPixel(x, y, paint);
			return false;
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(TerrainComp), "UpdatePaintMask")]
		private static bool UpdatePaintMaskPrefix(TerrainComp __instance, Heightmap hmap)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled == null || !Enabled.Value)
			{
				return true;
			}
			if (!__instance.m_initialized)
			{
				return false;
			}
			int num = __instance.m_width + 1;
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					int num2 = i * num + j;
					if (__instance.m_modifiedPaint[num2])
					{
						Color val = __instance.m_paintMask[num2];
						val.a = hmap.GetPaintMask(j, i).a;
						__instance.m_paintMask[num2] = val;
					}
				}
			}
			__instance.Save(false);
			hmap.Poke(0, false);
			return false;
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Heightmap), "UpdateTerrainAlpha", new Type[] { typeof(Heightmap) })]
		private static bool UpdateTerrainAlphaPrefix(Heightmap hmap, ref bool __result)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled == null || !Enabled.Value)
			{
				return true;
			}
			HMBuildData val = HeightmapBuilder.instance.RequestTerrainSync(((Component)hmap).transform.position, hmap.m_width, hmap.m_scale, hmap.IsDistantLod, WorldGenerator.instance);
			int num = hmap.m_width + 1;
			int num2 = 0;
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					float a = val.m_baseMask[i * num + j].a;
					Color paintMask = hmap.GetPaintMask(j, i);
					if (a != paintMask.a)
					{
						paintMask.a = a;
						hmap.SetPaintMask(j, i, paintMask);
						num2++;
					}
				}
			}
			if (num2 > 0)
			{
				hmap.GetAndCreateTerrainCompiler().UpdatePaintMask(hmap);
				Logger.LogInfo($"Corrected {num2} terrain alpha pixel(s) at {((Component)hmap).transform.position}.");
			}
			__result = num2 > 0;
			return false;
		}
	}
	[PatchSide(Side.Client)]
	[ModDisableable]
	[HarmonyPatch(typeof(Heightmap))]
	internal static class PaintSeamReconcilePatch
	{
		private enum Edge
		{
			East,
			West,
			North,
			South
		}

		private struct Corner
		{
			internal Heightmap Map;

			internal int X;

			internal int Y;

			internal Color Boundary;

			internal Color Inward;
		}

		internal static FixToggle Enabled;

		private static readonly List<Heightmap> DirtyNeighbours = new List<Heightmap>();

		private static readonly Corner[] CornerBuffer = new Corner[4];

		internal static void BindConfig()
		{
			Enabled = ValConfig.BindFixToggle(typeof(PaintSeamReconcilePatch), "Fixes - Terrain", "Fix Terrain Paint Seams", value: true, "Makes terrain paint agree along zone boundaries. Vanilla can end up with dirt painted on only one side of a 64m zone border, which draws a hard straight line across the ground. Paint that the ground next to the border already carries is carried across the boundary; a lone stripe sitting on the border with nothing behind it on either side is removed.");
		}

		[HarmonyPostfix]
		[HarmonyPatch("ApplyModifiers")]
		private static void ApplyModifiersPostfix(Heightmap __instance)
		{
			if (Enabled == null || !Enabled.Value || RunMode.IsDedicated || __instance.IsDistantLod || (Object)(object)__instance.m_paintMask == (Object)null)
			{
				return;
			}
			int width = __instance.m_width;
			if (width >= 2)
			{
				float num = (float)width * __instance.m_scale;
				DirtyNeighbours.Clear();
				if ((0u | (ReconcileEdge(__instance, FindNeighbour(__instance, num, 0f), Edge.East, width) ? 1u : 0u) | (ReconcileEdge(__instance, FindNeighbour(__instance, 0f - num, 0f), Edge.West, width) ? 1u : 0u) | (ReconcileEdge(__instance, FindNeighbour(__instance, 0f, num), Edge.North, width) ? 1u : 0u) | (ReconcileEdge(__instance, FindNeighbour(__instance, 0f, 0f - num), Edge.South, width) ? 1u : 0u) | (ReconcileCorner(__instance, num, highX: true, highZ: true, width) ? 1u : 0u) | (ReconcileCorner(__instance, num, highX: true, highZ: false, width) ? 1u : 0u) | (ReconcileCorner(__instance, num, highX: false, highZ: true, width) ? 1u : 0u) | (ReconcileCorner(__instance, num, highX: false, highZ: false, width) ? 1u : 0u)) != 0)
				{
					__instance.m_paintMask.Apply();
				}
				for (int i = 0; i < DirtyNeighbours.Count; i++)
				{
					DirtyNeighbours[i].m_paintMask.Apply();
				}
				DirtyNeighbours.Clear();
			}
		}

		private static bool ReconcileEdge(Heightmap ours, Heightmap theirs, Edge edge, int width)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: 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)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)theirs == (Object)null || (Object)(object)theirs.m_paintMask == (Object)null)
			{
				return false;
			}
			bool result = false;
			bool flag = false;
			for (int i = 1; i < width; i++)
			{
				int num;
				int num2;
				int num3;
				int num4;
				int num5;
				int num6;
				int num7;
				int num8;
				switch (edge)
				{
				case Edge.East:
					num = width;
					num2 = i;
					num3 = width - 1;
					num4 = i;
					num5 = 0;
					num6 = i;
					num7 = 1;
					num8 = i;
					break;
				case Edge.West:
					num = 0;
					num2 = i;
					num3 = 1;
					num4 = i;
					num5 = width;
					num6 = i;
					num7 = width - 1;
					num8 = i;
					break;
				case Edge.North:
					num = i;
					num2 = width;
					num3 = i;
					num4 = width - 1;
					num5 = i;
					num6 = 0;
					num7 = i;
					num8 = 1;
					break;
				default:
					num = i;
					num2 = 0;
					num3 = i;
					num4 = 1;
					num5 = i;
					num6 = width;
					num7 = i;
					num8 = width - 1;
					break;
				}
				Color pixel = ours.m_paintMask.GetPixel(num, num2);
				Color pixel2 = theirs.m_paintMask.GetPixel(num5, num6);
				if (!SamePaint(pixel, pixel2))
				{
					Color val = Merge(pixel, pixel2, ours.m_paintMask.GetPixel(num3, num4), theirs.m_paintMask.GetPixel(num7, num8));
					if (!SamePaint(val, pixel))
					{
						ours.m_paintMask.SetPixel(num, num2, new Color(val.r, val.g, val.b, pixel.a));
						result = true;
					}
					if (!SamePaint(val, pixel2))
					{
						theirs.m_paintMask.SetPixel(num5, num6, new Color(val.r, val.g, val.b, pixel2.a));
						flag = true;
					}
				}
			}
			if (flag)
			{
				MarkDirty(theirs);
			}
			return result;
		}

		private static bool ReconcileCorner(Heightmap ours, float zoneSize, bool highX, bool highZ, int width)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			float dx = (highX ? zoneSize : (0f - zoneSize));
			float dz = (highZ ? zoneSize : (0f - zoneSize));
			int count = 0;
			count = AddCorner(count, ours, width, highX, highZ);
			count = AddCorner(count, FindNeighbour(ours, dx, 0f), width, !highX, highZ);
			count = AddCorner(count, FindNeighbour(ours, 0f, dz), width, highX, !highZ);
			count = AddCorner(count, FindNeighbour(ours, dx, dz), width, !highX, !highZ);
			if (count < 2)
			{
				return false;
			}
			Color boundary = CornerBuffer[0].Boundary;
			Color boundary2 = CornerBuffer[0].Boundary;
			Color inward = CornerBuffer[0].Inward;
			for (int i = 1; i < count; i++)
			{
				Color boundary3 = CornerBuffer[i].Boundary;
				Color inward2 = CornerBuffer[i].Inward;
				((Color)(ref boundary))..ctor(Mathf.Min(boundary.r, boundary3.r), Mathf.Min(boundary.g, boundary3.g), Mathf.Min(boundary.b, boundary3.b));
				((Color)(ref boundary2))..ctor(Mathf.Max(boundary2.r, boundary3.r), Mathf.Max(boundary2.g, boundary3.g), Mathf.Max(boundary2.b, boundary3.b));
				((Color)(ref inward))..ctor(Mathf.Max(inward.r, inward2.r), Mathf.Max(inward.g, inward2.g), Mathf.Max(inward.b, inward2.b));
			}
			Color val = default(Color);
			((Color)(ref val))..ctor(Mathf.Clamp(inward.r, boundary.r, boundary2.r), Mathf.Clamp(inward.g, boundary.g, boundary2.g), Mathf.Clamp(inward.b, boundary.b, boundary2.b));
			bool result = false;
			for (int j = 0; j < count; j++)
			{
				Corner corner = CornerBuffer[j];
				if (!SamePaint(val, corner.Boundary))
				{
					corner.Map.m_paintMask.SetPixel(corner.X, corner.Y, new Color(val.r, val.g, val.b, corner.Boundary.a));
					if ((Object)(object)corner.Map == (Object)(object)ours)
					{
						result = true;
					}
					else
					{
						MarkDirty(corner.Map);
					}
				}
			}
			return result;
		}

		private static int AddCorner(int count, Heightmap hmap, int width, bool highX, bool highZ)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)hmap == (Object)null || (Object)(object)hmap.m_paintMask == (Object)null)
			{
				return count;
			}
			int num = (highX ? width : 0);
			int num2 = (highZ ? width : 0);
			int num3 = ((!highX) ? 1 : (width - 1));
			int num4 = ((!highZ) ? 1 : (width - 1));
			CornerBuffer[count] = new Corner
			{
				Map = hmap,
				X = num,
				Y = num2,
				Boundary = hmap.m_paintMask.GetPixel(num, num2),
				Inward = hmap.m_paintMask.GetPixel(num3, num4)
			};
			return count + 1;
		}

		private static Color Merge(Color a, Color b, Color supportA, Color supportB)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			return new Color(MergeChannel(a.r, b.r, supportA.r, supportB.r), MergeChannel(a.g, b.g, supportA.g, supportB.g), MergeChannel(a.b, b.b, supportA.b, supportB.b));
		}

		private static float MergeChannel(float a, float b, float supportA, float supportB)
		{
			return Mathf.Clamp(Mathf.Max(supportA, supportB), Mathf.Min(a, b), Mathf.Max(a, b));
		}

		private static bool SamePaint(Color merged, Color current)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if (merged.r == current.r && merged.g == current.g)
			{
				return merged.b == current.b;
			}
			return false;
		}

		private static void MarkDirty(Heightmap hmap)
		{
			if (!DirtyNeighbours.Contains(hmap))
			{
				DirtyNeighbours.Add(hmap);
			}
		}

		private static Heightmap FindNeighbour(Heightmap hmap, float dx, float dz)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			Heightmap val = Heightmap.FindHeightmap(((Component)hmap).transform.position + new Vector3(dx, 0f, dz));
			if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)hmap || val.IsDistantLod)
			{
				return null;
			}
			if (val.m_width != hmap.m_width || val.m_scale != hmap.m_scale)
			{
				return null;
			}
			return val;
		}
	}
	[PatchSide(Side.Both)]
	[ModDisableable]
	[HarmonyPatch(typeof(TerrainComp))]
	internal static class PaintSpreadOncePatch
	{
		private struct Record
		{
			internal TerrainComp Comp;

			internal int Index;

			internal Color Before;
		}

		[HarmonyPatch]
		internal static class SpreadHook
		{
			private static readonly FieldInfo ModifiedPaintField = AccessTools.Field(typeof(TerrainComp), "m_modifiedPaint");

			private static readonly MethodInfo RecordSpreadMethod = AccessTools.Method(typeof(PaintSpreadOncePatch), "RecordSpread", (Type[])null, (Type[])null);

			[HarmonyPrepare]
			private static bool Prepare()
			{
				if (FindLocalFunction("spread") != null)
				{
					return true;
				}
				Logger.LogWarning("TerrainComp.PaintCleared no longer has exactly one 'spread' local function, so 'Fix Terrain Paint Doubling' is inactive. A Valheim update has most likely changed how terrain paint reaches neighbouring zones.");
				return false;
			}

			[HarmonyTargetMethod]
			private static MethodBase TargetMethod()
			{
				return FindLocalFunction("spread");
			}

			[HarmonyTranspiler]
			[HarmonyPriority(0)]
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ad: Expected O, but got Unknown
				//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00db: Expected O, but got Unknown
				//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ed: Expected O, but got Unknown
				List<CodeInstruction> list = PatchHelper.Copy(instructions);
				int num = -1;
				int num2 = 0;
				for (int i = 1; i < list.Count - 1; i++)
				{
					if (CodeInstructionExtensions.LoadsField(list[i], ModifiedPaintField, false))
					{
						num = i;
						num2++;
					}
				}
				if (num2 != 1 || !CodeInstructionExtensions.IsLdloc(list[num - 1], (LocalBuilder)null) || !CodeInstructionExtensions.IsLdloc(list[num + 1], (LocalBuilder)null))
				{
					Logger.LogWarning($"TerrainComp.PaintCleared spread: expected one neighbour paint write, found {num2}, " + "so 'Fix Terrain Paint Doubling' is inactive. Another mod has most likely already rewritten the method - if so, nothing is wrong.");
					return instructions;
				}
				CodeInstruction val = list[num - 1];
				CodeInstruction val2 = list[num + 1];
				CodeInstruction val3 = new CodeInstruction(val.opcode, val.operand);
				CodeInstructionExtensions.MoveLabelsTo(val, val3);
				list.InsertRange(num - 1, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3]
				{
					val3,
					new CodeInstruction(val2.opcode, val2.operand),
					new CodeInstruction(OpCodes.Call, (object)RecordSpreadMethod)
				});
				return list;
			}
		}

		internal static FixToggle Enabled;

		private static readonly List<Record> Records = new List<Record>();

		private static readonly List<TerrainComp> Painted = new List<TerrainComp>();

		private static int _editFrame = -1;

		private static Vector3 _editPos;

		private static Settings _editSettings;

		internal static void BindConfig()
		{
			Enabled = ValConfig.BindFixToggle(typeof(PaintSpreadOncePatch), "Fixes - Terrain", "Fix Terrain Paint Doubling", value: true, "Applies a terrain edit once to the ground two zones share. When an edit reaches both sides of a 64m zone border, vanilla applies it twice along the border (up to four times at a corner). Normal paint barely shows it, but Deep North snow is added, so piling or clearing snow leaves a ridge or trench along the zone line.");
		}

		[HarmonyPrefix]
		[HarmonyPatch("PaintCleared")]
		private static void PaintClearedPrefix(TerrainComp __instance, Vector3 worldPos, Settings settings)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			int frameCount = Time.frameCount;
			if (frameCount != _editFrame || !((Vector3)(ref worldPos)).Equals(_editPos) || settings != _editSettings || Painted.Contains(__instance))
			{
				Records.Clear();
				Painted.Clear();
				_editFrame = frameCount;
				_editPos = worldPos;
				_editSettings = settings;
			}
			else if (Logger.DebugEnabled)
			{
				int num = 0;
				for (int i = 0; i < Records.Count; i++)
				{
					if (Records[i].Comp == __instance)
					{
						num++;
					}
				}
				if (num > 0)
				{
					Logger.LogDebug($"Fix Terrain Paint Doubling: the zone at {((Component)__instance).transform.position} paints {num} " + "border texel(s) from their value before this edit instead of painting them twice.");
				}
			}
			Painted.Add(__instance);
		}

		[HarmonyTranspiler]
		[HarmonyPriority(0)]
		[HarmonyPatch("PaintCleared")]
		private static IEnumerable<CodeInstruction> PaintClearedTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(PaintSpreadOncePatch), "ReadPaint", (Type[])null, (Type[])null);
			return PatchHelper.ReplaceCalls(instructions, FindGetMask(methodInfo), methodInfo, "TerrainComp.PaintCleared");
		}

		private static Color ReadPaint(Heightmap hmap, TerrainComp tc, int x, int y, int index)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < Records.Count; i++)
			{
				if (Records[i].Index == index && Records[i].Comp == tc)
				{
					return Records[i].Before;
				}
			}
			return VanillaGetMask(hmap, tc, x, y, index);
		}

		private static Color VanillaGetMask(Heightmap hmap, TerrainComp tc, int x, int y, int index)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			if (hmap.m_doLateUpdate != 1 || (Object)(object)tc == (Object)null)
			{
				return hmap.GetPaintMask(x, y);
			}
			return tc.m_paintMask[index];
		}

		private static void RecordSpread(TerrainComp target, int index)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled == null || !Enabled.Value || (Object)(object)target == (Object)null || (Object)(object)target.m_hmap == (Object)null)
			{
				return;
			}
			for (int i = 0; i < Records.Count; i++)
			{
				if (Records[i].Index == index && Records[i].Comp == target)
				{
					return;
				}
			}
			int num = target.m_width + 1;
			Records.Add(new Record
			{
				Comp = target,
				Index = index,
				Before = VanillaGetMask(target.m_hmap, target, index % num, index / num, index)
			});
		}

		private static MethodInfo FindLocalFunction(string name)
		{
			string prefix = "<PaintCleared>g__" + name + "|";
			MethodInfo[] array = (from m in AccessTools.GetDeclaredMethods(typeof(TerrainComp))
				where m.Name.StartsWith(prefix)
				select m).ToArray();
			if (array.Length != 1)
			{
				return null;
			}
			return array[0];
		}

		private static MethodInfo FindGetMask(MethodInfo readPaint)
		{
			MethodInfo methodInfo = FindLocalFunction("getMask");
			if (methodInfo == null || !methodInfo.IsStatic || methodInfo.ReturnType != readPaint.ReturnType)
			{
				return null;
			}
			if (!(from p in methodInfo.GetParameters()
				select p.ParameterType).SequenceEqual(from p in readPaint.GetParameters()
				select p.ParameterType))
			{
				return null;
			}
			return methodInfo;
		}
	}
	[PatchSide(Side.Client)]
	[ModDisableable]
	[HarmonyPatch(typeof(Heightmap))]
	internal static class SeamlessNormalsPatch
	{
		[HarmonyPatch(typeof(MonoUpdaters), "LateUpdate")]
		internal static class ProcessDirtyHook
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				if (Dirty.Count == 0)
				{
					return;
				}
				if (Enabled == null || !Enabled.Value || RunMode.IsDedicated)
				{
					foreach (Heightmap item in Dirty)
					{
						if ((Object)(object)item != (Object)null && (Object)(object)item.m_renderMesh != (Object)null)
						{
							item.m_renderMesh.RecalculateTangents();
						}
					}
					Dirty.Clear();
					return;
				}
				RebuiltScratch.Clear();
				AffectedScratch.Clear();
				foreach (Heightmap item2 in Dirty)
				{
					if (!((Object)(object)item2 == (Object)null))
					{
						RebuiltScratch.Add(item2);
						AffectedScratch.Add(item2);
						float num = (float)item2.m_width * item2.m_scale;
						AddNeighbour(item2, 0f - num, 0f);
						AddNeighbour(item2, num, 0f);
						AddNeighbour(item2, 0f, 0f - num);
						AddNeighbour(item2, 0f, num);
					}
				}
				Dirty.Clear();
				foreach (Heightmap item3 in AffectedScratch)
				{
					if (!ApplyNormals(item3) && RebuiltScratch.Contains(item3))
					{
						ApplyFallbackTangents(item3);
					}
				}
				RebuiltScratch.Clear();
				AffectedScratch.Clear();
			}

			private static void AddNeighbour(Heightmap origin, float dx, float dz)
			{
				Heightmap val = FindNeighbour(origin, dx, dz);
				if ((Object)(object)val != (Object)null)
				{
					AffectedScratch.Add(val);
				}
			}
		}

		[HarmonyPatch(typeof(Heightmap), "OnDestroy")]
		internal static class OnDestroyHook
		{
			[HarmonyPostfix]
			private static void Postfix(Heightmap __instance)
			{
				Dirty.Remove(__instance);
			}
		}

		private const string FixName = "Fix Terrain Seams";

		internal static FixToggle Enabled;

		internal static ConfigEntry<bool> VerifyTangents;

		private static readonly List<Vector3> NormalBuffer = new List<Vector3>();

		private static readonly List<Vector4> TangentBuffer = new List<Vector4>();

		private static readonly List<Vector4> VerifyBuffer = new List<Vector4>();

		private static readonly HashSet<Heightmap> Dirty = new HashSet<Heightmap>();

		private static readonly HashSet<Heightmap> RebuiltScratch = new HashSet<Heightmap>();

		private static readonly HashSet<Heightmap> AffectedScratch = new HashSet<Heightmap>();

		private static readonly MethodInfo RecalculateTangentsMethod = AccessTools.Method(typeof(Mesh), "RecalculateTangents", new Type[0], (Type[])null);

		private static readonly MethodInfo TangentsOrDeferMethod = AccessTools.Method(typeof(SeamlessNormalsPatch), "TangentsOrDefer", (Type[])null, (Type[])null);

		private static readonly TakeoverCheck Takeover = new TakeoverCheck(typeof(SeamlessNormalsPatch), AccessTools.DeclaredMethod(typeof(Heightmap), "RebuildRenderMesh", (Type[])null, (Type[])null), transpilers: false, (string owners) => "Terrain mesh building is changed by " + owners + ", so 'Fix Terrain Seams' stands down and that mod's terrain shading applies.");

		internal static void BindConfig()
		{
			Enabled = ValConfig.BindFixToggle(typeof(SeamlessNormalsPatch), "Fixes - Terrain", "Fix Terrain Seams", value: true, "Computes terrain lighting normals across zone boundaries instead of per zone. Vanilla shades the same ground differently on each side of a 64m zone border, which shows up as a hard crease running through flat terrain - most noticeable in the Plains and Meadows.");
			VerifyTangents = ValConfig.BindServerConfig("Debug", "Verify Terrain Tangents", value: false, "Diagnostic. Uses Unity's tangent recalculation instead of the analytic tangents the seam fix normally computes, compares the two on a sample of vertices, and logs any disagreement. Costs the mesh pass the analytic version exists to avoid, so leave it off unless terrain lighting looks wrong.", null, advanced: true);
		}

		[HarmonyTranspiler]
		[HarmonyPriority(0)]
		[HarmonyPatch("RebuildRenderMesh")]
		private static IEnumerable<CodeInstruction> RebuildRenderMeshTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			List<CodeInstruction> list = PatchHelper.Copy(instructions);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (CodeInstructionExtensions.Calls(list[i], RecalculateTangentsMethod))
				{
					list[i].opcode = OpCodes.Ldarg_0;
					list[i].operand = null;
					list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)TangentsOrDeferMethod));
					num++;
					i++;
				}
			}
			if (num != 1)
			{
				Logger.LogWarning($"Heightmap.RebuildRenderMesh: expected 1 RecalculateTangents call, found {num}, " + "so the tangent half of the terrain seam fix is inactive. Another mod has most likely already rewritten the method - if so, nothing is wrong.");
				return instructions;
			}
			return list;
		}

		private static void TangentsOrDefer(Mesh mesh, Heightmap hmap)
		{
			if (!WillProcess(hmap))
			{
				mesh.RecalculateTangents();
			}
		}

		private static bool WillProcess(Heightmap hmap)
		{
			if (Enabled != null && Enabled.Value && !RunMode.IsDedicated && !hmap.IsDistantLod && (Object)(object)MonoUpdaters.s_instance != (Object)null)
			{
				return !Takeover.TakenOver;
			}
			return false;
		}

		[HarmonyPostfix]
		[HarmonyPatch("RebuildRenderMesh")]
		private static void RebuildRenderMeshPostfix(Heightmap __instance)
		{
			if (WillProcess(__instance))
			{
				Dirty.Add(__instance);
			}
		}

		private static Vector4 AnalyticTangent(Vector3 normal)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			float num = 1f - normal.x * normal.x;
			float num2 = (0f - normal.x) * normal.y;
			float num3 = (0f - normal.x) * normal.z;
			float num4 = 1f / Mathf.Sqrt(num * num + num2 * num2 + num3 * num3);
			return new Vector4(num * num4, num2 * num4, num3 * num4, -1f);
		}

		private static bool ApplyNormals(Heightmap hmap)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			Mesh renderMesh = hmap.m_renderMesh;
			if ((Object)(object)renderMesh == (Object)null)
			{
				return false;
			}
			int width = hmap.m_width;
			int num = width + 1;
			if (renderMesh.vertexCount != num * num)
			{
				return false;
			}
			float num2 = (float)width * hmap.m_scale;
			Heightmap val = FindNeighbour(hmap, 0f - num2, 0f);
			Heightmap val2 = FindNeighbour(hmap, num2, 0f);
			Heightmap val3 = FindNeighbour(hmap, 0f, 0f - num2);
			Heightmap val4 = FindNeighbour(hmap, 0f, num2);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null)
			{
				return false;
			}
			float y = ((Component)hmap).transform.position.y;
			float y2 = ((Component)val).transform.position.y;
			float y3 = ((Component)val2).transform.position.y;
			float y4 = ((Component)val3).transform.position.y;
			float y5 = ((Component)val4).transform.position.y;
			float num3 = 1f / (2f * hmap.m_scale);
			bool flag = VerifyTangents == null || !VerifyTangents.Value;
			NormalBuffer.Clear();
			TangentBuffer.Clear();
			Vector3 val5 = default(Vector3);
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					float num4 = (Sample(hmap, j + 1, i, val, val2, val3, val4, y, y2, y3, y4, y5) - Sample(hmap, j - 1, i, val, val2, val3, val4, y, y2, y3, y4, y5)) * num3;
					float num5 = (Sample(hmap, j, i + 1, val, val2, val3, val4, y, y2, y3, y4, y5) - Sample(hmap, j, i - 1, val, val2, val3, val4, y, y2, y3, y4, y5)) * num3;
					float num6 = 1f / Mathf.Sqrt(num4 * num4 + 1f + num5 * num5);
					((Vector3)(ref val5))..ctor((0f - num4) * num6, num6, (0f - num5) * num6);
					NormalBuffer.Add(val5);
					if (flag)
					{
						TangentBuffer.Add(AnalyticTangent(val5));
					}
				}
			}
			renderMesh.SetNormals(NormalBuffer);
			if (flag)
			{
				renderMesh.SetTangents(TangentBuffer);
			}
			else
			{
				renderMesh.RecalculateTangents();
				CompareTangents(renderMesh);
			}
			return true;
		}

		private static void ApplyFallbackTangents(Heightmap hmap)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			Mesh renderMesh = hmap.m_renderMesh;
			if ((Object)(object)renderMesh == (Object)null)
			{
				return;
			}
			if (VerifyTangents != null && VerifyTangents.Value)
			{
				renderMesh.RecalculateTangents();
				return;
			}
			NormalBuffer.Clear();
			renderMesh.GetNormals(NormalBuffer);
			TangentBuffer.Clear();
			for (int i = 0; i < NormalBuffer.Count; i++)
			{
				TangentBuffer.Add(AnalyticTangent(NormalBuffer[i]));
			}
			renderMesh.SetTangents(TangentBuffer);
		}

		private static void CompareTangents(Mesh mesh)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			VerifyBuffer.Clear();
			mesh.GetTangents(VerifyBuffer);
			if (VerifyBuffer.Count != NormalBuffer.Count)
			{
				return;
			}
			int num = 0;
			float num2 = 1f;
			for (int i = 0; i < VerifyBuffer.Count; i += 173)
			{
				Vector4 val = AnalyticTangent(NormalBuffer[i]);
				Vector4 val2 = VerifyBuffer[i];
				float num3 = val.x * val2.x + val.y * val2.y + val.z * val2.z;
				if (num3 < num2)
				{
					num2 = num3;
				}
				if (num3 < 0.99f || val2.w > 0f)
				{
					num++;
				}
			}
			if (num == 0)
			{
				Logger.LogDebug($"Tangent verify: agreed (worst dot {num2:F4}).");
			}
			else
			{
				Logger.LogWarning($"Tangent verify: {num} sampled vertex(es) diverged (worst dot {num2:F4}). " + "Unity's tangents were used. Please report this.");
			}
		}

		private static float Sample(Heightmap hmap, int x, int y, Heightmap west, Heightmap east, Heightmap south, Heightmap north, float selfY, float westY, float eastY, float southY, float northY)
		{
			int width = hmap.m_width;
			if (x < 0)
			{
				return west.GetHeight(width - 1, y) + westY;
			}
			if (x > width)
			{
				return east.GetHeight(1, y) + eastY;
			}
			if (y < 0)
			{
				return south.GetHeight(x, width - 1) + southY;
			}
			if (y > width)
			{
				return north.GetHeight(x, 1) + northY;
			}
			return hmap.GetHeight(x, y) + selfY;
		}

		private static Heightmap FindNeighbour(Heightmap hmap, float dx, float dz)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			Heightmap val = Heightmap.FindHeightmap(((Component)hmap).transform.position + new Vector3(dx, 0f, dz));
			if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)hmap || val.IsDistantLod)
			{
				return null;
			}
			if (val.m_width != hmap.m_width || val.m_scale != hmap.m_scale)
			{
				return null;
			}
			return val;
		}
	}
	[PatchSide(Side.Both)]
	[ModDisableable]
	[HarmonyPatch(typeof(TerrainComp))]
	internal static class TerrainCompNullHmapPatch
	{
		internal static FixToggle Enabled;

		private static readonly HashSet<TerrainComp> RecoveryFailed = new HashSet<TerrainComp>();

		internal static void BindConfig()
		{
			Enabled = ValConfig.BindFixToggle(typeof(TerrainCompNullHmapPatch), "Fixes - Terrain", "Fix Terrain Compiler Init Race", value: true, "Recovers a terrain compiler that loaded before its zone's heightmap existed. In vanilla it throws a NullReferenceException every frame from then on and that zone stops accepting terrain edits entirely.");
		}

		[HarmonyPrefix]
		[HarmonyPatch("Update")]
		private static bool UpdatePrefix(TerrainComp __instance)
		{
			if (Enabled == null || !Enabled.Value)
			{
				return true;
			}
			if ((Object)(object)__instance.m_hmap != (Object)null && __instance.m_initialized)
			{
				return true;
			}
			if (RecoveryFailed.Count > 0 && RecoveryFailed.Contains(__instance))
			{
				return false;
			}
			TryRecover(__instance);
			return false;
		}

		private static bool TryRecover(TerrainComp comp)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			Heightmap val = (((Object)(object)comp.m_hmap != (Object)null) ? comp.m_hmap : Heightmap.FindHeightmap(((Component)comp).transform.position));
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if ((Object)(object)comp.m_nview == (Object)null || !comp.m_nview.IsValid())
			{
				return false;
			}
			comp.m_hmap = val;
			try
			{
				if (!comp.m_initialized)
				{
					TerrainComp val2 = TerrainComp.FindTerrainCompiler(((Component)comp).transform.position);
					if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)comp && (Object)(object)ZNetScene.instance != (Object)null)
					{
						Logger.LogWarning($"Found another terrain compiler at {((Component)comp).transform.position}, removing it. " + "Two compilers in one zone means one of their saved terrain edits would be discarded at random, so this resolves it the way TerrainComp.Awake does.");
						if ((Object)(object)val2.m_nview != (Object)null && val2.m_nview.IsValid() && !val2.m_nview.HasOwner())
						{
							val2.m_nview.ClaimOwnership();
						}
						ZNetScene.instance.Destroy(((Component)val2).gameObject);
						TerrainComp.s_instances.Remove(val2);
					}
					if (!TerrainComp.s_instances.Contains(comp))
					{
						TerrainComp.s_instances.Add(comp);
					}
					comp.m_nview.Register<ZPackage>("RPC_ApplyOperation", (Action<long, ZPackage>)comp.RPC_ApplyOperation);
					comp.Initialize();
				}
				else if (!TerrainComp.s_instances.Contains(comp))
				{
					TerrainComp.s_instances.Add(comp);
				}
				comp.CheckLoad();
			}
			catch (Exception arg)
			{
				RecoveryFailed.RemoveWhere((TerrainComp failed) => (Object)(object)failed == (Object)null);
				RecoveryFailed.Add(comp);
				Logger.LogError($"Failed to recover terrain compiler at {((Component)comp).transform.position}, and it will not " + $"be retried: {arg}");
				return false;
			}
			Logger.LogInfo($"Recovered terrain compiler at {((Component)comp).transform.position} after its heightmap loaded.");
			return true;
		}
	}
	[PatchSide(Side.Both)]
	[ModDisableable]
	[HarmonyPatch(typeof(TerrainOp))]
	internal static class TerrainOpPaintFanoutPatch
	{
		internal static FixToggle Enabled;

		private static readonly List<Heightmap> Reached = new List<Heightmap>();

		private static readonly List<Heightmap> PaintReached = new List<Heightmap>();

		internal static void BindConfig()
		{
			Enabled = ValConfig.BindFixToggle(typeof(TerrainOpPaintFanoutPatch), "Fixes - Terrain", "Fix Terrain Paint Zone Fanout", value: true, "Sends terrain paint to every zone the paint actually covers. Vanilla measures which zones an edit touches from its radius, but the paint itself reaches about a metre further west and south, so the neighbouring zone never records paint applied to ground it shares - which is what leaves dirt stopping dead along a 64m zone border.");
		}

		[HarmonyPostfix]
		[HarmonyPatch("Awake")]
		private static void AwakePostfix(TerrainOp __instance, bool __runOriginal)
		{
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			if (!__runOriginal || Enabled == null || !Enabled.Value || TerrainOp.m_forceDisableTerrainOps)
			{
				return;
			}
			Settings settings = __instance.m_settings;
			if (settings == null || !settings.m_paintCleared || settings.m_paintRadius <= 0f)
			{
				return;
			}
			Vector3 position = ((Component)__instance).transform.position;
			Heightmap val = Heightmap.FindHeightmap(position);
			float num = (((Object)(object)val != (Object)null) ? val.m_scale : 1f);
			float radius = settings.GetRadius();
			float num2 = settings.m_paintRadius + num;
			if (num2 <= radius)
			{
				return;
			}
			Reached.Clear();
			PaintReached.Clear();
			try
			{
				Heightmap.FindHeightmap(position, radius, Reached);
				Heightmap.FindHeightmap(position, num2, PaintReached);
				bool level = settings.m_level;
				bool raise = settings.m_raise;
				bool smooth = settings.m_smooth;
				settings.m_level = false;
				settings.m_raise = false;
				settings.m_smooth = false;
				try
				{
					for (int i = 0; i < PaintReached.Count; i++)
					{
						Heightmap val2 = PaintReached[i];
						if (!((Object)(object)val2 == (Object)null) && !val2.IsDistantLod && !Reached.Contains(val2) && IsBehindOperation(val2, position))
						{
							val2.GetAndCreateTerrainCompiler().ApplyOperation(__instance);
						}
					}
				}
				finally
				{
					settings.m_level = level;
					settings.m_raise = raise;
					settings.m_smooth = smooth;
				}
			}
			catch (Exception arg)
			{
				Logger.LogWarning($"Could not extend terrain paint past {position} into neighbouring zones: {arg}");
			}
			finally
			{
				Reached.Clear();
				PaintReached.Clear();
			}
		}

		private static bool IsBehindOperation(Heightmap hmap, Vector3 pos)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			float num = (float)hmap.m_width * hmap.m_scale * 0.5f;
			Vector3 position = ((Component)hmap).transform.position;
			if (!(position.x + num <= pos.x))
			{
				return position.z + num <= pos.z;
			}
			return true;
		}
	}
}
namespace ValheimCommunityPatch.Patches.Performance
{
	[PatchSide(Side.Client)]
	[ModDisableable]
	[HarmonyPatch(typeof(ZoneSystem), "PokeLocalZone")]
	internal static class ArrivingZoneGhostPatch
	{
		private const string FixName = "Fix Arriving Location Hitch";

		private static readonly FixSwitch ApiSwitch = FixRegistry.SwitchOf(typeof(ArrivingZoneGhostPatch));

		private static readonly TakeoverCheck PokeTakeover = new TakeoverCheck(typeof(ArrivingZoneGhostPatch), AccessTools.DeclaredMethod(typeof(ZoneSystem), "PokeLocalZone", (Type[])null, (Type[])null), transpilers: true, (string owners) => "Zone loading is changed by " + owners + ", so 'Fix Arriving Location Hitch' stands down and arriving zones generate as that mod decides.");

		private static readonly TakeoverCheck SpawnTakeover = new TakeoverCheck(typeof(ArrivingZoneGhostPatch), AccessTools.DeclaredMethod(typeof(ZoneSystem), "SpawnZone", (Type[])null, (Type[])null), transpilers: true, (string owners) => "Zone generation is changed by " + owners + ", so 'Fix Arriving Location Hitch' stands down and arriving zones generate in full.");

		[HarmonyPrefix]
		[HarmonyPriority(0)]