Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Groundwork v1.0.2
Groundwork.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
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;