Decompiled source of Groundwork v1.1.13

Groundwork.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Timers;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using LocalizationManager;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore;
using UnityEngine.UI;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Groundwork")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("Groundwork")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("8B54C3C1-8CA4-4B7E-B15B-4747326F4F4C")]
[assembly: AssemblyFileVersion("1.1.13")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.13.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
	{
		object IEnumerator.Current => _item;

		T IEnumerator<T>.Current => _item;

		public Enumerator(T item)
		{
			_item = item;
		}

		bool IEnumerator.MoveNext()
		{
			if (!_moveNextCalled)
			{
				return _moveNextCalled = true;
			}
			return false;
		}

		void IEnumerator.Reset()
		{
			_moveNextCalled = false;
		}

		void IDisposable.Dispose()
		{
		}
	}

	int ICollection.Count => 1;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object IList.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => 1;

	T IReadOnlyList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
	}

	int ICollection<T>.Count => 1;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlySingleElementList(T item)
	{
		_item = item;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection.CopyTo(Array array, int index)
	{
		array.SetValue(_item, index);
	}

	int IList.Add(object value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object value)
	{
		return EqualityComparer<T>.Default.Equals(_item, (T)value);
	}

	int IList.IndexOf(object value)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, (T)value))
		{
			return -1;
		}
		return 0;
	}

	void IList.Insert(int index, object value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return EqualityComparer<T>.Default.Equals(_item, item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		array[arrayIndex] = _item;
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, item))
		{
			return -1;
		}
		return 0;
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
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 LocalizationManager
{
	public class Localizer
	{
		private sealed class LanguageState(string language)
		{
			internal readonly string Language = language;
		}

		private static readonly FieldRef<Localization, Dictionary<string, string>> Translations = AccessTools.FieldRefAccess<Localization, Dictionary<string, string>>("m_translations");

		private static readonly Action<Localization, string, string> AddWord = AccessTools.MethodDelegate<Action<Localization, string, string>>(AccessTools.DeclaredMethod(typeof(Localization), "AddWord", new Type[2]
		{
			typeof(string),
			typeof(string)
		}, (Type[])null), (object)null, true);

		private static readonly Dictionary<string, Dictionary<string, Func<string>>> PlaceholderProcessors = new Dictionary<string, Dictionary<string, Func<string>>>();

		private static readonly Dictionary<string, Dictionary<string, string>> LoadedTexts = new Dictionary<string, Dictionary<string, string>>();

		private static ConditionalWeakTable<Localization, LanguageState> LocalizationLanguages = new ConditionalWeakTable<Localization, LanguageState>();

		private static readonly List<WeakReference<Localization>> LocalizationObjects = new List<WeakReference<Localization>>();

		private static readonly List<string> FileExtensions = new List<string>(2) { ".json", ".yml" };

		private static BaseUnityPlugin? _plugin;

		private static Harmony? _harmony;

		private static bool _patched;

		private static BaseUnityPlugin Plugin
		{
			get
			{
				//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ab: Expected O, but got Unknown
				if (_plugin != null)
				{
					return _plugin;
				}
				IEnumerable<TypeInfo> source;
				try
				{
					source = Assembly.GetExecutingAssembly().DefinedTypes.ToList();
				}
				catch (ReflectionTypeLoadException ex)
				{
					source = from t in ex.Types
						where t != null
						select t.GetTypeInfo();
				}
				TypeInfo typeInfo = source.First((TypeInfo t) => t.IsClass && typeof(BaseUnityPlugin).IsAssignableFrom(t));
				_plugin = (BaseUnityPlugin)Chainloader.ManagerObject.GetComponent((Type)typeInfo);
				return _plugin;
			}
		}

		public static event Action? OnLocalizationComplete;

		public static void Load()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Expected O, but got Unknown
			BaseUnityPlugin plugin = Plugin;
			if (_patched)
			{
				return;
			}
			Harmony val = new Harmony(plugin.Info.Metadata.GUID + ".LocalizationManager");
			try
			{
				val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "LoadLocalization", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "SetupGui", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "LoadLocalizationLater", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.DeclaredMethod(typeof(Localizer), "SafeCallLocalizeComplete", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_harmony = val;
				_patched = true;
			}
			catch
			{
				val.UnpatchSelf();
				throw;
			}
		}

		public static void Unload()
		{
			try
			{
				if (_patched)
				{
					Harmony? harmony = _harmony;
					if (harmony != null)
					{
						harmony.UnpatchSelf();
					}
				}
			}
			finally
			{
				_patched = false;
				_harmony = null;
				PlaceholderProcessors.Clear();
				LoadedTexts.Clear();
				LocalizationLanguages = new ConditionalWeakTable<Localization, LanguageState>();
				LocalizationObjects.Clear();
				Localizer.OnLocalizationComplete = null;
				_plugin = null;
			}
		}

		public static void AddPlaceholder<T>(string key, string placeholder, ConfigEntry<T> config, Func<T, string>? convertConfigValue = null) where T : notnull
		{
			if (convertConfigValue == null)
			{
				convertConfigValue = (T value) => value.ToString();
			}
			if (!PlaceholderProcessors.ContainsKey(key))
			{
				PlaceholderProcessors[key] = new Dictionary<string, Func<string>>();
			}
			config.SettingChanged += delegate
			{
				UpdatePlaceholder();
			};
			if (Localization.instance != null && LoadedTexts.ContainsKey(Localization.instance.GetSelectedLanguage()))
			{
				UpdatePlaceholder();
			}
			void UpdatePlaceholder()
			{
				PlaceholderProcessors[key][placeholder] = () => convertConfigValue(config.Value);
				if (Localization.instance != null)
				{
					UpdatePlaceholderText(Localization.instance, key);
				}
			}
		}

		public static void AddText(string key, string text)
		{
			List<WeakReference<Localization>> list = new List<WeakReference<Localization>>();
			foreach (WeakReference<Localization> localizationObject in LocalizationObjects)
			{
				LanguageState value;
				Dictionary<string, string> value2;
				if (!localizationObject.TryGetTarget(out var target))
				{
					list.Add(localizationObject);
				}
				else if (LocalizationLanguages.TryGetValue(target, out value) && LoadedTexts.TryGetValue(value.language, out value2) && !Translations.Invoke(target).ContainsKey(key))
				{
					value2[key] = text;
					AddWord(target, key, text);
				}
			}
			foreach (WeakReference<Localization> item in list)
			{
				LocalizationObjects.Remove(item);
			}
		}

		public static void LoadLocalizationLater()
		{
			if (Localization.instance != null)
			{
				LoadLocalization(Localization.instance, Localization.instance.GetSelectedLanguage());
			}
		}

		public static void SafeCallLocalizeComplete()
		{
			Localizer.OnLocalizationComplete?.Invoke();
		}

		private static void LoadLocalization(Localization __instance, string language)
		{
			if (!LocalizationLanguages.Remove(__instance))
			{
				LocalizationObjects.Add(new WeakReference<Localization>(__instance));
			}
			LocalizationLanguages.Add(__instance, new LanguageState(language));
			Dictionary<string, string> dictionary = FindExternalLocalizationFiles();
			byte[] array = LoadTranslationFromAssembly("English");
			if (array == null)
			{
				throw new Exception("Found no English localizations in mod " + Plugin.Info.Metadata.Name + ". Expected an embedded resource translations/English.json or translations/English.yml.");
			}
			Dictionary<string, string> dictionary2 = Deserialize(Encoding.UTF8.GetString(array));
			if (dictionary2 == null)
			{
				throw new Exception("Localization for mod " + Plugin.Info.Metadata.Name + " failed: Localization file was empty.");
			}
			string text = null;
			if (language != "English")
			{
				if (dictionary.TryGetValue(language, out var value))
				{
					text = File.ReadAllText(value);
				}
				else
				{
					byte[] array2 = LoadTranslationFromAssembly(language);
					if (array2 != null)
					{
						text = Encoding.UTF8.GetString(array2);
					}
				}
			}
			if (text == null && dictionary.TryGetValue("English", out var value2))
			{
				text = File.ReadAllText(value2);
			}
			if (text != null)
			{
				foreach (KeyValuePair<string, string> item in Deserialize(text) ?? new Dictionary<string, string>())
				{
					dictionary2[item.Key] = item.Value;
				}
			}
			LoadedTexts[language] = dictionary2;
			foreach (string key in dictionary2.Keys)
			{
				UpdatePlaceholderText(__instance, key);
			}
		}

		private static Dictionary<string, string> FindExternalLocalizationFiles()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			foreach (string item in from file in Directory.GetFiles(Path.GetDirectoryName(Paths.PluginPath) ?? Paths.PluginPath, Plugin.Info.Metadata.Name + ".*", SearchOption.AllDirectories)
				where FileExtensions.Contains(Path.GetExtension(file))
				select file)
			{
				string[] array = Path.GetFileNameWithoutExtension(item).Split('.');
				if (array.Length >= 2)
				{
					string text = array[1];
					if (dictionary.ContainsKey(text))
					{
						Debug.LogWarning((object)("Duplicate key " + text + " found for " + Plugin.Info.Metadata.Name + ". The duplicate file found at " + item + " will be skipped."));
					}
					else
					{
						dictionary[text] = item;
					}
				}
			}
			return dictionary;
		}

		private static void UpdatePlaceholderText(Localization localization, string key)
		{
			if (!LocalizationLanguages.TryGetValue(localization, out LanguageState value) || !LoadedTexts.TryGetValue(value.language, out Dictionary<string, string> value2) || !value2.TryGetValue(key, out var value3))
			{
				return;
			}
			if (PlaceholderProcessors.TryGetValue(key, out Dictionary<string, Func<string>> value4))
			{
				value3 = value4.Aggregate(value3, (string current, KeyValuePair<string, Func<string>> entry) => current.Replace("{" + entry.Key + "}", entry.Value()));
			}
			AddWord(localization, key, value3);
		}

		private static byte[]? LoadTranslationFromAssembly(string language)
		{
			foreach (string fileExtension in FileExtensions)
			{
				byte[] array = ReadEmbeddedFileBytes("translations." + language + fileExtension);
				if (array != null)
				{
					return array;
				}
			}
			return null;
		}

		public static byte[]? ReadEmbeddedFileBytes(string resourceFileName, Assembly? containingAssembly = null)
		{
			using MemoryStream memoryStream = new MemoryStream();
			if ((object)containingAssembly == null)
			{
				containingAssembly = Assembly.GetExecutingAssembly();
			}
			string text = containingAssembly.GetManifestResourceNames().FirstOrDefault((string name) => name.EndsWith(resourceFileName, StringComparison.Ordinal));
			if (text != null)
			{
				containingAssembly.GetManifestResourceStream(text)?.CopyTo(memoryStream);
			}
			return (memoryStream.Length == 0L) ? null : memoryStream.ToArray();
		}

		private static Dictionary<string, string>? Deserialize(string data)
		{
			return new DeserializerBuilder().IgnoreFields().Build().Deserialize<Dictionary<string, string>>(data);
		}
	}
	public static class LocalizationManagerVersion
	{
		public const string Version = "1.4.0";
	}
}
namespace Groundwork
{
	[BepInPlugin("sighsorry.Groundwork", "Groundwork", "1.1.13")]
	[BepInIncompatibility("advize.PlantEverything")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class GroundworkPlugin : BaseUnityPlugin
	{
		public enum Toggle
		{
			On = 1,
			Off = 0
		}

		public enum HoverHintMode
		{
			Off,
			Compact,
			Detailed
		}

		public enum FarmingRangeHarvestTargetMode
		{
			ForagingOnly,
			ForagingAndCrops
		}

		public enum TerrainToolRangePreviewMode
		{
			Vanilla,
			Grid
		}

		private enum YamlAuthorityMode
		{
			LocalFiles,
			SyncedOnly
		}

		internal sealed class PluginSettings
		{
			internal GeneralSettings General { get; } = new GeneralSettings();

			internal TerrainToolSettings TerrainTools { get; } = new TerrainToolSettings();

			internal FarmingSettings Farming { get; } = new FarmingSettings();

			internal void Bind(GroundworkPlugin plugin)
			{
				General.Bind(plugin);
				TerrainTools.Bind(plugin);
				Farming.Bind(plugin);
			}
		}

		internal sealed class GeneralSettings
		{
			internal ConfigEntry<Toggle> LockConfiguration;

			internal void Bind(GroundworkPlugin plugin)
			{
				LockConfiguration = plugin.config("1 - General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only.");
			}
		}

		internal sealed class TerrainToolSettings
		{
			internal ConfigEntry<float> TerrainToolRangeStep;

			internal ConfigEntry<TerrainToolRangePreviewMode> DefaultPreviewMode;

			internal ConfigEntry<KeyboardShortcut> TerrainToolPreviewToggleHotkey;

			internal ConfigEntry<Toggle> ToolHud;

			internal ConfigEntry<Toggle> TerrainHeightHint;

			internal ConfigEntry<Toggle> PavedRoadSmoothHeight;

			internal ConfigEntry<KeyboardShortcut> ToolWheelModifierHotkey;

			internal void Bind(GroundworkPlugin plugin)
			{
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: Expected O, but got Unknown
				//IL_006a: Unknown result type (might be due to invalid IL or missing references)
				//IL_007e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0089: Expected O, but got Unknown
				//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
				//IL_010f: Unknown result type (might be due to invalid IL or missing references)
				//IL_011a: Expected O, but got Unknown
				TerrainToolRangeStep = plugin.config("2 - Terrain Tools", "Terrain Tool Range Step", 0.5f, new ConfigDescription("Range adjustment step for terrain tool pieces configured in Groundwork.yml. Hoe/Cultivator use meters, and Pickaxe terrainDig uses scale units.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 5f), Array.Empty<object>()), synchronizedSetting: false);
				DefaultPreviewMode = plugin.config("2 - Terrain Tools", "Terrain Tool Default Preview Mode", TerrainToolRangePreviewMode.Vanilla, "Default Hoe/Cultivator terrain range preview mode. Vanilla scales the existing placement ghost visuals. Grid hides those visuals and draws the exact radius plus terrain grid candidate markers.", synchronizedSetting: false);
				TerrainToolPreviewToggleHotkey = plugin.config<KeyboardShortcut>("2 - Terrain Tools", "Terrain Tool Preview Toggle Hotkey", new KeyboardShortcut((KeyCode)103, Array.Empty<KeyCode>()), new ConfigDescription("Local hotkey for toggling Hoe/Cultivator terrain modifying pieces between Vanilla and Grid preview while placing.", (AcceptableValueBase)(object)new AcceptableShortcuts(), Array.Empty<object>()), synchronizedSetting: false);
				ToolHud = plugin.config("2 - Terrain Tools", "Tool HUD", Toggle.On, "If on, Hoe/Cultivator terrain range HUD, Pickaxe terrain dig scale key hint, and Pickaxe terrain dig tooltip are shown.", synchronizedSetting: false);
				TerrainHeightHint = plugin.config("2 - Terrain Tools", "Terrain Height Hint", Toggle.On, "Show the standing-height and Shift+Click guidance above the build panel while using Level Ground or Paved Road. Independent of Tool HUD; Paved Road requires Paved Road Smooth Height.", synchronizedSetting: false);
				PavedRoadSmoothHeight = plugin.config("2 - Terrain Tools", "Paved Road Smooth Height", Toggle.On, "If on, Paved Road applies its vanilla smooth height operation. Turn off to keep only the paved paint effect.", synchronizedSetting: false);
				ToolWheelModifierHotkey = plugin.config<KeyboardShortcut>("2 - Terrain Tools", "Tool Wheel Modifier Hotkey", new KeyboardShortcut((KeyCode)308, Array.Empty<KeyCode>()), new ConfigDescription("Local hotkey held while using mouse wheel for Groundwork tool features, including mass planting count, Hoe/Cultivator terrain range, and pickaxe terrainDig scale.", (AcceptableValueBase)(object)new AcceptableShortcuts(), Array.Empty<object>()), synchronizedSetting: false);
			}
		}

		internal sealed class FarmingSettings
		{
			internal ConfigEntry<Toggle> MassPlantingEnabled;

			internal ConfigEntry<float> MassPlantSpacingFactor;

			internal ConfigEntry<float> MassPlantSkillGainFactor;

			internal ConfigEntry<KeyboardShortcut> ToggleGridPlantingHotkey;

			internal ConfigEntry<float> ForagingPickupMaxRange;

			internal ConfigEntry<FarmingRangeHarvestTargetMode> RangeHarvestTargets;

			internal ConfigEntry<Toggle> NaturalPickableRemoval;

			internal ConfigEntry<string> NaturalPickableRemovalPrefabs;

			internal ConfigEntry<string> ScytheHandleRequiredGlobalKey;

			internal ConfigEntry<float> ForagingRespawnSpeedFactor;

			internal ConfigEntry<float> PlantGrowSpeedFactor;

			internal ConfigEntry<HoverHintMode> CropHoverHint;

			internal ConfigEntry<HoverHintMode> ForagingHoverHint;

			internal ConfigEntry<HoverHintMode> BeehiveHoverHint;

			internal ConfigEntry<int> BeehiveCapacityFarmingLevelsPerBonusHoney;

			internal ConfigEntry<float> BeehiveFarmingSkillGainPerHoney;

			internal ConfigEntry<float> BeehiveCoverMaxSpeedMultiplier;

			internal ConfigEntry<float> BeehiveNightHoneyRate;

			internal ConfigEntry<float> BeehiveRainHoneyRate;

			internal ConfigEntry<float> BeehivePollinationRadius;

			internal ConfigEntry<int> BeehivePollinationMaxPlants;

			internal ConfigEntry<Toggle> BeehivePollinationPreview;

			internal ConfigEntry<float> BeehivePollinationPlantGrowSpeedFactor;

			internal ConfigEntry<float> BeehivePollinationForagingRespawnSpeedFactor;

			internal ConfigEntry<float> BeehivePollinationHoneySpeedBonusPercentPerTarget;

			internal ConfigEntry<float> WetEnvironmentPlantGrowSpeedFactor;

			internal ConfigEntry<float> WetEnvironmentForagingRespawnSpeedFactor;

			internal void Bind(GroundworkPlugin plugin)
			{
				//IL_0030: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Unknown result type (might be due to invalid IL or missing references)
				//IL_004f: Expected O, but got Unknown
				//IL_007e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0089: Expected O, but got Unknown
				//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c3: Expected O, but got Unknown
				//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fd: Expected O, but got Unknown
				//IL_012c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0137: Expected O, but got Unknown
				//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
				//IL_01cc: Expected O, but got Unknown
				//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
				//IL_0206: Expected O, but got Unknown
				//IL_0235: Unknown result type (might be due to invalid IL or missing references)
				//IL_0240: Expected O, but got Unknown
				//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
				//IL_02c7: Expected O, but got Unknown
				//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
				//IL_0301: Expected O, but got Unknown
				//IL_0330: Unknown result type (might be due to invalid IL or missing references)
				//IL_033b: Expected O, but got Unknown
				//IL_036a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0375: Expected O, but got Unknown
				//IL_03a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_03af: Expected O, but got Unknown
				//IL_03fb: Unknown result type (might be due to invalid IL or missing references)
				//IL_0406: Expected O, but got Unknown
				//IL_042b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0436: Expected O, but got Unknown
				//IL_0465: Unknown result type (might be due to invalid IL or missing references)
				//IL_0470: Expected O, but got Unknown
				//IL_049f: Unknown result type (might be due to invalid IL or missing references)
				//IL_04aa: Expected O, but got Unknown
				//IL_04d9: Unknown result type (might be due to invalid IL or missing references)
				//IL_04e4: Expected O, but got Unknown
				MassPlantingEnabled = plugin.config("3 - Mass Planting", "Mass Planting Enabled", Toggle.On, "If on, mass planting unlocks by Farming level: 0-19 off, 20-39 plants 5, 40-59 plants 10, 60-79 plants 15, 80-99 plants 20, and 100 plants 25. Grid planting is always available.");
				ToggleGridPlantingHotkey = plugin.config<KeyboardShortcut>("3 - Mass Planting", "Toggle Grid Planting Hotkey", new KeyboardShortcut((KeyCode)103, Array.Empty<KeyCode>()), new ConfigDescription("Local hotkey for toggling world-grid snapping while a plant piece is selected.", (AcceptableValueBase)(object)new AcceptableShortcuts(), Array.Empty<object>()), synchronizedSetting: false);
				MassPlantSpacingFactor = plugin.config("3 - Mass Planting", "Mass Plant Spacing Factor", 1f, new ConfigDescription("Multiplier for automatic mass-plant spacing. Base spacing is the selected Plant prefab growRadius * 2, so modded crops with their own growRadius are spaced automatically.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 3f), Array.Empty<object>()));
				MassPlantSkillGainFactor = plugin.config("3 - Mass Planting", "Mass Plant Skill Gain Factor", 0.5f, new ConfigDescription("Additional Farming skill gain for mass planting. Vanilla grants one build-skill raise for the click; this adds (extra planted crops * factor). 0 keeps only the vanilla one-click skill gain.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
				PlantGrowSpeedFactor = plugin.config("4 - Plants and Foraging", "Plant Grow Speed Factor", 2.5f, new ConfigDescription("Grow speed factor at Farming skill 100 for placed Plant prefabs. Newly planted crops store the planter's Farming skill. 0 disables this feature.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
				ForagingPickupMaxRange = plugin.config("4 - Plants and Foraging", "Foraging Pickup Max Range", 5f, new ConfigDescription("Maximum nearby pickup range in meters at Farming skill 100 for targets selected by Farming Range Harvest Targets. 0 disables this feature.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
				RangeHarvestTargets = plugin.config("4 - Plants and Foraging", "Farming Range Harvest Targets", FarmingRangeHarvestTargetMode.ForagingOnly, "Controls which Pickables the Farming-scaled nearby pickup affects. ForagingOnly includes positive-respawn targets that drop edible food or are explicitly enabled in BepInEx/config/Groundwork/pickables.yml. ForagingAndCrops also includes mature Pickables produced by Plant grown prefabs. Scythe harvesting is unaffected.");
				NaturalPickableRemoval = plugin.config("4 - Plants and Foraging", "Natural Pickable Removal", Toggle.Off, "If on, the Cultivator can permanently remove natural Pickables whose exact prefab names are listed in Natural Pickable Removal Prefabs. Removal gives no items or harvest.");
				NaturalPickableRemovalPrefabs = plugin.config("4 - Plants and Foraging", "Natural Pickable Removal Prefabs", "Pickable_Branch, Pickable_Flint, Pickable_Dandelion, Pickable_Mushroom, Pickable_Thistle", "Comma-separated exact prefab names eligible for Natural Pickable Removal. This list has no effect while that setting is off.");
				ForagingRespawnSpeedFactor = plugin.config("4 - Plants and Foraging", "Foraging Respawn Speed Factor", 5f, new ConfigDescription("Respawn speed factor at Farming skill 100 for foraging-style pickables. Targets need a positive base respawn time and must either drop edible food or be explicitly enabled in BepInEx/config/Groundwork/pickables.yml. 0 disables this feature.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>()));
				WetEnvironmentPlantGrowSpeedFactor = plugin.config("4 - Plants and Foraging", "Rain Plant Grow Speed Factor", 2f, new ConfigDescription("Plant growth speed factor while the current environment is wet. 1 disables this bonus.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
				WetEnvironmentForagingRespawnSpeedFactor = plugin.config("4 - Plants and Foraging", "Rain Foraging Respawn Speed Factor", 2f, new ConfigDescription("Foraging respawn speed factor while the current environment is wet. Applies to automatic edible targets and prefabs explicitly enabled in BepInEx/config/Groundwork/pickables.yml. 1 disables this bonus.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
				CropHoverHint = plugin.config("4 - Plants and Foraging", "Crop Hover Hint", HoverHintMode.Detailed, "Controls Groundwork details added to growing crop hover text. Off keeps vanilla text, Compact adds only remaining growth time, and Detailed also adds active Farming, pollination, and rain multipliers.", synchronizedSetting: false);
				ForagingHoverHint = plugin.config("4 - Plants and Foraging", "Foraging Hover Hint", HoverHintMode.Detailed, "Controls Groundwork details shown for picked respawning forage targets. Off keeps only the target name when Groundwork must provide a hidden hover proxy, Compact adds only remaining respawn time, and Detailed also adds active Farming, pollination, and rain multipliers. Pollination and target discovery remain active in every mode.", synchronizedSetting: false);
				BeehiveHoverHint = plugin.config("5 - Beehives", "Beehive Hover Hint", HoverHintMode.Detailed, "Controls Groundwork details added to beehive hover text. Off keeps vanilla text, Compact adds capacity, production modifiers, and next-honey timing, and Detailed also explains how the configured bonuses work. Honey production, growth bonuses, and Farming effects remain active in every mode.", synchronizedSetting: false);
				BeehiveCapacityFarmingLevelsPerBonusHoney = plugin.config("5 - Beehives", "Beehive Capacity Farming Levels Per Bonus Honey", 20, new ConfigDescription("Farming levels required for each +1 beehive honey capacity. 0 disables the capacity bonus.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
				BeehiveFarmingSkillGainPerHoney = plugin.config("5 - Beehives", "Beehive Farming Skill Gain Per Honey", 0.25f, new ConfigDescription("Farming skill gain for each honey harvested from a beehive. 0 disables this bonus.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
				BeehiveCoverMaxSpeedMultiplier = plugin.config("5 - Beehives", "Beehive Cover Max Speed Multiplier", 3f, new ConfigDescription("Honey production multiplier at 0% cover. The bonus scales linearly from x1 at the beehive max cover threshold to this value when fully open.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
				BeehiveNightHoneyRate = plugin.config("5 - Beehives", "Beehive Night Honey Rate", 0.5f, new ConfigDescription("Honey production rate at night. 1 is the vanilla value, 0.5 makes night production half speed, and 0 pauses night production. Unloaded catch-up uses an average day/night rate.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
				BeehiveRainHoneyRate = plugin.config("5 - Beehives", "Beehive Rain Honey Rate", 0.5f, new ConfigDescription("Honey production rate while the current environment is wet. 1 is the vanilla value, 0.5 makes rain production half speed, and 0 pauses rain production. Rain is not accumulated during unloaded catch-up.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
				BeehivePollinationPreview = plugin.config("6 - Pollination", "Beehive Pollination Preview", Toggle.On, "If on, hovering a beehive draws both its centered terrain range guide and marker highlights around assigned Plant and foraging Pickable targets. Raising the beehive shrinks the range guide, and slopes change it by direction. If the ground directly below is outside the sphere, the centered guide is hidden. The range is gray while unavailable or paused; assigned target markers are gray while rain or night pauses pollination. Target colliders and assignment still determine the actual affected objects.", synchronizedSetting: false);
				BeehivePollinationRadius = plugin.config("6 - Pollination", "Beehive Pollination Radius", 3f, new ConfigDescription("Radius in meters for beehive pollination bonuses. 0 disables plant and foraging growth bonuses from beehives.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>()));
				BeehivePollinationMaxPlants = plugin.config("6 - Pollination", "Beehive Pollination Max Plants", 24, new ConfigDescription("Maximum nearby growing plants or foraging targets counted by one beehive for pollination.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
				BeehivePollinationPlantGrowSpeedFactor = plugin.config("6 - Pollination", "Beehive Pollination Plant Grow Speed Factor", 2f, new ConfigDescription("Plant growth speed factor near an empty active beehive. The bonus fades to x1 as the beehive fills with honey.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
				BeehivePollinationForagingRespawnSpeedFactor = plugin.config("6 - Pollination", "Beehive Pollination Foraging Respawn Speed Factor", 4f, new ConfigDescription("Foraging respawn speed factor near an empty active beehive. The bonus fades to x1 as the beehive fills with honey.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
				BeehivePollinationHoneySpeedBonusPercentPerTarget = plugin.config("6 - Pollination", "Beehive Pollination Honey Speed Bonus Percent Per Target", 10f, new ConfigDescription("Additional honey production speed percent per counted pollination target. For example, 10 means each target adds +10%, so 24 targets gives Honey rate x3.4.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
				ScytheHandleRequiredGlobalKey = plugin.config("7 - Scythe", "Scythe Handle Required Global Key", "defeated_bonemass", "Global key required for the Bog Witch to sell ScytheHandle. Vanilla uses defeated_dragon; Groundwork defaults to defeated_bonemass. For per-player progression and discovery of additional global keys, see YouAreNotWorthy.");
			}
		}

		private class AcceptableShortcuts : AcceptableValueBase
		{
			public AcceptableShortcuts()
				: base(typeof(KeyboardShortcut))
			{
			}

			public override object Clamp(object value)
			{
				return value;
			}

			public override bool IsValid(object value)
			{
				return true;
			}

			public override string ToDescriptionString()
			{
				return "# Acceptable values: " + string.Join(", ", UnityInput.Current.SupportedKeyCodes);
			}
		}

		private class ConfigurationManagerAttributes
		{
			[UsedImplicitly]
			public int? Order;
		}

		internal const string ModName = "Groundwork";

		internal const string ModVersion = "1.1.13";

		internal const string Author = "sighsorry";

		private const string ModGUID = "sighsorry.Groundwork";

		private const string JewelcraftingGuid = "org.bepinex.plugins.jewelcrafting";

		private const string ZenBeehiveGuid = "ZenDragon.ZenBeehive";

		internal const string ExpandWorldDataGuid = "expand_world_data";

		private const string TerrainToolsYamlFileName = "Groundwork.yml";

		private const string SyncedTerrainToolsYamlIdentifier = "groundwork_yaml";

		private const float YamlReloadDebounceSeconds = 0.35f;

		internal static readonly string YamlConfigDirectoryPath = Path.Combine(Paths.ConfigPath, "Groundwork");

		private static readonly string TerrainToolsYamlFilePath = Path.Combine(YamlConfigDirectoryPath, "Groundwork.yml");

		private static CustomSyncedValue<string>? _syncedTerrainToolsYaml;

		private static IReadOnlyList<NormalizedTerrainToolConfig> _terrainTools = Array.Empty<NormalizedTerrainToolConfig>();

		private static bool _suppressSyncedYamlChanged;

		private static YamlAuthorityMode _yamlAuthorityMode;

		private int _configurationManagerOrder;

		private readonly Harmony _harmony = new Harmony("sighsorry.Groundwork");

		private FileSystemWatcher? _watcher;

		private Coroutine? _yamlReloadCoroutine;

		public static readonly ManualLogSource ModLogger = Logger.CreateLogSource("Groundwork");

		internal static readonly ConfigSync ConfigSync = new ConfigSync("sighsorry.Groundwork")
		{
			DisplayName = "Groundwork",
			CurrentVersion = "1.1.13",
			MinimumRequiredVersion = "1.1.13"
		};

		internal static GroundworkPlugin? Instance { get; private set; }

		internal static PluginSettings Settings { get; } = new PluginSettings();

		internal static IReadOnlyList<NormalizedTerrainToolConfig> TerrainTools => _terrainTools;

		public void Awake()
		{
			Instance = this;
			Localizer.Load();
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				Settings.Bind(this);
				ConfigSync.AddLockingConfigEntry<Toggle>(Settings.General.LockConfiguration);
				InitializeSyncedYamlValue();
				GrowthOverrideSystem.Initialize(this, ConfigSync);
				GroundworkConfigLoader.EnsureLocalFileExists(YamlConfigDirectoryPath, TerrainToolsYamlFilePath);
				RefreshYamlAuthorityMode(force: true);
				Assembly executingAssembly = Assembly.GetExecutingAssembly();
				_harmony.PatchAll(executingAssembly);
				((BaseUnityPlugin)this).Config.Save();
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		public void OnDestroy()
		{
			RunShutdownStep("Harmony patches", (Action)_harmony.UnpatchSelf);
			RunShutdownStep("PlayerPlacePieceGroundworkPatch", PlayerPlacePieceGroundworkPatch.ClearContext);
			RunShutdownStep("FarmingSkillTooltipPosition", FarmingSkillTooltipPosition.Clear);
			RunShutdownStep("GrowthOverrideSystem", GrowthOverrideSystem.Shutdown);
			RunShutdownStep("CultivationSystem", CultivationSystem.Shutdown);
			RunShutdownStep("PickedVisualSystem", PickedVisualSystem.Shutdown);
			RunShutdownStep("MassPlantingSystem", MassPlantingSystem.Shutdown);
			RunShutdownStep("TerrainToolRangeSystem", TerrainToolRangeSystem.Shutdown);
			RunShutdownStep("PickaxeTerrainScalingSystem", PickaxeTerrainScalingSystem.Shutdown);
			RunShutdownStep("TerrainDigFloatingTextSystem", TerrainDigFloatingTextSystem.Clear);
			RunShutdownStep("PickableRespawnHoverSystem", PickableRespawnHoverSystem.Shutdown);
			RunShutdownStep("FarmingSkillSystem", FarmingSkillSystem.Shutdown);
			RunShutdownStep("HarvestSkillSync", HarvestSkillSync.Shutdown);
			RunShutdownStep("ZenBeehiveCompatSystem", ZenBeehiveCompatSystem.Shutdown);
			RunShutdownStep("BeehivePollinationSystem", BeehivePollinationSystem.Shutdown);
			RunShutdownStep("ScytheHarvestSystem", ScytheHarvestSystem.Shutdown);
			RunShutdownStep("ScytheHandleUnlockSystem", ScytheHandleUnlockSystem.Shutdown);
			RunShutdownStep("ScytheToolCompatSystem", ScytheToolCompatSystem.Shutdown);
			RunShutdownStep("CameraZoomInputSuppressionSystem", CameraZoomInputSuppressionSystem.Shutdown);
			RunShutdownStep("GroundworkInputIcons", GroundworkInputIcons.Shutdown);
			RunShutdownStep("Localizer", Localizer.Unload);
			RunShutdownStep("synced YAML state", DisposeSyncedYamlValue);
			RunShutdownStep("YAML file watcher", DisposeWatcher);
			_terrainTools = Array.Empty<NormalizedTerrainToolConfig>();
			_suppressSyncedYamlChanged = false;
			_yamlAuthorityMode = YamlAuthorityMode.LocalFiles;
			if (Instance == this)
			{
				Instance = null;
			}
		}

		private static void RunShutdownStep(string name, Action shutdown)
		{
			try
			{
				shutdown();
			}
			catch (Exception ex)
			{
				ModLogger.LogWarning((object)("Could not fully clean up " + name + " while unloading Groundwork: " + ex.GetBaseException().Message));
			}
		}

		internal static void ApplyToObjectDb(ObjectDB objectDb)
		{
			if (!((Object)(object)objectDb == (Object)null))
			{
				TerrainToolRangeSystem.RestoreObjectDb(objectDb);
				bool num = ScytheToolCompatSystem.ApplyToObjectDb(objectDb);
				TerrainToolRangeSystem.ApplyToObjectDb(objectDb, TerrainTools);
				CultivationSystem.ApplyToObjectDb(objectDb);
				if (num)
				{
					ScytheToolCompatSystem.NotifyJewelcraftingEffectRecalcIfPresent();
				}
			}
		}

		internal static void TryApplyPendingConfig()
		{
			Instance?.RefreshYamlAuthorityMode();
			GrowthOverrideSystem.RefreshAuthority();
		}

		private void SetupWatcher()
		{
			if (_watcher == null)
			{
				Directory.CreateDirectory(YamlConfigDirectoryPath);
				_watcher = new FileSystemWatcher(YamlConfigDirectoryPath, "Groundwork.yml");
				_watcher.Changed += OnYamlFileChanged;
				_watcher.Created += OnYamlFileChanged;
				_watcher.Renamed += OnYamlFileChanged;
				_watcher.IncludeSubdirectories = false;
				_watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
				_watcher.EnableRaisingEvents = true;
			}
		}

		private void OnYamlFileChanged(object sender, FileSystemEventArgs e)
		{
			if (_yamlAuthorityMode == YamlAuthorityMode.LocalFiles)
			{
				ScheduleLocalYamlReload();
			}
		}

		private void ScheduleLocalYamlReload()
		{
			CancelPendingYamlReload();
			_yamlReloadCoroutine = ((MonoBehaviour)this).StartCoroutine(ReloadLocalYamlAfterDelay());
		}

		private IEnumerator ReloadLocalYamlAfterDelay()
		{
			yield return (object)new WaitForSecondsRealtime(0.35f);
			_yamlReloadCoroutine = null;
			if (_yamlAuthorityMode == YamlAuthorityMode.LocalFiles)
			{
				ReloadLocalYaml();
			}
		}

		private void CancelPendingYamlReload()
		{
			if (_yamlReloadCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_yamlReloadCoroutine);
				_yamlReloadCoroutine = null;
			}
		}

		private void ReloadLocalYaml()
		{
			if (_yamlAuthorityMode != YamlAuthorityMode.LocalFiles)
			{
				return;
			}
			GroundworkConfigLoader.EnsureLocalFileExists(YamlConfigDirectoryPath, TerrainToolsYamlFilePath);
			string text = File.ReadAllText(TerrainToolsYamlFilePath);
			if (_syncedTerrainToolsYaml != null)
			{
				_suppressSyncedYamlChanged = true;
				try
				{
					_syncedTerrainToolsYaml.AssignLocalValue(text);
				}
				finally
				{
					_suppressSyncedYamlChanged = false;
				}
			}
			ApplyYamlText(text);
		}

		private void OnSyncedYamlChanged()
		{
			if (!_suppressSyncedYamlChanged)
			{
				ApplyYamlText(_syncedTerrainToolsYaml?.Value ?? "");
			}
		}

		private void RefreshYamlAuthorityMode(bool force = false)
		{
			YamlAuthorityMode yamlAuthorityMode = (((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) ? YamlAuthorityMode.SyncedOnly : YamlAuthorityMode.LocalFiles);
			if (force || yamlAuthorityMode != _yamlAuthorityMode)
			{
				_yamlAuthorityMode = yamlAuthorityMode;
				switch (yamlAuthorityMode)
				{
				case YamlAuthorityMode.LocalFiles:
					SetupWatcher();
					ReloadLocalYaml();
					ModLogger.LogInfo((object)"Groundwork YAML authority mode: LocalFiles.");
					break;
				case YamlAuthorityMode.SyncedOnly:
					DisposeWatcher();
					ApplyYamlText(_syncedTerrainToolsYaml?.Value ?? "");
					ModLogger.LogInfo((object)"Groundwork YAML authority mode: SyncedOnly.");
					break;
				}
			}
		}

		private static void ApplyYamlText(string yamlText)
		{
			if (GroundworkConfigLoader.TryParseTerrainToolsYaml(yamlText, out IReadOnlyList<NormalizedTerrainToolConfig> configs))
			{
				_terrainTools = configs ?? Array.Empty<NormalizedTerrainToolConfig>();
				if ((Object)(object)ObjectDB.instance != (Object)null)
				{
					ApplyToObjectDb(ObjectDB.instance);
				}
			}
		}

		private void InitializeSyncedYamlValue()
		{
			DisposeSyncedYamlValue();
			_syncedTerrainToolsYaml = new CustomSyncedValue<string>(ConfigSync, "groundwork_yaml", "");
			_syncedTerrainToolsYaml.ValueChanged += OnSyncedYamlChanged;
		}

		private void DisposeSyncedYamlValue()
		{
			if (_syncedTerrainToolsYaml != null)
			{
				_syncedTerrainToolsYaml.ValueChanged -= OnSyncedYamlChanged;
				_syncedTerrainToolsYaml = null;
			}
		}

		private void DisposeWatcher()
		{
			CancelPendingYamlReload();
			if (_watcher != null)
			{
				_watcher.Dispose();
				_watcher = null;
			}
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			object[] array = description.Tags.Concat(new <>z__ReadOnlySingleElementList<object>(new ConfigurationManagerAttributes
			{
				Order = -(_configurationManagerOrder++)
			})).ToArray();
			ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, array);
			ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val);
			ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting;
			return val2;
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, string description, bool synchronizedSetting = true)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting);
		}
	}
	public static class KeyboardExtensions
	{
		public static bool IsKeyDown(this KeyboardShortcut shortcut)
		{
			//IL_0002: 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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey))
			{
				return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey);
			}
			return false;
		}

		public static bool IsKeyHeld(this KeyboardShortcut shortcut)
		{
			//IL_0002: 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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKey(((KeyboardShortcut)(ref shortcut)).MainKey))
			{
				return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey);
			}
			return false;
		}
	}
	internal static class GameAccess
	{
		internal static readonly FieldRef<Attack, Humanoid> Attacker = AccessTools.FieldRefAccess<Attack, Humanoid>("m_character");

		internal static readonly FieldRef<Humanoid, ItemData> RightItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_rightItem");

		internal static readonly FieldRef<Humanoid, Attack> CurrentAttack = AccessTools.FieldRefAccess<Humanoid, Attack>("m_currentAttack");

		internal static readonly FieldRef<Humanoid, bool> SecondaryAttack = AccessTools.FieldRefAccess<Humanoid, bool>("m_currentAttackIsSecondary");

		internal static readonly FieldRef<Character, ZNetView> CharacterView = AccessTools.FieldRefAccess<Character, ZNetView>("m_nview");

		internal static readonly FieldRef<Pickable, ZNetView> PickableView = AccessTools.FieldRefAccess<Pickable, ZNetView>("m_nview");

		internal static readonly FieldRef<Plant, ZNetView> PlantView = AccessTools.FieldRefAccess<Plant, ZNetView>("m_nview");

		internal static readonly FieldRef<Beehive, ZNetView> BeehiveView = AccessTools.FieldRefAccess<Beehive, ZNetView>("m_nview");

		internal static readonly FieldRef<Player, PlacementStatus> PlacementStatus = AccessTools.FieldRefAccess<Player, PlacementStatus>("m_placementStatus");

		internal static readonly FieldRef<Player, GameObject> PlacementGhost = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		internal static readonly FieldRef<Player, int> InteractMask = AccessTools.FieldRefAccess<Player, int>("m_interactMask");

		internal static readonly FieldRef<Plant, int> PlantSeed = AccessTools.FieldRefAccess<Plant, int>("m_seed");

		internal static readonly FieldRef<Heightmap, List<float>> Heights = AccessTools.FieldRefAccess<Heightmap, List<float>>("m_heights");

		internal static readonly FieldRef<ZNetScene, Dictionary<int, GameObject>> NamedPrefabs = AccessTools.FieldRefAccess<ZNetScene, Dictionary<int, GameObject>>("m_namedPrefabs");

		internal static readonly FieldRef<InventoryGrid, List<InventoryElement>> InventoryElements = AccessTools.FieldRefAccess<InventoryGrid, List<InventoryElement>>("m_elements");

		internal static readonly FieldRef<SkillsDialog, List<GameObject>> SkillElements = AccessTools.FieldRefAccess<SkillsDialog, List<GameObject>>("m_elements");

		internal static readonly FieldRef<UITooltip, RectTransform?> TooltipAnchor = AccessTools.FieldRefAccess<UITooltip, RectTransform>("m_anchor");

		internal static readonly FieldRef<UITooltip, Vector2> TooltipPosition = AccessTools.FieldRefAccess<UITooltip, Vector2>("m_fixedPosition");

		internal static readonly FieldRef<UITooltip> CurrentTooltip = AccessTools.StaticFieldRefAccess<UITooltip>(AccessTools.Field(typeof(UITooltip), "m_current"));

		internal static readonly FieldRef<GameObject> TooltipRoot = AccessTools.StaticFieldRefAccess<GameObject>(AccessTools.Field(typeof(UITooltip), "m_tooltip"));

		internal static readonly Func<Attack, float> AttackStamina = AccessTools.MethodDelegate<Func<Attack, float>>(AccessTools.DeclaredMethod(typeof(Attack), "GetAttackStamina", Type.EmptyTypes, (Type[])null), (object)null, true);

		internal static readonly Func<Player, float> BuildStamina = AccessTools.MethodDelegate<Func<Player, float>>(AccessTools.DeclaredMethod(typeof(Player), "GetBuildStamina", Type.EmptyTypes, (Type[])null), (object)null, true);

		internal static readonly Func<Player, ItemData, float> PlaceDurability = AccessTools.MethodDelegate<Func<Player, ItemData, float>>(AccessTools.DeclaredMethod(typeof(Player), "GetPlaceDurability", new Type[1] { typeof(ItemData) }, (Type[])null), (object)null, true);

		internal static readonly Func<Plant, float> GrowTime = AccessTools.MethodDelegate<Func<Plant, float>>(AccessTools.DeclaredMethod(typeof(Plant), "GetGrowTime", Type.EmptyTypes, (Type[])null), (object)null, true);

		internal static readonly Func<Plant, double> TimeSincePlanted = AccessTools.MethodDelegate<Func<Plant, double>>(AccessTools.DeclaredMethod(typeof(Plant), "TimeSincePlanted", Type.EmptyTypes, (Type[])null), (object)null, true);

		internal static readonly Func<Heightmap, int, int, Vector3> CalcVertex = AccessTools.MethodDelegate<Func<Heightmap, int, int, Vector3>>(AccessTools.DeclaredMethod(typeof(Heightmap), "CalcVertex", new Type[2]
		{
			typeof(int),
			typeof(int)
		}, (Type[])null), (object)null, true);

		internal static readonly Func<Player, Recipe, bool, int, int, bool> HaveRequirementItems = AccessTools.MethodDelegate<Func<Player, Recipe, bool, int, int, bool>>(AccessTools.DeclaredMethod(typeof(Player), "HaveRequirementItems", new Type[4]
		{
			typeof(Recipe),
			typeof(bool),
			typeof(int),
			typeof(int)
		}, (Type[])null), (object)null, true);

		internal static bool BypassCheatChecks => PlayerProfile.s_bypassCheatChecks;
	}
	internal static class GroundworkLocalization
	{
		internal static string Text(string key, string fallback)
		{
			string text = "$" + key;
			Localization instance = Localization.instance;
			string text2 = ((instance != null) ? instance.Localize(text) : null);
			if (!string.IsNullOrWhiteSpace(text2) && !string.Equals(text2, text, StringComparison.Ordinal))
			{
				return text2;
			}
			return fallback;
		}

		internal static string Format(string key, string fallback, params object[] args)
		{
			return string.Format(CultureInfo.InvariantCulture, Text(key, fallback), args);
		}

		internal static string FormatDuration(float seconds)
		{
			int num = (int)Math.Ceiling(Math.Max(0f, seconds));
			if (num <= 0)
			{
				return Text("groundwork_time_ready", "ready");
			}
			if (num < 60)
			{
				return Format("groundwork_time_seconds", "{0}s", num);
			}
			int num2 = num / 60;
			int num3 = num % 60;
			if (num2 < 60)
			{
				if (num3 <= 0)
				{
					return Format("groundwork_time_minutes", "{0}m", num2);
				}
				return Format("groundwork_time_minutes_seconds", "{0}m {1}s", num2, num3);
			}
			int num4 = num2 / 60;
			num2 %= 60;
			if (num2 <= 0)
			{
				return Format("groundwork_time_hours", "{0}h", num4);
			}
			return Format("groundwork_time_hours_minutes", "{0}h {1}m", num4, num2);
		}
	}
	internal static class GroundworkConfigLoader
	{
		private const string DefaultTerrainToolsResourceName = "Groundwork.Resources.Defaults.Groundwork.yml";

		private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build();

		internal static void EnsureLocalFileExists(string configDirectoryPath, string terrainToolsYamlFilePath)
		{
			Directory.CreateDirectory(configDirectoryPath);
			if (!File.Exists(terrainToolsYamlFilePath))
			{
				File.WriteAllText(terrainToolsYamlFilePath, LoadDefaultTerrainToolsYaml());
			}
		}

		internal static bool TryParseTerrainToolsYaml(string yamlText, out IReadOnlyList<NormalizedTerrainToolConfig>? configs)
		{
			configs = null;
			if (!TryParseTerrainToolBlocks(yamlText, out Dictionary<string, Dictionary<string, TerrainToolPieceConfig>> parsed))
			{
				return false;
			}
			configs = Normalize(parsed);
			return true;
		}

		private static List<NormalizedTerrainToolConfig> Normalize(IReadOnlyDictionary<string, Dictionary<string, TerrainToolPieceConfig>> terrainTools)
		{
			List<NormalizedTerrainToolConfig> list = new List<NormalizedTerrainToolConfig>();
			foreach (KeyValuePair<string, Dictionary<string, TerrainToolPieceConfig>> terrainTool in terrainTools)
			{
				terrainTool.Deconstruct(out var key, out var value);
				string text = key;
				Dictionary<string, TerrainToolPieceConfig> dictionary = value;
				if (string.IsNullOrWhiteSpace(text) || dictionary == null)
				{
					continue;
				}
				string toolPrefabName = text.Trim();
				foreach (KeyValuePair<string, TerrainToolPieceConfig> item in dictionary)
				{
					item.Deconstruct(out key, out var value2);
					string text2 = key;
					TerrainToolPieceConfig terrainToolPieceConfig = value2;
					if (string.IsNullOrWhiteSpace(text2) || terrainToolPieceConfig == null)
					{
						continue;
					}
					Dictionary<string, int> dictionary2 = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
					if (terrainToolPieceConfig.Cost != null)
					{
						foreach (KeyValuePair<string, int> item2 in terrainToolPieceConfig.Cost)
						{
							item2.Deconstruct(out key, out var value3);
							string text3 = key;
							int val = value3;
							if (!string.IsNullOrWhiteSpace(text3))
							{
								dictionary2[text3.Trim()] = Math.Max(0, val);
							}
						}
					}
					TerrainToolRangeConfig range = terrainToolPieceConfig.Range;
					list.Add(new NormalizedTerrainToolConfig
					{
						ToolPrefabName = toolPrefabName,
						PiecePrefabName = text2.Trim(),
						Cost = dictionary2,
						HasCostOverride = (terrainToolPieceConfig.Cost != null),
						RangeEnabled = ((range?.Enabled == true) ?? false),
						HasRangeEnabled = (range?.Enabled.HasValue ?? false),
						MinRange = Math.Max(0.1f, (range?.Min).GetValueOrDefault()),
						MaxRange = Math.Max(0.1f, (range?.Max).GetValueOrDefault()),
						HasMaxRange = (range?.Max.HasValue ?? false),
						DefaultRange = Math.Max(0f, (range?.Default).GetValueOrDefault()),
						RadiusMax = Math.Max(0f, (range?.RadiusMax).GetValueOrDefault()),
						HasRadiusMax = (range?.RadiusMax.HasValue ?? false),
						DepthMax = Math.Max(0f, (range?.DepthMax).GetValueOrDefault()),
						HasDepthMax = (range?.DepthMax.HasValue ?? false),
						MaterialCostFactor = Math.Max(0f, range?.MaterialCostFactor ?? 1f),
						StaminaCostFactor = Math.Max(0f, range?.StaminaCostFactor ?? 1f),
						HasStaminaCostFactor = (range?.StaminaCostFactor.HasValue ?? false),
						DurabilityFactor = Math.Max(0f, range?.DurabilityFactor ?? 1f),
						HasDurabilityFactor = (range?.DurabilityFactor.HasValue ?? false)
					});
				}
			}
			return list;
		}

		private static bool TryParseTerrainToolBlocks(string yamlText, out Dictionary<string, Dictionary<string, TerrainToolPieceConfig>>? parsed)
		{
			parsed = null;
			if (string.IsNullOrWhiteSpace(yamlText))
			{
				parsed = new Dictionary<string, Dictionary<string, TerrainToolPieceConfig>>(StringComparer.OrdinalIgnoreCase);
				return true;
			}
			try
			{
				parsed = new Dictionary<string, Dictionary<string, TerrainToolPieceConfig>>(StringComparer.OrdinalIgnoreCase);
				YamlStream yamlStream = new YamlStream();
				yamlStream.Load(new StringReader(yamlText));
				if (yamlStream.Documents.Count == 0 || !(yamlStream.Documents[0].RootNode is YamlMappingNode yamlMappingNode))
				{
					return true;
				}
				foreach (KeyValuePair<YamlNode, YamlNode> child in yamlMappingNode.Children)
				{
					string text = (child.Key as YamlScalarNode)?.Value?.Trim() ?? "";
					if (string.IsNullOrWhiteSpace(text))
					{
						continue;
					}
					if (!(child.Value is YamlMappingNode node))
					{
						parsed[text] = new Dictionary<string, TerrainToolPieceConfig>(StringComparer.OrdinalIgnoreCase);
						continue;
					}
					try
					{
						Dictionary<string, TerrainToolPieceConfig> source = DeserializeYamlNode<Dictionary<string, TerrainToolPieceConfig>>(node) ?? new Dictionary<string, TerrainToolPieceConfig>();
						parsed[text] = source.ToDictionary<KeyValuePair<string, TerrainToolPieceConfig>, string, TerrainToolPieceConfig>((KeyValuePair<string, TerrainToolPieceConfig> pieceEntry) => pieceEntry.Key, (KeyValuePair<string, TerrainToolPieceConfig> pieceEntry) => pieceEntry.Value, StringComparer.OrdinalIgnoreCase);
					}
					catch (Exception ex)
					{
						GroundworkPlugin.ModLogger.LogWarning((object)("Skipping Groundwork.yml block '" + text + "': " + ex.Message));
					}
				}
				return true;
			}
			catch (Exception ex2)
			{
				GroundworkPlugin.ModLogger.LogError((object)("Failed to parse Groundwork.yml: " + ex2.Message));
				return false;
			}
		}

		private static string LoadDefaultTerrainToolsYaml()
		{
			using Stream stream = typeof(GroundworkConfigLoader).Assembly.GetManifestResourceStream("Groundwork.Resources.Defaults.Groundwork.yml");
			if (stream == null)
			{
				throw new InvalidOperationException("Embedded default YAML resource 'Groundwork.Resources.Defaults.Groundwork.yml' was not found.");
			}
			using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
			return streamReader.ReadToEnd();
		}

		private static T? DeserializeYamlNode<T>(YamlNode node)
		{
			using StringWriter stringWriter = new StringWriter();
			new YamlStream(new YamlDocument(node)).Save(stringWriter, assignAnchors: false);
			return Deserializer.Deserialize<T>(stringWriter.ToString());
		}
	}
	internal sealed class TerrainToolPieceConfig
	{
		public Dictionary<string, int>? Cost { get; set; }

		public TerrainToolRangeConfig? Range { get; set; }
	}
	internal sealed class TerrainToolRangeConfig
	{
		public bool? Enabled { get; set; }

		public float? Min { get; set; }

		public float? Max { get; set; }

		public float? Default { get; set; }

		public float? RadiusMax { get; set; }

		public float? DepthMax { get; set; }

		public float? MaterialCostFactor { get; set; }

		public float? StaminaCostFactor { get; set; }

		public float? DurabilityFactor { get; set; }
	}
	internal sealed class NormalizedTerrainToolConfig
	{
		public string ToolPrefabName { get; set; } = "";

		public string PiecePrefabName { get; set; } = "";

		public bool HasCostOverride { get; set; }

		public Dictionary<string, int> Cost { get; set; } = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		public bool RangeEnabled { get; set; }

		public bool HasRangeEnabled { get; set; }

		public float MinRange { get; set; }

		public float MaxRange { get; set; }

		public bool HasMaxRange { get; set; }

		public float DefaultRange { get; set; }

		public float RadiusMax { get; set; }

		public bool HasRadiusMax { get; set; }

		public float DepthMax { get; set; }

		public bool HasDepthMax { get; set; }

		public float MaterialCostFactor { get; set; } = 1f;

		public float StaminaCostFactor { get; set; } = 1f;

		public bool HasStaminaCostFactor { get; set; }

		public float DurabilityFactor { get; set; } = 1f;

		public bool HasDurabilityFactor { get; set; }
	}
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	internal static class ObjectDbAwakeGroundworkPatch
	{
		private static void Postfix(ObjectDB __instance)
		{
			GroundworkPlugin.ApplyToObjectDb(__instance);
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	internal static class ObjectDbCopyOtherDbGroundworkPatch
	{
		private static void Postfix(ObjectDB __instance)
		{
			GroundworkPlugin.ApplyToObjectDb(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class PlayerUpdateGroundworkPatch
	{
		private static void Prefix(Player __instance)
		{
			MassPlantingSystem.BeginPlayerUpdateInput(__instance);
		}

		private static void Postfix(Player __instance)
		{
			MassPlantingSystem.EndPlayerUpdateInputSuppression();
			try
			{
				GroundworkPlugin.TryApplyPendingConfig();
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					MassPlantingSystem.UpdateInput(__instance);
					TerrainToolRangeSystem.UpdateInput(__instance);
					PickaxeTerrainScalingSystem.UpdateInput(__instance);
					TerrainDigFloatingTextSystem.Update();
					BeehivePollinationSystem.UpdateHoverPreview(__instance);
				}
			}
			finally
			{
				MassPlantingSystem.ClearPlayerUpdateInput();
			}
		}

		private static void Finalizer()
		{
			MassPlantingSystem.ClearPlayerUpdateInput();
		}
	}
	[HarmonyPatch(typeof(Player), "UpdatePlacementGhost")]
	internal static class PlayerUpdatePlacementGhostGroundworkPatch
	{
		private static void Prefix(Player __instance, out GrowthOverrideSystem.PieceBiomeOverrideState __state)
		{
			__state = GrowthOverrideSystem.BeginPlacementBiomeOverride(__instance);
		}

		private static void Postfix(Player __instance)
		{
			MassPlantingSystem.TrySnapPlacementGhost(__instance);
			GrowthOverrideSystem.CheckCultivationPlacementBiome(__instance);
			MassPlantingSystem.UpdatePlacementPreview(__instance);
			TerrainToolRangeSystem.ApplyCurrentRangeToGhost(__instance);
		}

		private static Exception? Finalizer(GrowthOverrideSystem.PieceBiomeOverrideState __state, Exception? __exception)
		{
			GrowthOverrideSystem.EndPlacementBiomeOverride(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	internal static class PlayerTryPlacePieceGroundworkPatch
	{
		private static bool Prefix(Player __instance, Piece piece, ref bool __result)
		{
			TerrainToolRangeSystem.BeginTryPlacePiece(__instance, piece);
			return MassPlantingSystem.TryInterceptPlace(__instance, piece, ref __result);
		}

		private static Exception? Finalizer(Player __instance, Piece piece, bool __result, Exception? __exception)
		{
			TerrainToolRangeSystem.EndTryPlacePiece(__instance, piece, __exception == null && __result);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Player), "Start")]
	internal static class PlayerStartGroundworkPatch
	{
		private static void Postfix(Player __instance)
		{
			ScytheToolCompatSystem.NotifyPendingJewelcraftingEffectRecalcIfNeeded(__instance);
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class ZNetSceneAwakeGroundworkPatch
	{
		private static void Postfix(ZNetScene __instance)
		{
			PickedVisualSystem.InvalidateCache();
			GrowthOverrideSystem.OnZNetSceneReady(__instance);
			CultivationSystem.OnZNetSceneReady(__instance);
			ScytheHarvestSystem.RefreshCultivatedPickables(__instance);
			FarmingSkillSystem.RefreshForagingBonusEffectFallback(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "PlacePiece")]
	internal static class PlayerPlacePieceGroundworkPatch
	{
		private static bool _recordPlantPlanter;

		private static bool _recordBeehiveBuilder;

		internal static Player? PlacingPlayer { get; private set; }

		internal static Player? PlantPlanter
		{
			get
			{
				if (!_recordPlantPlanter)
				{
					return null;
				}
				return PlacingPlayer;
			}
		}

		internal static Player? BeehiveBuilder
		{
			get
			{
				if (!_recordBeehiveBuilder)
				{
					return null;
				}
				return PlacingPlayer;
			}
		}

		private static void Prefix(Player __instance, Piece piece)
		{
			PlacingPlayer = __instance;
			_recordPlantPlanter = GroundworkToolsDomain.PlantGrowFeatureEnabled;
			_recordBeehiveBuilder = (Object)(object)__instance != (Object)null && (Object)(object)piece != (Object)null && GroundworkToolsDomain.BeehiveCapacityFarmingLevelsPerBonusHoney > 0 && (Object)(object)((Component)piece).GetComponentInChildren<Beehive>(true) != (Object)null;
		}

		private static void Finalizer()
		{
			ClearContext();
		}

		internal static void ClearContext()
		{
			PlacingPlayer = null;
			_recordPlantPlanter = false;
			_recordBeehiveBuilder = false;
		}
	}
	[HarmonyPatch(typeof(Piece), "SetCreator")]
	internal static class PieceSetCreatorCultivationPatch
	{
		private static void Postfix(Piece __instance)
		{
			Player placingPlayer = PlayerPlacePieceGroundworkPatch.PlacingPlayer;
			if (!((Object)(object)placingPlayer == (Object)null))
			{
				Pickable component = ((Component)__instance).GetComponent<Pickable>();
				if ((Object)(object)component != (Object)null)
				{
					MassPlantingSystem.TrySynchronizePendingPickable(component);
					CultivationSystem.InitializePlacedPickable(__instance, placingPlayer);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Plant), "Awake")]
	internal static class PlantAwakeGroundworkPatch
	{
		private static void Postfix(Plant __instance)
		{
			MassPlantingSystem.TrySynchronizePendingPlant(__instance);
			BeehivePollinationSystem.TrackLoadedTarget((Component)(object)__instance);
			FarmingSkillSystem.TryStorePlanterSkill(__instance);
		}
	}
	[HarmonyPatch(typeof(Plant), "UpdateHealth")]
	internal static class PlantUpdateHealthGroundworkPatch
	{
		private static void Prefix(Plant __instance, out GrowthOverrideSystem.PlantBiomeOverrideState __state)
		{
			__state = GrowthOverrideSystem.BeginPlantHealthBiomeOverride(__instance);
		}

		private static Exception? Finalizer(GrowthOverrideSystem.PlantBiomeOverrideState __state, Exception? __exception)
		{
			GrowthOverrideSystem.EndPlantHealthBiomeOverride(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Hud), "Update")]
	internal static class HudUpdateGroundworkPatch
	{
		private static void Postfix(Hud __instance)
		{
			TerrainToolRangeSystem.UpdateAimHeightHud(__instance);
		}
	}
	[HarmonyPatch(typeof(Hud), "SetupPieceInfo")]
	internal static class HudSetupPieceInfoGroundworkPatch
	{
		private sealed class PieceDescriptionState(Piece piece, string description)
		{
			internal Piece Piece { get; } = piece;

			internal string Description { get; } = description;
		}

		private static void Prefix(Piece? piece, out PieceDescriptionState? __state)
		{
			__state = (((Object)(object)piece != (Object)null) ? new PieceDescriptionState(piece, piece.m_description) : null);
			TerrainToolRangeSystem.AppendPieceDescription(piece);
			MassPlantingSystem.AppendPieceDescription(piece);
		}

		private static void Finalizer(PieceDescriptionState? __state)
		{
			if ((Object)(object)__state?.Piece != (Object)null)
			{
				__state.Piece.m_description = __state.Description;
			}
		}
	}
	[HarmonyPatch(typeof(GameCamera), "UpdateCamera")]
	internal static class GameCameraUpdateCameraGroundworkPatch
	{
		private static void Prefix()
		{
			CameraZoomInputSuppressionSystem.BeginGameCameraUpdate();
		}

		private static void Finalizer()
		{
			CameraZoomInputSuppressionSystem.EndGameCameraUpdate();
			TerrainToolRangeSystem.ClearCameraZoomSuppression();
			PickaxeTerrainScalingSystem.ClearCameraZoomSuppression();
		}
	}
	internal static class CameraZoomInputSuppressionSystem
	{
		private static bool InsideGameCameraUpdate { get; set; }

		internal static void Shutdown()
		{
			InsideGameCameraUpdate = false;
		}

		internal static void BeginGameCameraUpdate()
		{
			InsideGameCameraUpdate = true;
		}

		internal static void EndGameCameraUpdate()
		{
			InsideGameCameraUpdate = false;
		}

		internal static bool ShouldBlockZInputMouseScrollWheel()
		{
			if (InsideGameCameraUpdate)
			{
				return ShouldSuppressCameraZoomInput();
			}
			return false;
		}

		private static bool ShouldSuppressCameraZoomInput()
		{
			if (!TerrainToolRangeSystem.ShouldSuppressCameraZoomInput() && !MassPlantingSystem.ShouldSuppressCameraZoomInput())
			{
				return PickaxeTerrainScalingSystem.ShouldSuppressCameraZoomInput();
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetMouseScrollWheel")]
	internal static class ZInputGetMouseScrollWheelGroundworkPatch
	{
		private static bool Prefix(ref float __result)
		{
			if (MassPlantingSystem.ShouldBlockPlayerUpdateMouseScrollWheel())
			{
				__result = 0f;
				return false;
			}
			if (!CameraZoomInputSuppressionSystem.ShouldBlockZInputMouseScrollWheel())
			{
				return true;
			}
			__result = 0f;
			return false;
		}
	}
	[HarmonyPatch(typeof(KeyHints), "Awake")]
	internal static class KeyHintsAwakeGroundworkPatch
	{
		private static void Postfix(KeyHints __instance)
		{
			GroundworkInputIcons.Initialize(__instance);
			try
			{
				MassPlantingSystem.InitializeBuildHints(__instance);
			}
			finally
			{
				PickaxeTerrainScalingSystem.InitializeKeyHints(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(KeyHints), "UpdateHints")]
	internal static class KeyHintsUpdateGroundworkPatch
	{
		private static void Postfix(KeyHints __instance)
		{
			try
			{
				MassPlantingSystem.UpdateBuildHint(__instance);
			}
			finally
			{
				PickaxeTerrainScalingSystem.UpdateKeyHint(__instance);
			}
		}
	}
	internal static class PickaxeTerrainScalingSystem
	{
		internal sealed class TerrainSpawnState
		{
			private readonly TerrainScalingScope? _scope;

			private readonly PendingTerrainDigText? _fallbackText;

			private bool _restored;

			internal TerrainSpawnState(TerrainScalingScope? scope, PendingTerrainDigText? fallbackText)
			{
				_scope = scope;
				_fallbackText = fallbackText;
			}

			internal void Complete(GameObject? spawnedTerrainObject)
			{
				//IL_003d: 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)
				try
				{
					if (_scope != null)
					{
						_scope.ApplyTerrainHitResult(spawnedTerrainObject);
					}
					else if ((Object)(object)spawnedTerrainObject != (Object)null)
					{
						PendingTerrainDigText? fallbackText = _fallbackText;
						if (fallbackText.HasValue)
						{
							PendingTerrainDigText valueOrDefault = fallbackText.GetValueOrDefault();
							TerrainDigFloatingTextSystem.Show(spawnedTerrainObject.transform.position, valueOrDefault.Text, valueOrDefault.Color);
						}
					}
				}
				finally
				{
					Restore();
				}
			}

			internal void Restore()
			{
				if (!_restored)
				{
					_scope?.Restore();
					_restored = true;
				}
			}
		}

		internal sealed class TerrainScalingScope
		{
			internal readonly List<TerrainOpState> TerrainOps = new List<TerrainOpState>();

			internal readonly List<TerrainModifierState> TerrainModifiers = new List<TerrainModifierState>();

			private readonly Character _character;

			private readonly ItemData _weapon;

			private readonly Attack? _attack;

			private readonly float _radiusScale;

			private readonly float _depthScale;

			private readonly float _staminaCostFactor;

			private readonly float _durabilityCostFactor;

			internal TerrainScalingScope(Character character, ItemData weapon, Attack? attack, float radiusScale, float depthScale, float staminaCostFactor, float durabilityCostFactor)
			{
				_character = character;
				_weapon = weapon;
				_attack = attack;
				_radiusScale = radiusScale;
				_depthScale = depthScale;
				_staminaCostFactor = staminaCostFactor;
				_durabilityCostFactor = durabilityCostFactor;
			}

			internal void ApplyTerrainHitResult(GameObject? spawnedTerrainObject)
			{
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)spawnedTerrainObject == (Object)null))
				{
					ApplyExtraCosts();
					ShowScaleText(spawnedTerrainObject.transform.position);
				}
			}

			internal void Restore()
			{
				foreach (TerrainOpState terrainOp in TerrainOps)
				{
					terrainOp.Restore();
				}
				foreach (TerrainModifierState terrainModifier in TerrainModifiers)
				{
					terrainModifier.Restore();
				}
			}

			private void ApplyExtraCosts()
			{
				float num = Mathf.Max(1f, _radiusScale * _radiusScale * _depthScale);
				float num2 = (num - 1f) * _staminaCostFactor;
				float num3 = (num - 1f) * _durabilityCostFactor;
				if (num2 <= 0.001f && num3 <= 0.001f)
				{
					return;
				}
				if (_attack != null)
				{
					float num4 = GameAccess.AttackStamina(_attack) * num2;
					if (num4 > 0f)
					{
						_character.UseStamina(num4);
					}
				}
				if (_weapon.m_shared.m_useDurability && _character.IsPlayer())
				{
					float num5 = GetItemDurabilityDrain(_weapon) * num3;
					if (num5 > 0f)
					{
						_weapon.m_durability = Mathf.Max(0f, _weapon.m_durability - num5);
					}
				}
			}

			private void ShowScaleText(Vector3 position)
			{
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				float scale = Mathf.Max(_radiusScale, _depthScale);
				TerrainDigFloatingTextSystem.Show(position, GroundworkLocalization.Format("groundwork_pickaxe_floating_scale", "scale: {0}", FormatScale(scale)), new Color(1f, 0.92f, 0.72f, 1f));
			}
		}

		internal readonly struct PendingTerrainDigText
		{
			internal string Text { get; }

			internal Color Color { get; }

			internal PendingTerrainDigText(string text, Color color)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				Text = text;
				Color = color;
			}
		}

		private readonly struct TerrainCostProfile
		{
			internal readonly float BaseStaminaCost;

			internal readonly float TotalStaminaMultiplier;

			internal readonly float TotalDurabilityMultiplier;

			internal readonly float SelectedScale;

			internal float ExtraStaminaMultiplier => Mathf.Max(0f, TotalStaminaMultiplier - 1f);

			public TerrainCostProfile(float baseStaminaCost, float totalStaminaMultiplier, float totalDurabilityMultiplier, float selectedScale)
			{
				BaseStaminaCost = baseStaminaCost;
				TotalStaminaMultiplier = totalStaminaMultiplier;
				TotalDurabilityMultiplier = totalDurabilityMultiplier;
				SelectedScale = selectedScale;
			}
		}

		private readonly struct TerrainDigRule
		{
			internal const float FixedMinScale = 1f;

			internal const float FixedDefaultScale = 1f;

			internal readonly float RadiusMaxScale;

			internal readonly float DepthMaxScale;

			internal readonly float StaminaCostFactor;

			internal readonly float DurabilityFactor;

			internal float SelectedMaxScale => Mathf.Max(RadiusMaxScale, DepthMaxScale);

			public TerrainDigRule(float radiusMaxScale, float depthMaxScale, float staminaCostFactor, float durabilityFactor)
			{
				RadiusMaxScale = radiusMaxScale;
				DepthMaxScale = depthMaxScale;
				StaminaCostFactor = staminaCostFactor;
				DurabilityFactor = durabilityFactor;
			}

			internal static TerrainDigRule FromConfig(NormalizedTerrainToolConfig config, NormalizedTerrainToolConfig? fallback)
			{
				if (!ResolveEnabled(config, fallback))
				{
					return new TerrainDigRule(1f, 1f, 0f, 0f);
				}
				float num = ResolveScaleCap(config, fallback, useRadius: true);
				float num2 = ResolveScaleCap(config, fallback, useRadius: false);
				return new TerrainDigRule(Mathf.Max(1f, num), Mathf.Max(1f, num2), Mathf.Max(0f, ResolveStaminaCostFactor(config, fallback)), Mathf.Max(0f, ResolveDurabilityFactor(config, fallback)));
			}

			private static bool ResolveEnabled(NormalizedTerrainToolConfig config, NormalizedTerrainToolConfig? fallback)
			{
				if (config.HasRangeEnabled)
				{
					return config.RangeEnabled;
				}
				return fallback?.RangeEnabled ?? false;
			}

			private static float ResolveScaleCap(NormalizedTerrainToolConfig config, NormalizedTerrainToolConfig? fallback, bool useRadius)
			{
				if (useRadius && config.HasRadiusMax)
				{
					return config.RadiusMax;
				}
				if (!useRadius && config.HasDepthMax)
				{
					return config.DepthMax;
				}
				if (config.HasMaxRange)
				{
					return config.MaxRange;
				}
				if (fallback == null)
				{
					return 1f;
				}
				if (useRadius && fallback.HasRadiusMax)
				{
					return fallback.RadiusMax;
				}
				if (!useRadius && fallback.HasDepthMax)
				{
					return fallback.DepthMax;
				}
				if (!fallback.HasMaxRange)
				{
					return 1f;
				}
				return fallback.MaxRange;
			}

			private static float ResolveStaminaCostFactor(NormalizedTerrainToolConfig config, NormalizedTerrainToolConfig? fallback)
			{
				if (!config.HasStaminaCostFactor)
				{
					return fallback?.StaminaCostFactor ?? 1f;
				}
				return config.StaminaCostFactor;
			}

			private static float ResolveDurabilityFactor(NormalizedTerrainToolConfig config, NormalizedTerrainToolConfig? fallback)
			{
				if (!config.HasDurabilityFactor)
				{
					return fallback?.DurabilityFactor ?? 1f;
				}
				return config.DurabilityFactor;
			}
		}

		internal sealed class TerrainOpState
		{
			private readonly Settings _settings;

			private readonly float _levelRadius;

			private readonly float _raiseRadius;

			private readonly float _raiseDelta;

			private readonly float _smoothRadius;

			internal TerrainOpState(Settings settings)
			{
				_settings = settings;
				_levelRadius = settings.m_levelRadius;
				_raiseRadius = settings.m_raiseRadius;
				_raiseDelta = settings.m_raiseDelta;
				_smoothRadius = settings.m_smoothRadius;
			}

			internal void Restore()
			{
				_settings.m_levelRadius = _levelRadius;
				_settings.m_raiseRadius = _raiseRadius;
				_settings.m_raiseDelta = _raiseDelta;
				_settings.m_smoothRadius = _smoothRadius;
			}
		}

		internal sealed class TerrainModifierState
		{
			private readonly TerrainModifier _modifier;

			private readonly float _levelOffset;

			private readonly float _levelRadius;

			private readonly float _smoothRadius;

			internal TerrainModifierState(TerrainModifier modifier)
			{
				_modifier = modifier;
				_levelOffset = modifier.m_levelOffset;
				_levelRadius = modifier.m_levelRadius;
				_smoothRadius = modifier.m_smoothRadius;
			}

			internal void Restore()
			{
				_modifier.m_levelOffset = _levelOffset;
				_modifier.m_levelRadius = _levelRadius;
				_modifier.m_smoothRadius = _smoothRadius;
			}
		}

		private const string GenericPickaxeToolPrefabName = "Pickaxe";

		private const string TerrainDigConfigName = "terrainDig";

		private static readonly Dictionary<string, float> CurrentScalesByPrefab = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private static Attack? _activeMeleeAttack;

		private static KeyHints? _activeKeyHints;

		private static KeyHintCell? _digHint;

		private static bool _showingDigHint;

		private static string? _lastDigHintLabel;

		private static string? _lastDigHintKeyText;

		private static bool SuppressCameraZoomThisFrame { get; set; }

		internal static void Shutdown()
		{
			if ((Object)(object)_digHint?.Root != (Object)null)
			{
				Object.Destroy((Object)(object)_digHint.Root);
			}
			CurrentScalesByPrefab.Clear();
			_activeMeleeAttack = null;
			_activeKeyHints = null;
			_digHint = null;
			_showingDigHint = false;
			SuppressCameraZoomThisFrame = false;
			ClearDigHintCache();
		}

		internal static void BeginMeleeAttack(Attack attack)
		{
			_activeMeleeAttack = attack;
		}

		internal static void EndMeleeAttack(Attack attack)
		{
			if (_activeMeleeAttack == attack)
			{
				_activeMeleeAttack = null;
			}
		}

		internal static bool TryBeginSpawn(GameObject? prefab, Character character, ItemData weapon, out TerrainSpawnState state)
		{
			PendingTerrainDigText? fallbackText;
			bool num = CanSpawnTerrainModifier(character, weapon, _activeMeleeAttack, out fallbackText);
			TerrainScalingScope scope = ((num && !fallbackText.HasValue) ? Begin(prefab, character, weapon) : null);
			state = new TerrainSpawnState(scope, fallbackText);
			return num;
		}

		private static TerrainScalingScope? Begin(GameObject? prefab, Character? character, ItemData? weapon)
		{
			if ((Object)(object)prefab == (Object)null || !TryResolvePrimaryTerrainDig(character, weapon, _activeMeleeAttack))
			{
				return null;
			}
			if (!TryResolveTerrainDigScales(weapon, out var radiusScale, out var depthScale, out var rule))
			{
				return null;
			}
			if (radiusScale <= 1.001f && depthScale <= 1.001f)
			{
				return null;
			}
			TerrainScalingScope terrainScalingScope = new TerrainScalingScope(character, weapon, _activeMeleeAttack, radiusScale, depthScale, rule.StaminaCostFactor, rule.DurabilityFactor);
			try
			{
				TerrainOp[] componentsInChildren = prefab.GetComponentsInChildren<TerrainOp>(true);
				foreach (TerrainOp val in componentsInChildren)
				{
					if (val?.m_settings != null)
					{
						terrainScalingScope.TerrainOps.Add(new TerrainOpState(val.m_settings));
						Apply(val.m_settings, radiusScale, depthScale);
					}
				}
				TerrainModifier[] componentsInChildren2 = prefab.GetComponentsInChildren<TerrainModifier>(true);
				foreach (TerrainModifier val2 in componentsInChildren2)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						terrainScalingScope.TerrainModifiers.Add(new TerrainModifierState(val2));
						Apply(val2, radiusScale, depthScale);
					}
				}
				return terrainScalingScope;
			}
			catch
			{
				terrainScalingScope.Restore();
				throw;
			}
		}

		private static bool CanSpawnTerrainModifier(Character character, ItemData weapon, Attack? attack, out PendingTerrainDigText? fallbackText)
		{
			fallbackText = null;
			if (!TryResolveCostProfile(character, weapon, attack, out var profile))
			{
				return true;
			}
			float num = profile.BaseStaminaCost * profile.ExtraStaminaMultiplier;
			bool flag = HasEnoughDurability(character, weapon, profile.TotalDurabilityMultiplier);
			bool flag2 = HasEnoughDurability(character, weapon, 1f);
			if (!flag && !flag2)
			{
				return false;
			}
			if (num > 0f && !character.HaveStamina(num))
			{
				if (character.IsPlayer())
				{
					Hud instance = Hud.instance;
					if (instance != null)
					{
						instance.StaminaBarEmptyFlash();
					}
				}
				fallbackText = CreateVanillaFallbackText(profile, "stamina");
				return true;
			}
			if (!flag)
			{
				fallbackText = CreateVanillaFallbackText(profile, "durability");
				return true;
			}
			return true;
		}

		internal static void UpdateInput(Player player)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !TryResolveEquippedPickaxeTerrainDig(player, out ItemData weapon))
			{
				return;
			}
			float mouseScrollWheel = ZInput.GetMouseScrollWheel();
			if (Mathf.Abs(mouseScrollWheel) < 0.01f || !IsScaleModifierHeld() || !TryGetTerrainDigRule(weapon, out var rule))
			{
				return;
			}
			float currentScale = GetCurrentScale(weapon, rule);
			float selectedMaxScale = rule.SelectedMaxScale;
			float terrainToolRangeStep = GroundworkToolsDomain.TerrainToolRangeStep;
			float num = Mathf.Clamp(currentScale + (float)Math.Sign(mouseScrollWheel) * terrainToolRangeStep, 1f, selectedMaxScale);
			num = Mathf.Round(num / terrainToolRangeStep) * terrainToolRangeStep;
			num = Mathf.Clamp(num, 1f, selectedMaxScale);
			if (!(Mathf.Abs(num - currentScale) < 0.001f))
			{
				string weaponPrefabName = GetWeaponPrefabName(weapon);
				if (!string.IsNullOrWhiteSpace(weaponPrefabName))
				{
					CurrentScalesByPrefab[weaponPrefabName] = num;
					SuppressCameraZoomThisFrame = true;
					RefreshKeyHintUi();
				}
			}
		}

		internal static bool ShouldSuppressCameraZoomInput()
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null || !IsScaleModifierHeld() || !TryResolveEquippedPickaxeTerrainDig(Player.m_localPlayer, out ItemData _))
			{
				return SuppressCameraZoomThisFrame;
			}
			return true;
		}

		internal static void ClearCameraZoomSuppression()
		{
			SuppressCameraZoomThisFrame = false;
		}

		internal static void InitializeKeyHints(KeyHints hints)
		{
			_activeKeyHints = hints;
			_digHint = null;
			_showingDigHint = false;
			ClearDigHintCache();
			UpdateKeyHint(hints);
		}

		internal static void RefreshKeyHintUi()
		{
			if ((Object)(object)_activeKeyHints != (Object)null)
			{
				UpdateKeyHint(_activeKeyHints);
			}
		}

		internal static void UpdateKeyHint(KeyHints hints)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)hints == (Object)null)
			{
				return;
			}
			_activeKeyHints = hints;
			if (!GroundworkToolsDomain.ToolHudEnabled)
			{
				HideDigHint();
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !TryResolveEquippedPickaxeTerrainDig(localPlayer, out ItemData weapon))
			{
				HideDigHint();
				return;
			}
			EnsureDigHint(hints);
			KeyHintCell? digHint = _digHint;
			if (digHint == null || !digHint.IsValid)
			{
				return;
			}
			float currentScale = GetCurrentScale(weapon);
			string text = FormatShortcut(GroundworkToolsDomain.ToolWheelModifierHotkey);
			string text2 = ((text.Length == 0) ? GroundworkLocalization.Text("groundwork_state_unbound", "Unbound") : (text + "+" + GroundworkInputIcons.MouseWheel));
			string text3 = GroundworkLocalization.Format("groundwork_pickaxe_dig_scale", "Dig Scale {0}x", FormatScale(currentScale));
			if (!_showingDigHint || !string.Equals(_lastDigHintLabel, text3, StringComparison.Ordinal) || !string.Equals(_lastDigHintKeyText, text2, StringComparison.Ordinal))
			{
				if (text.Length == 0)
				{
					_digHint.Set(text3, new string[1] { text2 }, 0f, hideExtraTexts: true);
				}
				else
				{
					_digHint.SetWithMouseWheel(text3, new string[1] { text }, 0f, hideExtraTexts: true);
				}
				if (_digHint.ConsumeLayoutChange())
				{
					_digHint.RebuildParentLayout();
				}
				_showingDigHint = true;
				_lastDigHintLabel = text3;
				_lastDigHintKeyText = text2;
			}
		}

		internal static void AppendTerrainDigTooltip(ItemData? item, ref string tooltip)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (GroundworkToolsDomain.ToolHudEnabled && TryGetTerrainDigRule(item, out var rule))
			{
				string text = FormatShortcut(GroundworkToolsDomain.ToolWheelModifierHotkey);
				string text2 = FormatScale(rule.SelectedMaxScale);
				string obj = ((text.Length == 0) ? GroundworkLocalization.Format("groundwork_pickaxe_dig_scale_tooltip_unbound", "<color=orange>Unbound</color>: Dig Scale x1~x{0}", text2) : GroundworkLocalization.Format("groundwork_pickaxe_dig_scale_tooltip", "<color=orange>{0} + {2}</color> : Dig Scale x1~x{1}", text, text2, GroundworkInputIcons.MouseWheel));
				string text3 = GroundworkLocalization.Text("groundwork_pickaxe_dig_scale_tooltip_note", "Current scale is shown in the key hint below while equipped.");
				string text4 = obj + "\n" + text3;
				tooltip = (string.IsNullOrWhiteSpace(tooltip) ? text4 : (tooltip + "\n\n" + text4));
			}
		}

		private static bool TryResolvePrimaryTerrainDig(Character? character, ItemData? weapon, Attack? attack)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			if ((Object)(object)character != (Object)null && weapon?.m_shared != null && (int)weapon.m_shared.m_skillType == 12 && (Object)(object)weapon.m_shared.m_spawnOnHitTerrain != (Object)null && TryGetTerrainDigRule(weapon, out var _) && attack != null)
			{
				return !IsAlternateAttack(character, attack);
			}
			return false;
		}

		private static bool TryResolveCostProfile(Character character, ItemData weapon, Attack? attack, out TerrainCostProfile profile)
		{
			profile = default(TerrainCostProfile);
			if (!TryResolvePrimaryTerrainDig(character, weapon, attack))
			{
				return false;
			}
			if (!TryResolveTerrainDigScales(weapon, out var radiusScale, out var depthScale, out var rule))
			{
				return false;
			}
			float num = Mathf.Max(1f, radiusScale * radiusScale * depthScale);
			float num2 = 1f + (num - 1f) * rule.StaminaCostFactor;
			float num3 = 1f + (num - 1f) * rule.DurabilityFactor;
			float selectedScale = Mathf.Max(radiusScale, depthScale);
			if (num2 <= 1.001f && num3 <= 1.001f)
			{
				return false;
			}
			profile = new TerrainCostProfile((attack != null) ? GameAccess.AttackStamina(attack) : 0f, num2, num3, selectedScale);
			return true;
		}

		private static bool TryResolveTerrainDigScales(ItemData weapon, out float radiusScale, out float depthScale, out TerrainDigRule rule)
		{
			radiusScale = 1f;
			depthScale = 1f;
			if (!TryGetTerrainDigRule(weapon, out rule))
			{
				return false;
			}
			float currentScale = GetCurrentScale(weapon, rule);
			radiusScale = Mathf.Min(currentScale, rule.RadiusMaxScale);
			depthScale = Mathf.Min(currentScale, rule.DepthMaxScale);
			return true;
		}

		private static bool TryGetTerrainDigRule(ItemData? weapon, out TerrainDigRule rule)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			rule = default(TerrainDigRule);
			if (weapon?.m_shared == null || (int)weapon.m_shared.m_skillType != 12 || (Object)(object)weapon.m_shared.m_spawnOnHitTerrain == (Object)null)
			{
				return false;
			}
			string weaponPrefabName = GetWeaponPrefabName(weapon);
			NormalizedTerrainToolConfig normalizedTerrainToolConfig = FindTerrainDigConfig("Pickaxe");
			NormalizedTerrainToolConfig normalizedTerrainToolConfig2 = (weaponPrefabName.Equals("Pickaxe", StringComparison.OrdinalIgnoreCase) ? null : FindTerrainDigConfig(weaponPrefabName));
			NormalizedTerrainToolConfig normalizedTerrainToolConfig3 = normalizedTerrainToolConfig2 ?? normalizedTerrainToolConfig;
			if (normalizedTerrainToolConfig3 == null)
			{
				return false;
			}
			rule = TerrainDigRule.FromConfig(normalizedTerrainToolConfig3, (normalizedTerrainToolConfig2 != null) ? normalizedTerrainToolConfig : null);
			return rule.SelectedMaxScale > 1.001f;
		}

		private static NormalizedTerrainToolConfig? FindTerrainDigConfig(string toolPrefabName)
		{
			if (string.IsNullOrWhiteSpace(toolPrefabName))
			{
				return null;
			}
			foreach (NormalizedTerrainToolConfig terrainTool in GroundworkPlugin.TerrainTools)
			{
				if (terrainTool.ToolPrefabName.Equals(toolPrefabName, StringComparison.OrdinalIgnoreCase) && terrainTool.PiecePrefabName.Equals("terrainDig", StringComparison.OrdinalIgnoreCase))
				{
					return terrainTool;
				}
			}
			return null;
		}

		private static float GetCurrentScale(ItemData weapon)
		{
			if (!TryGetTerrainDigRule(weapon, out var rule))
			{
				return 1f;
			}
			return GetCurrentScale(weapon, rule);
		}

		private static float GetCurrentScale(ItemData weapon, TerrainDigRule rule)
		{
			string weaponPrefabName = GetWeaponPrefabName(weapon);
			if (string.IsNullOrWhiteSpace(weaponPrefabName))
			{
				return 1f;
			}
			if (!CurrentScalesByPrefab.TryGetValue(weaponPrefabName, out var value))
			{
				value = 1f;
				CurrentScalesByPrefab[weaponPrefabName] = value;
			}
			float num = Mathf.Clamp(value, 1f, rule.SelectedMaxScale);
			if (Mathf.Abs(num - value) > 0.001f)
			{
				CurrentScalesByPrefab[weaponPrefabName] = num;
			}
			return num;
		}

		private static string GetWeaponPrefabName(ItemData? weapon)
		{
			if (!((Object)(object)weapon?.m_dropPrefab != (Object)null))
			{
				return "";
			}
			return ((Object)weapon.m_dropPrefab).name;
		}

		private static bool TryResolveEquippedPickaxeTerrainDig(Player player, out ItemData? weapon)
		{
			weapon = GameAccess.RightItem.Invoke((Humanoid)(object)player);
			TerrainDigRule rule;
			return TryGetTerrainDigRule(weapon, out rule);
		}

		private static bool IsAlternateAttack(Character character, Attack attack)
		{
			Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null);
			if (val != null && GameAccess.CurrentAttack.Invoke(val) == attack)
			{
				return GameAccess.SecondaryAttack.Invoke(val);
			}
			return false;
		}

		private static bool IsScaleModifierHeld()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return GroundworkToolsDomain.ToolWheelModifierHotkey.IsKeyHeld();
		}

		private static void EnsureDigHint(KeyHints hints)
		{
			GameObject val = ResolveVanillaCombatHintTemplate(hints);
			if ((Object)(object)val == (Object)null || (Object)(object)val.transform.parent == (Object)null)
			{
				return;
			}
			if (_digHint != null && (Object)(object)_digHint.Root.transform.parent == (Object)(object)val.transform.parent)
			{
				_digHint.MoveBefore(val);
				return;
			}
			if (_digHint != null)
			{
				Object.Destroy((Object)(object)_digHint.Root);
				_digHint = null;
				_showingDigHint = false;
				ClearDigHintCache();
			}
			_digHint = KeyHintCell.CloneFrom(val, "Groundwork_PickaxeTerrainDigHint", hideOnRestore: true);
			_digHint?.MoveBefore(val);
			ClearDigHintCache();
		}

		private static GameObject? ResolveVanillaCombatHintTemplate(KeyHints hints)
		{
			GameObject val = (ZInput.IsGamepadActive() ? hints.m_primaryAttackGP : hints.m_primaryAttackKB);
			if (KeyHintCell.IsUsableTemplate(val))
			{
				return val;
			}
			GameObject val2 = (ZInput.IsGamepadActive() ? hints.m_primaryAttackKB : hints.m_primaryAttackGP);
			if (KeyHintCell.IsUsableTemplate(val2))
			{
				return val2;
			}
			GameObject val3 = (ZInput.IsGamepadActive() ? hints.m_secondaryAttackGP : hints.m_secondaryAttackKB);
			if (KeyHintCell.IsUsableTemplate(val3))
			{
				return val3;
			}
			GameObject val4 = (ZInput.IsGamepadActive() ? hints.m_secondaryAttackKB : hints.m_secondaryAttackGP);
			if (KeyHintCell.IsUsableTemplate(val4))
			{
				return val4;
			}
			if ((Object)(object)hints.m_combatHints == (Object)null)
			{
				return null;
			}
			return (from Transform transform in (IEnumerable)(KeyHintCell.FindParentWithTemplates(hints.m_combatHints, ZInput.IsGamepadActive() ? "Gamepad" : "Keyboard") ?? KeyHintCell.FindParentWithTemplates(hints.m_combatHints, "Keyboard") ?? KeyHintCell.FindParentWithTemplates(hints.m_combatHints, "Gamepad") ?? hints.m_combatHints.transform)
				select ((Component)transform).gameObject).FirstOrDefault((Func<GameObject, bool>)KeyHintCell.IsUsableTemplate);
		}

		private static void SetDigHintActive(bool active)
		{
			if (!((Object)(object)_digHint?.Root == (Object)null) && _digHint.Root.activeSelf != active)
			{
				_digHint.SetActive(active);
				_digHint.RebuildParentLayout();
			}
		}

		private static void HideDigHint()
		{
			if (_showingDigHint)
			{
				SetDigHintActive(active: false);
				_showingDigHint = false;
				ClearDigHintCache();
			}
		}

		private static void ClearDigHintCache()
		{
			_lastDigHintLabel = null;
			_lastDigHintKeyText = null;
		}

		private unsafe static string FormatShortcut(KeyboardShortcut shortcut)
		{
			//IL_0002: 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)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0)
			{
				return "";
			}
			List<string> list = ((KeyboardShortcut)(ref shortcut)).Modifiers.Select((KeyCode modifier) => ((object)(*(KeyCode*)(&modifier))/*cast due to .constrained prefix*/).ToString()).ToList();
			list.Add(((object)((KeyboardShortcut)(ref shortcut)).MainKey/*cast due to .constrained prefix*/).ToString());
			return string.Join("+", list);
		}

		private static bool HasEnoughDurability(Character character, ItemData weapon, float totalDurabilityMultiplier)
		{
			if (!weapon.m_shared.m_useDurability || !character.IsPlayer())
			{
				return true;
			}
			float num = GetItemDurabilityDrain(weapon) * totalDurabilityMultiplier;
			if (!(num <= 0f))
			{
				return weapon.m_durability + 0.001f >= num;
			}
			return true;
		}

		private static PendingTerrainDigText CreateVanillaFallbackText(TerrainCostProfile profile, string missingResource)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			return new PendingTerrainDigText(GroundworkLocalization.Format("groundwork_pickaxe_not_enough_resource", "Not enough {0} for x{1} terrain dig", FormatResourceName(missingResource), FormatScale(profile.SelectedScale)), new Color(1f, 0.63f, 0.2f, 1f));
		}

		private static string FormatScale(float scale)
		{
			return scale.ToString("0.#", CultureInfo.InvariantCulture);
		}

		private static float GetItemDurabilityDrain(ItemData? weapon)
		{
			float valueOrDefault = (weapon?.m_shared?.m_useDurabilityDrain).GetValueOrDefault();
			return ((valueOrDefault > 0f) ? valueOrDefault : 1f) * Game.m_durabilityRate;
		}

		private static void Apply(Settings settings, float radiusScale, float depthScale)
		{
			if (settings.m_level)
			{
				settings.m_levelRadius *= radiusScale;
			}
			if (settings.m_raise)
			{
				settings.m_raiseRadius *= radiusScale;
				if (settings.m_raiseDelta < 0f)
				{
					settings.m_raiseDelta *= depthScale;
				}
			}
			if (settings.m_smooth)
			{
				settings.m_smoothRadius *= radiusScale;
			}
		}

		private static void Apply(TerrainModifier modifier, float radiusScale, float depthScale)
		{
			if (modifier.m_level)
			{
				modifier.m_levelRadius *= radiusScale;
				if (modifier.m_levelOffset < 0f)
				{
					modifier.m_levelOffset *= depthScale;
				}
			}
			if (modifier.m_smooth)
			{
				modifier.m_smoothRadius *= radiusScale;
			}
		}

		private static string FormatResourceName(string resource)
		{
			if (!(resource == "stamina"))
			{
				if (resource == "durability")
				{
					return GroundworkLocalization.Text("groundwork_resource_durability", "durability");
				}
				return resource;
			}
			return Groundwor