using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using ServerSync; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("BiomeGrowthDensity")] [assembly: AssemblyDescription("BepInEx plugin for Valheim that adjusts plant growth times based on biome and density.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BiomeGrowthDensity")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("a1b2c3d4-e5f6-7890-abcd-ef1234567890")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 BiomeGrowthDensity { public static class BiomeDetector { private static readonly Dictionary PrefabBiomeMap = new Dictionary { { "CloudberryBush", (Biome)16 }, { "Pickable_Thistle", (Biome)2 }, { "BlueberryBush", (Biome)8 }, { "Pickable_Mushroom_blue", (Biome)512 }, { "Pickable_SmokePuff", (Biome)32 } }; public static Biome GetNativeBiome(Pickable pickable) { //IL_0016: 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) string prefabName = GetPrefabName((Component)(object)pickable); if (PrefabBiomeMap.TryGetValue(prefabName, out var value)) { return value; } FieldInfo field = typeof(Pickable).GetField("m_biome", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { return (Biome)field.GetValue(pickable); } return (Biome)1; } public static Biome GetNativeBiome(Plant plant) { //IL_0016: 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) string prefabName = GetPrefabName((Component)(object)plant); if (PrefabBiomeMap.TryGetValue(prefabName, out var value)) { return value; } FieldInfo field = typeof(Plant).GetField("m_biome", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { return (Biome)field.GetValue(plant); } return (Biome)1; } public static Biome GetCurrentBiome(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Heightmap.FindBiome(position); } public static bool IsOutsideNativeBiome(Vector3 position, Biome nativeBiome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 return (GetCurrentBiome(position) & nativeBiome) == 0; } private static string GetPrefabName(Component component) { if ((Object)(object)component == (Object)null) { return string.Empty; } string text = ((Object)component).name; if (text.EndsWith("(Clone)")) { text = text.Substring(0, text.Length - 7); } return text; } } public static class Configuration { private const string GeneralSection = "General"; private const string DensitySection = "Density"; private const string PlantsSection = "Plants"; private const string SyncSection = "Sync"; public static ConfigEntry Enabled { get; private set; } public static ConfigEntry ClusterRadius { get; private set; } public static ConfigEntry Tier1Max { get; private set; } public static ConfigEntry Tier1Multiplier { get; private set; } public static ConfigEntry Tier2Max { get; private set; } public static ConfigEntry Tier2Multiplier { get; private set; } public static ConfigEntry Tier3Multiplier { get; private set; } public static ConfigEntry EnableCloudberry { get; private set; } public static ConfigEntry EnableThistle { get; private set; } public static ConfigEntry EnableBlueberry { get; private set; } public static ConfigEntry EnableBlueMushroom { get; private set; } public static ConfigEntry EnableSmokepuff { get; private set; } public static ConfigEntry LockConfig { get; private set; } public static ConfigSync ConfigSync { get; private set; } public static void Init(ConfigFile config) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown ConfigSync = new ConfigSync("com.suttawat.BiomeGrowthDensity") { IsLocked = true }; Enabled = config.Bind("General", "Enabled", true, "Enable/disable the mod."); ClusterRadius = config.Bind("General", "ClusterRadius", 10f, "Radius in meters to check for plant density."); Tier1Max = config.Bind("Density", "Tier1Max", 3, "Max plants for tier 1 density penalty."); Tier1Multiplier = config.Bind("Density", "Tier1Multiplier", 1.5f, "Growth time multiplier for tier 1 density (1-3 plants)."); Tier2Max = config.Bind("Density", "Tier2Max", 6, "Max plants for tier 2 density penalty."); Tier2Multiplier = config.Bind("Density", "Tier2Multiplier", 2f, "Growth time multiplier for tier 2 density (4-6 plants)."); Tier3Multiplier = config.Bind("Density", "Tier3Multiplier", 3f, "Growth time multiplier for tier 3 density (7+ plants)."); EnableCloudberry = config.Bind("Plants", "EnableCloudberry", true, "Apply density penalty to Cloudberry bushes."); EnableThistle = config.Bind("Plants", "EnableThistle", true, "Apply density penalty to Thistles."); EnableBlueberry = config.Bind("Plants", "EnableBlueberry", true, "Apply density penalty to Blueberry bushes."); EnableBlueMushroom = config.Bind("Plants", "EnableBlueMushroom", true, "Apply density penalty to Blue Mushrooms."); EnableSmokepuff = config.Bind("Plants", "EnableSmokepuff", true, "Apply density penalty to Smoke Puffs."); LockConfig = config.Bind("Sync", "LockConfig", true, "Lock configuration on server."); ConfigSync.AddLockingConfigEntry(LockConfig); ConfigSync.AddConfigEntry(Enabled); ConfigSync.AddConfigEntry(ClusterRadius); ConfigSync.AddConfigEntry(Tier1Max); ConfigSync.AddConfigEntry(Tier1Multiplier); ConfigSync.AddConfigEntry(Tier2Max); ConfigSync.AddConfigEntry(Tier2Multiplier); ConfigSync.AddConfigEntry(Tier3Multiplier); ConfigSync.AddConfigEntry(EnableCloudberry); ConfigSync.AddConfigEntry(EnableThistle); ConfigSync.AddConfigEntry(EnableBlueberry); ConfigSync.AddConfigEntry(EnableBlueMushroom); ConfigSync.AddConfigEntry(EnableSmokepuff); } public static bool IsPlantEnabled(string prefabName) { return prefabName switch { "CloudberryBush" => EnableCloudberry.Value, "Pickable_Thistle" => EnableThistle.Value, "BlueberryBush" => EnableBlueberry.Value, "Pickable_Mushroom_blue" => EnableBlueMushroom.Value, "Pickable_SmokePuff" => EnableSmokepuff.Value, _ => true, }; } } public static class DensityScanner { private class ZoneCacheEntry { public int Count; public float Timestamp; } private static readonly Dictionary ZoneCache = new Dictionary(); private const float CacheExpirySeconds = 60f; public static int CountPlantsInRadius(Vector3 center, float radius, string targetPrefabName) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) if (ZDOMan.instance == null) { return 0; } int num = 0; float num2 = radius * radius; Pickable[] array = Object.FindObjectsOfType(); foreach (Pickable val in array) { if ((Object)(object)val == (Object)null) { continue; } GameObject gameObject = ((Component)val).gameObject; if (!((Object)(object)gameObject == (Object)null) && !(Vector3.SqrMagnitude(gameObject.transform.position - center) > num2)) { string text = ((Object)gameObject).name; if (text.EndsWith("(Clone)")) { text = text.Substring(0, text.Length - 7); } if (text == targetPrefabName && Vector3.SqrMagnitude(gameObject.transform.position - center) > 0.01f) { num++; } } } Plant[] array2 = Object.FindObjectsOfType(); foreach (Plant val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } GameObject gameObject2 = ((Component)val2).gameObject; if (!((Object)(object)gameObject2 == (Object)null) && !(Vector3.SqrMagnitude(gameObject2.transform.position - center) > num2)) { string text2 = ((Object)gameObject2).name; if (text2.EndsWith("(Clone)")) { text2 = text2.Substring(0, text2.Length - 7); } if (text2 == targetPrefabName && Vector3.SqrMagnitude(gameObject2.transform.position - center) > 0.01f) { num++; } } } return num; } public static int CountPlantsInRadiusCached(Vector3 center, float radius, string targetPrefabName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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) int num = Mathf.FloorToInt(center.x / 64f); int num2 = Mathf.FloorToInt(center.z / 64f); string key = $"{num}:{num2}:{targetPrefabName}"; float time = Time.time; if (ZoneCache.TryGetValue(key, out var value) && time - value.Timestamp < 60f) { return value.Count; } int num3 = CountPlantsInRadius(center, radius, targetPrefabName); ZoneCache[key] = new ZoneCacheEntry { Count = num3, Timestamp = time }; return num3; } public static void ClearCache() { ZoneCache.Clear(); } public static float GetDensityMultiplier(int plantCount) { if (plantCount == 0) { return 1f; } if (plantCount <= Configuration.Tier1Max.Value) { return Configuration.Tier1Multiplier.Value; } if (plantCount <= Configuration.Tier2Max.Value) { return Configuration.Tier2Multiplier.Value; } return Configuration.Tier3Multiplier.Value; } } [BepInPlugin("com.suttawat.BiomeGrowthDensity", "BiomeGrowthDensity", "1.0.0")] public class Plugin : BaseUnityPlugin { public static Plugin Instance { get; private set; } public static Harmony HarmonyInstance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; HarmonyInstance = new Harmony("com.suttawat.BiomeGrowthDensity"); Configuration.Init(((BaseUnityPlugin)this).Config); HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)"BiomeGrowthDensity loaded."); } private void OnDestroy() { Harmony harmonyInstance = HarmonyInstance; if (harmonyInstance != null) { harmonyInstance.UnpatchSelf(); } Instance = null; Log = null; } } } namespace BiomeGrowthDensity.Patches { [HarmonyPatch] public static class ConfigSyncPatch { public static bool Prepare() { return Chainloader.PluginInfos.ContainsKey("Advize_PlantEverything"); } public static MethodBase TargetMethod() { Assembly assembly = Assembly.Load("PlantEverything"); if (assembly == null) { return null; } Type type = assembly.GetType("Advize_PlantEverything.ConfigSync"); if (type == null) { return null; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name.Contains("ConfigSync") || methodInfo.Name.Contains("RPC")) { return methodInfo; } } return null; } public static void Postfix() { DensityScanner.ClearCache(); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"Density cache cleared after PlantEverything config sync."); } } } [HarmonyPatch] public static class PickableUpdatePatch { private static readonly Dictionary OriginalRespawnTimes = new Dictionary(); public static MethodBase TargetMethod() { return AccessTools.Method(typeof(Pickable), "UpdateRespawn", (Type[])null, (Type[])null); } public static void Postfix(Pickable __instance) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (!Configuration.Enabled.Value || (Object)(object)__instance == (Object)null) { return; } string text = ((Object)__instance).name; if (text.EndsWith("(Clone)")) { text = text.Substring(0, text.Length - 7); } if (!Configuration.IsPlantEnabled(text)) { return; } Biome nativeBiome = BiomeDetector.GetNativeBiome(__instance); Vector3 position = ((Component)__instance).transform.position; if (!BiomeDetector.IsOutsideNativeBiome(position, nativeBiome)) { return; } float densityMultiplier = DensityScanner.GetDensityMultiplier(DensityScanner.CountPlantsInRadiusCached(position, Configuration.ClusterRadius.Value, text)); if (densityMultiplier <= 1f) { return; } FieldInfo field = typeof(Pickable).GetField("m_respawnTimeMinutes", BindingFlags.Instance | BindingFlags.NonPublic); if (!(field == null)) { float num = (float)field.GetValue(__instance); int instanceID = ((Object)__instance).GetInstanceID(); if (!OriginalRespawnTimes.TryGetValue(instanceID, out var value)) { value = num; OriginalRespawnTimes[instanceID] = value; } float num2 = value * densityMultiplier; if (Mathf.Abs(num - num2) > 0.01f) { field.SetValue(__instance, num2); } } } } [HarmonyPatch(typeof(Plant), "Grow")] public static class PlantGrowPatch { private static readonly Dictionary OriginalGrowTimes = new Dictionary(); private static readonly Dictionary OriginalGrowTimeMaxs = new Dictionary(); [HarmonyPriority(200)] public static void Postfix(Plant __instance) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (!Configuration.Enabled.Value || (Object)(object)__instance == (Object)null) { return; } string text = ((Object)__instance).name; if (text.EndsWith("(Clone)")) { text = text.Substring(0, text.Length - 7); } if (!Configuration.IsPlantEnabled(text)) { return; } Biome nativeBiome = BiomeDetector.GetNativeBiome(__instance); Vector3 position = ((Component)__instance).transform.position; if (!BiomeDetector.IsOutsideNativeBiome(position, nativeBiome)) { return; } float densityMultiplier = DensityScanner.GetDensityMultiplier(DensityScanner.CountPlantsInRadiusCached(position, Configuration.ClusterRadius.Value, text)); if (densityMultiplier <= 1f) { return; } FieldInfo field = typeof(Plant).GetField("m_growTime", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = typeof(Plant).GetField("m_growTimeMax", BindingFlags.Instance | BindingFlags.NonPublic); if (!(field == null) && !(field2 == null)) { float num = (float)field.GetValue(__instance); float num2 = (float)field2.GetValue(__instance); int instanceID = ((Object)__instance).GetInstanceID(); if (!OriginalGrowTimes.TryGetValue(instanceID, out var value)) { value = num; OriginalGrowTimes[instanceID] = value; } if (!OriginalGrowTimeMaxs.TryGetValue(instanceID, out var value2)) { value2 = num2; OriginalGrowTimeMaxs[instanceID] = value2; } float num3 = value * densityMultiplier; float num4 = value2 * densityMultiplier; if (Mathf.Abs(num - num3) > 0.01f) { field.SetValue(__instance, num3); } if (Mathf.Abs(num2 - num4) > 0.01f) { field2.SetValue(__instance, num4); } } } } }