Decompiled source of Groundwork v1.0.9

Groundwork.dll

Decompiled a week 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.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.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.0.9")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.9.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 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) && !target.m_translations.ContainsKey(key))
				{
					value2[key] = text;
					target.AddWord(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()));
			}
			localization.AddWord(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.0.9")]
	[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 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> 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_00de: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fd: 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);
				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<float> ForagingRespawnSpeedFactor;

			internal ConfigEntry<float> PlantGrowSpeedFactor;

			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<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_0166: Unknown result type (might be due to invalid IL or missing references)
				//IL_0171: Expected O, but got Unknown
				//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
				//IL_01ab: Expected O, but got Unknown
				//IL_01da: Unknown result type (might be due to invalid IL or missing references)
				//IL_01e5: Expected O, but got Unknown
				//IL_020a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0215: Expected O, but got Unknown
				//IL_0244: Unknown result type (might be due to invalid IL or missing references)
				//IL_024f: Expected O, but got Unknown
				//IL_027e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0289: Expected O, but got Unknown
				//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_02c3: Expected O, but got Unknown
				//IL_02f2: Unknown result type (might be due to invalid IL or missing references)
				//IL_02fd: Expected O, but got Unknown
				//IL_032c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0337: Expected O, but got Unknown
				//IL_035c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0367: Expected O, but got Unknown
				//IL_0396: Unknown result type (might be due to invalid IL or missing references)
				//IL_03a1: Expected O, but got Unknown
				//IL_03d0: Unknown result type (might be due to invalid IL or missing references)
				//IL_03db: Expected O, but got Unknown
				//IL_040a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0415: 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 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, 10f), Array.Empty<object>()));
				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>()));
				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", 2f, new ConfigDescription("Honey production multiplier at 0% cover. The bonus uses a fixed exponent 2 curve 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>()));
				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>()));
			}
		}

		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.0.9";

		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";

		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.0.9",
			MinimumRequiredVersion = "1.0.9"
		};

		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;
			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();
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
		}

		public void OnDestroy()
		{
			RunShutdownStep("Harmony patches", (Action)_harmony.UnpatchSelf);
			RunShutdownStep("GrowthOverrideSystem", GrowthOverrideSystem.Shutdown);
			RunShutdownStep("MassPlantingSystem", MassPlantingSystem.Shutdown);
			RunShutdownStep("TerrainToolRangeSystem", TerrainToolRangeSystem.Shutdown);
			RunShutdownStep("PickaxeTerrainScalingSystem", PickaxeTerrainScalingSystem.Shutdown);
			RunShutdownStep("TerrainDigFloatingTextSystem", TerrainDigFloatingTextSystem.Clear);
			RunShutdownStep("FarmingSkillSystem", FarmingSkillSystem.Shutdown);
			RunShutdownStep("ZenBeehiveCompatSystem", ZenBeehiveCompatSystem.Shutdown);
			RunShutdownStep("BeehivePollinationSystem", BeehivePollinationSystem.Shutdown);
			RunShutdownStep("ScytheHarvestSystem", ScytheHarvestSystem.Shutdown);
			RunShutdownStep("ScytheToolCompatSystem", ScytheToolCompatSystem.Shutdown);
			RunShutdownStep("CameraZoomInputSuppressionSystem", CameraZoomInputSuppressionSystem.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);
				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 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);
					TerrainDigFloatingTextSystem.Update();
				}
				PickaxeTerrainScalingSystem.UpdateInput(__instance);
			}
			finally
			{
				MassPlantingSystem.ClearPlayerUpdateInput();
			}
		}

		private static void Finalizer()
		{
			MassPlantingSystem.ClearPlayerUpdateInput();
		}
	}
	[HarmonyPatch(typeof(Player), "UpdatePlacementGhost")]
	internal static class PlayerUpdatePlacementGhostGroundworkPatch
	{
		private static void Postfix(Player __instance)
		{
			MassPlantingSystem.TrySnapPlacementGhost(__instance);
			MassPlantingSystem.UpdatePlacementPreview(__instance);
			TerrainToolRangeSystem.ApplyCurrentRangeToGhost(__instance);
		}
	}
	[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)
		{
			GrowthOverrideSystem.OnZNetSceneReady(__instance);
			ScytheHarvestSystem.RefreshCultivatedPickables(__instance);
			FarmingSkillSystem.RefreshForagingBonusEffectFallback(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "PlacePiece")]
	internal static class PlayerPlacePieceGroundworkPatch
	{
		private static void Prefix(Player __instance, Piece piece)
		{
			FarmingSkillSystem.BeginPlacePiece(__instance);
			BeehivePollinationSystem.BeginPlacePiece(__instance, piece);
		}

		private static void Finalizer()
		{
			FarmingSkillSystem.EndPlacePiece();
			BeehivePollinationSystem.EndPlacePiece();
		}
	}
	[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(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 IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo inputGetAxis = AccessTools.Method(typeof(Input), "GetAxis", new Type[1] { typeof(string) }, (Type[])null);
			MethodInfo inputGetAxisForCamera = AccessTools.Method(typeof(CameraZoomInputSuppressionSystem), "GetAxisForCamera", (Type[])null, (Type[])null);
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, inputGetAxis))
				{
					instruction.operand = inputGetAxisForCamera;
					yield return instruction;
				}
				else
				{
					yield return instruction;
				}
			}
		}

		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 float GetAxisForCamera(string axisName)
		{
			float axis = Input.GetAxis(axisName);
			if (!IsMouseScrollAxis(axisName) || !ShouldSuppressCameraZoomInput())
			{
				return axis;
			}
			return 0f;
		}

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

		private static bool ShouldSuppressCameraZoomInput()
		{
			if (!TerrainToolRangeSystem.ShouldSuppressCameraZoomInput() && !MassPlantingSystem.ShouldSuppressCameraZoomInput())
			{
				return PickaxeTerrainScalingSystem.ShouldSuppressCameraZoomInput();
			}
			return true;
		}

		private static bool IsMouseScrollAxis(string axisName)
		{
			if (!axisName.Equals("Mouse ScrollWheel", StringComparison.OrdinalIgnoreCase))
			{
				return axisName.Equals("Mouse Scroll Wheel", StringComparison.OrdinalIgnoreCase);
			}
			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)
		{
			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 = _attack.GetAttackStamina() * 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)
			{
				float currentScale = GetCurrentScale(weapon);
				string text = FormatShortcut(GroundworkToolsDomain.ToolWheelModifierHotkey);
				string text2 = ((text.Length == 0) ? GroundworkLocalization.Text("groundwork_state_unbound", "Unbound") : (text + "+Wheel"));
				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))
				{
					_digHint.Set(text3, new string[1] { text2 }, 0f, hideExtraTexts: true);
					_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} + Wheel</color>: Dig Scale x1~x{1}", text, text2));
				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) ? attack.GetAttackStamina() : 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 = ((Humanoid)player).GetRightItem();
			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 && val.m_currentAttack == attack)
			{
				return val.m_currentAttackIsSecondary;
			}
			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();
			if (!(valueOrDefault > 0f))
			{
				return 1f;
			}
			return valueOrDefault;
		}

		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 GroundworkLocalization.Text("groundwork_resource_stamina", "stamina");
		}
	}
	[HarmonyPatch(typeof(Attack), "DoMeleeAttack")]
	internal static class AttackDoMeleeAttackPickaxeTerrainScalingPatch
	{
		private static void Prefix(Attack __instance)
		{
			PickaxeTerrainScalingSystem.BeginMeleeAttack(__instance);
		}

		private static void Finalizer(Attack __instance)
		{
			PickaxeTerrainScalingSystem.EndMeleeAttack(__instance);
		}
	}
	[HarmonyPatch(typeof(Attack), "SpawnOnHitTerrain")]
	internal static class AttackSpawnOnHitTerrainPickaxeScalingPatch
	{
		private static bool Prefix(GameObject prefab, Character character, ItemData weapon, ref GameObject? __result, out PickaxeTerrainScalingSystem.TerrainSpawnState __state)
		{
			if (!PickaxeTerrainScalingSystem.TryBeginSpawn(prefab, character, weapon, out __state))
			{
				__result = null;
				return false;
			}
			return true;
		}

		private static void Postfix(GameObject? __result, PickaxeTerrainScalingSystem.TerrainSpawnState? __state)
		{
			__state?.Complete(__result);
		}

		private static Exception? Finalizer(PickaxeTerrainScalingSystem.TerrainSpawnState? __state, Exception? __exception)
		{
			__state?.Restore();
			return __exception;
		}
	}
	[HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[]
	{
		typeof(ItemData),
		typeof(int),
		typeof(bool),
		typeof(float),
		typeof(int)
	})]
	internal static class ItemDataGetTooltipPickaxeTerrainScalingPatch
	{
		private static void Postfix(ItemData item, ref string __result)
		{
			PickaxeTerrainScalingSystem.AppendTerrainDigTooltip(item, ref __result);
		}
	}
	internal static class GrowthOverrideSystem
	{
		private enum AuthorityMode
		{
			Unknown,
			LocalFiles,
			SyncedOnly
		}

		internal readonly struct ResolvedPickableRule
		{
			internal readonly bool HasRespawnOverride;

			internal readonly float RespawnMinutes;

			internal readonly bool? ForagingTarget;

			internal readonly bool? BonusYield;

			internal readonly bool HasMaxChance;

			internal readonly float MaxChanceAtLevel100;

			internal readonly bool HasBonusAmount;

			internal readonly int BonusAmount;

			internal ResolvedPickableRule(bool hasRespawnOverride, float respawnMinutes, bool? foragingTarget, bool? bonusYield, bool hasMaxChance, float maxChanceAtLevel100, bool hasBonusAmount, int bonusAmount)
			{
				HasRespawnOverride = hasRespawnOverride;
				RespawnMinutes = respawnMinutes;
				ForagingTarget = foragingTarget;
				BonusYield = bonusYield;
				HasMaxChance = hasMaxChance;
				MaxChanceAtLevel100 = maxChanceAtLevel100;
				HasBonusAmount = hasBonusAmount;
				BonusAmount = bonusAmount;
			}
		}

		internal readonly struct ResolvedPlantRule
		{
			internal readonly bool HasGrowTimeOverride;

			internal readonly float GrowSecondsMin;

			internal readonly float GrowSecondsMax;

			internal ResolvedPlantRule(bool hasGrowTimeOverride, float growSecondsMin, float growSecondsMax)
			{
				HasGrowTimeOverride = hasGrowTimeOverride;
				GrowSecondsMin = growSecondsMin;
				GrowSecondsMax = growSecondsMax;
			}
		}

		private sealed class FarmingTupleYamlConverter : IYamlTypeConverter
		{
			private const int TupleLength = 4;

			public bool Accepts(Type type)
			{
				return type == typeof(PickableFarmingOverride);
			}

			public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
			{
				SequenceStart sequenceStart = parser.Consume<SequenceStart>();
				if (sequenceStart.Style != SequenceStyle.Flow)
				{
					throw new InvalidDataException("farming must be a flow tuple: [foragingTarget, bonusYield, maxChanceAtLevel100, bonusAmount].");
				}
				if (!sequenceStart.Anchor.IsEmpty || !sequenceStart.Tag.IsEmpty)
				{
					throw new InvalidDataException("farming tuples cannot use YAML anchors or tags.");
				}
				List<YamlDotNet.Core.Events.Scalar> list = new List<YamlDotNet.Core.Events.Scalar>(4);
				SequenceEnd @event;
				while (!parser.Accept<SequenceEnd>(out @event))
				{
					if (list.Count == 4)
					{
						throw new InvalidDataException("farming must contain exactly four scalar values.");
					}
					YamlDotNet.Core.Events.Scalar scalar = parser.Consume<YamlDotNet.Core.Events.Scalar>();
					if (!scalar.Anchor.IsEmpty || !scalar.Tag.IsEmpty)
					{
						throw new InvalidDataException("farming tuple values cannot use YAML anchors or tags.");
					}
					list.Add(scalar);
				}
				parser.Consume<SequenceEnd>();
				if (list.Count != 4)
				{
					throw new InvalidDataException("farming must contain exactly four scalar values.");
				}
				return new PickableFarmingOverride
				{
					ForagingTarget = ParseNullableBool(list[0], "foragingTarget"),
					BonusYield = ParseNullableBool(list[1], "bonusYield"),
					MaxChanceAtLevel100 = ParseNullableChance(list[2], "maxChanceAtLevel100"),
					BonusAmount = ParseNullableNonNegativeInt(list[3], "bonusAmount")
				};
			}

			public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
			{
				if (!(value is PickableFarmingOverride pickableFarmingOverride))
				{
					throw new InvalidDataException("farming tuple value is missing.");
				}
				emitter.Emit(new SequenceStart(AnchorName.Empty, TagName.Empty, isImplicit: true, SequenceStyle.Flow));
				EmitScalar(emitter, FormatNullable(pickableFarmingOverride.ForagingTarget));
				EmitScalar(emitter, FormatNullable(pickableFarmingOverride.BonusYield));
				EmitScalar(emitter, FormatNullable(pickableFarmingOverride.MaxChanceAtLevel100));
				EmitScalar(emitter, FormatNullable(pickableFarmingOverride.BonusAmount));
				emitter.Emit(new SequenceEnd());
			}

			private static bool? ParseNullableBool(YamlDotNet.Core.Events.Scalar scalar, string field)
			{
				if (IsPlainNull(scalar))
				{
					return null;
				}
				if (!bool.TryParse(RequirePlainValue(scalar, field), out var result))
				{
					throw new InvalidDataException("farming." + field + " must be true, false, or null.");
				}
				return result;
			}

			private static float? ParseNullableChance(YamlDotNet.Core.Events.Scalar scalar, string field)
			{
				if (IsPlainNull(scalar))
				{
					return null;
				}
				if (!float.TryParse(RequirePlainValue(scalar, field), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !IsFinite(result) || result < 0f || result > 1f)
				{
					throw new InvalidDataException("farming." + field + " must be between 0 and 1, or null.");
				}
				return result;
			}

			private static int? ParseNullableNonNegativeInt(YamlDotNet.Core.Events.Scalar scalar, string field)
			{
				if (IsPlainNull(scalar))
				{
					return null;
				}
				if (!int.TryParse(RequirePlainValue(scalar, field), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0)
				{
					throw new InvalidDataException("farming." + field + " must be 0 or greater, or null.");
				}
				return result;
			}

			private static bool IsPlainNull(YamlDotNet.Core.Events.Scalar scalar)
			{
				if (scalar.Style == ScalarStyle.Plain)
				{
					if (!scalar.Value.Equals("null", StringComparison.OrdinalIgnoreCase))
					{
						return scalar.Value.Equals("~", StringComparison.Ordinal);
					}
					return true;
				}
				return false;
			}

			private static string RequirePlainValue(YamlDotNet.Core.Events.Scalar scalar, string field)
			{
				if (scalar.Style != ScalarStyle.Plain || string.IsNullOrWhiteSpace(scalar.Value))
				{
					throw new InvalidDataException("farming." + field + " must be an unquoted scalar value.");
				}
				return scalar.Value.Trim();
			}

			private static string FormatNullable(bool? value)
			{
				if (!value.HasValue)
				{
					return "null";
				}
				if (!value.Value)
				{
					return "false";
				}
				return "true";
			}

			private static string FormatNullable(float? value)
			{
				if (!value.HasValue)
				{
					return "null";
				}
				return FormatFloat(value.Value);
			}

			private static string FormatNullable(int? value)
			{
				return value?.ToString(CultureInfo.InvariantCulture) ?? "null";
			}

			private static void EmitScalar(IEmitter emitter, string value)
			{
				emitter.Emit(new YamlDotNet.Core.Events.Scalar(AnchorName.Empty, TagName.Empty, value, ScalarStyle.Plain, isPlainImplicit: true, isQuotedImplicit: false));
			}
		}

		private const string PickablesOverrideFileName = "pickables.yml";

		private const string PickablesReferenceFileName = "pickables.reference.yml";

		private const string PlantsOverrideFileName = "plants.yml";

		private const string PlantsReferenceFileName = "plants.reference.yml";

		private const string SyncedYamlIdentifier = "groundwork_growth_yaml";

		private const double ReloadDebounceMilliseconds = 350.0;

		private const float SceneSettleDelaySeconds = 1f;

		private static readonly FarmingTupleYamlConverter FarmingTupleConverter = new FarmingTupleYamlConverter();

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

		private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).WithTypeConverter(FarmingTupleConverter).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull)
			.DisableAliases()
			.Build();

		private static Dictionary<string, PickableGrowthOverride> _pickableRules = new Dictionary<string, PickableGrowthOverride>(StringComparer.OrdinalIgnoreCase);

		private static Dictionary<string, PlantGrowthOverride> _plantRules = new Dictionary<string, PlantGrowthOverride>(StringComparer.OrdinalIgnoreCase);

		private static GroundworkPlugin? _owner;

		private static ConfigSync? _configSync;

		private static CustomSyncedValue<string>? _syncedYaml;

		private static FileSystemWatcher? _watcher;

		private static System.Timers.Timer? _reloadTimer;

		private static Coroutine? _sceneRefreshCoroutine;

		private static ZNetScene? _scene;

		private static AuthorityMode _authorityMode;

		private static string? _lastAppliedNormalizedYaml;

		private static string ConfigDirectoryPath => GroundworkPlugin.YamlConfigDirectoryPath;

		private static string PickablesOverrideFilePath => Path.Combine(ConfigDirectoryPath, "pickables.yml");

		private static string PickablesReferenceFilePath => Path.Combine(ConfigDirectoryPath, "pickables.reference.yml");

		private static string PlantsOverrideFilePath => Path.Combine(ConfigDirectoryPath, "plants.yml");

		private static string PlantsReferenceFilePath => Path.Combine(ConfigDirectoryPath, "plants.reference.yml");

		internal static void Initialize(GroundworkPlugin owner, ConfigSync configSync)
		{
			_owner = owner;
			_configSync = configSync;
			_configSync.SourceOfTruthChanged += OnSourceOfTruthChanged;
			_syncedYaml = new CustomSyncedValue<string>(configSync, "groundwork_growth_yaml", "");
			_syncedYaml.ValueChanged += OnSyncedYamlChanged;
			RefreshAuthority(force: true);
		}

		internal static void Shutdown()
		{
			StopSceneRefreshCoroutine();
			DisposeWatcher();
			if (_syncedYaml != null)
			{
				_syncedYaml.ValueChanged -= OnSyncedYamlChanged;
				_syncedYaml = null;
			}
			if (_configSync != null)
			{
				_configSync.SourceOfTruthChanged -= OnSourceOfTruthChanged;
				_configSync = null;
			}
			_pickableRules = new Dictionary<string, PickableGrowthOverride>(StringComparer.OrdinalIgnoreCase);
			_plantRules = new Dictionary<string, PlantGrowthOverride>(StringComparer.OrdinalIgnoreCase);
			_lastAppliedNormalizedYaml = null;
			_scene = null;
			_owner = null;
			_authorityMode = AuthorityMode.Unknown;
		}

		internal static void RefreshAuthority(bool force = false)
		{
			AuthorityMode authorityMode = (UsesLocalAuthorityFiles() ? AuthorityMode.LocalFiles : AuthorityMode.SyncedOnly);
			if (force || authorityMode != _authorityMode)
			{
				_authorityMode = authorityMode;
				switch (authorityMode)
				{
				case AuthorityMode.LocalFiles:
					SetupWatcher();
					ReloadFromDiskAndSync();
					GroundworkPlugin.ModLogger.LogInfo((object)"Groundwork growth YAML authority mode: LocalFiles.");
					break;
				case AuthorityMode.SyncedOnly:
					DisposeWatcher();
					ApplySyncedYamlText(_syncedYaml?.Value ?? "", "server-synced growth configuration");
					GroundworkPlugin.ModLogger.LogInfo((object)"Groundwork growth YAML authority mode: SyncedOnly.");
					break;
				}
			}
		}