Decompiled source of Treadwell v0.1.2

plugins/Treadwell/Treadwell.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Treadwell.Core;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("JStack424")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Treadwell Valheim plugin.")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+8583a261d57d056dff56c24aee6a3d8fad92ebfd")]
[assembly: AssemblyProduct("Treadwell")]
[assembly: AssemblyTitle("Treadwell")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/JStack424/Treadwell")]
[assembly: AssemblyVersion("0.1.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Treadwell
{
	internal sealed class CompatibilityResult
	{
		internal bool IsCompatible { get; }

		internal string Reason { get; }

		internal CompatibilityResult(bool isCompatible, string reason)
		{
			IsCompatible = isCompatible;
			Reason = reason;
		}
	}
	internal static class CompatibilityGate
	{
		internal static CompatibilityResult Evaluate(FeatureHost features)
		{
			List<string> list = new List<string>();
			try
			{
				features.ValidateCompatibility(list);
			}
			catch (Exception ex)
			{
				list.Add("feature compatibility validation threw: " + ex.GetType().Name);
			}
			if (list.Count != 0)
			{
				return new CompatibilityResult(isCompatible: false, string.Join("; ", list) + " (" + RuntimeDiagnostics() + ")");
			}
			return new CompatibilityResult(isCompatible: true, "runtime contract verified (" + RuntimeDiagnostics() + ")");
		}

		internal static void RequireMethod(ICollection<string> failures, Type declaringType, string name, Type returnType, BindingFlags flags, Type[] parameterTypes, Func<MethodInfo, bool> additionalCheck = null)
		{
			MethodInfo[] array;
			try
			{
				array = ExactRuntimeContract.FindMethods(declaringType, name, flags, returnType, parameterTypes, additionalCheck);
			}
			catch (Exception ex)
			{
				failures.Add(declaringType.Name + "." + name + " contract inspection threw: " + ex.GetType().Name);
				return;
			}
			if (array.Length != 1)
			{
				failures.Add(declaringType.Name + "." + name + " requires one exact runtime signature; found " + array.Length);
			}
		}

		internal static void RequireConstructor(ICollection<string> failures, Type declaringType, BindingFlags flags, Type[] parameterTypes)
		{
			ConstructorInfo[] array;
			try
			{
				array = ExactRuntimeContract.FindConstructors(declaringType, flags, parameterTypes);
			}
			catch (Exception ex)
			{
				failures.Add(declaringType.Name + " constructor inspection threw: " + ex.GetType().Name);
				return;
			}
			if (array.Length != 1)
			{
				failures.Add(declaringType.Name + " requires one exact runtime constructor; found " + array.Length);
			}
		}

		internal static void RequireMethodNamedReturn(ICollection<string> failures, Type declaringType, string name, string returnTypeFullName, BindingFlags flags, Type[] parameterTypes)
		{
			MethodInfo[] array;
			try
			{
				array = (from method in declaringType.GetMethods(flags)
					where string.Equals(method.Name, name, StringComparison.Ordinal)
					where string.Equals(method.ReturnType.FullName, returnTypeFullName, StringComparison.Ordinal)
					where ParametersMatch(method.GetParameters(), parameterTypes)
					select method).ToArray();
			}
			catch (Exception ex)
			{
				failures.Add(declaringType.Name + "." + name + " contract inspection threw: " + ex.GetType().Name);
				return;
			}
			if (array.Length != 1)
			{
				failures.Add(declaringType.Name + "." + name + " requires one exact runtime signature; found " + array.Length);
			}
		}

		internal static void RequirePatchMethod(ICollection<string> failures, Type declaringType, string name, Type[] parameterTypes)
		{
			RequireMethod(failures, declaringType, name, typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic, parameterTypes, (MethodInfo method) => method.IsStatic);
		}

		internal static void RequireField(ICollection<string> failures, Type declaringType, string name, Type fieldType, BindingFlags flags)
		{
			FieldInfo[] array;
			try
			{
				array = ExactRuntimeContract.FindFields(declaringType, name, flags | BindingFlags.DeclaredOnly, fieldType);
			}
			catch (Exception ex)
			{
				failures.Add(declaringType.Name + "." + name + " field inspection threw: " + ex.GetType().Name);
				return;
			}
			if (array.Length != 1)
			{
				failures.Add(declaringType.Name + "." + name + " requires one exact runtime field; found " + array.Length);
			}
		}

		internal static void RequireProperty(ICollection<string> failures, Type declaringType, string name, Type propertyType, BindingFlags flags, bool requireGetter, bool requireSetter)
		{
			PropertyInfo[] array;
			try
			{
				array = (from property in ExactRuntimeContract.FindProperties(declaringType, name, flags, propertyType)
					where property.GetIndexParameters().Length == 0
					where !requireGetter || property.GetGetMethod(nonPublic: true) != null
					where !requireSetter || property.GetSetMethod(nonPublic: true) != null
					select property).ToArray();
			}
			catch (Exception ex)
			{
				failures.Add(declaringType.Name + "." + name + " property inspection threw: " + ex.GetType().Name);
				return;
			}
			if (array.Length != 1)
			{
				failures.Add(declaringType.Name + "." + name + " requires one exact runtime property; found " + array.Length);
			}
		}

		internal static void RequireGenericMethod(ICollection<string> failures, Type declaringType, string name, BindingFlags flags, Type[] parameterTypes, bool returnsArray)
		{
			MethodInfo[] array;
			try
			{
				array = (from method in declaringType.GetMethods(flags)
					where string.Equals(method.Name, name, StringComparison.Ordinal)
					where method.IsGenericMethodDefinition && method.GetGenericArguments().Length == 1
					where ParametersMatch(method.GetParameters(), parameterTypes)
					where GenericReturnMatches(method.ReturnType, returnsArray)
					select method).ToArray();
			}
			catch (Exception ex)
			{
				failures.Add(declaringType.Name + "." + name + " generic contract inspection threw: " + ex.GetType().Name);
				return;
			}
			if (array.Length != 1)
			{
				failures.Add(declaringType.Name + "." + name + " requires one exact generic runtime signature; found " + array.Length);
			}
		}

		internal static void RequireEnumValue(ICollection<string> failures, Type enumType, string name, int expectedValue)
		{
			try
			{
				if (!enumType.IsEnum || !Enum.IsDefined(enumType, name) || Convert.ToInt32(Enum.Parse(enumType, name)) != expectedValue)
				{
					failures.Add(enumType.Name + "." + name + " enum value is incompatible");
				}
			}
			catch (Exception ex)
			{
				failures.Add(enumType.Name + "." + name + " enum inspection threw: " + ex.GetType().Name);
			}
		}

		internal static void RequireColor(ICollection<string> failures, string label, Color observed, float red, float green, float blue, float alpha)
		{
			//IL_0000: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if (Math.Abs(observed.r - red) > 0.0001f || Math.Abs(observed.g - green) > 0.0001f || Math.Abs(observed.b - blue) > 0.0001f || Math.Abs(observed.a - alpha) > 0.0001f)
			{
				failures.Add(label + " encoding is incompatible");
			}
		}

		private static bool ParametersMatch(ParameterInfo[] observed, Type[] expected)
		{
			if (observed.Length != expected.Length)
			{
				return false;
			}
			for (int i = 0; i < observed.Length; i++)
			{
				if (observed[i].ParameterType != expected[i])
				{
					return false;
				}
			}
			return true;
		}

		private static bool GenericReturnMatches(Type returnType, bool returnsArray)
		{
			if (!returnsArray)
			{
				if (returnType.IsGenericParameter)
				{
					return returnType.GenericParameterPosition == 0;
				}
				return false;
			}
			Type elementType = returnType.GetElementType();
			if (returnType.IsArray && elementType != null && elementType.IsGenericParameter)
			{
				return elementType.GenericParameterPosition == 0;
			}
			return false;
		}

		private static string RuntimeDiagnostics()
		{
			string text = ReadStaticDiagnosticProperty(typeof(Player).Assembly.GetType("Version", throwOnError: false), "CurrentVersion");
			string text2 = ReadStaticDiagnosticProperty(typeof(Application), "unityVersion");
			string text3 = SafeDiagnostic(() => typeof(BaseUnityPlugin).Assembly.GetName().Version?.ToString());
			string text4 = SafeDiagnostic(() => typeof(Harmony).Assembly.GetName().Version?.ToString());
			string text5 = SafeDiagnostic(() => typeof(Player).Assembly.ManifestModule.ModuleVersionId.ToString());
			return "game " + text + ", Unity " + text2 + ", BepInEx " + text3 + ", Harmony " + text4 + ", assembly MVID " + text5;
		}

		private static string ReadStaticDiagnosticProperty(Type declaringType, string name)
		{
			if (declaringType == null)
			{
				return "unknown";
			}
			return SafeDiagnostic(delegate
			{
				PropertyInfo[] array = (from property in declaringType.GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
					where string.Equals(property.Name, name, StringComparison.Ordinal)
					where property.GetIndexParameters().Length == 0 && property.GetGetMethod(nonPublic: true) != null
					select property).ToArray();
				return (array.Length != 1) ? "unknown" : array[0].GetValue(null, null)?.ToString();
			});
		}

		private static string SafeDiagnostic(Func<string> read)
		{
			try
			{
				return read() ?? "unknown";
			}
			catch
			{
				return "unknown";
			}
		}
	}
	internal sealed class FeatureHost : IDisposable
	{
		private readonly IReadOnlyList<IFeatureModule> _modules;

		private readonly ManualLogSource _log;

		private bool _started;

		internal FeatureHost(IEnumerable<IFeatureModule> modules, ManualLogSource log)
		{
			_modules = (modules ?? throw new ArgumentNullException("modules")).ToArray();
			_log = log ?? throw new ArgumentNullException("log");
			if (_modules.Select((IFeatureModule module) => module.Id).Distinct<string>(StringComparer.Ordinal).Count() != _modules.Count)
			{
				throw new InvalidOperationException("Feature module ids must be unique.");
			}
		}

		internal void ValidateCompatibility(ICollection<string> failures)
		{
			foreach (IFeatureModule module in _modules)
			{
				module.ValidateCompatibility(failures);
			}
		}

		internal void Start()
		{
			if (_started)
			{
				return;
			}
			try
			{
				foreach (IFeatureModule module in _modules)
				{
					module.Enabled.SettingChanged += OnSettingChanged;
					if (module.Enabled.Value)
					{
						module.Enable();
					}
				}
				_started = true;
			}
			catch
			{
				Stop();
				throw;
			}
		}

		private void OnSettingChanged(object sender, EventArgs eventArgs)
		{
			try
			{
				foreach (IFeatureModule module in _modules)
				{
					if (module.Enabled.Value)
					{
						module.Enable();
					}
					else
					{
						module.Disable();
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogError((object)("A feature toggle failed; all feature modules were disabled: " + ex));
				Stop();
			}
		}

		internal void Stop()
		{
			foreach (IFeatureModule item in _modules.Reverse())
			{
				item.Enabled.SettingChanged -= OnSettingChanged;
				try
				{
					item.Disable();
				}
				catch (Exception ex)
				{
					_log.LogError((object)("Feature cleanup failed for " + item.Id + ": " + ex));
				}
			}
			_started = false;
		}

		public void Dispose()
		{
			Stop();
		}
	}
	internal interface IFeatureModule
	{
		string Id { get; }

		ConfigEntry<bool> Enabled { get; }

		void ValidateCompatibility(ICollection<string> failures);

		void Enable();

		void Disable();
	}
	internal abstract class FeatureModuleBase : IFeatureModule
	{
		private readonly string _harmonyId;

		private Harmony _harmony;

		private bool _active;

		private bool _cleanupPending;

		public string Id { get; }

		public ConfigEntry<bool> Enabled { get; }

		protected ManualLogSource Log { get; }

		protected Harmony Harmony => _harmony ?? throw new InvalidOperationException("Harmony was not initialized after compatibility validation.");

		protected FeatureModuleBase(string id, ConfigEntry<bool> enabled, ManualLogSource log)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				throw new ArgumentException("Feature id is required.", "id");
			}
			Id = id;
			Enabled = enabled ?? throw new ArgumentNullException("enabled");
			Log = log ?? throw new ArgumentNullException("log");
			_harmonyId = "com.jstack424.treadwell.feature." + id;
		}

		public abstract void ValidateCompatibility(ICollection<string> failures);

		public void Enable()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			if (_active)
			{
				return;
			}
			if (_cleanupPending)
			{
				Disable();
			}
			_cleanupPending = true;
			try
			{
				_harmony = new Harmony(_harmonyId);
				TransactionalInstall.Run(InstallPatches, delegate
				{
					Harmony harmony = _harmony;
					if (harmony != null)
					{
						harmony.UnpatchSelf();
					}
				}, OnDisabled);
				_active = true;
				_cleanupPending = false;
				Log.LogInfo((object)("Enabled feature module: " + Id));
			}
			catch
			{
				_active = false;
				throw;
			}
		}

		public void Disable()
		{
			if (!_active && !_cleanupPending)
			{
				return;
			}
			List<Exception> list = new List<Exception>();
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception item)
			{
				list.Add(item);
			}
			try
			{
				OnDisabled();
			}
			catch (Exception item2)
			{
				list.Add(item2);
			}
			_active = false;
			if (list.Count > 0)
			{
				_cleanupPending = true;
				throw new AggregateException("Feature cleanup failed for " + Id + ".", list);
			}
			_harmony = null;
			_cleanupPending = false;
			Log.LogInfo((object)("Disabled feature module: " + Id));
		}

		protected abstract void InstallPatches();

		protected virtual void OnDisabled()
		{
		}
	}
	internal sealed class RoadFeatureModule : FeatureModuleBase
	{
		private sealed class PieceTableEntryInspection
		{
			internal GameObject Root { get; }

			internal Piece[] Pieces { get; }

			internal TerrainModifier[] Modifiers { get; }

			internal PavedRoadCandidateShape Shape { get; }

			internal Piece Piece
			{
				get
				{
					if (Pieces.Length != 1)
					{
						return null;
					}
					return Pieces[0];
				}
			}

			internal PieceTableEntryInspection(GameObject root, Piece[] pieces, TerrainModifier[] modifiers, PavedRoadCandidateShape shape)
			{
				Root = root;
				Pieces = pieces;
				Modifiers = modifiers;
				Shape = shape;
			}

			internal string Describe()
			{
				Piece piece = Piece;
				StringBuilder stringBuilder = new StringBuilder("[root='");
				stringBuilder.Append(SanitizeDiagnostic(((Object)(object)Root != (Object)null) ? ((Object)Root).name : null));
				stringBuilder.Append("', pieces=").Append(Pieces.Length);
				stringBuilder.Append(", piece='").Append(SanitizeDiagnostic(((Object)(object)piece != (Object)null) ? piece.m_name : null)).Append("'");
				stringBuilder.Append(", station='");
				stringBuilder.Append(SanitizeDiagnostic(((Object)(object)piece != (Object)null && (Object)(object)piece.m_craftingStation != (Object)null && (Object)(object)((Component)piece.m_craftingStation).gameObject != (Object)null) ? ((Object)((Component)piece.m_craftingStation).gameObject).name : null));
				stringBuilder.Append("', terrain=");
				AppendTerrainSummary(stringBuilder, Modifiers);
				stringBuilder.Append(", resources=");
				AppendResourceSummary(stringBuilder, ((Object)(object)piece != (Object)null) ? piece.m_resources : null);
				stringBuilder.Append(']');
				return stringBuilder.ToString();
			}

			private static void AppendTerrainSummary(StringBuilder text, TerrainModifier[] modifiers)
			{
				text.Append('[');
				int num = Math.Min(modifiers.Length, 4);
				for (int i = 0; i < num; i++)
				{
					if (i > 0)
					{
						text.Append(',');
					}
					text.Append(((Object)(object)modifiers[i] != (Object)null) ? ((object)Unsafe.As<PaintType, PaintType>(ref modifiers[i].m_paintType)/*cast due to .constrained prefix*/).ToString() : "<null>");
				}
				if (modifiers.Length > num)
				{
					text.Append(",+").Append(modifiers.Length - num);
				}
				text.Append(']');
			}

			private static void AppendResourceSummary(StringBuilder text, Requirement[] requirements)
			{
				text.Append('[');
				if (requirements != null)
				{
					int num = Math.Min(requirements.Length, 4);
					for (int i = 0; i < num; i++)
					{
						if (i > 0)
						{
							text.Append(',');
						}
						Requirement val = requirements[i];
						string value = ((val != null && (Object)(object)val.m_resItem != (Object)null && (Object)(object)((Component)val.m_resItem).gameObject != (Object)null) ? ((Object)((Component)val.m_resItem).gameObject).name : null);
						text.Append(SanitizeDiagnostic(value)).Append(':').Append(val?.m_amount ?? 0);
					}
					if (requirements.Length > num)
					{
						text.Append(",+").Append(requirements.Length - num);
					}
				}
				text.Append(']');
			}
		}

		internal const double NaturalGapHoldSeconds = 0.18;

		private static RoadFeatureModule _activeModule;

		private static MethodInfo GetLastGroundColliderMethod;

		private static MethodInfo UpdateAvailablePiecesListMethod;

		private readonly ConfigEntry<bool> _pavedRoadWithoutStonecutter;

		private readonly ConfigEntry<float> _dirtSpeed;

		private readonly ConfigEntry<float> _dirtStamina;

		private readonly ConfigEntry<float> _pavedSpeed;

		private readonly ConfigEntry<float> _pavedStamina;

		private readonly RoadSurfaceTracker _surfaceTracker = new RoadSurfaceTracker(0.18);

		private readonly PavedRoadStationOverride<Piece, CraftingStation> _stationOverride;

		private PieceTable _lastPieceTable;

		private bool _pavedSettingSubscribed;

		private bool _loggedStationRemoved;

		private bool _loggedStationAlreadyAbsent;

		private bool _loggedDiscoveryFailure;

		private bool _loggedStationConflict;

		internal RoadFeatureModule(ConfigEntry<bool> enabled, ConfigEntry<bool> pavedRoadWithoutStonecutter, ConfigEntry<float> dirtSpeed, ConfigEntry<float> dirtStamina, ConfigEntry<float> pavedSpeed, ConfigEntry<float> pavedStamina, ManualLogSource log)
			: base("roads", enabled, log)
		{
			_pavedRoadWithoutStonecutter = pavedRoadWithoutStonecutter ?? throw new ArgumentNullException("pavedRoadWithoutStonecutter");
			_dirtSpeed = dirtSpeed ?? throw new ArgumentNullException("dirtSpeed");
			_dirtStamina = dirtStamina ?? throw new ArgumentNullException("dirtStamina");
			_pavedSpeed = pavedSpeed ?? throw new ArgumentNullException("pavedSpeed");
			_pavedStamina = pavedStamina ?? throw new ArgumentNullException("pavedStamina");
			_stationOverride = new PavedRoadStationOverride<Piece, CraftingStation>((Piece piece) => piece.m_craftingStation, delegate(Piece piece, CraftingStation? station)
			{
				piece.m_craftingStation = station;
			});
		}

		public override void ValidateCompatibility(ICollection<string> failures)
		{
			//IL_0a6a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a8e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ab2: Unknown result type (might be due to invalid IL or missing references)
			CompatibilityGate.RequireMethod(failures, typeof(PieceTable), "UpdateAvailable", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[4]
			{
				typeof(HashSet<string>),
				typeof(Player),
				typeof(bool),
				typeof(bool)
			}, (MethodInfo method) => !method.IsStatic);
			CompatibilityGate.RequireField(failures, typeof(PieceTable), "m_pieces", typeof(List<GameObject>), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireMethod(failures, typeof(ZNetScene), "OnDestroy", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic);
			CompatibilityGate.RequireMethod(failures, typeof(Player), "SetPlaceMode", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, new Type[1] { typeof(PieceTable) }, (MethodInfo method) => !method.IsStatic && method.IsFamily && method.IsVirtual);
			CompatibilityGate.RequireMethod(failures, typeof(Player), "GetBuildTool", typeof(PieceTable), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic);
			CompatibilityGate.RequireMethod(failures, typeof(Player), "UpdateAvailablePiecesList", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic);
			CompatibilityGate.RequireMethod(failures, typeof(Player), "HaveRequirements", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[2]
			{
				typeof(Piece),
				typeof(RequirementMode)
			}, (MethodInfo method) => !method.IsStatic);
			CompatibilityGate.RequireField(failures, typeof(Piece), "m_name", typeof(string), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Piece), "m_craftingStation", typeof(CraftingStation), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Piece), "m_resources", typeof(Requirement[]), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Requirement), "m_resItem", typeof(ItemDrop), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Requirement), "m_amount", typeof(int), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(TerrainModifier), "m_paintType", typeof(PaintType), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireMethod(failures, typeof(Player), "GetRunSpeedFactor", typeof(float), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes, (MethodInfo method) => method.IsFamily && method.IsVirtual);
			CompatibilityGate.RequireMethod(failures, typeof(SEMan), "ModifyRunStaminaDrain", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[4]
			{
				typeof(float),
				typeof(float).MakeByRefType(),
				typeof(Vector3),
				typeof(bool)
			}, (MethodInfo method) => !method.IsStatic && string.Equals(method.GetParameters()[1].Name, "drain", StringComparison.Ordinal));
			CompatibilityGate.RequireMethod(failures, typeof(Character), "IsOnGround", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes);
			CompatibilityGate.RequireMethod(failures, typeof(Character), "IsRunning", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes);
			CompatibilityGate.RequireMethodNamedReturn(failures, typeof(Character), "GetLastGroundCollider", "UnityEngine.Collider", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes);
			CompatibilityGate.RequireMethod(failures, typeof(Heightmap), "GetPaintMask", typeof(Color), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(Vector3) });
			CompatibilityGate.RequireField(failures, typeof(Player), "m_localPlayer", typeof(Player), BindingFlags.Static | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(SEMan), "m_character", typeof(Character), BindingFlags.Instance | BindingFlags.NonPublic);
			CompatibilityGate.RequireField(failures, typeof(Heightmap), "m_paintMaskDirt", typeof(Color), BindingFlags.Static | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Heightmap), "m_paintMaskCultivated", typeof(Color), BindingFlags.Static | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Heightmap), "m_paintMaskPaved", typeof(Color), BindingFlags.Static | BindingFlags.Public);
			CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PlayerSetPlaceModePrefix", new Type[1] { typeof(PieceTable) });
			CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PieceTableUpdateAvailablePrefix", new Type[1] { typeof(PieceTable) });
			CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PlayerHaveRequirementsPrefix", new Type[1] { typeof(Piece) });
			CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "ZNetSceneOnDestroyPrefix", Type.EmptyTypes);
			CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "GetRunSpeedFactorPostfix", new Type[2]
			{
				typeof(Player),
				typeof(float).MakeByRefType()
			});
			CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "ModifyRunStaminaDrainPostfix", new Type[2]
			{
				typeof(Character),
				typeof(float).MakeByRefType()
			});
			CompatibilityGate.RequireGenericMethod(failures, typeof(GameObject), "GetComponent", BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, returnsArray: false);
			CompatibilityGate.RequireGenericMethod(failures, typeof(GameObject), "GetComponentsInChildren", BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(bool) }, returnsArray: true);
			CompatibilityGate.RequireGenericMethod(failures, typeof(Component), "GetComponent", BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, returnsArray: false);
			CompatibilityGate.RequireGenericMethod(failures, typeof(Component), "GetComponentInParent", BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, returnsArray: false);
			CompatibilityGate.RequireProperty(failures, typeof(Component), "gameObject", typeof(GameObject), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false);
			CompatibilityGate.RequireProperty(failures, typeof(Component), "transform", typeof(Transform), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false);
			CompatibilityGate.RequireProperty(failures, typeof(Transform), "position", typeof(Vector3), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false);
			CompatibilityGate.RequireProperty(failures, typeof(Time), "unscaledTime", typeof(float), BindingFlags.Static | BindingFlags.Public, requireGetter: true, requireSetter: false);
			CompatibilityGate.RequireProperty(failures, typeof(Object), "name", typeof(string), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false);
			CompatibilityGate.RequireField(failures, typeof(Color), "r", typeof(float), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Color), "g", typeof(float), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Color), "b", typeof(float), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireField(failures, typeof(Color), "a", typeof(float), BindingFlags.Instance | BindingFlags.Public);
			CompatibilityGate.RequireMethod(failures, typeof(Object), "op_Equality", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[2]
			{
				typeof(Object),
				typeof(Object)
			}, (MethodInfo method) => method.IsStatic);
			CompatibilityGate.RequireMethod(failures, typeof(Object), "op_Inequality", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[2]
			{
				typeof(Object),
				typeof(Object)
			}, (MethodInfo method) => method.IsStatic);
			CompatibilityGate.RequireConstructor(failures, typeof(Harmony), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(string) });
			CompatibilityGate.RequireConstructor(failures, typeof(HarmonyMethod), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[3]
			{
				typeof(Type),
				typeof(string),
				typeof(Type[])
			});
			CompatibilityGate.RequireMethod(failures, typeof(Harmony), "Patch", typeof(MethodInfo), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[6]
			{
				typeof(MethodBase),
				typeof(HarmonyMethod),
				typeof(HarmonyMethod),
				typeof(HarmonyMethod),
				typeof(HarmonyMethod),
				typeof(HarmonyMethod)
			}, (MethodInfo method) => !method.IsStatic);
			CompatibilityGate.RequireMethod(failures, typeof(Harmony), "UnpatchSelf", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic);
			CompatibilityGate.RequireMethod(failures, typeof(AccessTools), "DeclaredMethod", typeof(MethodInfo), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[4]
			{
				typeof(Type),
				typeof(string),
				typeof(Type[]),
				typeof(Type[])
			}, (MethodInfo method) => method.IsStatic);
			CompatibilityGate.RequireEnumValue(failures, typeof(PaintType), "Dirt", 0);
			CompatibilityGate.RequireEnumValue(failures, typeof(PaintType), "Cultivate", 1);
			CompatibilityGate.RequireEnumValue(failures, typeof(PaintType), "Paved", 2);
			CompatibilityGate.RequireColor(failures, "dirt paint", Heightmap.m_paintMaskDirt, 1f, 0f, 0f, 1f);
			CompatibilityGate.RequireColor(failures, "cultivated paint", Heightmap.m_paintMaskCultivated, 0f, 1f, 0f, 1f);
			CompatibilityGate.RequireColor(failures, "paved paint", Heightmap.m_paintMaskPaved, 0f, 0f, 1f, 1f);
		}

		protected override void InstallPatches()
		{
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Expected O, but got Unknown
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Expected O, but got Unknown
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Expected O, but got Unknown
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Expected O, but got Unknown
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Expected O, but got Unknown
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ab: Expected O, but got Unknown
			if (_activeModule != null && _activeModule != this)
			{
				throw new InvalidOperationException("Another road feature module is already active.");
			}
			GetLastGroundColliderMethod = AccessTools.DeclaredMethod(typeof(Character), "GetLastGroundCollider", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException(typeof(Character).FullName, "GetLastGroundCollider");
			UpdateAvailablePiecesListMethod = AccessTools.DeclaredMethod(typeof(Player), "UpdateAvailablePiecesList", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException(typeof(Player).FullName, "UpdateAvailablePiecesList");
			_activeModule = this;
			base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "SetPlaceMode", new Type[1] { typeof(PieceTable) }, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "PlayerSetPlaceModePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(PieceTable), "UpdateAvailable", new Type[4]
			{
				typeof(HashSet<string>),
				typeof(Player),
				typeof(bool),
				typeof(bool)
			}, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "PieceTableUpdateAvailablePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "HaveRequirements", new Type[2]
			{
				typeof(Piece),
				typeof(RequirementMode)
			}, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "PlayerHaveRequirementsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNetScene), "OnDestroy", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "ZNetSceneOnDestroyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "GetRunSpeedFactor", Type.EmptyTypes, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "GetRunSpeedFactorPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(SEMan), "ModifyRunStaminaDrain", new Type[4]
			{
				typeof(float),
				typeof(float).MakeByRefType(),
				typeof(Vector3),
				typeof(bool)
			}, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "ModifyRunStaminaDrainPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_pavedRoadWithoutStonecutter.SettingChanged += OnPavedRoadSettingChanged;
			_pavedSettingSubscribed = true;
			RefreshCurrentBuildPieces();
			RefreshPlayerAvailablePieces();
		}

		protected override void OnDisabled()
		{
			List<Exception> list = new List<Exception>();
			if (_pavedSettingSubscribed)
			{
				try
				{
					_pavedRoadWithoutStonecutter.SettingChanged -= OnPavedRoadSettingChanged;
					_pavedSettingSubscribed = false;
				}
				catch (Exception item)
				{
					list.Add(item);
				}
			}
			try
			{
				RestorePavedRoadStation();
			}
			catch (Exception item2)
			{
				list.Add(item2);
			}
			_lastPieceTable = null;
			if (_activeModule == this)
			{
				_activeModule = null;
			}
			_surfaceTracker.Reset();
			try
			{
				RefreshPlayerAvailablePieces();
			}
			catch (Exception ex)
			{
				try
				{
					base.Log.LogWarning((object)("Could not refresh vanilla build-piece availability during cleanup: " + ex));
				}
				catch
				{
				}
			}
			GetLastGroundColliderMethod = null;
			UpdateAvailablePiecesListMethod = null;
			if (list.Count != 0)
			{
				throw new AggregateException("Road feature cleanup was incomplete.", list);
			}
		}

		private static void PlayerSetPlaceModePrefix(PieceTable __0)
		{
			_activeModule?.RefreshPavedRoadStation(__0, reportMissingCandidate: true);
		}

		private static void PieceTableUpdateAvailablePrefix(PieceTable __instance)
		{
			_activeModule?.RefreshPavedRoadStation(__instance, reportMissingCandidate: false);
		}

		private static void PlayerHaveRequirementsPrefix(Piece __0)
		{
			_activeModule?.RefreshCurrentBuildPieces();
		}

		private static void ZNetSceneOnDestroyPrefix()
		{
			RoadFeatureModule activeModule = _activeModule;
			if (activeModule != null)
			{
				activeModule.RestorePavedRoadStation();
				activeModule._lastPieceTable = null;
			}
		}

		private void OnPavedRoadSettingChanged(object sender, EventArgs eventArgs)
		{
			if (_activeModule != this)
			{
				return;
			}
			try
			{
				if (_pavedRoadWithoutStonecutter.Value)
				{
					RefreshCurrentBuildPieces();
				}
				else
				{
					RestorePavedRoadStation();
				}
				RefreshPlayerAvailablePieces();
			}
			catch (Exception ex)
			{
				RestorePavedRoadStation();
				base.Log.LogError((object)("Paved Road recipe update failed; the vanilla station requirement was restored: " + ex));
			}
		}

		private void RefreshCurrentBuildPieces()
		{
			if (!_pavedRoadWithoutStonecutter.Value)
			{
				RestorePavedRoadStation();
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			PieceTable val = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetBuildTool() : null);
			if ((Object)(object)val != (Object)null)
			{
				RefreshPavedRoadStation(val, reportMissingCandidate: true);
			}
			else if ((Object)(object)_lastPieceTable != (Object)null)
			{
				RefreshPavedRoadStation(_lastPieceTable, reportMissingCandidate: false);
			}
		}

		private static void RefreshPlayerAvailablePieces()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				UpdateAvailablePiecesListMethod.Invoke(localPlayer, null);
			}
		}

		private void RefreshPavedRoadStation(PieceTable table, bool reportMissingCandidate)
		{
			if ((Object)(object)table == (Object)null)
			{
				return;
			}
			if (!_pavedRoadWithoutStonecutter.Value)
			{
				RestorePavedRoadStation();
				return;
			}
			List<PieceTableEntryInspection> list = InspectPieceTable(table);
			List<PavedRoadCandidateShape> list2 = new List<PavedRoadCandidateShape>(list.Count);
			foreach (PieceTableEntryInspection item in list)
			{
				list2.Add(item.Shape);
			}
			PavedRoadCandidateSelection pavedRoadCandidateSelection = PavedRoadCandidateSelector.Select(list2);
			if (pavedRoadCandidateSelection.Outcome != PavedRoadDiscoveryOutcome.Unique)
			{
				RestorePavedRoadStation();
				if (_lastPieceTable == table)
				{
					_lastPieceTable = null;
				}
				if (reportMissingCandidate && ShouldReportDiscoveryFailure(table, list))
				{
					LogDiscoveryFailureOnce(table, pavedRoadCandidateSelection, list);
				}
			}
			else
			{
				PieceTableEntryInspection pieceTableEntryInspection = list[pavedRoadCandidateSelection.CandidateIndex];
				_lastPieceTable = table;
				ApplyPavedRoadStationOverride(pieceTableEntryInspection.Piece, pieceTableEntryInspection.Describe());
			}
		}

		private void ApplyPavedRoadStationOverride(Piece piece, string candidateDescription)
		{
			if (!_pavedRoadWithoutStonecutter.Value || (Object)(object)piece == (Object)null)
			{
				return;
			}
			switch (_stationOverride.Apply(piece))
			{
			case StationOverrideApplyResult.Removed:
				if (!_loggedStationRemoved)
				{
					_loggedStationRemoved = true;
					base.Log.LogInfo((object)("Semantic Paved Road candidate found " + candidateDescription + "; station field removed, so no nearby stonecutter is required."));
				}
				break;
			case StationOverrideApplyResult.AlreadyAbsent:
				if (!_loggedStationAlreadyAbsent)
				{
					_loggedStationAlreadyAbsent = true;
					base.Log.LogInfo((object)("Semantic Paved Road candidate found " + candidateDescription + "; its station field was already absent, so no change was needed."));
				}
				break;
			case StationOverrideApplyResult.Conflict:
				if (!_loggedStationConflict)
				{
					_loggedStationConflict = true;
					base.Log.LogWarning((object)"Paved Road station field changed while Treadwell was active; the conflicting value was left untouched.");
				}
				break;
			case StationOverrideApplyResult.InvalidPiece:
				throw new InvalidOperationException("Semantic Paved Road discovery returned an invalid piece.");
			}
		}

		private List<PieceTableEntryInspection> InspectPieceTable(PieceTable table)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Invalid comparison between Unknown and I4
			List<PieceTableEntryInspection> list = new List<PieceTableEntryInspection>();
			if (table.m_pieces == null)
			{
				return list;
			}
			foreach (GameObject piece in table.m_pieces)
			{
				if ((Object)(object)piece == (Object)null)
				{
					continue;
				}
				Piece component = piece.GetComponent<Piece>();
				Piece[] componentsInChildren = piece.GetComponentsInChildren<Piece>(true);
				TerrainModifier[] componentsInChildren2 = piece.GetComponentsInChildren<TerrainModifier>(true);
				Piece val = ((componentsInChildren.Length == 1 && componentsInChildren[0] == component) ? component : null);
				int num = 0;
				TerrainModifier[] array = componentsInChildren2;
				foreach (TerrainModifier val2 in array)
				{
					if ((Object)(object)val2 != (Object)null && (int)val2.m_paintType == 2)
					{
						num++;
					}
				}
				Requirement[] array2 = (((Object)(object)val != (Object)null) ? val.m_resources : null);
				int resourceRequirementCount = ((array2 != null) ? array2.Length : 0);
				int num2 = 0;
				int num3 = 0;
				if (array2 != null)
				{
					Requirement[] array3 = array2;
					foreach (Requirement val3 in array3)
					{
						if (val3 != null && !((Object)(object)val3.m_resItem == (Object)null) && val3.m_amount == 1)
						{
							num2++;
							if (IsExpectedStoneResource(val3.m_resItem))
							{
								num3++;
							}
						}
					}
				}
				PavedRoadCandidateShape shape = new PavedRoadCandidateShape(componentsInChildren.Length, (Object)(object)val != (Object)null, componentsInChildren2.Length, num, (Object)(object)val != (Object)null && ((Object)(object)val.m_craftingStation != (Object)null || _stationOverride.IsAppliedTo(val)), resourceRequirementCount, num2, num3);
				list.Add(new PieceTableEntryInspection(piece, componentsInChildren, componentsInChildren2, shape));
			}
			return list;
		}

		private static bool IsExpectedStoneResource(ItemDrop resource)
		{
			if ((Object)(object)resource == (Object)null || (Object)(object)((Component)resource).gameObject == (Object)null)
			{
				return false;
			}
			string name = ((Object)((Component)resource).gameObject).name;
			if (!string.Equals(name, "Stone", StringComparison.Ordinal))
			{
				return string.Equals(name, "Stone(Clone)", StringComparison.Ordinal);
			}
			return true;
		}

		private static bool ShouldReportDiscoveryFailure(PieceTable table, List<PieceTableEntryInspection> entries)
		{
			if (IsLikelyHoePieceTable(table))
			{
				return true;
			}
			foreach (PieceTableEntryInspection entry in entries)
			{
				if (entry.Shape.PavedTerrainModifierCount > 0)
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsLikelyHoePieceTable(PieceTable table)
		{
			string text = (((Object)(object)table != (Object)null && (Object)(object)((Component)table).gameObject != (Object)null) ? ((Object)((Component)table).gameObject).name : null);
			if (text != null)
			{
				return text.IndexOf("hoe", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private void LogDiscoveryFailureOnce(PieceTable table, PavedRoadCandidateSelection selection, List<PieceTableEntryInspection> entries)
		{
			if (_loggedDiscoveryFailure)
			{
				return;
			}
			_loggedDiscoveryFailure = true;
			string text = ((selection.Outcome == PavedRoadDiscoveryOutcome.Ambiguous) ? ("ambiguous (" + selection.CandidateCount + " semantic matches)") : "no semantic match");
			string text2 = (((Object)(object)table != (Object)null && (Object)(object)((Component)table).gameObject != (Object)null) ? SanitizeDiagnostic(((Object)((Component)table).gameObject).name) : "<unnamed>");
			StringBuilder stringBuilder = new StringBuilder();
			int num = Math.Min(entries.Count, 12);
			for (int i = 0; i < num; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append("; ");
				}
				stringBuilder.Append(entries[i].Describe());
			}
			if (entries.Count > num)
			{
				stringBuilder.Append("; +").Append(entries.Count - num).Append(" more");
			}
			base.Log.LogWarning((object)("Paved Road semantic discovery was " + text + " in active table '" + text2 + "'; vanilla station requirements remain unchanged. Candidate shapes: " + stringBuilder));
		}

		private static string SanitizeDiagnostic(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "<none>";
			}
			string text = value.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');
			if (text.Length > 64)
			{
				return text.Substring(0, 64) + "...";
			}
			return text;
		}

		private void RestorePavedRoadStation()
		{
			if (!_stationOverride.IsApplied)
			{
				return;
			}
			try
			{
				if (_stationOverride.Restore() == StationOverrideRestoreResult.Conflict)
				{
					base.Log.LogWarning((object)"Did not restore the Paved Road station because another runtime change replaced it.");
				}
			}
			catch (MissingReferenceException)
			{
				_stationOverride.Forget();
			}
		}

		private static void GetRunSpeedFactorPostfix(Player __instance, ref float __result)
		{
			RoadFeatureModule activeModule = _activeModule;
			if (activeModule != null && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && ((Character)__instance).IsRunning())
			{
				__result *= activeModule.CurrentTuning().SpeedMultiplier(activeModule.ResolveSurface(__instance));
			}
		}

		private static void ModifyRunStaminaDrainPostfix(Character ___m_character, ref float drain)
		{
			RoadFeatureModule activeModule = _activeModule;
			Player val = (Player)(object)((___m_character is Player) ? ___m_character : null);
			if (activeModule != null && !((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer))
			{
				drain *= activeModule.CurrentTuning().StaminaMultiplier(activeModule.ResolveSurface(val));
			}
		}

		private RoadTuning CurrentTuning()
		{
			return new RoadTuning(_dirtSpeed.Value, _dirtStamina.Value, _pavedSpeed.Value, _pavedStamina.Value);
		}

		private RoadSurface ResolveSurface(Player player)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: 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_0093: Unknown result type (might be due to invalid IL or missing references)
			double nowSeconds = Time.unscaledTime;
			if (!((Character)player).IsOnGround())
			{
				return _surfaceTracker.Observe(TerrainSurface.NonTerrain, nowSeconds);
			}
			object? obj = GetLastGroundColliderMethod?.Invoke(player, null);
			Component val = (Component)((obj is Component) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				return _surfaceTracker.Observe(TerrainSurface.NonTerrain, nowSeconds);
			}
			Heightmap val2 = val.GetComponent<Heightmap>() ?? val.GetComponentInParent<Heightmap>();
			if ((Object)(object)val2 == (Object)null)
			{
				return _surfaceTracker.Observe(TerrainSurface.NonTerrain, nowSeconds);
			}
			Color paintMask = val2.GetPaintMask(((Component)player).transform.position);
			TerrainSurface observed = TerrainClassifier.Classify(paintMask.r, paintMask.g, paintMask.b);
			return _surfaceTracker.Observe(observed, nowSeconds);
		}
	}
	[BepInPlugin("com.jstack424.treadwell", "Treadwell", "0.1.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.jstack424.treadwell";

		public const string PluginName = "Treadwell";

		public const string PluginVersion = "0.1.2";

		private FeatureHost _features;

		private void Awake()
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 100f);
			ConfigEntry<bool> enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enable mod", true, "Master switch for all Treadwell features.");
			ConfigEntry<bool> pavedRoadWithoutStonecutter = ((BaseUnityPlugin)this).Config.Bind<bool>("Road building", "Paved roads without stonecutter", true, "Allow the vanilla paved-road terrain piece to be placed without a nearby stonecutter. Stone cost and every other placement rule remain unchanged.");
			ConfigEntry<float> dirtSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Dirt sprint speed bonus (%)", 10f, new ConfigDescription("Extra sprint speed on vanilla dirt paths.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			ConfigEntry<float> dirtStamina = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Dirt sprint stamina reduction (%)", 10f, new ConfigDescription("Reduction to sprint stamina drain on vanilla dirt paths.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			ConfigEntry<float> pavedSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Paved sprint speed bonus (%)", 20f, new ConfigDescription("Extra sprint speed on vanilla paved roads.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			ConfigEntry<float> pavedStamina = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Paved sprint stamina reduction (%)", 20f, new ConfigDescription("Reduction to sprint stamina drain on vanilla paved roads.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Treadwell 0.1.2 (8583a261d57d056dff56c24aee6a3d8fad92ebfd) loading.");
			_features = new FeatureHost(new IFeatureModule[1]
			{
				new RoadFeatureModule(enabled, pavedRoadWithoutStonecutter, dirtSpeed, dirtStamina, pavedSpeed, pavedStamina, ((BaseUnityPlugin)this).Logger)
			}, ((BaseUnityPlugin)this).Logger);
			CompatibilityResult compatibilityResult = CompatibilityGate.Evaluate(_features);
			if (!compatibilityResult.IsCompatible)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Compatibility gate failed. Treadwell is fully disabled before any gameplay hooks were installed: " + compatibilityResult.Reason));
				_features.Dispose();
				_features = null;
				return;
			}
			try
			{
				_features.Start();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Compatibility gate passed: " + compatibilityResult.Reason + "; configured feature modules are enabled."));
			}
			catch (Exception ex)
			{
				_features.Stop();
				((BaseUnityPlugin)this).Logger.LogError((object)("Treadwell disabled because feature installation failed: " + ex));
			}
		}

		private void OnDisable()
		{
			_features?.Stop();
		}

		private void OnDestroy()
		{
			_features?.Dispose();
			_features = null;
		}
	}
	internal static class GeneratedBuildInfo
	{
		public const string Version = "0.1.2";

		public const string Commit = "8583a261d57d056dff56c24aee6a3d8fad92ebfd";
	}
}
namespace Treadwell.Core
{
	public enum PavedRoadDiscoveryOutcome
	{
		None,
		Unique,
		Ambiguous
	}
	public sealed class PavedRoadCandidateShape
	{
		public int PieceComponentCount { get; }

		public bool HasRootPiece { get; }

		public int TerrainModifierCount { get; }

		public int PavedTerrainModifierCount { get; }

		public bool HasStationRequirement { get; }

		public int ResourceRequirementCount { get; }

		public int SingleUnitResourceRequirementCount { get; }

		public int SingleUnitStoneResourceRequirementCount { get; }

		public bool IsSemanticCandidate
		{
			get
			{
				if (PieceComponentCount == 1 && HasRootPiece && PavedTerrainModifierCount == 1 && HasStationRequirement && ResourceRequirementCount == 1 && SingleUnitResourceRequirementCount == 1)
				{
					return SingleUnitStoneResourceRequirementCount == 1;
				}
				return false;
			}
		}

		public PavedRoadCandidateShape(int pieceComponentCount, bool hasRootPiece, int terrainModifierCount, int pavedTerrainModifierCount, bool hasStationRequirement, int resourceRequirementCount, int singleUnitResourceRequirementCount, int singleUnitStoneResourceRequirementCount)
		{
			PieceComponentCount = pieceComponentCount;
			HasRootPiece = hasRootPiece;
			TerrainModifierCount = terrainModifierCount;
			PavedTerrainModifierCount = pavedTerrainModifierCount;
			HasStationRequirement = hasStationRequirement;
			ResourceRequirementCount = resourceRequirementCount;
			SingleUnitResourceRequirementCount = singleUnitResourceRequirementCount;
			SingleUnitStoneResourceRequirementCount = singleUnitStoneResourceRequirementCount;
		}
	}
	public sealed class PavedRoadCandidateSelection
	{
		public PavedRoadDiscoveryOutcome Outcome { get; }

		public int CandidateIndex { get; }

		public int CandidateCount { get; }

		internal PavedRoadCandidateSelection(PavedRoadDiscoveryOutcome outcome, int candidateIndex, int candidateCount)
		{
			Outcome = outcome;
			CandidateIndex = candidateIndex;
			CandidateCount = candidateCount;
		}
	}
	public static class PavedRoadCandidateSelector
	{
		public static PavedRoadCandidateSelection Select(IReadOnlyList<PavedRoadCandidateShape> shapes)
		{
			if (shapes == null)
			{
				throw new ArgumentNullException("shapes");
			}
			int candidateIndex = -1;
			int num = 0;
			for (int i = 0; i < shapes.Count; i++)
			{
				PavedRoadCandidateShape pavedRoadCandidateShape = shapes[i];
				if (pavedRoadCandidateShape != null && pavedRoadCandidateShape.IsSemanticCandidate)
				{
					candidateIndex = i;
					num++;
				}
			}
			return num switch
			{
				0 => new PavedRoadCandidateSelection(PavedRoadDiscoveryOutcome.None, -1, 0), 
				1 => new PavedRoadCandidateSelection(PavedRoadDiscoveryOutcome.Unique, candidateIndex, 1), 
				_ => new PavedRoadCandidateSelection(PavedRoadDiscoveryOutcome.Ambiguous, -1, num), 
			};
		}
	}
	public enum StationOverrideApplyResult
	{
		InvalidPiece,
		Removed,
		AlreadyAbsent,
		Conflict
	}
	public enum StationOverrideRestoreResult
	{
		NothingToRestore,
		Restored,
		AlreadyRestored,
		Conflict
	}
	public sealed class PavedRoadStationOverride<TPiece, TStation> where TPiece : class where TStation : class
	{
		private readonly Func<TPiece, TStation?> _getStation;

		private readonly Action<TPiece, TStation?> _setStation;

		private TPiece? _piece;

		private TStation? _originalStation;

		public bool IsApplied => _piece != null;

		public PavedRoadStationOverride(Func<TPiece, TStation?> getStation, Action<TPiece, TStation?> setStation)
		{
			_getStation = getStation ?? throw new ArgumentNullException("getStation");
			_setStation = setStation ?? throw new ArgumentNullException("setStation");
		}

		public bool IsAppliedTo(TPiece piece)
		{
			if (piece != null)
			{
				return _piece == piece;
			}
			return false;
		}

		public StationOverrideApplyResult Apply(TPiece piece)
		{
			if (piece == null)
			{
				return StationOverrideApplyResult.InvalidPiece;
			}
			TStation val = _getStation(piece);
			if (_piece == piece)
			{
				if (val == null)
				{
					return StationOverrideApplyResult.AlreadyAbsent;
				}
				if (val != _originalStation)
				{
					return StationOverrideApplyResult.Conflict;
				}
				_setStation(piece, null);
				return StationOverrideApplyResult.Removed;
			}
			if (val == null)
			{
				return StationOverrideApplyResult.AlreadyAbsent;
			}
			if (_piece != null)
			{
				Restore();
			}
			_piece = piece;
			_originalStation = val;
			_setStation(piece, null);
			return StationOverrideApplyResult.Removed;
		}

		public StationOverrideRestoreResult Restore()
		{
			TPiece piece = _piece;
			TStation originalStation = _originalStation;
			if (piece == null)
			{
				return StationOverrideRestoreResult.NothingToRestore;
			}
			TStation val = _getStation(piece);
			if (val != null)
			{
				int result = ((val == originalStation) ? 2 : 3);
				Forget();
				return (StationOverrideRestoreResult)result;
			}
			if (originalStation == null)
			{
				Forget();
				return StationOverrideRestoreResult.Conflict;
			}
			_setStation(piece, originalStation);
			Forget();
			return StationOverrideRestoreResult.Restored;
		}

		public void Forget()
		{
			_piece = null;
			_originalStation = null;
		}
	}
	public enum TerrainSurface
	{
		Natural,
		DirtPath,
		Cultivated,
		PavedRoad,
		NonTerrain
	}
	public enum RoadSurface
	{
		None,
		Dirt,
		Paved
	}
	public static class TerrainClassifier
	{
		public const float MinimumPaintStrength = 0.1f;

		public const float DominanceMargin = 0.02f;

		public static TerrainSurface Classify(float red, float green, float blue)
		{
			if (!IsFinite(red) || !IsFinite(green) || !IsFinite(blue))
			{
				return TerrainSurface.Natural;
			}
			if (green >= 0.1f && green >= red - 0.02f && green >= blue - 0.02f)
			{
				return TerrainSurface.Cultivated;
			}
			if (blue >= 0.1f && blue > red + 0.02f && blue > green + 0.02f)
			{
				return TerrainSurface.PavedRoad;
			}
			if (red >= 0.1f && red > green + 0.02f && red > blue + 0.02f)
			{
				return TerrainSurface.DirtPath;
			}
			return TerrainSurface.Natural;
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public sealed class RoadTuning
	{
		public float DirtSpeedPercent { get; }

		public float DirtStaminaReductionPercent { get; }

		public float PavedSpeedPercent { get; }

		public float PavedStaminaReductionPercent { get; }

		public RoadTuning(float dirtSpeedPercent, float dirtStaminaReductionPercent, float pavedSpeedPercent, float pavedStaminaReductionPercent)
		{
			DirtSpeedPercent = ClampPercent(dirtSpeedPercent);
			DirtStaminaReductionPercent = ClampPercent(dirtStaminaReductionPercent);
			PavedSpeedPercent = ClampPercent(pavedSpeedPercent);
			PavedStaminaReductionPercent = ClampPercent(pavedStaminaReductionPercent);
		}

		public float SpeedMultiplier(RoadSurface surface)
		{
			return surface switch
			{
				RoadSurface.Dirt => 1f + DirtSpeedPercent / 100f, 
				RoadSurface.Paved => 1f + PavedSpeedPercent / 100f, 
				_ => 1f, 
			};
		}

		public float StaminaMultiplier(RoadSurface surface)
		{
			return surface switch
			{
				RoadSurface.Dirt => 1f - DirtStaminaReductionPercent / 100f, 
				RoadSurface.Paved => 1f - PavedStaminaReductionPercent / 100f, 
				_ => 1f, 
			};
		}

		public static float ClampPercent(float value)
		{
			if (float.IsNaN(value) || float.IsNegativeInfinity(value))
			{
				return 0f;
			}
			if (float.IsPositiveInfinity(value))
			{
				return 100f;
			}
			if (value < 0f)
			{
				return 0f;
			}
			if (!(value > 100f))
			{
				return value;
			}
			return 100f;
		}
	}
	public sealed class RoadSurfaceTracker
	{
		private readonly double _naturalGapHoldSeconds;

		private RoadSurface _active;

		private double _lastRoadTime = double.NegativeInfinity;

		private double _lastObservationTime = double.NegativeInfinity;

		public RoadSurfaceTracker(double naturalGapHoldSeconds)
		{
			if (double.IsNaN(naturalGapHoldSeconds) || double.IsInfinity(naturalGapHoldSeconds) || naturalGapHoldSeconds < 0.0)
			{
				throw new ArgumentOutOfRangeException("naturalGapHoldSeconds");
			}
			_naturalGapHoldSeconds = naturalGapHoldSeconds;
		}

		public RoadSurface Observe(TerrainSurface observed, double nowSeconds)
		{
			if (double.IsNaN(nowSeconds) || double.IsInfinity(nowSeconds) || nowSeconds < _lastObservationTime)
			{
				Reset();
				return RoadSurface.None;
			}
			_lastObservationTime = nowSeconds;
			switch (observed)
			{
			case TerrainSurface.DirtPath:
			case TerrainSurface.PavedRoad:
				_active = ((observed == TerrainSurface.DirtPath) ? RoadSurface.Dirt : RoadSurface.Paved);
				_lastRoadTime = nowSeconds;
				return _active;
			case TerrainSurface.Cultivated:
			case TerrainSurface.NonTerrain:
				ResetAt(nowSeconds);
				return RoadSurface.None;
			default:
				if (_active != RoadSurface.None && nowSeconds - _lastRoadTime <= _naturalGapHoldSeconds)
				{
					return _active;
				}
				ResetAt(nowSeconds);
				return RoadSurface.None;
			}
		}

		public void Reset()
		{
			ResetAt(double.NegativeInfinity);
		}

		private void ResetAt(double observationTime)
		{
			_active = RoadSurface.None;
			_lastRoadTime = double.NegativeInfinity;
			_lastObservationTime = observationTime;
		}
	}
	public static class ExactRuntimeContract
	{
		public static MethodInfo[] FindMethods(Type type, string name, BindingFlags flags, Type returnType, IReadOnlyList<Type> parameterTypes, Func<MethodInfo, bool>? additionalCheck = null)
		{
			return (from method in type.GetMethods(flags)
				where string.Equals(method.Name, name, StringComparison.Ordinal)
				where method.ReturnType == returnType
				where ParametersEqual(method.GetParameters(), parameterTypes)
				where additionalCheck == null || additionalCheck(method)
				select method).ToArray();
		}

		public static ConstructorInfo[] FindConstructors(Type type, BindingFlags flags, IReadOnlyList<Type> parameterTypes)
		{
			return (from constructor in type.GetConstructors(flags)
				where ParametersEqual(constructor.GetParameters(), parameterTypes)
				select constructor).ToArray();
		}

		public static FieldInfo[] FindFields(Type type, string name, BindingFlags flags, Type fieldType)
		{
			return (from field in type.GetFields(flags)
				where string.Equals(field.Name, name, StringComparison.Ordinal)
				where field.FieldType == fieldType
				select field).ToArray();
		}

		public static PropertyInfo[] FindProperties(Type type, string name, BindingFlags flags, Type propertyType)
		{
			return (from property in type.GetProperties(flags)
				where string.Equals(property.Name, name, StringComparison.Ordinal)
				where property.PropertyType == propertyType
				select property).ToArray();
		}

		private static bool ParametersEqual(ParameterInfo[] observed, IReadOnlyList<Type> expected)
		{
			if (observed.Length != expected.Count)
			{
				return false;
			}
			for (int i = 0; i < observed.Length; i++)
			{
				if (observed[i].ParameterType != expected[i])
				{
					return false;
				}
			}
			return true;
		}
	}
	public static class TransactionalInstall
	{
		public static void Run(Action install, params Action[] rollbackSteps)
		{
			if (install == null)
			{
				throw new ArgumentNullException("install");
			}
			if (rollbackSteps == null)
			{
				throw new ArgumentNullException("rollbackSteps");
			}
			try
			{
				install();
			}
			catch (Exception item)
			{
				List<Exception> list = new List<Exception> { item };
				foreach (Action action in rollbackSteps)
				{
					if (action != null)
					{
						try
						{
							action();
						}
						catch (Exception item2)
						{
							list.Add(item2);
						}
					}
				}
				if (list.Count > 1)
				{
					throw new AggregateException("Installation failed and rollback was incomplete.", list);
				}
				throw;
			}
		}
	}
}