Decompiled source of Groundwork v1.0.2

Groundwork.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.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 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.2")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.2.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;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class ExtensionMarkerAttribute : Attribute
	{
		private readonly string <Name>k__BackingField;

		public string Name => <Name>k__BackingField;

		public ExtensionMarkerAttribute(string name)
		{
			<Name>k__BackingField = name;
		}
	}
}
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;

		private static readonly Dictionary<string, Dictionary<string, string>> LoadedTexts;

		private static readonly ConditionalWeakTable<Localization, LanguageState> LocalizationLanguages;

		private static readonly List<WeakReference<Localization>> LocalizationObjects;

		private static readonly List<string> FileExtensions;

		private static BaseUnityPlugin? _plugin;

		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()
		{
			_ = Plugin;
		}

		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);
		}

		static Localizer()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Expected O, but got Unknown
			PlaceholderProcessors = new Dictionary<string, Dictionary<string, Func<string>>>();
			LoadedTexts = new Dictionary<string, Dictionary<string, string>>();
			LocalizationLanguages = new ConditionalWeakTable<Localization, LanguageState>();
			LocalizationObjects = new List<WeakReference<Localization>>();
			FileExtensions = new List<string>(2) { ".json", ".yml" };
			Harmony val = new Harmony("org.bepinex.helpers.LocalizationManager");
			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);
		}
	}
	public static class LocalizationManagerVersion
	{
		public const string Version = "1.4.0";
	}
}
namespace Groundwork
{
	[BepInPlugin("sighsorry.Groundwork", "Groundwork", "1.0.2")]
	[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 and Pickaxe terrain dig scale HUD 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 must have respawnTimeMinutes > 0 and drop edible food. 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 must have respawnTimeMinutes > 0 and drop edible food. 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 respawning edible pickables such as berry bushes. 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;

			[UsedImplicitly]
			public bool? Browsable;

			[UsedImplicitly]
			public string? Category;

			[UsedImplicitly]
			public Action<ConfigEntryBase>? CustomDrawer;
		}

		internal const string ModName = "Groundwork";

		internal const string ModVersion = "1.0.2";

		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 long ReloadDelayTicks = 10000000L;

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

		private static readonly object ReloadLock = new object();

		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 DateTime _lastYamlReloadTime;

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

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

		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();
			GroundworkConfigLoader.EnsureLocalFileExists(Paths.ConfigPath, TerrainToolsYamlFilePath);
			RefreshYamlAuthorityMode(force: true);
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			_harmony.PatchAll(executingAssembly);
			((BaseUnityPlugin)this).Config.Save();
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
		}

		public void OnDestroy()
		{
			_harmony.UnpatchSelf();
			DisposeSyncedYamlValue();
			DisposeWatcher();
			if (Instance == this)
			{
				Instance = null;
			}
		}

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

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

		private void SetupWatcher()
		{
			if (_watcher == null)
			{
				Directory.CreateDirectory(Paths.ConfigPath);
				_watcher = new FileSystemWatcher(Paths.ConfigPath, "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)
			{
				return;
			}
			DateTime now = DateTime.Now;
			if (now.Ticks - _lastYamlReloadTime.Ticks < 10000000)
			{
				return;
			}
			lock (ReloadLock)
			{
				ReloadLocalYaml();
				_lastYamlReloadTime = now;
			}
		}

		private void ReloadLocalYaml()
		{
			if (_yamlAuthorityMode != YamlAuthorityMode.LocalFiles)
			{
				return;
			}
			GroundworkConfigLoader.EnsureLocalFileExists(Paths.ConfigPath, 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()
		{
			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
	{
		[SpecialName]
		public sealed class <G>$8D1D3E80A18AA9715780B6CB7003B2F1
		{
			[SpecialName]
			public static class <M>$895AB635D4D087636CF1C26BA650BA11
			{
			}

			[ExtensionMarker("<M>$895AB635D4D087636CF1C26BA650BA11")]
			public bool IsKeyDown()
			{
				throw new NotSupportedException();
			}

			[ExtensionMarker("<M>$895AB635D4D087636CF1C26BA650BA11")]
			public bool IsKeyHeld()
			{
				throw new NotSupportedException();
			}
		}

		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;
		}
	}
	public static class ToggleExtentions
	{
		[SpecialName]
		public sealed class <G>$7D3A7D21A6278DC448B44D17CEAD26C3
		{
			[SpecialName]
			public static class <M>$BB06BE796F16E57A5B3F898CD89BBFD3
			{
			}

			[ExtensionMarker("<M>$BB06BE796F16E57A5B3F898CD89BBFD3")]
			public bool IsOn()
			{
				throw new NotSupportedException();
			}

			[ExtensionMarker("<M>$BB06BE796F16E57A5B3F898CD89BBFD3")]
			public bool IsOff()
			{
				throw new NotSupportedException();
			}
		}

		public static bool IsOn(this GroundworkPlugin.Toggle value)
		{
			return value == GroundworkPlugin.Toggle.On;
		}

		public static bool IsOff(this GroundworkPlugin.Toggle value)
		{
			return value == GroundworkPlugin.Toggle.Off;
		}
	}
	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 = GroundworkTerrainToolNormalizer.Normalize(parsed);
			return true;
		}

		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 static class GroundworkHarmonyDispatch
	{
		internal static void PlayerUpdatePostfix(Player player)
		{
			if ((Object)(object)player == (Object)(object)Player.m_localPlayer)
			{
				MassPlantingSystem.RefreshBuildHintUi();
				MassPlantingSystem.UpdateInput(player);
				TerrainToolRangeSystem.UpdateInput(player);
				PickaxeTerrainScalingSystem.RefreshKeyHintUi();
				TerrainDigFloatingTextSystem.Update();
			}
			PickaxeTerrainScalingSystem.UpdateInput(player);
		}

		internal static void PlayerUpdatePlacementGhostPostfix(Player player)
		{
			MassPlantingSystem.TrySnapPlacementGhost(player);
			MassPlantingSystem.UpdatePlacementPreview(player);
			TerrainToolRangeSystem.ApplyCurrentRangeToGhost(player);
		}

		internal static bool PlayerTryPlacePiecePrefix(Player player, Piece piece, ref bool result)
		{
			TerrainToolRangeSystem.BeginTryPlacePiece(player, piece);
			return MassPlantingSystem.TryInterceptPlace(player, piece, ref result);
		}

		internal static void PlayerTryPlacePiecePostfix(Player player, Piece piece, bool result)
		{
			TerrainToolRangeSystem.EndTryPlacePiece(player, piece, result);
		}
	}
	[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();
				GroundworkHarmonyDispatch.PlayerUpdatePostfix(__instance);
			}
			finally
			{
				MassPlantingSystem.ClearPlayerUpdateInput();
			}
		}

		private static void Finalizer()
		{
			MassPlantingSystem.ClearPlayerUpdateInput();
		}
	}
	[HarmonyPatch(typeof(Player), "UpdatePlacementGhost")]
	internal static class PlayerUpdatePlacementGhostGroundworkPatch
	{
		private static void Postfix(Player __instance)
		{
			GroundworkHarmonyDispatch.PlayerUpdatePlacementGhostPostfix(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	internal static class PlayerTryPlacePieceGroundworkPatch
	{
		private static bool Prefix(Player __instance, Piece piece, ref bool __result)
		{
			return GroundworkHarmonyDispatch.PlayerTryPlacePiecePrefix(__instance, piece, ref __result);
		}

		private static void Postfix(Player __instance, Piece piece, bool __result)
		{
			GroundworkHarmonyDispatch.PlayerTryPlacePiecePostfix(__instance, piece, __result);
		}
	}
	[HarmonyPatch(typeof(Player), "Start")]
	internal static class PlayerStartGroundworkPatch
	{
		private static void Postfix(Player __instance)
		{
			ScytheToolCompatSystem.NotifyPendingJewelcraftingEffectRecalcIfNeeded(__instance);
		}
	}
	internal static class PickaxeTerrainScalingSystem
	{
		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));
			}
		}

		private 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 readonly HashSet<Attack> VanillaFallbackAttacks = new HashSet<Attack>();

		private static readonly Dictionary<Attack, PendingTerrainDigText> PendingFallbackTexts = new Dictionary<Attack, PendingTerrainDigText>();

		private static Attack? _activeMeleeAttack;

		private static KeyHints? _activeKeyHints;

		private static KeyHintCell? _digHint;

		private static bool _showingDigHint;

		private static string? _lastDigHintLabel;

		private static string? _lastDigHintKeyText;

		internal static Attack? ActiveMeleeAttack => _activeMeleeAttack;

		private static bool SuppressCameraZoomThisFrame { get; set; }

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

		internal static void EndMeleeAttack(Attack attack)
		{
			if (_activeMeleeAttack == attack)
			{
				_activeMeleeAttack = null;
			}
			VanillaFallbackAttacks.Remove(attack);
			PendingFallbackTexts.Remove(attack);
		}

		internal static TerrainScalingScope? Begin(GameObject? prefab, Character? character, ItemData? weapon)
		{
			if ((Object)(object)prefab == (Object)null || !TryResolvePrimaryTerrainDig(character, weapon, _activeMeleeAttack))
			{
				return null;
			}
			if (_activeMeleeAttack != null && VanillaFallbackAttacks.Remove(_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);
			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;
		}

		internal static void End(TerrainScalingScope? scope, GameObject? spawnedTerrainObject)
		{
			if (scope != null)
			{
				scope.ApplyTerrainHitResult(spawnedTerrainObject);
			}
			else
			{
				ShowPendingFallbackText(spawnedTerrainObject);
			}
			scope?.Restore();
		}

		internal static bool CanSpawnTerrainModifier(Character character, ItemData weapon, Attack? attack)
		{
			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();
					}
				}
				UseVanillaFallback(attack, profile, "stamina");
				return true;
			}
			if (!flag)
			{
				UseVanillaFallback(attack, 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 ShouldSuppressCameraZoom()
		{
			if (!ShouldSuppressCameraZoomInput() || Mathf.Abs(ZInput.GetMouseScrollWheel()) < 0.01f)
			{
				return SuppressCameraZoomThisFrame;
			}
			return true;
		}

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

		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 void UseVanillaFallback(Attack? attack, TerrainCostProfile profile, string missingResource)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			if (attack != null)
			{
				VanillaFallbackAttacks.Add(attack);
				PendingFallbackTexts[attack] = 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 void ShowPendingFallbackText(GameObject? spawnedTerrainObject)
		{
			//IL_003a: 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 (!((Object)(object)spawnedTerrainObject == (Object)null) && _activeMeleeAttack != null && PendingFallbackTexts.TryGetValue(_activeMeleeAttack, out var value))
			{
				PendingFallbackTexts.Remove(_activeMeleeAttack);
				TerrainDigFloatingTextSystem.Show(spawnedTerrainObject.transform.position, value.Text, value.Color);
			}
		}

		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 Postfix(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.TerrainScalingScope? __state)
		{
			if (!PickaxeTerrainScalingSystem.CanSpawnTerrainModifier(character, weapon, PickaxeTerrainScalingSystem.ActiveMeleeAttack))
			{
				__state = null;
				__result = null;
				return false;
			}
			__state = PickaxeTerrainScalingSystem.Begin(prefab, character, weapon);
			return true;
		}

		private static void Postfix(GameObject? __result, PickaxeTerrainScalingSystem.TerrainScalingScope? __state)
		{
			PickaxeTerrainScalingSystem.End(__state, __result);
		}
	}
	[HarmonyPatch(typeof(KeyHints), "Awake")]
	internal static class KeyHintsAwakePickaxeTerrainScalingPatch
	{
		private static void Postfix(KeyHints __instance)
		{
			PickaxeTerrainScalingSystem.InitializeKeyHints(__instance);
		}
	}
	[HarmonyPatch(typeof(KeyHints), "UpdateHints")]
	internal static class KeyHintsUpdatePickaxeTerrainScalingPatch
	{
		private static void Postfix(KeyHints __instance)
		{
			PickaxeTerrainScalingSystem.UpdateKeyHint(__instance);
		}
	}
	internal static class FarmingSkillSystem
	{
		[StructLayout(LayoutKind.Sequential, Size = 1)]
		internal readonly struct RangePickupSuppression : IDisposable
		{
			public void Dispose()
			{
				if (_suppressRangePickup > 0)
				{
					_suppressRangePickup--;
				}
			}
		}

		private const string ForagingPickerSkillKey = "Groundwork_ForagingPickerFarmingSkill";

		private const string PlantPlanterSkillKey = "Groundwork_PlanterFarmingSkill";

		private const string PreferredBonusEffectSourcePrefab = "Pickable_Fiddlehead";

		private static readonly Collider[] PickupHits = (Collider[])(object)new Collider[200];

		private static readonly HashSet<Pickable> SeenPickables = new HashSet<Pickable>();

		private static Player? _placingPlayer;

		private static bool _rangePicking;

		private static int _suppressRangePickup;

		private static int _pickupMask;

		internal static bool IsForagingTarget(Pickable? pickable)
		{
			if ((Object)(object)pickable != (Object)null && pickable.m_respawnTimeMinutes > 0f)
			{
				return DropsEdibleItem(pickable);
			}
			return false;
		}

		internal static void ApplyForagingBonusEffectFallbacks(ZNetScene scene)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Invalid comparison between Unknown and I4
			EffectList val = ResolveForagingBonusEffectFallback(scene);
			if (val == null || !val.HasEffects())
			{
				return;
			}
			int num = 0;
			foreach (GameObject item in EnumerateScenePrefabs(scene))
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				Pickable[] componentsInChildren = item.GetComponentsInChildren<Pickable>(true);
				foreach (Pickable val2 in componentsInChildren)
				{
					if (IsForagingTarget(val2) && (int)val2.m_pickRaiseSkill == 106 && !HasEffect(val2.m_bonusEffect))
					{
						val2.m_bonusEffect = CloneEffectList(val);
						num++;
					}
				}
			}
			if (num > 0)
			{
				GroundworkPlugin.ModLogger.LogInfo((object)$"Added fallback Farming bonus effect to {num} foraging pickable prefab(s) with empty m_bonusEffect.");
			}
		}

		internal static void TryPickupNearbyForagingTargets(Pickable source, Humanoid character)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			if (_rangePicking || _suppressRangePickup > 0)
			{
				return;
			}
			Player val = (Player)(object)((character is Player) ? character : null);
			if (val == null || !IsForagingTarget(source))
			{
				return;
			}
			float foragingPickupMaxRange = GroundworkToolsDomain.ForagingPickupMaxRange;
			if (foragingPickupMaxRange <= 0f)
			{
				return;
			}
			float num = ((Character)val).GetSkillFactor((SkillType)106) * foragingPickupMaxRange;
			if (num <= 0.05f)
			{
				return;
			}
			int num2 = Physics.OverlapSphereNonAlloc(((Component)source).transform.position, num, PickupHits, GetPickupMask(), (QueryTriggerInteraction)0);
			_rangePicking = true;
			SeenPickables.Clear();
			SeenPickables.Add(source);
			try
			{
				for (int i = 0; i < num2; i++)
				{
					Collider val2 = PickupHits[i];
					if (!((Object)(object)val2 == (Object)null))
					{
						Pickable componentInParent = ((Component)val2).GetComponentInParent<Pickable>();
						if (!((Object)(object)componentInParent == (Object)null) && SeenPickables.Add(componentInParent) && IsForagingTarget(componentInParent) && componentInParent.CanBePicked())
						{
							componentInParent.Interact((Humanoid)(object)val, false, false);
						}
					}
				}
			}
			finally
			{
				SeenPickables.Clear();
				_rangePicking = false;
			}
		}

		internal static RangePickupSuppression SuppressRangePickup()
		{
			_suppressRangePickup++;
			return default(RangePickupSuppression);
		}

		internal static void RememberForagingPickerSkill(Pickable pickable, long sender)
		{
			if (GroundworkToolsDomain.ForagingFeatureEnabled && IsForagingTarget(pickable) && TryGetPickableZdo(pickable, requireOwner: true, out ZDO zdo))
			{
				zdo.Set("Groundwork_ForagingPickerFarmingSkill", ResolveSenderFarmingSkill(sender));
			}
		}

		internal static void EnsureForagingPickerSkill(Pickable pickable, bool picked)
		{
			if (picked && IsForagingTarget(pickable) && TryGetPickableZdo(pickable, requireOwner: true, out ZDO zdo) && !(zdo.GetFloat("Groundwork_ForagingPickerFarmingSkill", -1f) >= 0f))
			{
				zdo.Set("Groundwork_ForagingPickerFarmingSkill", ResolveLocalFarmingSkill());
			}
		}

		internal static bool TryGetForagingRespawnSeconds(Pickable pickable, out float respawnSeconds)
		{
			respawnSeconds = 0f;
			if ((Object)(object)pickable == (Object)null)
			{
				return false;
			}
			respawnSeconds = Math.Max(0f, pickable.m_respawnTimeMinutes) * 60f;
			if (respawnSeconds <= 0f || !IsForagingTarget(pickable))
			{
				return false;
			}
			bool result = false;
			float foragingRespawnSpeedMultiplierForHover = GetForagingRespawnSpeedMultiplierForHover(pickable);
			if (foragingRespawnSpeedMultiplierForHover > 1.001f)
			{
				respawnSeconds /= foragingRespawnSpeedMultiplierForHover;
				result = true;
			}
			if (BeehivePollinationSystem.TryModifyForagingRespawnSeconds(pickable, ref respawnSeconds))
			{
				result = true;
			}
			if (EnvironmentEffectSystem.TryModifyForagingRespawnSeconds(pickable, ref respawnSeconds))
			{
				result = true;
			}
			return result;
		}

		internal static bool ShouldRunVanillaShouldRespawn(Pickable pickable, ref bool result)
		{
			if (!TryGetForagingRespawnSeconds(pickable, out var respawnSeconds) || !TryGetPickableZdo(pickable, requireOwner: false, out ZDO zdo))
			{
				return true;
			}
			long num = zdo.GetLong(ZDOVars.s_pickedTime, 0L);
			if (num <= 1)
			{
				result = PassesSpawnCheck(pickable);
				return false;
			}
			double totalSeconds = TimeSpan.FromTicks(Math.Max(0L, GetCurrentTicks() - num)).TotalSeconds;
			result = totalSeconds > (double)respawnSeconds && PassesSpawnCheck(pickable);
			return false;
		}

		internal static void TryReplayRangePickBonusEffect(Pickable pickable, int bonus)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			if (_rangePicking && bonus > 0 && !(GroundworkToolsDomain.ForagingPickupMaxRange <= 0f) && IsForagingTarget(pickable))
			{
				Vector3 position = ((Component)pickable).transform.position;
				if (pickable.m_bonusEffect != null && pickable.m_bonusEffect.HasEffects())
				{
					pickable.m_bonusEffect.Create(position, Quaternion.identity, (Transform)null, 1f, -1);
				}
				else if (pickable.m_pickEffector != null && pickable.m_pickEffector.HasEffects())
				{
					pickable.m_pickEffector.Create(position, Quaternion.identity, (Transform)null, 1f, -1);
				}
			}
		}

		internal static void BeginPlacePiece(Player player)
		{
			if (!GroundworkToolsDomain.PlantGrowFeatureEnabled)
			{
				_placingPlayer = null;
			}
			else
			{
				_placingPlayer = player;
			}
		}

		internal static void EndPlacePiece()
		{
			_placingPlayer = null;
		}

		internal static void TryStorePlanterSkill(Plant plant)
		{
			Player placingPlayer = _placingPlayer;
			if (!((Object)(object)placingPlayer == (Object)null) && GroundworkToolsDomain.PlantGrowFeatureEnabled && TryGetPlantZdo(plant, requireOwner: true, out ZDO zdo))
			{
				zdo.Set("Groundwork_PlanterFarmingSkill", ((Character)placingPlayer).GetSkillFactor((SkillType)106));
			}
		}

		internal static void TryModifyGrowTime(Plant plant, ref float growTime)
		{
			if (!((Object)(object)plant == (Object)null) && !(growTime <= 0f))
			{
				TryModifyPlanterGrowTime(plant, ref growTime);
				BeehivePollinationSystem.TryModifyPlantGrowTime(plant, ref growTime);
				EnvironmentEffectSystem.TryModifyPlantGrowTime(plant, ref growTime);
			}
		}

		private static void TryModifyPlanterGrowTime(Plant plant, ref float growTime)
		{
			if (!(GroundworkToolsDomain.PlantGrowSpeedFactor <= 0f) && !(growTime <= 0f))
			{
				float plantGrowSpeedMultiplierForHover = GetPlantGrowSpeedMultiplierForHover(plant);
				if (plantGrowSpeedMultiplierForHover > 1.001f)
				{
					growTime /= plantGrowSpeedMultiplierForHover;
				}
			}
		}

		internal static float GetPlantGrowSpeedMultiplierForHover(Plant plant)
		{
			float plantGrowSpeedFactor = GroundworkToolsDomain.PlantGrowSpeedFactor;
			if ((Object)(object)plant == (Object)null || plantGrowSpeedFactor <= 0f || !TryGetPlantZdo(plant, requireOwner: false, out ZDO zdo))
			{
				return 1f;
			}
			float skillFactor = Mathf.Clamp01(zdo.GetFloat("Groundwork_PlanterFarmingSkill", 0f));
			return ResolveSkillSpeedMultiplier(plantGrowSpeedFactor, skillFactor);
		}

		internal static float GetForagingRespawnSpeedMultiplierForHover(Pickable pickable)
		{
			float foragingRespawnSpeedFactor = GroundworkToolsDomain.ForagingRespawnSpeedFactor;
			if ((Object)(object)pickable == (Object)null || foragingRespawnSpeedFactor <= 0f || !IsForagingTarget(pickable))
			{
				return 1f;
			}
			return ResolveSkillSpeedMultiplier(foragingRespawnSpeedFactor, ResolveForagingPickerSkill(pickable));
		}

		private static bool DropsEdibleItem(Pickable pickable)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			if (IsEdibleItemPrefab(pickable.m_itemPrefab))
			{
				return true;
			}
			if (pickable.m_extraDrops?.m_drops == null)
			{
				return false;
			}
			foreach (DropData drop in pickable.m_extraDrops.m_drops)
			{
				if (IsEdibleItemPrefab(drop.m_item))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsEdibleItemPrefab(GameObject? itemPrefab)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null);
			if (val?.m_itemData?.m_shared == null)
			{
				return false;
			}
			SharedData shared = val.m_itemData.m_shared;
			if ((int)shared.m_itemType == 2)
			{
				if (!(shared.m_food > 0f) && !(shared.m_foodStamina > 0f))
				{
					return shared.m_foodEitr > 0f;
				}
				return true;
			}
			return false;
		}

		private static EffectList? ResolveForagingBonusEffectFallback(ZNetScene scene)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Invalid comparison between Unknown and I4
			GameObject prefab = scene.GetPrefab("Pickable_Fiddlehead");
			Pickable val = ((prefab != null) ? prefab.GetComponentInChildren<Pickable>(true) : null);
			if (HasEffect(val?.m_bonusEffect))
			{
				return val.m_bonusEffect;
			}
			foreach (GameObject item in EnumerateScenePrefabs(scene))
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				Pickable[] componentsInChildren = item.GetComponentsInChildren<Pickable>(true);
				foreach (Pickable val2 in componentsInChildren)
				{
					if (IsForagingTarget(val2) && (int)val2.m_pickRaiseSkill == 106 && HasEffect(val2.m_bonusEffect))
					{
						return val2.m_bonusEffect;
					}
				}
			}
			return BuildPickEffectFallback(scene);
		}

		private static EffectList BuildPickEffectFallback(ZNetScene scene)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			List<EffectData> list = new List<EffectData>();
			AddEffectIfFound(scene, list, "sfx_pickable_pick");
			AddEffectIfFound(scene, list, "vfx_pickable_pick");
			return new EffectList
			{
				m_effectPrefabs = list.ToArray()
			};
		}

		private static void AddEffectIfFound(ZNetScene scene, List<EffectData> effects, string prefabName)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			GameObject prefab = scene.GetPrefab(prefabName);
			if (!((Object)(object)prefab == (Object)null))
			{
				effects.Add(new EffectData
				{
					m_prefab = prefab,
					m_enabled = true,
					m_variant = -1
				});
			}
		}

		private static IEnumerable<GameObject> EnumerateScenePrefabs(ZNetScene scene)
		{
			foreach (GameObject prefab in scene.m_prefabs)
			{
				yield return prefab;
			}
			foreach (GameObject nonNetViewPrefab in scene.m_nonNetViewPrefabs)
			{
				yield return nonNetViewPrefab;
			}
		}

		private static bool HasEffect(EffectList? effectList)
		{
			if (effectList != null)
			{
				return effectList.HasEffects();
			}
			return false;
		}

		private static EffectList CloneEffectList(EffectList source)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			EffectData[] array = source.m_effectPrefabs ?? Array.Empty<EffectData>();
			EffectData[] array2 = (EffectData[])(object)new EffectData[array.Length];
			for (int i = 0; i < array.Length; i++)
			{
				EffectData val = array[i];
				array2[i] = new EffectData
				{
					m_prefab = val.m_prefab,
					m_enabled = val.m_enabled,
					m_variant = val.m_variant,
					m_attach = val.m_attach,
					m_follow = val.m_follow,
					m_inheritParentRotation = val.m_inheritParentRotation,
					m_inheritParentScale = val.m_inheritParentScale,
					m_multiplyParentVisualScale = val.m_multiplyParentVisualScale,
					m_randomRotation = val.m_randomRotation,
					m_scale = val.m_scale,
					m_childTransform = val.m_childTransform
				};
			}
			return new EffectList
			{
				m_effectPrefabs = array2
			};
		}

		private static float ResolveSenderFarmingSkill(long sender)
		{
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				ZNetView nview = ((Character)allPlayer).m_nview;
				ZDO val = (((Object)(object)nview != (Object)null && nview.IsValid()) ? nview.GetZDO() : null);
				if (val != null && ((ZDOID)(ref val.m_uid)).UserID == sender)
				{
					return ((Character)allPlayer).GetSkillFactor((SkillType)106);
				}
			}
			return ResolveLocalFarmingSkill();
		}

		private static float ResolveLocalFarmingSkill()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer != (Object)null))
			{
				return 0f;
			}
			return ((Character)localPlayer).GetSkillFactor((SkillType)106);
		}

		private static float ResolveForagingPickerSkill(Pickable pickable)
		{
			if (!TryGetPickableZdo(pickable, requireOwner: false, out ZDO zdo))
			{
				return ResolveLocalFarmingSkill();
			}
			return Mathf.Clamp01(zdo.GetFloat("Groundwork_ForagingPickerFarmingSkill", ResolveLocalFarmingSkill()));
		}

		private static float ResolveSkillSpeedMultiplier(float speedFactor, float skillFactor)
		{
			return Mathf.Lerp(1f, Mathf.Max(1f, speedFactor), Mathf.Clamp01(skillFactor));
		}

		private static long GetCurrentTicks()
		{
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				return DateTime.Now.Ticks;
			}
			return ZNet.instance.GetTime().Ticks;
		}

		private static bool PassesSpawnCheck(Pickable pickable)
		{
			if (pickable.m_spawnCheck != null)
			{
				return pickable.m_spawnCheck.Invoke(pickable);
			}
			return true;
		}

		private static bool TryGetPickableZdo(Pickable pickable, bool requireOwner, out ZDO? zdo)
		{
			zdo = null;
			ZNetView nview = pickable.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || (requireOwner && !nview.IsOwner()))
			{
				return false;
			}
			zdo = nview.GetZDO();
			return zdo != null;
		}

		private static bool TryGetPlantZdo(Plant plant, bool requireOwner, out ZDO? zdo)
		{
			zdo = null;
			ZNetView nview = plant.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || (requireOwner && !nview.IsOwner()))
			{
				return false;
			}
			zdo = nview.GetZDO();
			return zdo != null;
		}

		private static int GetPickupMask()
		{
			if (_pickupMask == 0)
			{
				_pickupMask = LayerMask.GetMask(new string[4] { "item", "Default_small", "piece_nonsolid", "piece" });
			}
			return _pickupMask;
		}
	}
	[HarmonyPatch(typeof(Pickable), "Interact")]
	internal static class PickableInteractForagingPickupPatch
	{
		private static void Postfix(Pickable __instance, Humanoid character)
		{
			FarmingSkillSystem.TryPickupNearbyForagingTargets(__instance, character);
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class ZNetSceneAwakeFarmingBonusEffectPatch
	{
		private static void Postfix(ZNetScene __instance)
		{
			FarmingSkillSystem.ApplyForagingBonusEffectFallbacks(__instance);
		}
	}
	[HarmonyPatch(typeof(Pickable), "RPC_Pick")]
	internal static class PickableRpcPickForagingSkillPatch
	{
		private static void Prefix(Pickable __instance, long sender, int bonus)
		{
			FarmingSkillSystem.RememberForagingPickerSkill(__instance, sender);
			FarmingSkillSystem.TryReplayRangePickBonusEffect(__instance, bonus);
		}
	}
	[HarmonyPatch(typeof(Pickable), "SetPicked")]
	internal static class PickableSetPickedForagingSkillPatch
	{
		private static void Postfix(Pickable __instance, bool picked)
		{
			FarmingSkillSystem.EnsureForagingPickerSkill(__instance, picked);
		}
	}
	[HarmonyPatch(typeof(Pickable), "ShouldRespawn")]
	internal static class PickableShouldRespawnForagingPatch
	{
		private static bool Prefix(Pickable __instance, ref bool __result)
		{
			return FarmingSkillSystem.ShouldRunVanillaShouldRespawn(__instance, ref __result);
		}
	}
	[HarmonyPatch(typeof(Player), "PlacePiece")]
	internal static class PlayerPlacePieceFarmingTrackingPatch
	{
		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 PlantAwakePlanterSkillPatch
	{
		private static void Postfix(Plant __instance)
		{
			BeehivePollinationSystem.TrackLoadedTarget((Component)(object)__instance);
			FarmingSkillSystem.TryStorePlanterSkill(__instance);
		}
	}
	[HarmonyPatch(typeof(Plant), "GetGrowTime")]
	internal static class PlantGetGrowTimeGroundworkPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(Plant __instance, ref float __result)
		{
			FarmingSkillSystem.TryModifyGrowTime(__instance, ref __result);
		}
	}
	internal static class BeehivePollinationSystem
	{
		internal readonly struct PollinationSummary
		{
			internal readonly int Count;

			internal readonly int MaxCount;

			internal readonly float HoneyMultiplier;

			public PollinationSummary(int count, int maxCount, float honeyMultiplier)
			{
				Count = count;
				MaxCount = maxCount;
				HoneyMultiplier = honeyMultiplier;
			}
		}

		private readonly struct PollinationTargetCandidate
		{
			internal readonly Plant? Plant;

			internal readonly Pickable? Pickable;

			internal readonly float HorizontalDistance;

			internal readonly float HeightDistance;

			internal readonly int InstanceId;

			public PollinationTargetCandidate(Plant? plant, Pickable? pickable, float horizontalDistance, float heightDistance, int instanceId)
			{
				Plant = plant;
				Pickable = pickable;
				HorizontalDistance = horizontalDistance;
				HeightDistance = heightDistance;
				InstanceId = instanceId;
			}
		}

		private sealed class PollinationCache
		{
			internal readonly HashSet<Plant> Plants = new HashSet<Plant>();

			internal readonly HashSet<Pickable> Pickables = new HashSet<Pickable>();

			internal float RefreshedAt = -60f;

			internal int MaxCount;

			internal float Radius;

			internal bool CanPollinate;

			internal bool RequireDaylight;

			internal int Count => Plants.Count + Pickables.Count;

			internal bool IsFresh(float now, int maxCount, float radius, bool canPollinate, bool requireDaylight)
			{
				if (now - RefreshedAt <= 3f && MaxCount == maxCount && Mathf.Approximately(Radius, radius) && CanPollinate == canPollinate)
				{
					return RequireDaylight == requireDaylight;