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 SmarterHoe v1.1.1
SmarterHoe.dll
Decompiled 2 hours 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.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using SmarterHoe.Compatibility; using SmarterHoe.CustomPieces; using SmarterHoe.I18n; using SmarterHoe.Undo; using SmarterHoe.Visuals; using TMPro; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Jubaroo")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2026 Jubaroo")] [assembly: AssemblyDescription("Comprehensive hoe terrain tools with ServerSync, Undo/Redo, Altitude Lock, and Spline Roads for Valheim — by Jubaroo")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.1.1")] [assembly: AssemblyProduct("SmarterHoe")] [assembly: AssemblyTitle("SmarterHoe")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServerSync { [PublicAPI] public abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] public class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } public abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] public sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] public class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> list = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId != null) ? ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })) : adminList.Contains(hostName); }).ToList(); SendAdmin(ZNet.instance.GetPeers().Except(list).ToList(), isAdmin: false); SendAdmin(list, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend != 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> list = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { list.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); list.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)methodInfo == null) ? ((object)val.Contains(rpc.GetSocket().GetHostName())) : methodInfo.Invoke(ZNet.instance, new object[2] { val, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, list, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section; public string key; public Type type; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected; public string received; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0051; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (num) { return !lockExempt; } goto IL_0051; IL_0051: return false; } set { forceConfigLocking = value; } } public bool IsAdmin { get { if (!lockExempt) { return isSourceOfTruth; } return true; } } public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(0L, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(0L, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { MemoryStream stream = new MemoryStream(package.ReadByteArray()); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } if (!configSync.IsSourceOfTruth && config.SynchronizedConfig && config.LocalBaseValue != null) { if (!configSync.IsLocked) { if (config == configSync.lockedConfig) { return lockExempt; } return true; } return false; } return true; } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer.m_socket.IsConnected()) { ZPackage val = new ZPackage(); val.Write((byte)2); val.Write(packageIdentifier); val.Write(fragment); val.Write(fragments); val.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(val); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } IEnumerable<bool> waitForQueue() { float timeout = Time.time + 30f; while (peer.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout"); peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty<object>().GetEnumerator(); } List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != 0L) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] array = package.GetArray(); if (array != null && array.LongLength > 10000) { ZPackage val = new ZPackage(); val.Write((byte)4); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal)) { deflateStream.Write(array, 0, array.Length); } val.Write(memoryStream.ToArray()); package = val; } List<IEnumerator<bool>> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator<bool> writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault(); } public static SyncedConfigEntry<T>? ConfigData<T>(ConfigEntry<T> config) { return ((ConfigEntryBase)config).Description.Tags?.OfType<SyncedConfigEntry<T>>().SingleOrDefault(); } private static T configAttribute<T>(ConfigEntryBase config) { return config.Description.Tags.OfType<T>().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { if (!type.IsEnum) { return type; } return Enum.GetUnderlyingType(type); } private static ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase>? configs = null, IEnumerable<CustomSyncedValueBase>? customValues = null, IEnumerable<PackageEntry>? packageEntries = null, bool partial = true) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown List<ConfigEntryBase> list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>(); List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>(); ZPackage val = new ZPackage(); val.Write(partial ? ((byte)1) : ((byte)0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty<PackageEntry>()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List<string>) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List<object> source = new List<object>(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] public class VersionCheck { private static readonly HashSet<VersionCheck> versionChecks; private static readonly Dictionary<string, string> notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List<ZRpc> ValidatedClients = new List<ZRpc>(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { string text = minimumRequiredVersion; if (text == null) { if (!ModRequired) { return "0.0.0"; } text = CurrentVersion; } return text; } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet<VersionCheck>(); notProcessedNames = new Dictionary<string, string>(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool num = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion); bool flag = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion); return num && flag; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } if (!(new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion))) { return DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."; } return DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + "."; } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { if (rpc != null) { return ErrorServer(rpc); } return ErrorClient(); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action<ZRpc, ZPackage>? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; for (int i = 0; i < array2.Length; i++) { Debug.LogWarning((object)array2[i].Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action<ZRpc, ZPackage> action = (Action<ZRpc, ZPackage>)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register<ZPackage>("ServerSync VersionCheck", (Action<ZRpc, ZPackage>)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair<string, string> item in notProcessedNames.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent<RectTransform>(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent<RectTransform>(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } } namespace SmarterHoe { public enum RampPaintMode { MatchEquippedTool, AlwaysDirt, AlwaysPaved, AlwaysCultivated, KeepOriginal } public enum ResourceCostMode { Freebuild, BalancedSurvival } public enum BrushShape { Circle, Square } public enum SuppressZoomMode { WhileAdjusting, AlwaysWithHoe, Disabled } public enum AdjustMode { None, Area, Height } public enum SquareRotationMode { ScrollWheelAndButtons, ButtonsOnly, ScrollWheelOnly } [BepInPlugin("com.jubaroo.smarterhoe", "SmarterHoe", "1.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private struct RoadSegment { public Vector2 a2D; public Vector2 b2D; public Vector2 ab; public float abLenSqr; public float aY; public float bY; public float minX; public float maxX; public float minZ; public float maxZ; } public const string PluginGuid = "com.jubaroo.smarterhoe"; public const string PluginName = "SmarterHoe"; public const string PluginVersion = "1.1.1"; public static ManualLogSource Log = null; private static readonly ConfigSync _configSync = new ConfigSync("com.jubaroo.smarterhoe") { DisplayName = "SmarterHoe", CurrentVersion = "1.1.1", MinimumRequiredVersion = "1.1.1", ModRequired = false }; private static ConfigEntry<bool> _serverConfigLocked = null; private static ConfigEntry<KeyCode> _altitudeLockKey = null; private static ConfigEntry<KeyCode> _snapAltitudeKey = null; private static ConfigEntry<KeyCode> _rampKey = null; private static ConfigEntry<KeyCode> _autoCarveKey = null; private static ConfigEntry<KeyCode> _undoWaypointKey = null; private static ConfigEntry<KeyCode> _increaseWidthKey = null; private static ConfigEntry<KeyCode> _decreaseWidthKey = null; private static ConfigEntry<KeyCode> _togglePaintKey = null; private static ConfigEntry<KeyCode> _restoreTerrainKey = null; private static ConfigEntry<KeyCode> _toggleBrushShapeKey = null; private static ConfigEntry<KeyCode> _toggleCostKey = null; private static ConfigEntry<KeyCode> _rotateSquareLeftKey = null; private static ConfigEntry<KeyCode> _rotateSquareRightKey = null; private static ConfigEntry<KeyCode> _resetSquareRotationKey = null; private static ConfigEntry<SquareRotationMode> _squareRotationMode = null; private static ConfigEntry<float> _squareRotationStep = null; private static ConfigEntry<float> _rampWidth = null; private static ConfigEntry<float> _rampWidthStep = null; private static ConfigEntry<float> _marginWidth = null; private static ConfigEntry<RampPaintMode> _paintMode = null; private static ConfigEntry<bool> _curvedSwitchbacks = null; private static ConfigEntry<bool> _clearGrass = null; private static ConfigEntry<float> _staminaCost = null; private static ConfigEntry<bool> _playEffects = null; private static ConfigEntry<float> _altitudeStep = null; private static ConfigEntry<float> _altitudeHoldDelay = null; private static ConfigEntry<float> _altitudeRepeatRate = null; private static ConfigEntry<bool> _instantFlatten = null; private static ConfigEntry<bool> _instantFlattenVanilla = null; private static ConfigEntry<float> _flattenEdgeMargin = null; private static float _nextAltitudeRepeatTime; private static ConfigEntry<string> _corridorColor = null; private static ConfigEntry<string> _centerLineColor = null; private static ConfigEntry<float> _indicatorAlpha = null; private static ConfigEntry<bool> _showElevationGrid = null; private static ConfigEntry<string> _elevationGridColor = null; private static ConfigEntry<bool> _dynamicSlopeColoring = null; private static ConfigEntry<ResourceCostMode> _resourceCostMode = null; private static ConfigEntry<float> _stoneCostPerVolume = null; private static ConfigEntry<float> _stoneCostPerPavedSquare = null; private static ConfigEntry<bool> _requireCraftingStation = null; private static ConfigEntry<bool> _requireToolsForDebris = null; private static ConfigEntry<float> _staminaCostLevel = null; private static ConfigEntry<float> _durabilityCostLevel = null; private static ConfigEntry<float> _staminaPerDebris = null; private static ConfigEntry<bool> _dropResourcesOnClear = null; private static ConfigEntry<bool> _showHud = null; private static ConfigEntry<bool> _holdToLevel = null; private static ConfigEntry<float> _holdInterval = null; private static ConfigEntry<float> _brushRadiusConfig = null; private static ConfigEntry<float> _brushRadiusStep = null; private static ConfigEntry<BrushShape> _brushShapeConfig = null; private static ConfigEntry<float> _hudOffsetY = null; private static ConfigEntry<bool> _enableUndo = null; private static ConfigEntry<KeyCode> _undoKey = null; private static ConfigEntry<KeyCode> _redoKey = null; private static ConfigEntry<UndoModifierMode> _undoModifier = null; private static ConfigEntry<int> _maxUndoSteps = null; private static ConfigEntry<bool> _rampStepBackWithUndoKey = null; private static ConfigEntry<float> _hoeReach = null; private static ConfigEntry<bool> _allowBeyondGameLimit = null; private static ConfigEntry<float> _maxTerrainDelta = null; private static ConfigEntry<float> _maxBrushRadius = null; private static ConfigEntry<float> _raiseHeight = null; private static ConfigEntry<float> _raiseStep = null; private static ConfigEntry<float> _minRaise = null; private static ConfigEntry<float> _maxRaise = null; private static ConfigEntry<KeyCode> _areaKey = null; private static ConfigEntry<KeyCode> _heightKey = null; private static ConfigEntry<bool> _suppressCameraZoom = null; private static ConfigEntry<SuppressZoomMode> _suppressZoomMode = null; private static ConfigEntry<string> _language = null; private static ConfigEntry<bool> _enableRaisePreview = null; private static ConfigEntry<string> _validPreviewColor = null; private static ConfigEntry<string> _invalidPreviewColor = null; private static ConfigEntry<float> _validPreviewAlpha = null; private static ConfigEntry<float> _invalidPreviewAlpha = null; public static float SelectedRaiseHeight = 0.5f; public static AdjustMode ActiveAdjustMode = AdjustMode.None; public static RaiseCylinderVisualizer? RaisePreviewVisualizer = null; private static Player? _rangePlayer = null; private static float _originalPlaceDistance = -1f; private static Color _cachedValidColor = new Color(0.5f, 0.5f, 0.5f); private static Color _cachedInvalidColor = new Color(0.8f, 0.2f, 0.2f); public static bool AltitudeLockEnabled = false; public static float LockedAltitude = 0f; private static float _brushRadius = 3.5f; public static BrushShape BrushShape = BrushShape.Circle; private static int _lastPlaceRotation = 0; private static readonly Dictionary<Piece, int> _originalStoneAmounts = new Dictionary<Piece, int>(); public static readonly List<Vector3> RampWaypoints = new List<Vector3>(); private static readonly List<Vector3> _previewPointsBuffer = new List<Vector3>(32); private static readonly List<Heightmap> _cachedHeightmapList = new List<Heightmap>(16); private static readonly Collider[] _debrisColliderBuffer = (Collider[])(object)new Collider[512]; private static PieceTable? _lastCheckedPieceTable; private static Color _cachedCorridorColor; private static Color _cachedCenterLineColor; private static Color _cachedElevationGridColor; private float _holdTimer; private Harmony? _harmony; private static string _lastNoticeText = ""; private static float _lastNoticeTime = 0f; public static int MaxUndoStepsConfig => _maxUndoSteps?.Value ?? 15; public static float MaxBrushRadius => _maxBrushRadius?.Value ?? 20f; public static bool AllowBeyondGameLimit => _allowBeyondGameLimit?.Value ?? true; public static float MaxTerrainDelta => _maxTerrainDelta?.Value ?? 50f; public static float HoeReach => _hoeReach?.Value ?? 20f; public static float BrushRadius { get { return Mathf.Min(_brushRadius, MaxBrushRadius); } set { _brushRadius = Mathf.Clamp(value, 0.5f, MaxBrushRadius); } } public static float RampWidth => _rampWidth?.Value ?? 3.5f; public static bool DropResourcesOnClear => _dropResourcesOnClear?.Value ?? false; public static bool InstantFlatten => _instantFlatten?.Value ?? true; public static bool InstantFlattenVanilla => _instantFlattenVanilla?.Value ?? false; public static float FlattenEdgeMargin => _flattenEdgeMargin?.Value ?? 0f; public static bool CurvedSwitchbacks => _curvedSwitchbacks?.Value ?? true; public static bool DynamicSlopeColoring => _dynamicSlopeColoring?.Value ?? true; public static ResourceCostMode CostMode => _resourceCostMode?.Value ?? ResourceCostMode.BalancedSurvival; public static bool RequireCraftingStation => _requireCraftingStation?.Value ?? true; public static float StonePerCubicMeter => _stoneCostPerVolume?.Value ?? 0.25f; public static float StonePerPavedSquare => _stoneCostPerPavedSquare?.Value ?? 0.03f; public static SquareRotationMode SquareRotationInputMode => _squareRotationMode?.Value ?? SquareRotationMode.ScrollWheelAndButtons; public static float SquareRotationStep => _squareRotationStep?.Value ?? 22.5f; public static int RampState => RampWaypoints.Count; public static bool RampModeActive => RampWaypoints.Count >= 2; public static Vector3 RampStart { get { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (RampWaypoints.Count <= 0) { return Vector3.zero; } return RampWaypoints[0]; } } public static Vector3 RampEnd { get { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (RampWaypoints.Count <= 1) { return Vector3.zero; } return RampWaypoints[RampWaypoints.Count - 1]; } } private ConfigEntry<T> BindSynced<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true) { ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description); _configSync.AddConfigEntry<T>(val).SynchronizedConfig = synchronizedSetting; return val; } private ConfigEntry<T> BindSynced<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 BindSynced(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting); } private void Awake() { //IL_0969: Unknown result type (might be due to invalid IL or missing references) //IL_096f: Invalid comparison between Unknown and I4 //IL_098c: Unknown result type (might be due to invalid IL or missing references) //IL_0996: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _serverConfigLocked = BindSynced("0 - General", "Lock Configuration", value: true, "If On, the configuration is locked to server settings and can only be modified by admins."); _configSync.AddLockingConfigEntry<bool>(_serverConfigLocked); _altitudeLockKey = BindSynced<KeyCode>("1 - Hotkeys", "AltitudeLockKey", (KeyCode)108, "Hotkey to lock/unlock current altitude for laser-flat ground.", synchronizedSetting: false); _snapAltitudeKey = BindSynced<KeyCode>("1 - Hotkeys", "SnapAltitudeKey", (KeyCode)325, "Hotkey to snap locked altitude to targeted building piece (Middle Mouse Button by default).", synchronizedSetting: false); _rampKey = BindSynced<KeyCode>("1 - Hotkeys", "RampKey", (KeyCode)107, "Hotkey to add Ramp Waypoints (Points A, B, C...) or clear ramp.", synchronizedSetting: false); _autoCarveKey = BindSynced<KeyCode>("1 - Hotkeys", "AutoCarveKey", (KeyCode)104, "Hotkey to instantly auto-carve all connected ramp segments. Hold Shift to force Paved Stone.", synchronizedSetting: false); _undoWaypointKey = BindSynced<KeyCode>("1 - Hotkeys", "UndoWaypointKey", (KeyCode)8, "Hotkey to undo the most recent ramp waypoint.", synchronizedSetting: false); _increaseWidthKey = BindSynced<KeyCode>("1 - Hotkeys", "IncreaseWidthKey", (KeyCode)93, "Hotkey to increase ramp corridor width (or brush radius if holding Alt).", synchronizedSetting: false); _decreaseWidthKey = BindSynced<KeyCode>("1 - Hotkeys", "DecreaseWidthKey", (KeyCode)91, "Hotkey to decrease ramp corridor width (or brush radius if holding Alt).", synchronizedSetting: false); _togglePaintKey = BindSynced<KeyCode>("1 - Hotkeys", "TogglePaintKey", (KeyCode)112, "Hotkey to toggle/cycle ramp surface paint mode (Paved Cobblestone, Dirt, Keep Original Grass, Cultivated, Match Tool).", synchronizedSetting: false); _restoreTerrainKey = BindSynced<KeyCode>("1 - Hotkeys", "RestoreTerrainKey", (KeyCode)117, "Hotkey to restore terrain back to its original unmodified state (removes digging, leveling, and painting).", synchronizedSetting: false); _toggleBrushShapeKey = BindSynced<KeyCode>("1 - Hotkeys", "ToggleBrushShapeKey", (KeyCode)103, "Hotkey to toggle hoe brush shape between Circle and Square.", synchronizedSetting: false); _toggleCostKey = BindSynced<KeyCode>("1 - Hotkeys", "ToggleCostKey", (KeyCode)98, "Hotkey to toggle Resource Cost Mode between BalancedSurvival (full survival costs) and Freebuild (no cost creative mode).", synchronizedSetting: false); _rotateSquareLeftKey = BindSynced<KeyCode>("1 - Hotkeys", "RotateSquareLeftKey", (KeyCode)276, "Hotkey to rotate the square brush counter-clockwise / left.", synchronizedSetting: false); _rotateSquareRightKey = BindSynced<KeyCode>("1 - Hotkeys", "RotateSquareRightKey", (KeyCode)275, "Hotkey to rotate the square brush clockwise / right.", synchronizedSetting: false); _resetSquareRotationKey = BindSynced<KeyCode>("1 - Hotkeys", "ResetSquareRotationKey", (KeyCode)278, "Hotkey to reset the square brush rotation back to 0° (North). Set to None to disable.", synchronizedSetting: false); _rampWidth = BindSynced("2 - Ramp Settings", "RampWidth", 3.5f, "Default width of the carved ramp corridor in meters.", synchronizedSetting: false); _rampWidthStep = BindSynced("2 - Ramp Settings", "RampWidthStep", 0.5f, "Meters to adjust ramp width per [ or ] press.", synchronizedSetting: false); _marginWidth = BindSynced("2 - Ramp Settings", "MarginWidth", 1f, "Width of the soft blend transition margin along the outer edges of the ramp in meters.", synchronizedSetting: false); _paintMode = BindSynced("2 - Ramp Settings", "TerrainPaintMode", RampPaintMode.MatchEquippedTool, "How terrain surface texture is handled when carving:\n- MatchEquippedTool: Matches selected hoe piece (Level/Path -> Dirt, Paved Road -> Stone, Cultivate -> Farmland)\n- AlwaysDirt: Always paints bare dirt and clears grass\n- AlwaysPaved: Always paints paved cobblestone\n- AlwaysCultivated: Always paints cultivated farmland\n- KeepOriginal: Leaves surface textures completely unchanged (retains grass, snow, etc., only shapes height)", synchronizedSetting: false); _curvedSwitchbacks = BindSynced("2 - Ramp Settings", "CurvedSwitchbacks", value: true, "Use smooth Catmull-Rom spline curves for winding roads and mountain switchbacks with 3+ waypoints.", synchronizedSetting: false); _clearGrass = BindSynced("2 - Ramp Settings", "ClearGrass", value: true, "Whether to clear tall grass, bushes, and weeds along the ramp corridor.", synchronizedSetting: false); _staminaCost = BindSynced("2 - Ramp Settings", "StaminaCost", 20f, "Stamina consumed when auto-carving a ramp (set to 0 for free carving)."); _playEffects = BindSynced("2 - Ramp Settings", "PlayEffects", value: true, "Play dust and thud placement sound effects when carving.", synchronizedSetting: false); _altitudeStep = BindSynced("3 - Altitude Lock", "AltitudeStep", 0.1f, "Meters to adjust locked altitude per PageUp / PageDown press.", synchronizedSetting: false); _altitudeHoldDelay = BindSynced("3 - Altitude Lock", "AltitudeHoldDelay", 0.35f, "Delay in seconds when holding PageUp / PageDown before rapid elevation adjustment begins.", synchronizedSetting: false); _altitudeRepeatRate = BindSynced("3 - Altitude Lock", "AltitudeRepeatRate", 0.05f, "Interval in seconds between elevation adjustments while holding PageUp / PageDown (lower = faster movement).", synchronizedSetting: false); _instantFlatten = BindSynced("3 - Altitude Lock", "InstantFlatten", value: true, "Instantly level 100% of terrain vertices inside brush radius to exact target altitude in a single click (eliminates gradual multi-click leveling and stepped ridges)."); _instantFlattenVanilla = BindSynced("3 - Altitude Lock", "InstantFlattenVanilla", value: false, "Apply instant single-click flattening to vanilla hoe 'Level Ground' tool even when altitude lock is off (default: false, so vanilla hoe retains gradual leveling while Instant Plateau tool provides 1-click leveling)."); _flattenEdgeMargin = BindSynced("3 - Altitude Lock", "FlattenEdgeMargin", 0f, "Edge blend feathering margin in meters for leveling. Default is 0.0m for razor-flat plateau across the entire brush circle. Set > 0 (e.g. 1.0m) for softened borders into hills."); _corridorColor = BindSynced("4 - Visual Indicators", "CorridorColor", "#00F5FF", "Hex color code (#RRGGBB or #RRGGBBAA) or HTML color name (e.g. cyan, lime, yellow) for outer corridor boundary box.", synchronizedSetting: false); _centerLineColor = BindSynced("4 - Visual Indicators", "CenterLineColor", "#55FF33", "Hex color code (#RRGGBB or #RRGGBBAA) or HTML color name for the central guide spine.", synchronizedSetting: false); _indicatorAlpha = BindSynced("4 - Visual Indicators", "IndicatorAlpha", 0.9f, "Opacity of the 3D ramp guide visualizer (0.1 to 1.0).", synchronizedSetting: false); _showElevationGrid = BindSynced("4 - Visual Indicators", "ShowElevationGrid", value: true, "Display holographic elevation grid rings and crosshairs at the locked altitude plane.", synchronizedSetting: false); _elevationGridColor = BindSynced("4 - Visual Indicators", "ElevationGridColor", "#00FFAA", "Hex color code (#RRGGBB or #RRGGBBAA) or HTML color name for holographic elevation grid overlay.", synchronizedSetting: false); _dynamicSlopeColoring = BindSynced("4 - Visual Indicators", "DynamicSlopeColoring", value: true, "Dynamically color the central guide spine based on slope angle and cart friendliness (Green = <=20° Cart Friendly, Yellow = 20-25° Steep, Red = >25° Too Steep for Carts).", synchronizedSetting: false); _resourceCostMode = BindSynced("6 - Resource & Economy", "CostMode", ResourceCostMode.BalancedSurvival, "Resource consumption mode:\n- BalancedSurvival: Full vanilla survival balance (stone costs for earth raised and cobblestone paved, stamina consumption, tool durability loss, workbench/stonecutter station checks, and tool requirements for clearing debris).\n- Freebuild: Creative freebuild mode with zero resource, stamina, durability, or station costs."); _stoneCostPerVolume = BindSynced("6 - Resource & Economy", "StonePerCubicMeter", 0.25f, "Stone required per cubic meter of terrain raised in BalancedSurvival mode (default 0.25 = 1 stone per 4 m³ of dirt raised)."); _stoneCostPerPavedSquare = BindSynced("6 - Resource & Economy", "StonePerPavedSquare", 0.03f, "Stone required per square meter of cobblestone paved in BalancedSurvival mode (default 0.03 = ~1 stone per 33 m²)."); _requireCraftingStation = BindSynced("6 - Resource & Economy", "RequireCraftingStation", value: true, "In BalancedSurvival mode, require a nearby Workbench to raise earth, and a nearby Stonecutter to pave stone (matching vanilla Valheim)."); _requireToolsForDebris = BindSynced("6 - Resource & Economy", "RequireToolsForDebris", value: true, "In BalancedSurvival mode, require an Axe in inventory to chop trees/logs, and a Pickaxe to mine rocks/ore when using Clear Debris."); _staminaCostLevel = BindSynced("6 - Resource & Economy", "StaminaCostLevel", 5f, "Base stamina cost for Leveling, Soften, and Restore tools in BalancedSurvival mode."); _durabilityCostLevel = BindSynced("6 - Resource & Economy", "DurabilityCostLevel", 1f, "Tool durability lost per operation in BalancedSurvival mode."); _staminaPerDebris = BindSynced("6 - Resource & Economy", "StaminaPerDebris", 8f, "Stamina consumed per tree or rock cleared with Clear Debris in BalancedSurvival mode."); _dropResourcesOnClear = BindSynced("7 - Clear Debris", "DropResourcesOnClear", value: false, "When clearing debris, drop wood, stone, and items onto the ground instead of cleanly deleting them (default: false for instant clutter-free clearing)."); _showHud = BindSynced("5 - UI & Controls", "ShowHud", value: true, "Display status HUD widget when holding the hoe.", synchronizedSetting: false); _holdToLevel = BindSynced("5 - UI & Controls", "HoldToLevel", value: true, "Hold left-click to continuously sweep and level terrain.", synchronizedSetting: false); _holdInterval = BindSynced("5 - UI & Controls", "HoldInterval", 0.25f, "Seconds between placement sweeps when holding left-click.", synchronizedSetting: false); _brushRadiusConfig = BindSynced("5 - UI & Controls", "BrushRadius", 3.5f, "Default brush radius for Leveling, Soften, and Restore in meters (1.0 to 15.0).", synchronizedSetting: false); _brushRadiusStep = BindSynced("5 - UI & Controls", "BrushRadiusStep", 0.5f, "Radius increment per Alt+Scroll or Alt+[/] press.", synchronizedSetting: false); _brushShapeConfig = BindSynced("5 - UI & Controls", "BrushShape", BrushShape.Circle, "Default hoe brush shape (Circle or Square).", synchronizedSetting: false); _squareRotationMode = BindSynced("5 - UI & Controls", "SquareRotationMode", SquareRotationMode.ScrollWheelAndButtons, "Control method for rotating the square brush: ScrollWheelAndButtons (default: mouse scroll or arrow keys), ButtonsOnly (keyboard only, mouse scroll will not rotate), or ScrollWheelOnly.", synchronizedSetting: false); _squareRotationStep = BindSynced("5 - UI & Controls", "SquareRotationDegrees", 22.5f, "Degrees to rotate the square brush per step/click. Vanilla Valheim uses 22.5° (16 steps in 360°).", synchronizedSetting: false); _hudOffsetY = BindSynced("5 - UI & Controls", "HudOffsetY", 20f, "Vertical pixel offset from the top of the screen for the top-middle HUD panel.", synchronizedSetting: false); BrushRadius = _brushRadiusConfig.Value; BrushShape = _brushShapeConfig.Value; _enableUndo = BindSynced("8 - Undo Settings", "EnableUndo", value: true, "Enable multi-step terrain modification undo and redo."); _undoKey = BindSynced<KeyCode>("8 - Undo Settings", "UndoKey", (KeyCode)122, "Key to undo the last terrain modification (default: Z).", synchronizedSetting: false); _redoKey = BindSynced<KeyCode>("8 - Undo Settings", "RedoKey", (KeyCode)121, "Key to redo the last undone terrain modification (default: Y).", synchronizedSetting: false); _undoModifier = BindSynced("8 - Undo Settings", "UndoModifier", UndoModifierMode.AltOrControl, "Modifier key required for Undo/Redo: AltOrControl (Alt+Z or Ctrl+Z), LeftAlt, LeftControl, or None.", synchronizedSetting: false); _maxUndoSteps = BindSynced("8 - Undo Settings", "MaxUndoSteps", 15, "Maximum number of terrain changes remembered in undo history (1 to 50)."); _rampStepBackWithUndoKey = BindSynced("8 - Undo Settings", "RampStepBackWithUndoKey", value: true, "While placing a ramp with active waypoints, pressing the undo hotkey removes the most recent waypoint first.", synchronizedSetting: false); _hoeReach = BindSynced("9 - Reach & Limits", "HoeReach", 20f, "Maximum placement distance while the hoe is equipped, in meters (default: 20m)."); _allowBeyondGameLimit = BindSynced("9 - Reach & Limits", "AllowBeyondGameLimit", value: true, "Allows terrain leveling, raising, and digging to exceed Valheim's standard 8m limit."); _maxTerrainDelta = BindSynced("9 - Reach & Limits", "MaxTerrainDelta", 50f, "Maximum terrain elevation offset from natural ground when AllowBeyondGameLimit is enabled (meters)."); _maxBrushRadius = BindSynced("9 - Reach & Limits", "MaxBrushRadius", 20f, "Maximum allowable brush radius for leveling, soften, and clearing tools, in meters (default: 20m)."); _raiseHeight = BindSynced("10 - Raise & Elevation Settings", "DefaultRaiseHeight", 0.5f, "Default height added per raise operation, in meters.", synchronizedSetting: false); _raiseStep = BindSynced("10 - Raise & Elevation Settings", "RaiseScrollStep", 0.5f, "Height added per mouse-wheel step when adjusting height, in meters.", synchronizedSetting: false); _minRaise = BindSynced("10 - Raise & Elevation Settings", "MinRaise", 0.05f, "Minimum selectable raise height, in meters."); _maxRaise = BindSynced("10 - Raise & Elevation Settings", "MaxRaise", 8f, "Maximum selectable raise height, in meters."); _areaKey = BindSynced<KeyCode>("1 - Hotkeys", "AreaKey", (KeyCode)113, "Selects Area adjustment mode while the hoe is equipped.", synchronizedSetting: false); _heightKey = BindSynced<KeyCode>("1 - Hotkeys", "HeightKey", (KeyCode)101, "Selects Height adjustment mode while the hoe is equipped.", synchronizedSetting: false); _suppressCameraZoom = BindSynced("5 - UI & Controls", "SuppressCameraZoom", value: true, "Prevents mouse wheel from zooming camera while adjusting the hoe.", synchronizedSetting: false); _suppressZoomMode = BindSynced("5 - UI & Controls", "SuppressZoomMode", SuppressZoomMode.WhileAdjusting, "When to suppress camera zoom: WhileAdjusting (only when holding modifiers or in active adjust mode), AlwaysWithHoe (anytime hoe is held), or Disabled.", synchronizedSetting: false); _language = BindSynced("Localization", "Language", "Auto", "In-game language. Supported values: Auto (automatically detects Valheim's active language), English, German, French, Spanish, Russian, Chinese, Portuguese, Polish, Swedish.", synchronizedSetting: false); _enableRaisePreview = BindSynced("4 - Visual Indicators", "EnableRaiseCylinderPreview", value: true, "Display translucent 3D cylinder preview showing the exact raise height and radius.", synchronizedSetting: false); _validPreviewColor = BindSynced("4 - Visual Indicators", "ValidPreviewColor", "#80D0FF", "Cylinder preview color when within reach (#RRGGBB or #RRGGBBAA).", synchronizedSetting: false); _invalidPreviewColor = BindSynced("4 - Visual Indicators", "InvalidPreviewColor", "#CC3333", "Cylinder preview color when out of reach (#RRGGBB or #RRGGBBAA).", synchronizedSetting: false); _validPreviewAlpha = BindSynced("4 - Visual Indicators", "ValidPreviewAlpha", 0.35f, "Cylinder preview opacity when within reach (0.0 to 1.0).", synchronizedSetting: false); _invalidPreviewAlpha = BindSynced("4 - Visual Indicators", "InvalidPreviewAlpha", 0.3f, "Cylinder preview opacity when out of reach (0.0 to 1.0).", synchronizedSetting: false); SelectedRaiseHeight = _raiseHeight.Value; HoeLocalization.ConfiguredLanguage = _language.Value; if ((int)SystemInfo.graphicsDeviceType != 4) { RaisePreviewVisualizer = ((Component)this).gameObject.AddComponent<RaiseCylinderVisualizer>(); } UpdateCachedColors(); _harmony = new Harmony("com.jubaroo.smarterhoe"); ModCompatibilityManager.ApplyPrePatchCompatibility(_harmony); Type[] types = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type type in types) { if (type.Namespace == null || !type.Namespace.StartsWith("ServerSync")) { try { _harmony.PatchAll(type); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[SmarterHoe] Error applying Harmony patch for " + type.FullName + ": " + ex.Message)); } } } ModCompatibilityManager.ApplyPostPatchCompatibility(_harmony); ModCompatibilityManager.RegisterGizmoIgnoredPrefabs(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"SmarterHoe v1.1.1 initialized successfully with ServerSync dedicated server support!"); } private void OnDestroy() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } RestorePlaceDistance(); RestoreOriginalPieceRequirements(); if ((int)SystemInfo.graphicsDeviceType != 4) { RampVisualizer.Instance.Hide(); ElevationGridVisualizer.Instance.Hide(); SquareBrushVisualizer.Instance.Hide(); RaisePreviewVisualizer?.Hide(); } } public static float ClampDelta(float delta) { if (_allowBeyondGameLimit != null && _allowBeyondGameLimit.Value) { float num = _maxTerrainDelta?.Value ?? 50f; return Mathf.Clamp(delta, 0f - num, num); } return Mathf.Clamp(delta, -8f, 8f); } public static float ClampAppliedHeight(float value, float min, float max) { if (_allowBeyondGameLimit != null && _allowBeyondGameLimit.Value) { float num = _maxTerrainDelta?.Value ?? 50f; float num2 = (min + max) * 0.5f; return Mathf.Clamp(value, num2 - num, num2 + num); } return Mathf.Clamp(value, min, max); } public static float ClampDeltaLimit(float value, float min, float max) { if (_allowBeyondGameLimit != null && _allowBeyondGameLimit.Value) { float num = _maxTerrainDelta?.Value ?? 50f; return Mathf.Clamp(value, 0f - num, num); } return Mathf.Clamp(value, min, max); } public static bool IsHoeEquipped(Player player) { if ((Object)(object)player == (Object)null) { return false; } ItemData rightItem = ((Humanoid)player).GetRightItem(); if (rightItem == null || rightItem.m_shared == null) { return false; } string name = rightItem.m_shared.m_name; if (!string.IsNullOrEmpty(name)) { return name.IndexOf("hoe", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static void ExtendHoeReach(Player player) { if ((Object)(object)_rangePlayer != (Object)(object)player) { RestorePlaceDistance(); _rangePlayer = player; _originalPlaceDistance = player.m_maxPlaceDistance; } player.m_maxPlaceDistance = Mathf.Max(_originalPlaceDistance, HoeReach); } private static void RestorePlaceDistance() { if ((Object)(object)_rangePlayer != (Object)null && _originalPlaceDistance >= 0f) { _rangePlayer.m_maxPlaceDistance = _originalPlaceDistance; } _rangePlayer = null; _originalPlaceDistance = -1f; } public static float GetCameraScrollWheel() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !IsHoeEquipped(localPlayer) || _suppressCameraZoom == null || !_suppressCameraZoom.Value) { return ZInput.GetMouseScrollWheel(); } if (_suppressZoomMode.Value == SuppressZoomMode.WhileAdjusting) { if (Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307) || Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305) || Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303) || ActiveAdjustMode != AdjustMode.None) { return 0f; } if (BrushShape == BrushShape.Square && _squareRotationMode.Value != SquareRotationMode.ButtonsOnly) { return 0f; } return ZInput.GetMouseScrollWheel(); } if (_suppressZoomMode.Value == SuppressZoomMode.AlwaysWithHoe) { return 0f; } return ZInput.GetMouseScrollWheel(); } public static void UpdateCachedColors() { //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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) _cachedCorridorColor = ParseColor(_corridorColor.Value, new Color(0.2f, 0.9f, 1f, 1f)); _cachedCorridorColor.a = Mathf.Clamp01(_indicatorAlpha.Value); _cachedCenterLineColor = ParseColor(_centerLineColor.Value, new Color(0.4f, 1f, 0.3f, 1f)); _cachedCenterLineColor.a = Mathf.Clamp01(_indicatorAlpha.Value); _cachedElevationGridColor = ParseColor(_elevationGridColor.Value, new Color(0f, 1f, 0.66f, 1f)); _cachedElevationGridColor.a = Mathf.Clamp01(_indicatorAlpha.Value); _cachedValidColor = ParseColor(_validPreviewColor.Value, new Color(0.5f, 0.5f, 0.5f)); _cachedInvalidColor = ParseColor(_invalidPreviewColor.Value, new Color(0.8f, 0.2f, 0.2f)); } public static Color GetCorridorColor() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return _cachedCorridorColor; } public static Color GetCenterLineColor() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (_paintMode.Value == RampPaintMode.AlwaysPaved && _centerLineColor.Value == "#55FF33") { return new Color(0.95f, 0.85f, 0.55f, Mathf.Clamp01(_indicatorAlpha.Value)); } return _cachedCenterLineColor; } public static Color GetElevationGridColor() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return _cachedElevationGridColor; } private static void CyclePaintMode() { switch (_paintMode.Value) { case RampPaintMode.MatchEquippedTool: _paintMode.Value = RampPaintMode.AlwaysPaved; break; case RampPaintMode.AlwaysPaved: _paintMode.Value = RampPaintMode.AlwaysDirt; break; case RampPaintMode.AlwaysDirt: _paintMode.Value = RampPaintMode.KeepOriginal; break; case RampPaintMode.KeepOriginal: _paintMode.Value = RampPaintMode.AlwaysCultivated; break; default: _paintMode.Value = RampPaintMode.MatchEquippedTool; break; } string paintModeLabel = GetPaintModeLabel(_paintMode.Value); ShowHudNotice("<color=#88FFFF>[SmarterHoe]</color> Ramp Surface: <b>" + paintModeLabel + "</b>"); } public static string GetPaintModeLabel(RampPaintMode mode) { return mode switch { RampPaintMode.AlwaysPaved => "<color=#88FFFF>PAVED STONE (Cobblestone)</color>", RampPaintMode.AlwaysDirt => "<color=#FFCC88>DIRT</color>", RampPaintMode.KeepOriginal => "<color=#88FF88>ORIGINAL (Grass/Snow)</color>", RampPaintMode.AlwaysCultivated => "<color=#CCAA88>CULTIVATED SOIL</color>", _ => "<color=#CCCCCC>MATCH TOOL</color>", }; } private static Color ParseColor(string hexOrName, Color fallback) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(hexOrName)) { return fallback; } hexOrName = hexOrName.Trim(); Color result = default(Color); if (ColorUtility.TryParseHtmlString(hexOrName, ref result)) { return result; } Color result2 = default(Color); if (!hexOrName.StartsWith("#") && ColorUtility.TryParseHtmlString("#" + hexOrName, ref result2)) { return result2; } return fallback; } private void Update() { //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_080d: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0360: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_0535: Unknown result type (might be due to invalid IL or missing references) //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_0a37: Unknown result type (might be due to invalid IL or missing references) //IL_088f: Unknown result type (might be due to invalid IL or missing references) //IL_0af4: Unknown result type (might be due to invalid IL or missing references) //IL_089b: Unknown result type (might be due to invalid IL or missing references) //IL_072f: Unknown result type (might be due to invalid IL or missing references) //IL_063a: Unknown result type (might be due to invalid IL or missing references) //IL_0b0d: Unknown result type (might be due to invalid IL or missing references) //IL_0913: Unknown result type (might be due to invalid IL or missing references) //IL_0924: Unknown result type (might be due to invalid IL or missing references) //IL_079f: Unknown result type (might be due to invalid IL or missing references) //IL_06ad: Unknown result type (might be due to invalid IL or missing references) //IL_08d6: Unknown result type (might be due to invalid IL or missing references) //IL_0d01: Unknown result type (might be due to invalid IL or missing references) //IL_0b5f: Unknown result type (might be due to invalid IL or missing references) //IL_0e9b: Unknown result type (might be due to invalid IL or missing references) //IL_0d23: Unknown result type (might be due to invalid IL or missing references) //IL_0d28: Unknown result type (might be due to invalid IL or missing references) //IL_0fe0: Unknown result type (might be due to invalid IL or missing references) //IL_0d9e: Unknown result type (might be due to invalid IL or missing references) //IL_0d4e: Unknown result type (might be due to invalid IL or missing references) //IL_0d53: Unknown result type (might be due to invalid IL or missing references) //IL_09ac: Unknown result type (might be due to invalid IL or missing references) //IL_1021: Unknown result type (might be due to invalid IL or missing references) //IL_0ded: Unknown result type (might be due to invalid IL or missing references) //IL_0df2: Unknown result type (might be due to invalid IL or missing references) //IL_0e42: Unknown result type (might be due to invalid IL or missing references) //IL_0e54: Unknown result type (might be due to invalid IL or missing references) //IL_0dbc: Unknown result type (might be due to invalid IL or missing references) //IL_1039: Unknown result type (might be due to invalid IL or missing references) //IL_0f6f: Unknown result type (might be due to invalid IL or missing references) //IL_0fac: Unknown result type (might be due to invalid IL or missing references) //IL_106a: Unknown result type (might be due to invalid IL or missing references) //IL_10c6: Unknown result type (might be due to invalid IL or missing references) //IL_0fc9: Unknown result type (might be due to invalid IL or missing references) //IL_10f1: Unknown result type (might be due to invalid IL or missing references) //IL_10fb: Unknown result type (might be due to invalid IL or missing references) //IL_1156: Unknown result type (might be due to invalid IL or missing references) //IL_115b: Unknown result type (might be due to invalid IL or missing references) //IL_1162: Unknown result type (might be due to invalid IL or missing references) //IL_116e: Unknown result type (might be due to invalid IL or missing references) //IL_117e: Unknown result type (might be due to invalid IL or missing references) //IL_1221: Unknown result type (might be due to invalid IL or missing references) //IL_1231: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || ((Character)localPlayer).InCutscene()) { RampVisualizer.Instance.Hide(); } else { if (((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) || Console.IsVisible() || TextInput.IsVisible() || Menu.IsVisible()) { return; } bool flag = (Object)(object)localPlayer.m_placementGhost != (Object)null; Piece val = (flag ? localPlayer.m_placementGhost.GetComponent<Piece>() : null); bool flag2 = (Object)(object)val != (Object)null && CustomPieceManager.IsTerrainToolPiece(val, localPlayer.m_placementGhost); if ((Object)(object)localPlayer.m_buildPieces != (Object)(object)_lastCheckedPieceTable) { _lastCheckedPieceTable = localPlayer.m_buildPieces; if ((Object)(object)_lastCheckedPieceTable != (Object)null) { if (CustomPieceManager.IsHoePieceTable(_lastCheckedPieceTable)) { CustomPieceManager.TryInitPieces(_lastCheckedPieceTable); } else { CustomPieceManager.RemoveCustomPiecesFrom(_lastCheckedPieceTable); AltitudeLockEnabled = false; } } else { AltitudeLockEnabled = false; } } bool flag3 = IsHoeEquipped(localPlayer); if (flag3) { ExtendHoeReach(localPlayer); } else { RestorePlaceDistance(); RestoreOriginalPieceRequirements(); } if ((Object)(object)val != (Object)null) { SyncTerrainPieceRequirements(localPlayer, val); } bool flag4 = Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307); bool flag5 = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); if (flag2 || flag3) { if (Input.GetKeyDown(_areaKey.Value)) { ActiveAdjustMode = AdjustMode.Area; ShowHudNotice("<color=#88FFFF>[SmarterHoe]</color> " + HoeLocalization.Localize("Adjusting: Area") + " (Scroll wheel to resize radius)"); } else if (Input.GetKeyDown(_heightKey.Value)) { ActiveAdjustMode = AdjustMode.Height; ShowHudNotice("<color=#88FFFF>[SmarterHoe]</color> " + HoeLocalization.Localize("Adjusting: Height") + $" ({SelectedRaiseHeight:F2}m - Scroll to change)"); } } bool flag6 = (Object)(object)val != (Object)null && (((Object)val).name.StartsWith("SmarterHoe_Ramp") || ((Object)((Component)val).gameObject).name.StartsWith("SmarterHoe_Ramp") || val.m_name == "Smooth Ramp"); int num; if ((Object)(object)val != (Object)null) { if (!((Object)val).name.StartsWith("raise") && !((Object)((Component)val).gameObject).name.StartsWith("raise")) { TerrainOp component = ((Component)val).GetComponent<TerrainOp>(); num = ((component != null && component.m_settings?.m_raise == true) ? 1 : 0); } else { num = 1; } } else { num = 0; } bool flag7 = (byte)num != 0; if (_enableRaisePreview != null && _enableRaisePreview.Value && flag7 && flag && (Object)(object)localPlayer.m_placementGhost != (Object)null) { Vector3 position = localPlayer.m_placementGhost.transform.position; bool num2 = Vector3.Distance(((Component)localPlayer).transform.position, position) <= localPlayer.m_maxPlaceDistance + 0.1f; bool isVanillaRaise; bool isVanillaPaved; int terrainPieceStoneCost = GetTerrainPieceStoneCost(val, out isVanillaRaise, out isVanillaPaved); int num3; if (CostMode != ResourceCostMode.Freebuild && !localPlayer.NoCostCheat()) { Inventory inventory = ((Humanoid)localPlayer).GetInventory(); num3 = ((((inventory != null) ? inventory.CountItems("$item_stone", -1, true) : 0) >= terrainPieceStoneCost) ? 1 : 0); } else { num3 = 1; } bool flag8 = (byte)num3 != 0; bool inReach = num2 && flag8; RaisePreviewVisualizer?.SetVisuals(position, localPlayer.m_placementGhost.transform.rotation, BrushShape, BrushRadius, SelectedRaiseHeight, inReach, _cachedValidColor, _cachedInvalidColor, _validPreviewAlpha.Value, _invalidPreviewAlpha.Value); } else { RaisePreviewVisualizer?.Hide(); } if (flag2 || flag3) { float axis = Input.GetAxis("Mouse ScrollWheel"); if (Mathf.Abs(axis) > 0.01f) { if (ActiveAdjustMode == AdjustMode.Height || (flag4 && flag5)) { float value = _raiseStep.Value; if (axis > 0.01f) { SelectedRaiseHeight = Mathf.Clamp(Mathf.Round((SelectedRaiseHeight + value) * 100f) / 100f, _minRaise.Value, _maxRaise.Value); } else { SelectedRaiseHeight = Mathf.Clamp(Mathf.Round((SelectedRaiseHeight - value) * 100f) / 100f, _minRaise.Value, _maxRaise.Value); } _raiseHeight.Value = SelectedRaiseHeight; ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Raise Height: <b>{SelectedRaiseHeight:F2}m</b>"); } else if (flag4 || ActiveAdjustMode == AdjustMode.Area) { float value2 = _brushRadiusStep.Value; if (axis > 0.01f) { BrushRadius = Mathf.Clamp(Mathf.Round((BrushRadius + value2) * 10f) / 10f, 0.5f, MaxBrushRadius); } else { BrushRadius = Mathf.Clamp(Mathf.Round((BrushRadius - value2) * 10f) / 10f, 0.5f, MaxBrushRadius); } _brushRadiusConfig.Value = BrushRadius; ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Brush Radius: <b>{BrushRadius:F1}m</b>"); } } else if (flag4 && Input.GetKeyDown(_increaseWidthKey.Value)) { BrushRadius = Mathf.Clamp(Mathf.Round((BrushRadius + _brushRadiusStep.Value) * 10f) / 10f, 0.5f, MaxBrushRadius); _brushRadiusConfig.Value = BrushRadius; ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Brush Radius: <b>{BrushRadius:F1}m</b>"); } else if (flag4 && Input.GetKeyDown(_decreaseWidthKey.Value)) { BrushRadius = Mathf.Clamp(Mathf.Round((BrushRadius - _brushRadiusStep.Value) * 10f) / 10f, 0.5f, MaxBrushRadius); _brushRadiusConfig.Value = BrushRadius; ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Brush Radius: <b>{BrushRadius:F1}m</b>"); } else if (!flag4 && (flag6 || RampWaypoints.Count > 0)) { if (Input.GetKeyDown(_increaseWidthKey.Value)) { _rampWidth.Value = Mathf.Clamp(Mathf.Round((_rampWidth.Value + _rampWidthStep.Value) * 10f) / 10f, 1f, MaxBrushRadius); ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Ramp Width: <b>{_rampWidth.Value:F1}m</b>"); } else if (Input.GetKeyDown(_decreaseWidthKey.Value)) { _rampWidth.Value = Mathf.Clamp(Mathf.Round((_rampWidth.Value - _rampWidthStep.Value) * 10f) / 10f, 1f, MaxBrushRadius); ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Ramp Width: <b>{_rampWidth.Value:F1}m</b>"); } } else if (!flag4 && flag) { if (Input.GetKeyDown(_increaseWidthKey.Value)) { BrushRadius = Mathf.Clamp(Mathf.Round((BrushRadius + _brushRadiusStep.Value) * 10f) / 10f, 0.5f, MaxBrushRadius); _brushRadiusConfig.Value = BrushRadius; ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Brush Radius: <b>{BrushRadius:F1}m</b>"); } else if (Input.GetKeyDown(_decreaseWidthKey.Value)) { BrushRadius = Mathf.Clamp(Mathf.Round((BrushRadius - _brushRadiusStep.Value) * 10f) / 10f, 0.5f, MaxBrushRadius); _brushRadiusConfig.Value = BrushRadius; ShowHudNotice($"<color=#88FFFF>[SmarterHoe]</color> Brush Radius: <b>{BrushRadius:F1}m</b>"); } } } if (Input.GetKeyDown(_toggleBrushShapeKey.Value) && flag2) { BrushShape = ((BrushShape == BrushShape.Circle) ? BrushShape.Square : BrushShape.Circle);