Decompiled source of Fairoots v1.0.0

Fairoots.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using Fairoots.Core;
using Fairoots.Core.Presets;
using Fairoots.Creatures;
using Fairoots.Diagnostics;
using Fairoots.Networking;
using Fairoots.SporeAreas;
using Fairoots.SporeBombs;
using Fairoots.Spores;
using Fairoots.Ui;
using Fairoots.Wind;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Peak.Afflictions;
using Peak.UI;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Animations.Rigging;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Fairoots")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+9323b671618d00c20c84a5679b5ed23d15ab02d7")]
[assembly: AssemblyProduct("Fairoots")]
[assembly: AssemblyTitle("Fairoots")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 Fairoots
{
	internal static class GameUnits
	{
		internal static float UnitsToMetersFactor => WorldUnits.SafeFactor(CharacterStats.unitsToMeters);

		internal static float MetersToUnits(float meters)
		{
			return WorldUnits.MetersToUnits(meters, CharacterStats.unitsToMeters);
		}

		internal static float ToMeters(float units)
		{
			return WorldUnits.UnitsToMeters(units, CharacterStats.unitsToMeters);
		}
	}
	internal static class ParticleOpacity
	{
		private static readonly HashSet<string> OpacityProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "_Opacity", "_Alpha", "_Transparency" };

		private static readonly Dictionary<int, MinMaxGradient> VanillaStartColors = new Dictionary<int, MinMaxGradient>();

		private static readonly Dictionary<(int RendererId, int PropertyId), float> VanillaOpacities = new Dictionary<(int, int), float>();

		private static readonly HashSet<int> LoggedMaterials = new HashSet<int>();

		internal static int Apply(GameObject root, double multiplier)
		{
			int num = 0;
			ParticleSystem[] componentsInChildren = root.GetComponentsInChildren<ParticleSystem>(true);
			foreach (ParticleSystem val in componentsInChildren)
			{
				ParticleSystemRenderer component = ((Component)val).GetComponent<ParticleSystemRenderer>();
				LogInventoryOnce(component);
				if (ApplyShaderOpacity(component, multiplier))
				{
					ApplyStartColorAlpha(val, 1.0);
				}
				else
				{
					ApplyStartColorAlpha(val, multiplier);
				}
				num++;
			}
			return num;
		}

		private static bool ApplyShaderOpacity(ParticleSystemRenderer renderer, double multiplier)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Invalid comparison between Unknown and I4
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Invalid comparison between Unknown and I4
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			Material val = (((Object)(object)renderer != (Object)null) ? ((Renderer)renderer).sharedMaterial : null);
			Shader val2 = (((Object)(object)val != (Object)null) ? val.shader : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return false;
			}
			int instanceID = ((Object)renderer).GetInstanceID();
			MaterialPropertyBlock val3 = null;
			int propertyCount = val2.GetPropertyCount();
			for (int i = 0; i < propertyCount; i++)
			{
				ShaderPropertyType propertyType = val2.GetPropertyType(i);
				if (((int)propertyType == 2 || (int)propertyType == 3) && OpacityProperties.Contains(val2.GetPropertyName(i)))
				{
					int propertyNameId = val2.GetPropertyNameId(i);
					(int, int) key = (instanceID, propertyNameId);
					if (!VanillaOpacities.TryGetValue(key, out var value))
					{
						value = val.GetFloat(propertyNameId);
						VanillaOpacities[key] = value;
					}
					if (val3 == null)
					{
						val3 = new MaterialPropertyBlock();
						((Renderer)renderer).GetPropertyBlock(val3);
					}
					val3.SetFloat(propertyNameId, SporeCloudOpacity.IsVanilla(multiplier) ? value : SporeCloudOpacity.ScaleAlpha(value, multiplier));
				}
			}
			if (val3 == null)
			{
				return false;
			}
			((Renderer)renderer).SetPropertyBlock(val3);
			return true;
		}

		private static void ApplyStartColorAlpha(ParticleSystem ps, double multiplier)
		{
			//IL_0008: 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)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			int instanceID = ((Object)ps).GetInstanceID();
			MainModule main = ps.main;
			if (!VanillaStartColors.TryGetValue(instanceID, out var value))
			{
				value = ((MainModule)(ref main)).startColor;
				VanillaStartColors[instanceID] = value;
			}
			((MainModule)(ref main)).startColor = (SporeCloudOpacity.IsVanilla(multiplier) ? value : ScaleAlpha(value, multiplier));
		}

		private static MinMaxGradient ScaleAlpha(MinMaxGradient authored, double multiplier)
		{
			//IL_0002: 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_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected I4, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: 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)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: 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_0071: 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)
			ParticleSystemGradientMode mode = ((MinMaxGradient)(ref authored)).mode;
			switch ((int)mode)
			{
			case 0:
				return new MinMaxGradient(WithScaledAlpha(((MinMaxGradient)(ref authored)).color, multiplier));
			case 2:
				return new MinMaxGradient(WithScaledAlpha(((MinMaxGradient)(ref authored)).colorMin, multiplier), WithScaledAlpha(((MinMaxGradient)(ref authored)).colorMax, multiplier));
			case 3:
				return new MinMaxGradient(ScaleAlpha(((MinMaxGradient)(ref authored)).gradientMin, multiplier), ScaleAlpha(((MinMaxGradient)(ref authored)).gradientMax, multiplier));
			case 1:
			case 4:
			{
				MinMaxGradient result = default(MinMaxGradient);
				((MinMaxGradient)(ref result))..ctor(ScaleAlpha(((MinMaxGradient)(ref authored)).gradient, multiplier));
				((MinMaxGradient)(ref result)).mode = ((MinMaxGradient)(ref authored)).mode;
				return result;
			}
			default:
				return authored;
			}
		}

		private static Gradient ScaleAlpha(Gradient authored, double multiplier)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			GradientAlphaKey[] alphaKeys = authored.alphaKeys;
			for (int i = 0; i < alphaKeys.Length; i++)
			{
				alphaKeys[i].alpha = SporeCloudOpacity.ScaleAlpha(alphaKeys[i].alpha, multiplier);
			}
			Gradient val = new Gradient
			{
				mode = authored.mode
			};
			val.SetKeys(authored.colorKeys, alphaKeys);
			return val;
		}

		private static Color WithScaledAlpha(Color authored, double multiplier)
		{
			//IL_0000: 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_000c: 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_001e: Unknown result type (might be due to invalid IL or missing references)
			return new Color(authored.r, authored.g, authored.b, SporeCloudOpacity.ScaleAlpha(authored.a, multiplier));
		}

		private static void LogInventoryOnce(ParticleSystemRenderer renderer)
		{
			//IL_005c: 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)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Invalid comparison between Unknown and I4
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Invalid comparison between Unknown and I4
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			if (!Diag.Enabled || (Object)(object)renderer == (Object)null)
			{
				return;
			}
			Material sharedMaterial = ((Renderer)renderer).sharedMaterial;
			Shader val = (((Object)(object)sharedMaterial != (Object)null) ? sharedMaterial.shader : null);
			if (!((Object)(object)val == (Object)null) && LoggedMaterials.Add(((Object)sharedMaterial).GetInstanceID()))
			{
				List<string> list = new List<string>();
				int propertyCount = val.GetPropertyCount();
				for (int i = 0; i < propertyCount; i++)
				{
					ShaderPropertyType propertyType = val.GetPropertyType(i);
					string propertyName = val.GetPropertyName(i);
					bool flag = ((int)propertyType == 2 || (int)propertyType == 3) && OpacityProperties.Contains(propertyName);
					list.Add(string.Format("{0}:{1}{2}", propertyName, propertyType, flag ? " <-- opacity lever" : string.Empty));
				}
				Diag.V("[ParticleOpacity] material \"" + ((Object)sharedMaterial).name + "\" on \"" + ((Object)renderer).name + "\": shader \"" + ((Object)val).name + "\", " + $"render mode {renderer.renderMode}, declares: " + ((list.Count == 0) ? "(nothing)" : string.Join(", ", list)));
			}
		}
	}
	[BepInPlugin("OnlyCook.Fairoots", "Fairoots", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		internal static Plugin Instance { get; private set; }

		internal static PluginConfig Cfg { get; private set; }

		private void Awake()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0461: Unknown result type (might be due to invalid IL or missing references)
			//IL_0466: Unknown result type (might be due to invalid IL or missing references)
			//IL_046c: Expected O, but got Unknown
			//IL_046c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0473: Unknown result type (might be due to invalid IL or missing references)
			//IL_047f: Expected O, but got Unknown
			//IL_0523: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Cfg = new PluginConfig(((BaseUnityPlugin)this).Config);
			Diag.Source = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("OnlyCook.Fairoots");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			TriggerRadiusOverlay.Sync();
			Cfg.EnableDebugLogging.SettingChanged += delegate
			{
				SyncDebugOverlays();
			};
			Cfg.ShowSporeBombTriggerRadius.SettingChanged += delegate
			{
				TriggerRadiusOverlay.Sync();
			};
			Cfg.ShowRemovedSporeBombMarkers.SettingChanged += delegate
			{
				DebugOverlayHost.Sync();
			};
			Cfg.KeepVanillaTriggerRadius.SettingChanged += delegate
			{
				SporeBombCullPatch.ReapplyTriggerRadiusToAll();
			};
			Cfg.SporeBombTriggerRadiusMultiplierOverride.SettingChanged += delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					SporeBombCullPatch.ReapplyTriggerRadiusToAll();
				}
			};
			Cfg.Preset.SettingChanged += delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					SporeBombCullPatch.ReapplyTriggerRadiusToAll();
					WindChillZoneTuningPatch.ReapplyAll();
					SporeAreaTuningPatch.ReapplyToAll();
					CreatureSpeedPatch.ReapplyToAll();
					CreatureKnockbackPatch.ReapplyToAll();
					CreatureRagdollPatch.ReapplyToAll();
					SporeDecayPatch.ReapplyToAll();
				}
			};
			EventHandler eventHandler = delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					WindChillZoneTuningPatch.ReapplyAll();
				}
			};
			Cfg.WindForceMultiplierOverride.SettingChanged += eventHandler;
			Cfg.WindGustDurationMultiplierOverride.SettingChanged += eventHandler;
			Cfg.WindItemForceMultiplierOverride.SettingChanged += eventHandler;
			Cfg.WindObstacleOcclusionRangeMultiplierOverride.SettingChanged += eventHandler;
			Cfg.DisableWindEntirely.SettingChanged += delegate
			{
				WindChillZoneTuningPatch.ReapplyAll();
			};
			Cfg.DisableSporeAreas.SettingChanged += delegate
			{
				SporeAreaDisablePatch.ReapplyToAll();
			};
			EventHandler eventHandler2 = delegate
			{
				CreatureDisablePatch.ReapplyToAll();
			};
			Cfg.DisableZombies.SettingChanged += eventHandler2;
			Cfg.DisableBeetles.SettingChanged += eventHandler2;
			Cfg.DisableSpiders.SettingChanged += eventHandler2;
			EventHandler eventHandler3 = delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					CreatureSpeedPatch.ReapplyToAll();
				}
			};
			Cfg.ZombieSpeedMultiplierOverride.SettingChanged += eventHandler3;
			Cfg.BeetleSpeedMultiplierOverride.SettingChanged += eventHandler3;
			Cfg.BeetleKnockbackMultiplierOverride.SettingChanged += delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					CreatureKnockbackPatch.ReapplyToAll();
				}
			};
			Cfg.CreatureRagdollMultiplierOverride.SettingChanged += delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					CreatureRagdollPatch.ReapplyToAll();
				}
			};
			EventHandler eventHandler4 = delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					SporeAreaTuningPatch.ReapplyToAll();
				}
			};
			Cfg.SporeAreaRadiusMultiplierOverride.SettingChanged += eventHandler4;
			Cfg.SporeAreaStatusRateMultiplierOverride.SettingChanged += eventHandler4;
			Cfg.SporeClearTimeMultiplierOverride.SettingChanged += delegate
			{
				if (Cfg.LiveUpdatesActive)
				{
					SporeDecayPatch.ReapplyToAll();
				}
			};
			Cfg.CoverMouthPoseEmote.SettingChanged += delegate
			{
				CoverMouthPose.InvalidateEmote();
			};
			Cfg.CoverMouthPoseEmoteTime.SettingChanged += delegate
			{
				CoverMouthPose.InvalidateEmote();
			};
			Cfg.RecolorSporeBombs.SettingChanged += delegate
			{
				SporeBombRecolorPatch.ReapplyToAll();
			};
			Cfg.SporeAreaCloudOpacity.SettingChanged += delegate
			{
				SporeCloudOpacityPatch.ReapplyToAll();
			};
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				HostAuthority.PublishAll();
			};
			GameObject val = new GameObject("FairootsNetworking");
			Object.DontDestroyOnLoad((Object)val);
			val.AddComponent<HostAuthoritySync>();
			val.AddComponent<ModPresenceCheck>();
			DebugOverlayHost.Bind(val);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Fairoots 1.0.0 loaded. " + $"seed={Cfg.Seed.Value}, preset={Cfg.Preset.Value}, " + $"spore-bomb cull fraction={Cfg.SporeBombCullFraction:0.##}"));
			if (Cfg.EnableDebugLogging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)($"Debug logging ON. Auto scene scan on load={Cfg.LogSceneScanOnLoad.Value}, " + $"scan hotkey={Cfg.SceneScanHotkey.Value}."));
			}
		}

		private void Update()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: 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_009b: Unknown result type (might be due to invalid IL or missing references)
			if (Cfg == null)
			{
				return;
			}
			RootsLevelWatcher.CheckAndRun();
			CoverMouthController.Tick();
			SporeBombCloudWarning.Tick();
			SporeWarningLabel.Tick();
			SpiderWarningLabel.Tick();
			CoverMouthPose.Prewarm();
			if (Cfg.EnableDebugLogging.Value)
			{
				KeyCode value = Cfg.SceneScanHotkey.Value;
				if ((int)value != 0 && Input.GetKeyDown(value))
				{
					SceneDiagnostics.DumpReport($"hotkey {value}");
				}
				KeyCode value2 = Cfg.FoliageProbeHotkey.Value;
				if ((int)value2 != 0 && Input.GetKeyDown(value2))
				{
					SceneDiagnostics.ProbeFoliageNearestSporeBomb();
				}
				KeyCode value3 = Cfg.MaterialProbeHotkey.Value;
				if ((int)value3 != 0 && Input.GetKeyDown(value3))
				{
					MaterialProbe.DumpLookedAt();
				}
			}
		}

		private static void SyncDebugOverlays()
		{
			TriggerRadiusOverlay.Sync();
			DebugOverlayHost.Sync();
		}
	}
	public class PluginConfig
	{
		private bool _snapshotTaken;

		private double _snapCullFraction;

		private double _snapTriggerRadiusMultiplier;

		private double _snapKnockbackMultiplier;

		private float _snapScreenshakeRangeCapMeters;

		private double _snapVfxCountMultiplier;

		private double _snapTriggerHeightMultiplier;

		private double _snapSporeAreaRadiusMultiplier;

		private double _snapSporeAreaRemovalFraction;

		private double _snapSporeAreaRadiusMultiplierValue;

		private double _snapSporeAreaStatusRateMultiplier;

		private double _snapSporeClearTimeMultiplier;

		private double _snapSporeBuildUpMultiplier;

		private double _snapZombieSpeedMultiplier;

		private double _snapBeetleSpeedMultiplier;

		private double _snapBeetleKnockbackMultiplier;

		private double _snapCreatureRagdollMultiplier;

		private bool _snapZombieDeaggroEnabled;

		private double _snapZombieDeaggroMultiplier;

		private double _snapBeetleDeaggroMultiplier;

		private double _snapWindForceMultiplier;

		private double _snapWindGustDurationMultiplier;

		private double _snapWindItemForceMultiplier;

		private double _snapWindObstacleOcclusionRangeMultiplier;

		private double _snapWindFallCameraDampenClamp;

		private double _snapClimbWindSpeedMultiplier;

		private double _snapClimbWindUpwardSpeedMultiplier;

		private double _snapClimbWindIntoWindSpeedMultiplier;

		private double _snapClimbWindGraceForceMultiplier;

		private bool _snapLiveUpdates;

		public ConfigEntry<int> Seed { get; }

		public ConfigEntry<PresetId> Preset { get; }

		public ConfigEntry<bool> ApplyPurePreset { get; }

		public ConfigEntry<bool> RecolorSporeBombs { get; }

		public ConfigEntry<double> SporeAreaCloudOpacity { get; }

		public ConfigEntry<double> SporeBombCloudOpacity { get; }

		public ConfigEntry<bool> ShowOverlayInSporeBombClouds { get; }

		public ConfigEntry<bool> ShowSporeCloudLabel { get; }

		public ConfigEntry<bool> ShowSpiderWarningLabel { get; }

		public ConfigEntry<double> SporeBombCullFractionOverride { get; }

		public ConfigEntry<bool> EnableFoliageRemovalOverride { get; }

		public ConfigEntry<double> SporeBombTriggerRadiusMultiplierOverride { get; }

		public ConfigEntry<double> SporeBombKnockbackMultiplierOverride { get; }

		public ConfigEntry<double> SporeBombScreenshakeRangeCapOverride { get; }

		public ConfigEntry<double> SporeBombVfxCountMultiplierOverride { get; }

		public ConfigEntry<double> SporeBombTriggerHeightMultiplierOverride { get; }

		public ConfigEntry<double> SporeBombSporeAreaRadiusMultiplierOverride { get; }

		public ConfigEntry<KeyCode> CoverMouthKey { get; }

		public ConfigEntry<bool> CoverMouthHold { get; }

		public ConfigEntry<bool> CoverMouthUseBonusStamina { get; }

		public ConfigEntry<bool> DisableSporeAreas { get; }

		public ConfigEntry<double> SporeAreaRemovalFractionOverride { get; }

		public ConfigEntry<double> SporeAreaRadiusMultiplierOverride { get; }

		public ConfigEntry<double> SporeAreaStatusRateMultiplierOverride { get; }

		public ConfigEntry<double> SporeClearTimeMultiplierOverride { get; }

		public ConfigEntry<double> SporeBuildUpMultiplierOverride { get; }

		public ConfigEntry<bool> DisableZombies { get; }

		public ConfigEntry<bool> DisableBeetles { get; }

		public ConfigEntry<bool> DisableSpiders { get; }

		public ConfigEntry<double> ZombieSpeedMultiplierOverride { get; }

		public ConfigEntry<double> BeetleSpeedMultiplierOverride { get; }

		public ConfigEntry<double> BeetleKnockbackMultiplierOverride { get; }

		public ConfigEntry<double> CreatureRagdollMultiplierOverride { get; }

		public ConfigEntry<bool> ZombieDeaggroEnabledOverride { get; }

		public ConfigEntry<double> ZombieDeaggroMultiplierOverride { get; }

		public ConfigEntry<double> BeetleDeaggroMultiplierOverride { get; }

		public ConfigEntry<double> ZombieKnockoutSecondsOverride { get; }

		public ConfigEntry<double> BeetleKnockoutSecondsOverride { get; }

		public ConfigEntry<double> CreatureKnockoutMinThrowSpeedOverride { get; }

		public ConfigEntry<double> CreatureKnockoutMaxThrowDistanceOverride { get; }

		public ConfigEntry<bool> BlowgunAffectsCreaturesOverride { get; }

		public ConfigEntry<double> BlowgunCreatureStunSecondsOverride { get; }

		public ConfigEntry<double> ZombieWindMultiplierOverride { get; }

		public ConfigEntry<double> BeetleWindSusceptibilityOverride { get; }

		public ConfigEntry<bool> EnableCoverMouthOverride { get; }

		public ConfigEntry<float> CoverMouthStaminaPerSecondOverride { get; }

		public ConfigEntry<bool> CoverMouthBlocksSporeBombsOverride { get; }

		public ConfigEntry<bool> DisableWindEntirely { get; }

		public ConfigEntry<bool> WindBackpackAlwaysImmuneOverride { get; }

		public ConfigEntry<double> WindForceMultiplierOverride { get; }

		public ConfigEntry<double> WindGustDurationMultiplierOverride { get; }

		public ConfigEntry<double> WindItemForceMultiplierOverride { get; }

		public ConfigEntry<double> WindObstacleOcclusionRangeMultiplierOverride { get; }

		public ConfigEntry<double> WindFallCameraDampenClampOverride { get; }

		public ConfigEntry<float> WindRecentForceWindowSecondsOverride { get; }

		public ConfigEntry<bool> PreventWindRagdollOverride { get; }

		public ConfigEntry<bool> ClimbSheltersFromWindOverride { get; }

		public ConfigEntry<double> ClimbWindSpeedMultiplierOverride { get; }

		public ConfigEntry<double> ClimbWindUpwardSpeedMultiplierOverride { get; }

		public ConfigEntry<double> ClimbWindIntoWindSpeedMultiplierOverride { get; }

		public ConfigEntry<float> ClimbShelterGraceSecondsOverride { get; }

		public ConfigEntry<double> ClimbWindGraceForceMultiplierOverride { get; }

		public ConfigEntry<bool> ApplyChangesLive { get; }

		public ConfigEntry<bool> EnableDebugLogging { get; }

		public ConfigEntry<bool> LogSceneScanOnLoad { get; }

		public ConfigEntry<KeyCode> SceneScanHotkey { get; }

		public ConfigEntry<bool> LogScreenshakeSources { get; }

		public ConfigEntry<KeyCode> FoliageProbeHotkey { get; }

		public ConfigEntry<KeyCode> MaterialProbeHotkey { get; }

		public ConfigEntry<bool> ShowRemovedSporeBombMarkers { get; }

		public ConfigEntry<bool> ShowSporeBombTriggerRadius { get; }

		public ConfigEntry<bool> CoverMouthPosePreview { get; }

		public ConfigEntry<string> CoverMouthPoseEmote { get; }

		public ConfigEntry<float> CoverMouthPoseEmoteTime { get; }

		public ConfigEntry<float> CoverMouthPoseForwardCm { get; }

		public ConfigEntry<float> CoverMouthPoseBelowHeadCm { get; }

		public ConfigEntry<float> CoverMouthPoseHandGapCm { get; }

		public ConfigEntry<float> CoverMouthPoseSideCm { get; }

		public ConfigEntry<float> CoverMouthPoseHandYawDeg { get; }

		public ConfigEntry<float> CoverMouthPoseHandRollDeg { get; }

		public ConfigEntry<float> CoverMouthPoseHandPitchDeg { get; }

		public ConfigEntry<bool> KeepVanillaTriggerRadius { get; }

		public double SporeBombCullFraction => OverrideResolution.Resolve(PresetCatalog.SporeBombCullFraction(Preset.Value), SporeBombCullFractionOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeBombTriggerRadiusMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombTriggerRadiusMultiplier(Preset.Value), SporeBombTriggerRadiusMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeBombKnockbackMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombKnockbackMultiplier(Preset.Value), SporeBombKnockbackMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public float SporeBombScreenshakeRangeCapMeters => (float)OverrideResolution.Resolve(PresetCatalog.SporeBombScreenshakeRangeCapMeters(Preset.Value), SporeBombScreenshakeRangeCapOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeBombVfxCountMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombVfxCountMultiplier(Preset.Value), SporeBombVfxCountMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeBombTriggerHeightMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombTriggerHeightMultiplier(Preset.Value), SporeBombTriggerHeightMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeBombSporeAreaRadiusMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombSporeAreaRadiusMultiplier(Preset.Value), SporeBombSporeAreaRadiusMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeAreaRemovalFraction => OverrideResolution.Resolve(PresetCatalog.SporeAreaRemovalFraction(Preset.Value), SporeAreaRemovalFractionOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeAreaRadiusMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeAreaRadiusMultiplier(Preset.Value), SporeAreaRadiusMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeAreaStatusRateMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeAreaStatusRateMultiplier(Preset.Value), SporeAreaStatusRateMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeClearTimeMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeClearTimeMultiplier(Preset.Value), SporeClearTimeMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double SporeBuildUpMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBuildUpMultiplier(Preset.Value), SporeBuildUpMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double ZombieSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ZombieSpeedMultiplier(Preset.Value), ZombieSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double BeetleSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.BeetleSpeedMultiplier(Preset.Value), BeetleSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double BeetleKnockbackMultiplier => OverrideResolution.Resolve(PresetCatalog.BeetleKnockbackMultiplier(Preset.Value), BeetleKnockbackMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double CreatureRagdollMultiplier => OverrideResolution.Resolve(PresetCatalog.CreatureRagdollMultiplier(Preset.Value), CreatureRagdollMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public bool ZombieDeaggroEnabled => OverrideResolution.Resolve(PresetCatalog.ZombieDeaggroEnabled(Preset.Value), ZombieDeaggroEnabledOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public double ZombieDeaggroMultiplier => ZombieDeaggro.ClampMultiplier(OverrideResolution.Resolve(PresetCatalog.ZombieDeaggroMultiplier(Preset.Value), ZombieDeaggroMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value));

		public double BeetleDeaggroMultiplier => BeetleDeaggro.ClampMultiplier(OverrideResolution.Resolve(PresetCatalog.BeetleDeaggroMultiplier(Preset.Value), BeetleDeaggroMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value));

		public double WindForceMultiplier => OverrideResolution.Resolve(PresetCatalog.WindForceMultiplier(Preset.Value), WindForceMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double WindGustDurationMultiplier => OverrideResolution.Resolve(PresetCatalog.WindGustDurationMultiplier(Preset.Value), WindGustDurationMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double WindItemForceMultiplier => OverrideResolution.Resolve(PresetCatalog.WindItemForceMultiplier(Preset.Value), WindItemForceMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double WindObstacleOcclusionRangeMultiplier => OverrideResolution.Resolve(PresetCatalog.WindObstacleOcclusionRangeMultiplier(Preset.Value), WindObstacleOcclusionRangeMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double WindFallCameraDampenClamp => OverrideResolution.Resolve(PresetCatalog.WindFallCameraDampenClamp(Preset.Value), WindFallCameraDampenClampOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value);

		public double ClimbWindSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindSpeedMultiplier(Preset.Value), ClimbWindSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double ClimbWindUpwardSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindUpwardSpeedMultiplier(Preset.Value), ClimbWindUpwardSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double ClimbWindIntoWindSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindIntoWindSpeedMultiplier(Preset.Value), ClimbWindIntoWindSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double ClimbWindGraceForceMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindGraceForceMultiplier(Preset.Value), ClimbWindGraceForceMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		internal bool LiveUpdatesActive
		{
			get
			{
				if (_snapshotTaken)
				{
					return _snapLiveUpdates;
				}
				return true;
			}
		}

		private bool UseLiveValue => LiveUpdatesActive;

		public int EffectiveSeed => HostAuthority.Resolve("Seed", Seed.Value);

		public double EffectiveSporeBombCullFraction => HostAuthority.Resolve("SporeBombCullFraction", UseLiveValue ? SporeBombCullFraction : _snapCullFraction);

		public double EffectiveSporeBombTriggerRadiusMultiplier => HostAuthority.Resolve("SporeBombTriggerRadiusMultiplier", UseLiveValue ? SporeBombTriggerRadiusMultiplier : _snapTriggerRadiusMultiplier);

		public double EffectiveSporeBombKnockbackMultiplier => HostAuthority.Resolve("SporeBombKnockbackMultiplier", UseLiveValue ? SporeBombKnockbackMultiplier : _snapKnockbackMultiplier);

		public float EffectiveSporeBombScreenshakeRangeCapMeters => HostAuthority.Resolve("SporeBombScreenshakeRangeCapMeters", UseLiveValue ? SporeBombScreenshakeRangeCapMeters : _snapScreenshakeRangeCapMeters);

		public double EffectiveSporeBombVfxCountMultiplier => HostAuthority.Resolve("SporeBombVfxCountMultiplier", UseLiveValue ? SporeBombVfxCountMultiplier : _snapVfxCountMultiplier);

		public double EffectiveSporeBombTriggerHeightMultiplier => HostAuthority.Resolve("SporeBombTriggerHeightMultiplier", UseLiveValue ? SporeBombTriggerHeightMultiplier : _snapTriggerHeightMultiplier);

		public double EffectiveSporeBombSporeAreaRadiusMultiplier => HostAuthority.Resolve("SporeBombSporeAreaRadiusMultiplier", UseLiveValue ? SporeBombSporeAreaRadiusMultiplier : _snapSporeAreaRadiusMultiplier);

		public double EffectiveSporeAreaRemovalFraction => HostAuthority.Resolve("SporeAreaRemovalFraction", UseLiveValue ? SporeAreaRemovalFraction : _snapSporeAreaRemovalFraction);

		public double EffectiveSporeAreaRadiusMultiplier => HostAuthority.Resolve("SporeAreaRadiusMultiplier", UseLiveValue ? SporeAreaRadiusMultiplier : _snapSporeAreaRadiusMultiplierValue);

		public double EffectiveSporeAreaStatusRateMultiplier => HostAuthority.Resolve("SporeAreaStatusRateMultiplier", UseLiveValue ? SporeAreaStatusRateMultiplier : _snapSporeAreaStatusRateMultiplier);

		public double EffectiveSporeClearTimeMultiplier => HostAuthority.Resolve("SporeClearTimeMultiplier", UseLiveValue ? SporeClearTimeMultiplier : _snapSporeClearTimeMultiplier);

		public double EffectiveSporeBuildUpMultiplier => HostAuthority.Resolve("SporeBuildUpMultiplier", UseLiveValue ? SporeBuildUpMultiplier : _snapSporeBuildUpMultiplier);

		public double EffectiveWindForceMultiplier => HostAuthority.Resolve("WindForceMultiplier", UseLiveValue ? WindForceMultiplier : _snapWindForceMultiplier);

		public double EffectiveWindGustDurationMultiplier => HostAuthority.Resolve("WindGustDurationMultiplier", UseLiveValue ? WindGustDurationMultiplier : _snapWindGustDurationMultiplier);

		public double EffectiveWindItemForceMultiplier => HostAuthority.Resolve("WindItemForceMultiplier", UseLiveValue ? WindItemForceMultiplier : _snapWindItemForceMultiplier);

		public double EffectiveWindObstacleOcclusionRangeMultiplier => HostAuthority.Resolve("WindObstacleOcclusionRangeMultiplier", UseLiveValue ? WindObstacleOcclusionRangeMultiplier : _snapWindObstacleOcclusionRangeMultiplier);

		public bool EffectiveWindBackpackAlwaysImmune => HostAuthority.Resolve("WindBackpackAlwaysImmune", WindBackpackAlwaysImmune);

		public bool WindBackpackAlwaysImmune => OverrideResolution.Resolve(PresetCatalog.WindBackpackAlwaysImmune(Preset.Value), WindBackpackAlwaysImmuneOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public bool EffectiveCoverMouthBlocksSporeBombs => HostAuthority.Resolve("CoverMouthBlocksSporeBombs", CoverMouthBlocksSporeBombs);

		public bool CoverMouthBlocksSporeBombs => OverrideResolution.Resolve(PresetCatalog.CoverMouthBlocksSporeBombs(Preset.Value), CoverMouthBlocksSporeBombsOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public bool EnableCoverMouth => OverrideResolution.Resolve(PresetCatalog.EnableCoverMouth(Preset.Value), EnableCoverMouthOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public bool EffectiveEnableCoverMouth => HostAuthority.Resolve("EnableCoverMouth", EnableCoverMouth);

		public float EffectiveCoverMouthStaminaPerSecond => HostAuthority.Resolve("CoverMouthStaminaPerSecond", CoverMouthStaminaPerSecond);

		public float CoverMouthStaminaPerSecond => OverrideResolution.Resolve(PresetCatalog.CoverMouthStaminaPerSecond(Preset.Value), CoverMouthStaminaPerSecondOverride.Value, 0.03f, Preset.Value, ApplyPurePreset.Value);

		public bool EffectiveDisableSporeAreas => HostAuthority.Resolve("DisableSporeAreas", DisableSporeAreas.Value);

		public double EffectiveZombieSpeedMultiplier => HostAuthority.Resolve("ZombieSpeedMultiplier", UseLiveValue ? ZombieSpeedMultiplier : _snapZombieSpeedMultiplier);

		public double EffectiveBeetleSpeedMultiplier => HostAuthority.Resolve("BeetleSpeedMultiplier", UseLiveValue ? BeetleSpeedMultiplier : _snapBeetleSpeedMultiplier);

		public double EffectiveBeetleKnockbackMultiplier => HostAuthority.Resolve("BeetleKnockbackMultiplier", UseLiveValue ? BeetleKnockbackMultiplier : _snapBeetleKnockbackMultiplier);

		public double EffectiveCreatureRagdollMultiplier => HostAuthority.Resolve("CreatureRagdollMultiplier", UseLiveValue ? CreatureRagdollMultiplier : _snapCreatureRagdollMultiplier);

		public double EffectiveZombieKnockoutSeconds => HostAuthority.Resolve("ZombieKnockoutSeconds", ZombieKnockoutSeconds);

		public double ZombieKnockoutSeconds => OverrideResolution.Resolve(PresetCatalog.ZombieKnockoutSeconds(Preset.Value), ZombieKnockoutSecondsOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value);

		public double EffectiveBeetleKnockoutSeconds => HostAuthority.Resolve("BeetleKnockoutSeconds", BeetleKnockoutSeconds);

		public double BeetleKnockoutSeconds => OverrideResolution.Resolve(PresetCatalog.BeetleKnockoutSeconds(Preset.Value), BeetleKnockoutSecondsOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value);

		public double EffectiveCreatureKnockoutMinThrowSpeed => HostAuthority.Resolve("CreatureKnockoutMinThrowSpeed", CreatureKnockoutMinThrowSpeed);

		public double CreatureKnockoutMinThrowSpeed => OverrideResolution.Resolve(PresetCatalog.CreatureKnockoutMinThrowSpeed(Preset.Value), CreatureKnockoutMinThrowSpeedOverride.Value, 36.0, Preset.Value, ApplyPurePreset.Value);

		public double EffectiveCreatureKnockoutMaxThrowDistance => HostAuthority.Resolve("CreatureKnockoutMaxThrowDistance", CreatureKnockoutMaxThrowDistance);

		public double CreatureKnockoutMaxThrowDistance => OverrideResolution.Resolve(PresetCatalog.CreatureKnockoutMaxThrowDistance(Preset.Value), CreatureKnockoutMaxThrowDistanceOverride.Value, 12.0, Preset.Value, ApplyPurePreset.Value);

		public bool EffectiveBlowgunAffectsCreatures => HostAuthority.Resolve("BlowgunAffectsCreatures", BlowgunAffectsCreatures);

		public bool BlowgunAffectsCreatures => OverrideResolution.Resolve(PresetCatalog.BlowgunAffectsCreatures(Preset.Value), BlowgunAffectsCreaturesOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public double EffectiveBlowgunCreatureStunSeconds => HostAuthority.Resolve("BlowgunCreatureStunSeconds", BlowgunCreatureStunSeconds);

		public double BlowgunCreatureStunSeconds => OverrideResolution.Resolve(PresetCatalog.BlowgunCreatureStunSeconds(Preset.Value), BlowgunCreatureStunSecondsOverride.Value, 45.0, Preset.Value, ApplyPurePreset.Value);

		public double EffectiveZombieWindMultiplier => HostAuthority.Resolve("ZombieWindMultiplier", ZombieWindMultiplier);

		public double ZombieWindMultiplier => OverrideResolution.Resolve(PresetCatalog.ZombieWindMultiplier(Preset.Value), ZombieWindMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value);

		public double EffectiveBeetleWindSusceptibility => HostAuthority.Resolve("BeetleWindSusceptibility", BeetleWindSusceptibility);

		public double BeetleWindSusceptibility => OverrideResolution.Resolve(PresetCatalog.BeetleWindSusceptibility(Preset.Value), BeetleWindSusceptibilityOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value);

		public bool EffectiveZombieDeaggroEnabled => HostAuthority.Resolve("ZombieDeaggroEnabled", UseLiveValue ? ZombieDeaggroEnabled : _snapZombieDeaggroEnabled);

		public double EffectiveZombieDeaggroMultiplier => HostAuthority.Resolve("ZombieDeaggroMultiplier", UseLiveValue ? ZombieDeaggroMultiplier : _snapZombieDeaggroMultiplier);

		public double EffectiveBeetleDeaggroMultiplier => HostAuthority.Resolve("BeetleDeaggroMultiplier", UseLiveValue ? BeetleDeaggroMultiplier : _snapBeetleDeaggroMultiplier);

		public bool EffectiveDisableZombies => HostAuthority.Resolve("DisableZombies", DisableZombies.Value);

		public bool EffectiveDisableBeetles => HostAuthority.Resolve("DisableBeetles", DisableBeetles.Value);

		public bool EffectiveDisableSpiders => HostAuthority.Resolve("DisableSpiders", DisableSpiders.Value);

		public bool EffectiveDisableWindEntirely => HostAuthority.Resolve("DisableWindEntirely", DisableWindEntirely.Value);

		public double EffectiveWindFallCameraDampenClamp => HostAuthority.Resolve("WindFallCameraDampenClamp", UseLiveValue ? WindFallCameraDampenClamp : _snapWindFallCameraDampenClamp);

		public float EffectiveWindRecentForceWindowSeconds => HostAuthority.Resolve("WindRecentForceWindowSeconds", WindRecentForceWindowSeconds);

		public float WindRecentForceWindowSeconds => OverrideResolution.Resolve(PresetCatalog.WindRecentForceWindowSeconds(Preset.Value), WindRecentForceWindowSecondsOverride.Value, 1.5f, Preset.Value, ApplyPurePreset.Value);

		public bool EffectivePreventWindRagdoll => HostAuthority.Resolve("PreventWindRagdoll", PreventWindRagdoll);

		public bool PreventWindRagdoll => OverrideResolution.Resolve(PresetCatalog.PreventWindRagdoll(Preset.Value), PreventWindRagdollOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public bool EnableFoliageRemoval => OverrideResolution.Resolve(PresetCatalog.EnableFoliageRemoval(Preset.Value), EnableFoliageRemovalOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public bool EffectiveEnableFoliageRemoval => HostAuthority.Resolve("EnableFoliageRemoval", EnableFoliageRemoval);

		public bool ClimbSheltersFromWind => OverrideResolution.Resolve(PresetCatalog.ClimbSheltersFromWind(Preset.Value), ClimbSheltersFromWindOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value);

		public bool EffectiveClimbSheltersFromWind => HostAuthority.Resolve("ClimbSheltersFromWind", ClimbSheltersFromWind);

		public double EffectiveClimbWindSpeedMultiplier => HostAuthority.Resolve("ClimbWindSpeedMultiplier", UseLiveValue ? ClimbWindSpeedMultiplier : _snapClimbWindSpeedMultiplier);

		public double EffectiveClimbWindUpwardSpeedMultiplier => HostAuthority.Resolve("ClimbWindUpwardSpeedMultiplier", UseLiveValue ? ClimbWindUpwardSpeedMultiplier : _snapClimbWindUpwardSpeedMultiplier);

		public double EffectiveClimbWindIntoWindSpeedMultiplier => HostAuthority.Resolve("ClimbWindIntoWindSpeedMultiplier", UseLiveValue ? ClimbWindIntoWindSpeedMultiplier : _snapClimbWindIntoWindSpeedMultiplier);

		public double EffectiveClimbWindGraceForceMultiplier => HostAuthority.Resolve("ClimbWindGraceForceMultiplier", UseLiveValue ? ClimbWindGraceForceMultiplier : _snapClimbWindGraceForceMultiplier);

		public float EffectiveClimbShelterGraceSeconds => HostAuthority.Resolve("ClimbShelterGraceSeconds", ClimbShelterGraceSeconds);

		public float ClimbShelterGraceSeconds => OverrideResolution.Resolve(PresetCatalog.ClimbShelterGraceSeconds(Preset.Value), ClimbShelterGraceSecondsOverride.Value, 0.5f, Preset.Value, ApplyPurePreset.Value);

		public PluginConfig(ConfigFile config)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Expected O, but got Unknown
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Expected O, but got Unknown
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Expected O, but got Unknown
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Expected O, but got Unknown
			//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d4: Expected O, but got Unknown
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: Expected O, but got Unknown
			//IL_0354: Unknown result type (might be due to invalid IL or missing references)
			//IL_035e: Expected O, but got Unknown
			//IL_0399: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a3: Expected O, but got Unknown
			//IL_0416: Unknown result type (might be due to invalid IL or missing references)
			//IL_0420: Expected O, but got Unknown
			//IL_045b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0465: Expected O, but got Unknown
			//IL_04a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_04aa: Expected O, but got Unknown
			//IL_04e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ef: Expected O, but got Unknown
			//IL_052a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0534: Expected O, but got Unknown
			//IL_05c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cd: Expected O, but got Unknown
			//IL_0608: Unknown result type (might be due to invalid IL or missing references)
			//IL_0612: Expected O, but got Unknown
			//IL_064d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0657: Expected O, but got Unknown
			//IL_0692: Unknown result type (might be due to invalid IL or missing references)
			//IL_069c: Expected O, but got Unknown
			//IL_06f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_06fd: Expected O, but got Unknown
			//IL_0738: Unknown result type (might be due to invalid IL or missing references)
			//IL_0742: Expected O, but got Unknown
			//IL_077d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0787: Expected O, but got Unknown
			//IL_07c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_07cc: Expected O, but got Unknown
			//IL_0807: Unknown result type (might be due to invalid IL or missing references)
			//IL_0811: Expected O, but got Unknown
			//IL_084c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0856: Expected O, but got Unknown
			//IL_08ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_08b7: Expected O, but got Unknown
			//IL_08f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_08fc: Expected O, but got Unknown
			//IL_0937: Unknown result type (might be due to invalid IL or missing references)
			//IL_0941: Expected O, but got Unknown
			//IL_098c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0996: Expected O, but got Unknown
			//IL_0a09: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a13: Expected O, but got Unknown
			//IL_0a4e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a58: Expected O, but got Unknown
			//IL_0a93: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a9d: Expected O, but got Unknown
			//IL_0ad8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ae2: Expected O, but got Unknown
			//IL_0b1d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b27: Expected O, but got Unknown
			//IL_0b56: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b60: Expected O, but got Unknown
			//IL_0bd3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bdd: Expected O, but got Unknown
			//IL_0c18: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c22: Expected O, but got Unknown
			//IL_0c5d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c67: Expected O, but got Unknown
			//IL_0c96: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ca0: Expected O, but got Unknown
			//IL_0cdb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ce5: Expected O, but got Unknown
			//IL_0e4c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e56: Expected O, but got Unknown
			//IL_0e85: Unknown result type (might be due to invalid IL or missing references)
			//IL_0e8f: Expected O, but got Unknown
			//IL_0ebe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ec8: Expected O, but got Unknown
			//IL_0ef7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f01: Expected O, but got Unknown
			//IL_0f30: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f3a: Expected O, but got Unknown
			//IL_0f69: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f73: Expected O, but got Unknown
			//IL_0fa2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0fac: Expected O, but got Unknown
			//IL_0fdb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0fe5: Expected O, but got Unknown
			RecolorSporeBombs = config.Bind<bool>("General", "recolor-spore-bombs", true, "Tints spore bombs (and explosive spore bombs) toward the pink/red the game's own Spores status effect uses, instead of leaving them vanilla green - a green hazard on green grass camouflages into the terrain, which is exactly what a hazard shouldn't do. Purely cosmetic and PER-PLAYER: unlike every other gameplay setting here, this one is NOT host-authoritative - it only changes what you see on your own screen, so set it however you like regardless of what the host or anyone else in the lobby has. Applies immediately, regardless of apply-changes-live.");
			SporeAreaCloudOpacity = config.Bind<double>("General", "spore-area-cloud-opacity", 0.35, new ConfigDescription("How opaque the mushroom spore clouds are drawn, as a fraction of vanilla (1.0 = untouched, 0 = fully invisible). Lower makes them see-through, so the game's own green Spores screen overlay is readable through them - in vanilla the cloud and the overlay are the same colour and blend together, so it's hard to tell whether you're actually inside one and taking spores or just standing next to it. The cloud stays visible enough to spot and walk around; the hazard itself (its size and how fast it applies spores) is NOT changed - that's what the Spore-Areas settings are for. Purely cosmetic and PER-PLAYER: not host-authoritative, set it however you like. Applies immediately, regardless of apply-changes-live.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 1.0), Array.Empty<object>()));
			SporeBombCloudOpacity = config.Bind<double>("General", "spore-bomb-cloud-opacity", 0.5, new ConfigDescription("The same see-through-cloud setting as spore-area-cloud-opacity, but for the temporary spore cloud a spore bomb leaves when it goes off (1.0 = vanilla, 0 = fully invisible). Separate from the spore-area one because a bomb's cloud erupts right on top of you while a spore area is a landmark seen from a distance. Cosmetic only - the blast, the knockback and the spores it applies are unchanged. Purely cosmetic and PER-PLAYER: not host-authoritative. Applies immediately, regardless of apply-changes-live.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 1.0), Array.Empty<object>()));
			ShowOverlayInSporeBombClouds = config.Bind<bool>("General", "show-overlay-in-spore-bomb-clouds", true, "Keeps the game's green 'you're standing in spores' screen overlay up for as long as you're inside the cloud a spore bomb leaves behind - the same way it already stays up inside a mushroom spore cloud. In vanilla a bomb's cloud only gives you the brief flash each time it damages you, with nothing in between telling you you're still standing in it. The damage flash is untouched, so it still stands out on top of the steady overlay. Purely a readability fix and PER-PLAYER: not host-authoritative, and it changes nothing about the cloud itself. Applies immediately, regardless of apply-changes-live.");
			ShowSporeCloudLabel = config.Bind<bool>("General", "show-spore-cloud-label", false, "Shows a text warning on screen, in the game's own font and in the Spores status colour, whenever you're standing in spores - both the mushroom spore clouds and the cloud a spore bomb leaves. Off by default: the other readability settings just make the game's own feedback legible, while this adds a HUD element PEAK doesn't have, which is more a matter of taste. Purely cosmetic and PER-PLAYER: not host-authoritative. Applies immediately, regardless of apply-changes-live.");
			ShowSpiderWarningLabel = config.Bind<bool>("General", "show-spider-warning-label", false, "Shows a text warning on screen, in the game's own font and in the Poison status colour, while a spider is dropping down on you, and hides it again about a second after the spider lands without catching anyone. Worth turning on if spiders keep getting you from above: the game gives you no warning at all for this - the only spider sound plays at the same moment the drop starts, and being grabbed is instant on contact - so this turns the time the spider spends descending into time you can actually react in. Off by default like the other on-screen labels, since it adds a HUD element PEAK doesn't have. Purely cosmetic and PER-PLAYER: not host-authoritative. Applies immediately, regardless of apply-changes-live.");
			CoverMouthKey = config.Bind<KeyCode>("General", "cover-mouth-key", (KeyCode)120, "Key to cover your mouth with both hands, making you immune to spore areas while you hold it. Your hands are busy while covering: you can't climb, pick things up, or switch items, and anything you're holding is put away (an item you're carrying in your hands with no free pocket for it gets dropped). Set to None to disable the mechanic entirely. PER-PLAYER: this and cover-mouth-hold below are yours alone - unlike most settings, the host's value has no effect on you (how much stamina it costs IS host-controlled, though).");
			CoverMouthHold = config.Bind<bool>("General", "cover-mouth-hold", true, "How cover-mouth-key behaves: on (default) means hold the key to keep your mouth covered and let go to stop; off makes it a toggle - press once to start, press again to stop. PER-PLAYER, same as cover-mouth-key above.");
			CoverMouthUseBonusStamina = config.Bind<bool>("General", "cover-mouth-use-bonus-stamina", false, "Whether covering your mouth is allowed to eat into your bonus stamina (the extra you get from food) once your normal stamina runs out. Off by default - covering simply stops when you run out of normal stamina, leaving your bonus intact for climbing. PER-PLAYER: this is your own call, like the keybind; how fast covering drains stamina in the first place is still the host's setting.");
			Seed = config.Bind<int>("Host", "seed", 0, "Deterministic seed for every random decision Fairoots makes (which spore bombs get culled, etc.). Same seed + same Roots level = identical result, every load. HOST-AUTHORITATIVE: everyone in the lobby must have Fairoots installed, but only the HOST's seed is ever actually used - non-host players' own seed value here is ignored entirely, so there's nothing to coordinate manually (see ROADMAP.md's Host authority section).");
			Preset = config.Bind<PresetId>("Host", "preset", PresetId.Balanced, "Overall balance preset. Subtle (1) is the lightest touch, Tame (4) the heaviest, Balanced (2) is the default. Custom (5) ignores the hard-coded preset numbers entirely and uses your own Spore-Bombs/Wind settings directly instead. Under presets 1-4, the per-mechanic settings below are ignored entirely, even if you've changed them, UNLESS apply-pure-preset (also in this Host section) is turned off - see that setting to change just one or two things about a preset without switching all the way to Custom. HOST-AUTHORITATIVE: only the host's preset (and, under Custom, the host's own per-mechanic values) is ever actually used for the whole lobby.");
			ApplyPurePreset = config.Bind<bool>("Host", "apply-pure-preset", true, "When a preset other than Custom is active (on by default): if true, the preset applies as a single all-or-nothing block, exactly as before - every per-mechanic Custom-only setting below is ignored, even if you've changed it. If false, the preset still supplies every setting you've left at its vanilla default, but any setting you've actually changed from its default keeps your own value instead - so you can pick, say, Subtle, turn this off, change only climb-shelters-from-wind, and get Subtle with exactly that one difference, without copying every one of Subtle's numbers into Custom first. Has no effect under Custom, where your own values already always apply. HOST-AUTHORITATIVE, like preset itself.");
			SporeBombCullFractionOverride = config.Bind<double>("Spore-Bombs", "cull-fraction", 0.0, new ConfigDescription("Fraction of spore bombs to remove overall (foliage removal + seeded cull combined), e.g. 0.5 cuts them in half. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. 0 (the default) = remove none beyond whatever the foliage pass takes, which is itself off by default, so an untouched Custom preset removes nothing at all.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 1.0), Array.Empty<object>()));
			EnableFoliageRemovalOverride = config.Bind<bool>("Spore-Bombs", "enable-foliage-removal", false, "Removes spore bombs that spawned hidden inside a bush or clump of ferns, because a bomb you physically cannot see before setting it off isn't a hazard you can play around. Off = vanilla, where the game leaves them wherever they landed. Turning it on does NOT mean fewer spore bombs overall than cull-fraction asks for: that removal target still applies, the seeded removal just picks the hidden ones first. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby. Takes effect on the next Roots level load (spore-bomb removal happens once, when the biome is placed).");
			SporeBombTriggerRadiusMultiplierOverride = config.Bind<double>("Spore-Bombs", "trigger-radius-multiplier", 1.0, new ConfigDescription("Multiplier applied to every kept spore bomb's trigger hitbox, e.g. 0.7 shrinks it to 70% of vanilla size, 2.0 doubles it. 1.0 always means vanilla size, no matter what else you change - handy for A/B-testing against unmodified behavior. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			SporeBombKnockbackMultiplierOverride = config.Bind<double>("Spore-Bombs", "knockback-multiplier", 1.0, new ConfigDescription("Multiplier applied to a spore bomb's knockback/explosion force on detonation, e.g. 0.6 cuts it to 60% of vanilla, 2.0 doubles it. 1.0 always means vanilla force, no matter what else you change - handy for A/B-testing against unmodified behavior. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 5.0), Array.Empty<object>()));
			SporeBombScreenshakeRangeCapOverride = config.Bind<double>("Spore-Bombs", "screenshake-range-cap-meters", 0.0, new ConfigDescription("Caps how far away (in meters) a spore-bomb detonation's screen-shake can still be felt. 0 leaves the vanilla range (~75m, uncapped) alone; a positive value caps it, e.g. 20 means no shake past 20m. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 100.0), Array.Empty<object>()));
			SporeBombVfxCountMultiplierOverride = config.Bind<double>("Spore-Bombs", "vfx-count-multiplier", 1.0, new ConfigDescription("Multiplier applied to a spore bomb's particle/VFX orb count on detonation, e.g. 0.5 halves it, 2.0 doubles it. 1.0 always means vanilla count, no matter what else you change - handy for A/B-testing against unmodified behavior. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 5.0), Array.Empty<object>()));
			SporeBombTriggerHeightMultiplierOverride = config.Bind<double>("Spore-Bombs", "trigger-height-multiplier", 1.0, new ConfigDescription("Multiplier controlling how high above its base a player can be and still set off a \"Spore Bomb\" or \"Poison Spore Bomb\" (not the round \"Explosive Spore Bomb\", which is unaffected) - fixes vanilla's oversized trigger sphere reaching absurdly far above the actual mushroom mesh, which made it impossible to jump over one without setting it off. 1.0 always means vanilla (cutoff disabled), no matter what else you change - lower values engage the fix more aggressively. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			SporeBombSporeAreaRadiusMultiplierOverride = config.Bind<double>("Spore-Bombs", "spore-area-radius-multiplier", 1.0, new ConfigDescription("Multiplier applied to the radius (and proportionally, the inner/outer fade) of the temporary spore area a regular \"Spore Bomb\"/\"Poison Spore Bomb\" creates when triggered, e.g. 0.5 halves how far it reaches, 2.0 doubles it. 1.0 always means vanilla size. Doesn't affect the \"Explosive Spore Bomb\" variant, which has no spore area. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 5.0), Array.Empty<object>()));
			CoverMouthBlocksSporeBombsOverride = config.Bind<bool>("Spore-Bombs", "cover-mouth-blocks-spore-bombs", false, "Whether covering your mouth (see General/cover-mouth-key) also protects you from the small spore cloud a spore bomb leaves behind when it goes off. Off by default: the mechanic is meant for the biome's spore areas, which you can see coming and choose to walk into, whereas a spore bomb is a surprise you've already set off. Either way this only stops the spores - the blast still knocks you around. Only takes effect when preset is set to Custom (5) - it is off under presets 1-4 as well, for now. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.");
			DisableSporeAreas = config.Bind<bool>("Spore-Areas", "disable-spore-areas", false, "Master switch: when on, the Roots biome's spore areas (\"Mushroom Spore Clouds\") are removed entirely - no Spores status, no green screen filter, and the emitter mushroom in the middle of the cloud plus the cloud itself disappear with them. Doesn't touch the small temporary spore area a spore bomb leaves behind when it goes off (that's the Spore-Bombs section). HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby. Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live.");
			SporeAreaRemovalFractionOverride = config.Bind<double>("Spore-Areas", "removal-fraction", 0.0, new ConfigDescription("Fraction of the level's spore areas to remove entirely, e.g. 0.25 removes a quarter of them. Which ones is decided by the seed, and always starts with the spore area closest to another one - so overlapping clouds (the ones you can't get past without taking spores) get thinned first, and isolated ones you can simply walk around are left alone. 0 = remove none. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, which remove none at all on Subtle/Balanced. Only applies on the next Roots level load either way, since a spore area that's already gone can't come back mid-level.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 1.0), Array.Empty<object>()));
			SporeAreaRadiusMultiplierOverride = config.Bind<double>("Spore-Areas", "radius-multiplier", 1.0, new ConfigDescription("Multiplier applied to how far every spore area reaches, e.g. 0.7 shrinks it to 70% of vanilla, 1.5 makes it half again as big. The visible cloud is resized to match, so what you can see is what actually gives you spores. 1.0 always means vanilla size (radius 16, about 26m across from the middle). How quickly the spores themselves are applied is a separate setting - this only changes the size. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			SporeAreaStatusRateMultiplierOverride = config.Bind<double>("Spore-Areas", "status-rate-multiplier", 1.0, new ConfigDescription("Multiplier applied to how often/quickly you're given spores while standing in a spore area, e.g. 0.5 means the Spores meter fills half as fast, 2.0 twice as fast, 0 means a spore area never gives you spores at all (its cloud is still there - use disable-spore-areas to remove them outright). 1.0 always means vanilla. How far the area reaches is the separate radius-multiplier above; this only changes the rate. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			SporeClearTimeMultiplierOverride = config.Bind<double>("Spores", "clear-time-multiplier", 1.0, new ConfigDescription("Multiplier applied to how long it takes for spores to wear off once you're out of them, e.g. 0.5 means they clear in half the time, 2.0 means twice as long. Covers the whole wait, including the short pause before the meter starts going down at all. 1.0 always means vanilla. This only affects recovery - how fast you GET spores is build-up-multiplier below. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.05, 5.0), Array.Empty<object>()));
			SporeBuildUpMultiplierOverride = config.Bind<double>("Spores", "build-up-multiplier", 1.0, new ConfigDescription("Multiplier applied to every dose of spores you're given, no matter what gave it to you - a spore area, a spore bomb's cloud, or a zombie's bite. 0.5 means every dose counts for half, 0 means you never get spores at all, 2.0 means double. 1.0 always means vanilla. NOTE: this stacks on top of the per-hazard settings - if Spore-Areas/status-rate-multiplier is 0.5 and this is 0.5, standing in a spore area gives you a quarter of vanilla. Because of that this one is fully OPT-IN: it defaults to 1.0 (vanilla) and no preset changes it, so it does nothing at all until you set it yourself. The presets tune each hazard separately instead. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			DisableZombies = config.Bind<bool>("Creatures", "disable-zombies", false, "Master switch: when on, the Roots biome's mushroom zombies never spawn, and any already wandering around are removed. A zombie raised from a dead player is NOT affected - that's a player's death, not a hazard. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby (the game only ever spawns zombies on the host in the first place). Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live.");
			DisableBeetles = config.Bind<bool>("Creatures", "disable-beetles", false, "Master switch: when on, the Roots biome's beetles (about 15 per level) are removed entirely - nothing to walk into, nothing to knock you off a ledge. Turning it back off brings back exactly the beetles this mod removed. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby. Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live.");
			DisableSpiders = config.Bind<bool>("Creatures", "disable-spiders", false, "Master switch: when on, the Roots biome's ceiling spiders (about 90 per level) never drop and never grab you, and their webs and bodies are hidden. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby. Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live.");
			ZombieSpeedMultiplierOverride = config.Bind<double>("Creatures", "zombie-speed-multiplier", 1.0, new ConfigDescription("Multiplier applied to how fast mushroom zombies move, e.g. 0.5 makes them half as fast, 1.5 half again as fast. 1.0 always means vanilla. Affects both their walk and their sprint, since the sprint is a multiple of the same speed. 0 means a zombie can still notice you, turn towards you and bite you if you stand next to it, but never closes any distance. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			BeetleSpeedMultiplierOverride = config.Bind<double>("Creatures", "beetle-speed-multiplier", 1.0, new ConfigDescription("Multiplier applied to how fast beetles move, e.g. 0.5 makes them half as fast, 1.5 half again as fast. 1.0 always means vanilla. 0 means a beetle still turns to face you and still hits you if you walk into it, but never chases. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			BeetleKnockbackMultiplierOverride = config.Bind<double>("Creatures", "beetle-knockback-multiplier", 1.0, new ConfigDescription("Multiplier applied to how hard a beetle's hit throws you, e.g. 0.5 halves the shove, 0 means it doesn't move you at all. 1.0 always means vanilla. This is only the force - whether the hit knocks you off your feet is the separate creature-ragdoll-resistance-multiplier below. Zombies have no equivalent setting because the game gives them no knockback of their own: what a zombie does is ragdoll you (see that other setting) and shove you with its body, which is ordinary physics. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			CreatureRagdollMultiplierOverride = config.Bind<double>("Creatures", "creature-ragdoll-multiplier", 1.0, new ConfigDescription("Multiplier applied to how long a beetle's hit or a zombie's bite knocks you off your feet, e.g. 0.5 gets you back up twice as fast. 0 means you are never ragdolled by either creature and always keep control. 1.0 always means vanilla (2 seconds for a beetle, 3 for a zombie bite). The hit still lands either way: you still take the injury and spores, and a beetle still shoves you (that's beetle-knockback-multiplier above) - this only decides whether you lose control of your character. Zombies have no separate knockback setting because this ragdoll IS what a zombie's hit costs you. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			ZombieDeaggroEnabledOverride = config.Bind<bool>("Creatures", "zombie-deaggro-enabled", false, "Whether zombies can ever lose track of you. In the unmodded game they CANNOT: once a zombie has seen you it chases you forever, at any distance, with no way to shake it. Turn this off to keep that vanilla behavior. Only takes effect when preset is set to Custom (5) - under presets 1-4 this is off on Subtle (which is meant to be vanilla) and on for the other three.");
			ZombieDeaggroMultiplierOverride = config.Bind<double>("Creatures", "zombie-deaggro-multiplier", 1.0, new ConfigDescription("How hard it is to shake a zombie once it's after you. NOTE: unlike every other multiplier in this file, 1.0 does NOT mean vanilla - vanilla zombies never lose you at all, so there's no vanilla number to scale. Here 1.0 is the TOUGHEST setting (you must stay out of the zombie's sight for a full 30 seconds, or get about 120m away) and 0.1 is the most forgiving (about 3 seconds or 12m). Either escape works on its own. 0 is not allowed - a zombie that deaggros instantly is a disabled zombie, which is what disable-zombies is for. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.1, 1.0), Array.Empty<object>()));
			BeetleDeaggroMultiplierOverride = config.Bind<double>("Creatures", "beetle-deaggro-multiplier", 1.0, new ConfigDescription("How hard it is to shake a beetle once it's after you, as a multiplier on the distance it will keep chasing you from. 1.0 means vanilla (about 22m in Roots), 0.5 is twice as easy to escape, 2.0 twice as hard. Unlike zombies, beetles DO give up on their own in the unmodded game - both by distance and by losing sight of you - so this only tunes how sticky that already is. It doesn't change how far away a beetle can first notice you. Once a beetle does give up, it ignores everyone for 5 seconds, so it can't instantly re-notice you. 0.1 is the minimum - a beetle that can never hold a target at all is a disabled beetle, which is what disable-beetles is for. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.1, 3.0), Array.Empty<object>()));
			ZombieKnockoutSecondsOverride = config.Bind<double>("Creatures", "zombie-knockout-seconds", 0.0, new ConfigDescription("How many seconds a zombie is knocked out for when you hit it with a thrown item, the way you can already stun a spider by throwing something at it. The unmodded game already knocks a zombie down for about a second, so this mostly decides how long that lasts; 0 (the default) leaves the vanilla behaviour alone. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. Deliberately less than the 5 seconds a spider gets. Note the zombie also needs a moment to get up and re-orient afterwards, so the total time it's out of the fight is a few seconds longer than this. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 60.0), Array.Empty<object>()));
			BeetleKnockoutSecondsOverride = config.Bind<double>("Creatures", "beetle-knockout-seconds", 0.0, new ConfigDescription("How many seconds a beetle is knocked onto its back for when you hit it with a thrown item. Unlike zombies and spiders, beetles are completely immune to thrown items in the unmodded game - nothing happens at all - so this adds the interaction outright; 0 (the default) turns it back off. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. Shortest of the three on purpose: a beetle has a shell, so it should shrug off a thrown rock better than a zombie or a spider does. A knocked-out beetle can't chase or attack, and rights itself with its own flip animation afterwards. Only counts if the item is actually thrown hard - dropping something on it does nothing. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 60.0), Array.Empty<object>()));
			CreatureKnockoutMinThrowSpeedOverride = config.Bind<double>("Creatures", "creature-knockout-min-throw-speed", 36.0, new ConfigDescription("How fast a thrown item has to be travelling, in meters per second, to knock out a beetle or a zombie - so a genuine throw does it and gently lobbing or dropping something doesn't. Applies to both creatures, so \"a hard throw\" means the same thing for each. Lower it if throws that feel hard aren't landing, raise it if soft ones are. 0 means any contact counts at all, however gentle. Turn on Debug/enable-debug-logging to see the measured speed of each throw next to this threshold in the log, which is the easy way to pick a value. The default is set from measured throws: medium throws land around 23-31 m/s and near-full-strength ones around 37-43, and the default of 36 was picked by play-testing as the point where a knockout needs a genuinely committed throw. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 50.0), Array.Empty<object>()));
			CreatureKnockoutMaxThrowDistanceOverride = config.Bind<double>("Creatures", "creature-knockout-max-throw-distance", 12.0, new ConfigDescription("How close you have to be to a beetle or zombie, in meters, for a thrown item to knock it out. Together with creature-knockout-min-throw-speed above this is what the knockout costs you: a charged throw from close range, which takes time to wind up and usually loses you the item. Without a distance limit a hard throw is still fast a long way out, so you could pick creatures off from somewhere safe. 0 removes the distance requirement. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 200.0), Array.Empty<object>()));
			BlowgunAffectsCreaturesOverride = config.Bind<bool>("Creatures", "blowgun-affects-creatures", false, "When on, shooting a creature with a blowgun dart takes it out of the fight: a zombie dies (exactly the way it already does on its own after two minutes, skeleton included), while a spider or a beetle is knocked out for a long time (see blowgun-creature-stun-seconds). Spiders and beetles get stunned rather than killed because the game has no death state for them at all. In the unmodded game a dart passes straight through a spider or beetle and merely poisons a zombie, which is what off (the default) restores. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on, since darts are consumable and the blowgun is uncommon, so this can't be spammed. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.");
			BlowgunCreatureStunSecondsOverride = config.Bind<double>("Creatures", "blowgun-creature-stun-seconds", 45.0, new ConfigDescription("How many seconds a blowgun dart knocks out a spider or a beetle for. Doesn't apply to zombies, which die outright instead; set this to 0 if you want darts to kill zombies but leave spiders and beetles alone. Much longer than a thrown item's knockout on purpose - a dart costs you a consumable and only works if you actually hit. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 600.0), Array.Empty<object>()));
			ZombieWindMultiplierOverride = config.Bind<double>("Creatures", "zombie-wind-multiplier", 1.0, new ConfigDescription("Multiplier on how hard the wind pushes zombies around, e.g. 2.0 pushes them twice as hard as normal, 0 makes them immune. 1.0 means vanilla - note that vanilla is NOT zero: the game already pushes zombies at 60% of the force it uses on you, because a zombie counts as a bot character. Useful for making a storm a real hazard for the things chasing you and not just for you. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 5.0), Array.Empty<object>()));
			BeetleWindSusceptibilityOverride = config.Bind<double>("Creatures", "beetle-wind-susceptibility", 0.0, new ConfigDescription("How much the wind slides beetles around, as a fraction of their own walking speed - 1.0 means wind moves a beetle about as fast as it walks, 0.5 half that. NOTE: 0 is the vanilla value here, not 1.0, because beetles are completely immune to wind in the unmodded game and can't be made otherwise by scaling anything - the game resets a walking beetle's velocity every physics tick, so any wind force on it is erased before it moves. Set 0 to restore that. Only applies while a beetle is walking normally, never while it's tumbling, flipped or knocked out. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 5.0), Array.Empty<object>()));
			EnableCoverMouthOverride = config.Bind<bool>("Spore-Areas", "enable-cover-mouth", false, "Master switch for the cover-your-mouth mechanic: hold the key (see General/cover-mouth-key) to breathe through your hands and take no spores while inside a spore area, at the cost of stamina and both hands. Off = vanilla, where the key does nothing at all. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all; only the host's value counts for the whole lobby. If you just don't want to use the mechanic yourself, set cover-mouth-key to None in General instead - that works regardless of the host. Applies immediately, regardless of apply-changes-live.");
			CoverMouthStaminaPerSecondOverride = config.Bind<float>("Spore-Areas", "cover-mouth-stamina-per-second", 0.03f, new ConfigDescription("How much stamina covering your mouth drains per second. Small by default - for scale, climbing a wall costs up to 0.2 per second, so this is about a sixth of that. You stop covering automatically when you run out of stamina. 0 makes it free. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, which get cheaper the more forgiving they are. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby (the keybind itself is per-player - see General).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>()));
			DisableWindEntirely = config.Bind<bool>("Wind", "disable-wind-entirely", false, "Master switch: when on, wind should NEVER occur at all - not vanilla-strength wind, genuinely no wind, ever. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby (same for everyone in the run, no exceptions - see ROADMAP.md's Host authority section). Any gust already blowing stops immediately when the host turns this on, and none can start again while it stays on. Off by default - no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live.");
			WindBackpackAlwaysImmuneOverride = config.Bind<bool>("Wind", "backpack-always-immune", false, "Whether backpacks are fully immune to wind force, regardless of item-force-multiplier. Off = vanilla, where a gust sends a dropped backpack down the mountain like anything else. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby, regardless of what non-host players have set locally. Applies immediately, regardless of apply-changes-live.");
			WindForceMultiplierOverride = config.Bind<double>("Wind", "force-multiplier", 1.0, new ConfigDescription("Multiplier applied to wind's push force only (see gust-duration-multiplier below for timing), e.g. 0.6 cuts it to 60% of vanilla, 2.0 doubles it, 0 means no push at all. 1.0 always means vanilla force, no matter what else you change. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			WindGustDurationMultiplierOverride = config.Bind<double>("Wind", "gust-duration-multiplier", 1.0, new ConfigDescription("Multiplier applied to how long a gust lasts once it starts (in the same direction, the calm period between gusts scales inversely) - independent of force-multiplier above, so you can test gust timing/frequency without changing push strength. E.g. 0.6 makes gusts noticeably shorter and less frequent. 1.0 always means vanilla timing. Gust duration is always floored at 1 second regardless of how low this goes - a genuinely zero-length gust breaks the game's own wind on/off timer (confirmed live 2026-07-22). Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			WindItemForceMultiplierOverride = config.Bind<double>("Wind", "item-force-multiplier", 1.0, new ConfigDescription("Multiplier applied to wind's push force on dropped items other than backpacks (backpacks are always fully immune to wind, on every preset), e.g. 0.4 cuts it to 40% of vanilla, 0.0 makes every item fully immune too. 1.0 always means vanilla force. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 3.0), Array.Empty<object>()));
			WindObstacleOcclusionRangeMultiplierOverride = config.Bind<double>("Wind", "obstacle-occlusion-range-multiplier", 1.0, new ConfigDescription("Multiplier applied to the existing obstacle-occlusion raycast's min/max distance (already enabled in Roots vanilla) - widening it lets standing behind an obstacle block wind from further away, e.g. 1.6 widens both distances by 60%. 1.0 always means vanilla range. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.5, 4.0), Array.Empty<object>()));
			WindFallCameraDampenClampOverride = config.Bind<double>("Wind", "fall-camera-dampen-clamp", 0.0, new ConfigDescription("Floor applied to camera-control while falling, but only when the fall was preceded by recent wind force (see fall-camera-dampen-window-seconds below) - keeps the camera partially player-controlled instead of fully surrendering to ragdoll-head physics, so you have a chance to grab a wall or use a Rescue Hook after wind blows you off a ledge. 0 disables it (vanilla - full ragdoll camera on every fall). An ordinary fall that wasn't wind-preceded is never affected. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 1.0), Array.Empty<object>()));
			WindRecentForceWindowSecondsOverride = config.Bind<float>("Wind", "fall-camera-dampen-window-seconds", 1.5f, new ConfigDescription("How many seconds after wind last pushed you that a fall still counts as wind-preceded for fall-camera-dampen-clamp above and for prevent-wind-ragdoll below. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, and it does nothing while both of those settings are off anyway. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>()));
			PreventWindRagdollOverride = config.Bind<bool>("Wind", "prevent-wind-ragdoll", false, "Whether wind is allowed to ragdoll you. On (every preset 1-4): when wind blows you off a ledge you keep full control of your character on the way down, so you can grab a wall or use a Rescue Hook instead of flailing helplessly. Off: vanilla, where being pushed off an edge collapses you into physics - fall-camera-dampen-clamp above then still softens it partway, if the preset sets one. Only ever applies to a fall wind actually caused (see fall-camera-dampen-window-seconds); an ordinary fall you walked into yourself ragdolls exactly like vanilla either way. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby. Applies immediately, regardless of apply-changes-live.");
			ClimbSheltersFromWindOverride = config.Bind<bool>("Wind", "climb-shelters-from-wind", false, "Whether holding onto something shelters you from wind: while climbing a wall, a rope, a vine or a climb handle, wind can't push you at all - instead climbing gets much slower for as long as the wind is actually blowing on you (see the three climb-*-multiplier settings below). Vanilla only shelters you on a climb handle, so a gust mid-climb normally rips you off the wall, which is why walking into the wind is the only reliable tactic. Off = vanilla, and the default. Only takes effect when preset is set to Custom (5) - under presets 1-4 it is on everywhere except Subtle (1). If the wind can't reach you anyway - behind a rock, no gust - you climb at full speed, so this never costs you anything when it isn't protecting you. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby. Applies immediately, regardless of apply-changes-live.");
			ClimbWindSpeedMultiplierOverride = config.Bind<double>("Wind", "climb-speed-multiplier-in-wind", 1.0, new ConfigDescription("How fast you climb while wind is pushing on you, as a fraction of normal climbing speed - the price of climb-shelters-from-wind above. E.g. 0.90 means a tenth slower at full wind pressure; 1.0 means no slowdown at all (free shelter). Fades in with how hard the wind is actually blowing, so partial cover means only a partial slowdown. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.05, 1.0), Array.Empty<object>()));
			ClimbWindUpwardSpeedMultiplierOverride = config.Bind<double>("Wind", "climb-upward-speed-multiplier-in-wind", 1.0, new ConfigDescription("Extra slowdown on climbing UPWARD in wind, multiplied on top of climb-speed-multiplier-in-wind above (so 0.90 and 0.85 together mean about three quarters of normal speed climbing up through a full-strength gust). Climbing down is never slowed beyond the base multiplier. 1.0 means upward climbing costs no more than any other direction. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.05, 1.0), Array.Empty<object>()));
			ClimbWindIntoWindSpeedMultiplierOverride = config.Bind<double>("Wind", "climb-into-wind-speed-multiplier", 1.0, new ConfigDescription("Extra slowdown on climbing INTO the wind (toward where it's blowing from), multiplied on top of climb-speed-multiplier-in-wind above. Climbing with the wind is never sped up. 1.0 means direction doesn't matter. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.05, 1.0), Array.Empty<object>()));
			ClimbShelterGraceSecondsOverride = config.Bind<float>("Wind", "climb-shelter-grace-seconds", 0.5f, new ConfigDescription("How long wind stays much weaker after you let go of a climb - the window that stops finishing a climb mid-gust from catapulting you, and gives you time to start sprinting away or re-grab the wall if you let go by accident. Wind is held at climb-shelter-grace-force-multiplier below for most of the window, then ramps back to full over the tail of it so it doesn't end in a sudden shove. Set to 0 to switch the window off entirely (full wind force the instant you let go, like vanilla). Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, and it does nothing while the climb shelter itself is off. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			ClimbWindGraceForceMultiplierOverride = config.Bind<double>("Wind", "climb-shelter-grace-force-multiplier", 1.0, new ConfigDescription("How strong wind is during the grace window above, as a fraction of normal - e.g. 0.15 means 15% force, close to immune but not quite. Deliberately not 0: full immunity would let you tap a wall over and over to cross an exposed stretch wind-free, turning the climb shelter into a movement exploit. 1.0 means no reduction at all. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 1.0), Array.Empty<object>()));
			ApplyChangesLive = config.Bind<bool>("Debug", "apply-changes-live", false, "OFF by default, and off is the fast path: Fairoots resolves every gameplay value once as the Roots biome loads and then leaves it alone for the rest of the run, which is why this lives in Debug - it is a testing convenience, not something normal play needs. Turn it on to have settings take effect the instant you change them in-game (e.g. via PEAKLib.ModConfig): kept spore bombs resize live, the next detonation uses the new knockback/VFX/shake numbers, and the jump-over-height cutoff updates immediately - useful while tuning, at the cost of re-resolving those values as the game asks for them instead of reading one frozen set. IMPORTANT: this is read once, when a Roots biome loads. Switching it on while already standing in Roots does nothing until the next Roots load - so turn it on first, then load in. The spore-bomb removal fraction and the seed are always level-load-only either way, since which spore bombs were already removed can't be undone mid-level; the spore-bomb recolor and the cloud-opacity sliders are per-player cosmetics and always immediate either way. This is a behavior override, not a diagnostic, so it works regardless of the debug-logging switch below.");
			EnableDebugLogging = config.Bind<bool>("Debug", "enable-debug-logging", false, "Master switch for Fairoots' verbose diagnostic logging. When on, the mod reports what it can and can't find in a loaded Roots level (spore bombs, wind zone, spore areas, zombies) so you can see what's working. Leave off for normal play - it's noisy and only useful for development/bug reports.");
			LogSceneScanOnLoad = config.Bind<bool>("Debug", "log-scene-scan-on-load", false, "When debug logging is on, automatically dump a scene diagnostics report each time a Roots biome finishes loading. Off by default, like everything else in this section - the dump walks the whole level, so it belongs behind a deliberate choice rather than riding along with the logging switch. Leave it off and trigger the report manually with the hotkey below instead.");
			LogScreenshakeSources = config.Bind<bool>("Debug", "log-screenshake-sources", false, "When debug logging is on, log every camera shake the game queues along with the code that asked for it, and how far away the source was. Use this to work out why a shake you expected to be distance-capped still happened. Very noisy (the game shakes the camera constantly while climbing) - leave off unless you're chasing a specific shake.");
			SceneScanHotkey = config.Bind<KeyCode>("Debug", "scene-scan-hotkey", (KeyCode)0, "When debug logging is on, press this key in-game to dump a scene diagnostics report on demand (e.g. right when you're standing next to a spore bomb). Set to None to disable the hotkey.");
			FoliageProbeHotkey = config.Bind<KeyCode>("Debug", "foliage-probe-hotkey", (KeyCode)0, "When debug logging is on, press this key while standing next to a spore bomb visibly camouflaged in bush/grass to probe it for a foliage-detection method (grass-blade density, nearby colliders/renderers). Research tool for Phase 4 (ROADMAP.md); not part of the shipped cull feature. Set to None to disable the hotkey.");
			MaterialProbeHotkey = config.Bind<KeyCode>("Debug", "material-probe-hotkey", (KeyCode)0, "When debug logging is on, look at something and press this key to dump its material and shader setup: every color slot the shader declares, its value, and whether Fairoots is currently overriding it. Use it to work out why something looks miscolored - the report says outright whether the mod touched a given object or not. Set to None to disable the hotkey.");
			ShowRemovedSporeBombMarkers = config.Bind<bool>("Debug", "show-removed-spore-bomb-markers", false, "When debug logging is on, draw a 2D on-screen label over every spot a spore bomb was removed this level load, tagged by why (foliage vs. seeded cull) - useful for eyeballing what the cull actually touched. Off by default.");
			ShowSporeBombTriggerRadius = config.Bind<bool>("Debug", "show-spore-bomb-trigger-radius", false, "When debug logging is on, draw a red 3D wireframe around every kept spore bomb's current trigger hitbox (matching its exact shape/size after the configured radius multiplier is applied) for spore bombs within 10m - useful for eyeballing how much the trigger box was actually shrunk against the real prefab. Off by default.");
			CoverMouthPosePreview = config.Bind<bool>("Debug", "cover-mouth-pose-preview", false, "Holds the cover-your-mouth pose on permanently so you can tune the pose settings below without having to keep the key held down while you edit them. This is PURELY VISUAL: it doesn't make you immune to spores, doesn't drain stamina, doesn't tie up your hands, and other players don't see it - the mechanic itself behaves exactly as normal while this is on. Off by default.");
			CoverMouthPoseEmote = config.Bind<string>("Debug", "cover-mouth-pose-emote", string.Empty, "Which animation the cover-your-mouth pose borrows its hand and finger shape from. Leave empty to auto-pick the \"it's so over\" emote, which is what the pose is designed around. With debug logging on, every available emote is listed in the log the first time you cover your mouth, so you can copy an exact name from there if you'd rather use a different one. Pose-tuning knob.");
			CoverMouthPoseEmoteTime = config.Bind<float>("Debug", "cover-mouth-pose-emote-time", 0.6f, new ConfigDescription("Which moment of the pose animation to hold, from 0 (its first frame) to 1 (its last). The pose is frozen on a single frame rather than played, so this picks which one. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			CoverMouthPoseForwardCm = config.Bind<float>("Debug", "cover-mouth-pose-forward-cm", 80f, new ConfigDescription("How far in front of the face your hands are held while covering your mouth, in centimetres. Default is the maintainer's own playtest-tuned value. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 80f), Array.Empty<object>()));
			CoverMouthPoseBelowHeadCm = config.Bind<float>("Debug", "cover-mouth-pose-below-head-cm", 0f, new ConfigDescription("How far below eye level your hands are held while covering your mouth, in centimetres - i.e. mouth height. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-20f, 40f), Array.Empty<object>()));
			CoverMouthPoseHandGapCm = config.Bind<float>("Debug", "cover-mouth-pose-hand-gap-cm", 7f, new ConfigDescription("Vertical gap between your two hands while covering your mouth, in centimetres - they're stacked one above the other so they don't intersect. Default is the maintainer's own playtest-tuned value. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 40f), Array.Empty<object>()));
			CoverMouthPoseSideCm = config.Bind<float>("Debug", "cover-mouth-pose-side-cm", 13f, new ConfigDescription("How far to each side of centre your hands sit while covering your mouth, in centimetres. Near 0 means both hands are on the centre line, which is what makes the pose read as covering your mouth rather than gesturing. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 25f), Array.Empty<object>()));
			CoverMouthPoseHandYawDeg = config.Bind<float>("Debug", "cover-mouth-pose-hand-yaw-deg", 0f, new ConfigDescription("Turns both hands outward (or inward) while covering your mouth, in degrees - this is the one that decides where your thumbs point. 0 leaves the hands as the borrowed animation has them, which is palm-to-palm like praying; turning them opens the palms toward your face so the thumbs point away from it. Mirrored, so the two hands always stay symmetric. The sign is what decides which side of your hands faces you: negative turns the palms toward your face (correct for covering your mouth), positive turns them away. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-180f, 180f), Array.Empty<object>()));
			CoverMouthPoseHandRollDeg = config.Bind<float>("Debug", "cover-mouth-pose-hand-roll-deg", 10f, new ConfigDescription("Tips both hands so the thumbs ride higher or lower while covering your mouth, in degrees - use it with cover-mouth-pose-hand-yaw-deg to aim the thumbs up-and-out rather than straight out. Mirrored between the two hands; negate it if the thumbs end up pointing down instead of up. Pose-tuning knob.", (AcceptableValueBase