Decompiled source of PEAK Ticks v1.1.0

tony4twentys-PEAK Ticks.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PEAKLib.Core;
using Peak.Afflictions;
using Photon.Pun;
using Photon.Realtime;
using PhotonCustomPropsUtils;
using UnityEngine;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyTitle("PEAK Ticks")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PEAK Ticks")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("30fd5e37-dd72-4b02-9e20-7eeeee2178cc")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace PEAK_Ticks
{
	public static class PeakticksInterop
	{
		public const string PluginGuid = "tony4twentys.PEAK_Ticks";

		public static bool IsGameplayEnabled => TickModAuthorizationService.IsGameplayEnabled;

		public static bool CanAttachAnotherTick(Character character)
		{
			return TickAttachService.CanAttachAnotherTick(character);
		}

		public static int CountTicksOnCharacter(Character character)
		{
			return TickAttachService.CountTicksOnCharacter(character);
		}

		public static bool TryAttachTick(Character target, string attachSource, out int tickNumber)
		{
			return TickAttachService.TrySpawnAttachedTick(target, attachSource, null, out tickNumber);
		}
	}
	[BepInPlugin("tony4twentys.PEAK_Ticks", "PEAK Ticks", "1.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class PEAKTicksPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "tony4twentys.PEAK_Ticks";

		public const string PluginName = "PEAK Ticks";

		public const string PluginVersion = "1.1.0";

		public const string PhotonCustomPropsPluginGuid = "com.snosz.photoncustompropsutils";

		public const float VanillaGrowthDurationSeconds = 300f;

		public const float VanillaMaxScale = 1f;

		public const float VanillaPoisonPulseIntervalSeconds = 29f;

		public const float VanillaPoisonPerPulse = 0.05f;

		public const float VanillaMaxPoisonPerTick = 0.5f;

		public const float VanillaVegetationAttachChance = 0.01f;

		private const string ExtrasConfigSection = "Extras";

		private const string PoisonConfigSection = "Poison";

		private const float MaxGrowthDurationConfigSeconds = 999999f;

		private const float MaxScaleConfigValue = 999999f;

		private const float MaxPoisonPulseIntervalConfigSeconds = 999999f;

		private const float MaxPoisonPerPulseConfigValue = 999999f;

		private const float MaxPoisonPerTickConfigValue = 999999f;

		private const float MaxPoisonScaleTierSizeConfigValue = 999999f;

		private const float MinPoisonScaleTierSizeConfigValue = 0.01f;

		private Harmony _harmony;

		private ConfigEntry<int> _maxTicksPerPlayer;

		private ConfigEntry<string> _attachBodyparts;

		private ConfigEntry<bool> _enableVegetationAttach;

		private ConfigEntry<float> _vegetationAttachChance;

		private ConfigEntry<bool> _enableTickBomb;

		private ConfigEntry<float> _tickBombChance;

		private ConfigEntry<int> _tickBombMinTicks;

		private ConfigEntry<int> _tickBombMaxTicks;

		private ConfigEntry<float> _vegetationTouchCooldownSeconds;

		private ConfigEntry<bool> _logVegetationTouch;

		private ConfigEntry<float> _poisonPulseIntervalSeconds;

		private ConfigEntry<bool> _useInjuryInsteadOfPoison;

		private ConfigEntry<float> _poisonPerPulse;

		private ConfigEntry<float> _maxPoisonPerTick;

		private ConfigEntry<bool> _uncappedPoisonPerTick;

		private ConfigEntry<bool> _sizeBasedPoison;

		private ConfigEntry<float> _startingPoisonPerPulse;

		private ConfigEntry<float> _poisonScaleTierSize;

		private ConfigEntry<float> _growthDurationSeconds;

		private ConfigEntry<float> _maxScale;

		private ConfigEntry<bool> _uncappedGrowth;

		private Coroutine _segmentProbeCoroutine;

		private Coroutine _runtimeCoroutine;

		private int _lastPolledMapSegmentKey = int.MinValue;

		private int _lastPolledVegetationKey = int.MinValue;

		private bool _pendingIncrementalPass;

		private float _nextProbeRetryTime;

		private static readonly WaitForSeconds RuntimePollWait = new WaitForSeconds(1f);

		public static PEAKTicksPlugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Expected O, but got Unknown
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Expected O, but got Unknown
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Expected O, but got Unknown
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Expected O, but got Unknown
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Expected O, but got Unknown
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0286: Expected O, but got Unknown
			//IL_02db: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e5: Expected O, but got Unknown
			//IL_0319: Unknown result type (might be due to invalid IL or missing references)
			//IL_0323: Expected O, but got Unknown
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_0361: Expected O, but got Unknown
			//IL_03b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c0: Expected O, but got Unknown
			//IL_03f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fe: Expected O, but got Unknown
			//IL_044c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0456: Expected O, but got Unknown
			//IL_04d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_04de: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			_maxTicksPerPlayer = ((BaseUnityPlugin)this).Config.Bind<int>("Attach", "MaxTicksPerPlayer", 18, new ConfigDescription("Maximum number of ticks that can be attached to one player at once. Vanilla is 1.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 18), Array.Empty<object>()));
			_attachBodyparts = ((BaseUnityPlugin)this).Config.Bind<string>("Attach", "AttachBodyparts", "Knee_R,Knee_L,Hip_R,Hip_L,Hip,Torso,Shoulder_R,Shoulder_L,Elbow_R,Elbow_L,Arm_L,Arm_R,Foot_R,Foot_L,Neck,Head,Hand_R,Hand_L", "Comma-separated bodyparts eligible for random tick attach. Each bodypart holds at most one tick.");
			_enableVegetationAttach = ((BaseUnityPlugin)this).Config.Bind<bool>("Vegetation", "EnableVegetationAttach", true, "When true, walking through whitelisted plants can attach ticks in Shore, Tropics, Roots, Alpine, Mesa, and Gloom (Swamp).");
			_vegetationAttachChance = ((BaseUnityPlugin)this).Config.Bind<float>("Vegetation", "AttachChance", 0.01f, new ConfigDescription("Chance (0-1) that touching whitelisted vegetation triggers a tick attach burst. Vanilla TickTrigger tickChance is 0.01.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			_enableTickBomb = ((BaseUnityPlugin)this).Config.Bind<bool>("Vegetation", "EnableTickBomb", true, "When true, successful attach bursts can rarely roll a tick bomb instead of a normal burst.");
			_tickBombChance = ((BaseUnityPlugin)this).Config.Bind<float>("Vegetation", "TickBombChance", 0.01f, new ConfigDescription("Chance (0-1) of a tick bomb on each successful attach burst (only if EnableTickBomb is true). Defaults to AttachChance (vanilla tickChance 0.01).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			_tickBombMinTicks = ((BaseUnityPlugin)this).Config.Bind<int>("Vegetation", "TickBombMinTicks", 2, new ConfigDescription("Minimum ticks attached during a tick bomb burst.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 18), Array.Empty<object>()));
			_tickBombMaxTicks = ((BaseUnityPlugin)this).Config.Bind<int>("Vegetation", "TickBombMaxTicks", 18, new ConfigDescription("Maximum ticks attached during a tick bomb burst.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 18), Array.Empty<object>()));
			_vegetationTouchCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Vegetation", "TouchCooldownSeconds", 60f, new ConfigDescription("Seconds before the same player can trigger another vegetation attach burst.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			_logVegetationTouch = ((BaseUnityPlugin)this).Config.Bind<bool>("Vegetation", "LogVegetationTouch", false, "When true, logs each vegetation overlap/bounds touch detection (useful for debugging).");
			_poisonPulseIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Poison", "PoisonPulseIntervalSeconds", 29f, new ConfigDescription("Seconds between tick status pulses. Vanilla is 29.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 999999f), Array.Empty<object>()));
			_useInjuryInsteadOfPoison = ((BaseUnityPlugin)this).Config.Bind<bool>("Poison", "UseInjuryInsteadOfPoison", false, "When true, tick pulses apply Injury instead of Poison. All other Poison settings still control pulse rate, amount, caps, and size tiers.");
			_poisonPerPulse = ((BaseUnityPlugin)this).Config.Bind<float>("Poison", "PoisonPerPulse", 0.05f, new ConfigDescription("Flat poison added on each pulse. Ignored when SizeBasedPoison is true. Vanilla is 0.05.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.001f, 999999f), Array.Empty<object>()));
			_sizeBasedPoison = ((BaseUnityPlugin)this).Config.Bind<bool>("Poison", "SizeBasedPoison", false, "When true, poison per pulse scales with tick size instead of using PoisonPerPulse.");
			_startingPoisonPerPulse = ((BaseUnityPlugin)this).Config.Bind<float>("Poison", "StartingPoisonPerPulse", 0.1f, new ConfigDescription("Base poison per pulse at tier 0 when SizeBasedPoison is true. Each tier adds another full step (0.1 -> 0.2 -> 0.3).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.001f, 999999f), Array.Empty<object>()));
			_poisonScaleTierSize = ((BaseUnityPlugin)this).Config.Bind<float>("Poison", "PoisonScaleTierSize", 0.5f, new ConfigDescription("Each time tick scale rises by this amount, poison per pulse increases by StartingPoisonPerPulse. Default 0.5 (tier 1 at scale 0.5, ~150s with vanilla 300s growth).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 999999f), Array.Empty<object>()));
			_maxPoisonPerTick = ((BaseUnityPlugin)this).Config.Bind<float>("Poison", "MaxPoisonPerTick", 0.5f, new ConfigDescription("Maximum poison status one tick can contribute over its lifetime. Ignored when UncappedPoisonPerTick is true. Vanilla is 0.5.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.001f, 999999f), Array.Empty<object>()));
			_uncappedPoisonPerTick = ((BaseUnityPlugin)this).Config.Bind<bool>("Poison", "UncappedPoisonPerTick", false, "When true, each tick keeps adding poison every pulse and MaxPoisonPerTick is ignored.");
			_growthDurationSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Extras", "GrowthDurationSeconds", 300f, new ConfigDescription("Seconds for an attached tick to reach MaxScale. Vanilla is 300 (5 minutes). Minimum 1.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 999999f), Array.Empty<object>()));
			_maxScale = ((BaseUnityPlugin)this).Config.Bind<float>("Extras", "MaxScale", 1f, new ConfigDescription("Target scale when growth reaches GrowthDurationSeconds. Vanilla is 1. Ignored when UncappedGrowth is true.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 999999f), Array.Empty<object>()));
			_uncappedGrowth = ((BaseUnityPlugin)this).Config.Bind<bool>("Extras", "UncappedGrowth", false, "When true, ticks keep growing forever and MaxScale is ignored. When false, growth stops at MaxScale.");
			((BaseUnityPlugin)this).Config.Save();
			ApplySnapshot(BuildSnapshot());
			BindConfigChangedHandlers();
			TickModAuthorizationService.Initialize();
			_harmony = new Harmony("tony4twentys.PEAK_Ticks");
			MethodInfo methodInfo = AccessTools.Method(typeof(Interaction), "DoInteractableRaycasts", new Type[3]
			{
				typeof(IInteractible).MakeByRefType(),
				typeof(float),
				typeof(bool)
			}, (Type[])null);
			if (methodInfo == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"[PEAK Ticks] Failed to find Interaction.DoInteractableRaycasts for patching.");
			}
			else
			{
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(InteractionLookDownTickRaycastPrefixPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			_harmony.PatchAll(typeof(BugfixTryFindCharacterAndAttachBugPatch));
			_harmony.PatchAll(typeof(BugfixUnregisterAssignedBodypartPatch));
			_harmony.PatchAll(typeof(BugfixTickRemovalIsInteractiblePatch));
			_harmony.PatchAll(typeof(MapHandlerSegmentVegetationRefreshPatch));
			_harmony.PatchAll(typeof(VanillaTickTriggerAttachPatch));
			_runtimeCoroutine = ((MonoBehaviour)this).StartCoroutine(RuntimeLoop());
			try
			{
				_harmony.PatchAll(typeof(TickScaleDurationTranspiler));
				_harmony.PatchAll(typeof(TickScalePatch));
				((MonoBehaviour)this).StartCoroutine(TickScaleService.ApplyTickScaleEndOfFrame());
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("[PEAK Ticks] Tick growth patches failed to apply: " + ex));
			}
			try
			{
				_harmony.PatchAll(typeof(TickPoisonLateUpdatePrefixPatch));
				_harmony.PatchAll(typeof(TickPoisonLateUpdateTranspiler));
				_harmony.PatchAll(typeof(SkipPreventPoisonHealingWhenInjuryModePatch));
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("[PEAK Ticks] Tick poison patches failed to apply: " + ex2));
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("PEAK Ticks v1.1.0 loaded. Host handshake + config sync enabled. Growth: " + TickScaleService.FormatStartupSummary() + ". Status: " + TickPoisonService.FormatStartupSummary() + "."));
			if (TickVegetationAttachService.Enabled)
			{
				TickVegetationAttachService.DestroyRegisteredPlantTriggers();
			}
		}

		internal TickConfigSnapshot BuildSnapshot()
		{
			return new TickConfigSnapshot
			{
				MaxTicksPerPlayer = _maxTicksPerPlayer.Value,
				AttachBodypartsCsv = _attachBodyparts.Value,
				EnableVegetationAttach = _enableVegetationAttach.Value,
				VegetationAttachChance = _vegetationAttachChance.Value,
				EnableTickBomb = _enableTickBomb.Value,
				TickBombChance = _tickBombChance.Value,
				TickBombMinTicks = _tickBombMinTicks.Value,
				TickBombMaxTicks = _tickBombMaxTicks.Value,
				VegetationTouchCooldownSeconds = _vegetationTouchCooldownSeconds.Value,
				LogVegetationTouch = _logVegetationTouch.Value,
				PoisonPulseIntervalSeconds = _poisonPulseIntervalSeconds.Value,
				UseInjuryInsteadOfPoison = _useInjuryInsteadOfPoison.Value,
				PoisonPerPulse = _poisonPerPulse.Value,
				SizeBasedPoison = _sizeBasedPoison.Value,
				StartingPoisonPerPulse = _startingPoisonPerPulse.Value,
				PoisonScaleTierSize = _poisonScaleTierSize.Value,
				MaxPoisonPerTick = _maxPoisonPerTick.Value,
				UncappedPoisonPerTick = _uncappedPoisonPerTick.Value,
				GrowthDurationSeconds = _growthDurationSeconds.Value,
				MaxScale = _maxScale.Value,
				UncappedGrowth = _uncappedGrowth.Value
			};
		}

		internal void ApplySnapshot(TickConfigSnapshot snapshot)
		{
			if (snapshot != null)
			{
				TickAttachService.RefreshConfig(snapshot.MaxTicksPerPlayer, snapshot.AttachBodypartsCsv);
				TickScaleService.RefreshConfig(snapshot.GrowthDurationSeconds, snapshot.MaxScale, snapshot.UncappedGrowth);
				TickPoisonService.RefreshConfig(snapshot.PoisonPulseIntervalSeconds, snapshot.PoisonPerPulse, snapshot.MaxPoisonPerTick, snapshot.UncappedPoisonPerTick, snapshot.SizeBasedPoison, snapshot.StartingPoisonPerPulse, snapshot.PoisonScaleTierSize, snapshot.UseInjuryInsteadOfPoison);
				TickVegetationAttachService.RefreshConfig(snapshot.EnableVegetationAttach, snapshot.VegetationAttachChance, snapshot.EnableTickBomb, snapshot.TickBombChance, snapshot.TickBombMinTicks, snapshot.TickBombMaxTicks, snapshot.VegetationTouchCooldownSeconds);
				TickVegetationAttachService.LogVegetationTouch = snapshot.LogVegetationTouch;
			}
		}

		private void BindConfigChangedHandlers()
		{
			_maxTicksPerPlayer.SettingChanged += OnGameplayConfigChanged;
			_attachBodyparts.SettingChanged += OnGameplayConfigChanged;
			_enableVegetationAttach.SettingChanged += OnGameplayConfigChanged;
			_vegetationAttachChance.SettingChanged += OnGameplayConfigChanged;
			_enableTickBomb.SettingChanged += OnGameplayConfigChanged;
			_tickBombChance.SettingChanged += OnGameplayConfigChanged;
			_tickBombMinTicks.SettingChanged += OnGameplayConfigChanged;
			_tickBombMaxTicks.SettingChanged += OnGameplayConfigChanged;
			_vegetationTouchCooldownSeconds.SettingChanged += OnGameplayConfigChanged;
			_logVegetationTouch.SettingChanged += OnGameplayConfigChanged;
			_poisonPulseIntervalSeconds.SettingChanged += OnGameplayConfigChanged;
			_useInjuryInsteadOfPoison.SettingChanged += OnGameplayConfigChanged;
			_poisonPerPulse.SettingChanged += OnGameplayConfigChanged;
			_sizeBasedPoison.SettingChanged += OnGameplayConfigChanged;
			_startingPoisonPerPulse.SettingChanged += OnGameplayConfigChanged;
			_poisonScaleTierSize.SettingChanged += OnGameplayConfigChanged;
			_maxPoisonPerTick.SettingChanged += OnGameplayConfigChanged;
			_uncappedPoisonPerTick.SettingChanged += OnGameplayConfigChanged;
			_growthDurationSeconds.SettingChanged += OnGameplayConfigChanged;
			_maxScale.SettingChanged += OnGameplayConfigChanged;
			_uncappedGrowth.SettingChanged += OnGameplayConfigChanged;
		}

		private void OnGameplayConfigChanged(object sender, EventArgs args)
		{
			TickModAuthorizationService.OnHostConfigChangedLocally();
		}

		public static bool IsVegetationProbeSetupRunning()
		{
			if ((Object)(object)Instance != (Object)null)
			{
				return Instance._segmentProbeCoroutine != null;
			}
			return false;
		}

		public static void ScheduleVegetationProbeSetup(bool forceRestart = true)
		{
			if ((Object)(object)Instance == (Object)null)
			{
				return;
			}
			Instance._pendingIncrementalPass = false;
			if (Instance._segmentProbeCoroutine != null)
			{
				if (!forceRestart)
				{
					return;
				}
				((MonoBehaviour)Instance).StopCoroutine(Instance._segmentProbeCoroutine);
				Instance._segmentProbeCoroutine = null;
			}
			Instance._segmentProbeCoroutine = ((MonoBehaviour)Instance).StartCoroutine(Instance.SegmentProbeSetupCoroutine());
		}

		public static void ScheduleVegetationIncrementalSetup()
		{
			if (!((Object)(object)Instance == (Object)null))
			{
				if (Instance._segmentProbeCoroutine != null)
				{
					Instance._pendingIncrementalPass = true;
				}
				else
				{
					Instance._segmentProbeCoroutine = ((MonoBehaviour)Instance).StartCoroutine(Instance.IncrementalProbeSetupCoroutine());
				}
			}
		}

		private IEnumerator IncrementalProbeSetupCoroutine()
		{
			yield return null;
			if (!TickModAuthorizationService.IsLocalVegetationActive || !TickVegetationAttachService.Enabled || !TickVegetationAttachService.IsCurrentBiomeAllowed())
			{
				_segmentProbeCoroutine = null;
				yield break;
			}
			yield return TickVegetationAttachService.SetupVegetationProbesAsync(incrementalOnly: true);
			_segmentProbeCoroutine = null;
			if (_pendingIncrementalPass)
			{
				_pendingIncrementalPass = false;
				ScheduleVegetationIncrementalSetup();
			}
		}

		private IEnumerator SegmentProbeSetupCoroutine()
		{
			yield return null;
			yield return (object)new WaitForEndOfFrame();
			if (!TickModAuthorizationService.IsLocalVegetationActive || !TickVegetationAttachService.Enabled)
			{
				_segmentProbeCoroutine = null;
				yield break;
			}
			if (!TickVegetationAttachService.IsCurrentBiomeAllowed())
			{
				TickVegetationAttachService.LogMapHandlerStateForDiagnostics("probe setup skipped", force: true);
				_segmentProbeCoroutine = null;
				yield break;
			}
			for (int attempt = 0; attempt < 24; attempt++)
			{
				if (TickVegetationAttachService.GetActiveRegistrationRoots().Length != 0)
				{
					break;
				}
				yield return (object)new WaitForSeconds(0.5f);
			}
			if (TickVegetationAttachService.ShouldRunDelayedVegetationPass())
			{
				yield return TickVegetationAttachService.RunSwampGloomSetupWithRetriesAsync();
			}
			else
			{
				yield return TickVegetationAttachService.SetupVegetationProbesAsync();
			}
			_segmentProbeCoroutine = null;
			if (_pendingIncrementalPass)
			{
				ScheduleVegetationIncrementalSetup();
			}
		}

		private void TryRefreshVegetationProbes()
		{
			if (!TickModAuthorizationService.IsLocalVegetationActive || !TickVegetationAttachService.Enabled || (Object)(object)Singleton<MapHandler>.Instance == (Object)null)
			{
				return;
			}
			int currentSegmentCacheKey = TickVegetationProbeService.GetCurrentSegmentCacheKey();
			int vegetationCacheKey = TickVegetationProbeService.GetVegetationCacheKey();
			TickVegetationAttachService.LogMapHandlerStateForDiagnostics("segment poll");
			if (TickVegetationAttachService.IsCurrentBiomeAllowed() && (vegetationCacheKey != _lastPolledVegetationKey || currentSegmentCacheKey != _lastPolledMapSegmentKey || !TickVegetationProbeService.IsReadyForCurrentSegment()))
			{
				if (vegetationCacheKey != _lastPolledVegetationKey || currentSegmentCacheKey != _lastPolledMapSegmentKey)
				{
					_lastPolledVegetationKey = vegetationCacheKey;
					_lastPolledMapSegmentKey = currentSegmentCacheKey;
					TickVegetationAttachService.RequestSegmentVegetationRefresh();
				}
				else if (!TickVegetationProbeService.IsReadyForCurrentSegment() && !IsVegetationProbeSetupRunning() && !(Time.time < _nextProbeRetryTime))
				{
					_nextProbeRetryTime = Time.time + 5f;
					ScheduleVegetationProbeSetup(forceRestart: false);
				}
			}
		}

		internal static void NotifySegmentProbeSetupScheduled(int mapSegmentKey)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				Instance._lastPolledMapSegmentKey = mapSegmentKey;
				Instance._lastPolledVegetationKey = TickVegetationProbeService.GetVegetationCacheKey();
			}
		}

		private IEnumerator RuntimeLoop()
		{
			while (true)
			{
				TickModAuthorizationService.EnsureMasterClientAuthorized();
				if (TickModAuthorizationService.IsLocalVegetationActive && TickVegetationAttachService.Enabled)
				{
					Character localCharacter = Character.localCharacter;
					if ((Object)(object)localCharacter != (Object)null)
					{
						PeakTickVegetationScanner.AttachToCharacter(localCharacter);
					}
				}
				TryRefreshVegetationProbes();
				yield return RuntimePollWait;
			}
		}

		private void OnDestroy()
		{
			if (_runtimeCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_runtimeCoroutine);
				_runtimeCoroutine = null;
			}
			if (_segmentProbeCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_segmentProbeCoroutine);
				_segmentProbeCoroutine = null;
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
			Log = null;
		}
	}
	internal static class TickVegetationAttachService
	{
		internal struct VegetationDetectionCandidate
		{
			public GameObject DetectionHost;

			public GameObject SourceRoot;

			public string SourceLabel;

			public float MaxBoundsExtent;

			public bool BoundsOnly;
		}

		private const float MaxSpreadTagBoundsExtent = 1.25f;

		private const float MaxFoliageTagBoundsExtent = 2.5f;

		private static readonly string[] SpreadGroupKeywords = new string[9] { "beachgrass", "fern", "driftwood", "vine", "weed", "bushes", "shrub", "dead grass", "cattails" };

		private static readonly string[] GloomPropFolderNames = new string[3] { "Cattails", "VenusLeaves", "Vines" };

		private static float _nextBurstAllowedTime;

		private static int _burstCharacterId = int.MinValue;

		private static int _vegetationBurstCounter;

		private static int _lastCooldownAppliedBurstId = -1;

		private static float _nextBurstSkipLogTime;

		private static string _lastBurstSkipReason;

		private static readonly string[] VegetationAttachKeywords = new string[18]
		{
			"poisonivy", "bushleaves", "berrybush", "beachgrass", "driftwood", "fern", "vine", "hangingvine", "jungle_tiny", "jungle_minibanana",
			"jungle_sharpplant", "monstera", "ice_horsetail", "ice_deadshrub", "ice_pinebushleaves", "jungle_weed", "cattails", "venusleaves"
		};

		private static readonly string[] NamedVegetationTriggerKeywords = new string[20]
		{
			"beachgrass", "fern", "driftwood", "vine", "weed", "monstera", "poisonivy", "bushleaves", "hangingvine", "jungle_tiny",
			"jungle_minibanana", "jungle_sharpplant", "shrub", "dead grass", "ice_horsetail", "ice_deadshrub", "ice_pinebushleaves", "jungle_weed", "cattails", "venusleaves"
		};

		private static readonly SpawnPool[] AllowedAttachSpawnPools = (SpawnPool[])(object)new SpawnPool[2]
		{
			(SpawnPool)8,
			(SpawnPool)16
		};

		private const int NormalBurstTickCount = 1;

		private static int _lastLoggedMapHandlerKey = int.MinValue;

		private static int _lastSegmentRefreshKey = int.MinValue;

		private static int _lastMapSegmentKey = int.MinValue;

		public static bool Enabled { get; private set; } = true;

		public static float AttachChance { get; private set; } = 0.01f;

		public static bool EnableTickBomb { get; private set; } = true;

		public static float TickBombChance { get; private set; } = 0.01f;

		public static int TickBombMinTicks { get; private set; } = 2;

		public static int TickBombMaxTicks { get; private set; } = 18;

		public static float TouchCooldownSeconds { get; private set; } = 60f;

		public static bool LogVegetationTouch { get; set; }

		public static void RefreshConfig(bool enabled, float attachChance, bool enableTickBomb, float tickBombChance, int tickBombMinTicks, int tickBombMaxTicks, float touchCooldownSeconds)
		{
			Enabled = enabled;
			AttachChance = Mathf.Clamp01(attachChance);
			EnableTickBomb = enableTickBomb;
			TickBombChance = Mathf.Clamp01(tickBombChance);
			TickBombMinTicks = Mathf.Clamp(tickBombMinTicks, 1, 18);
			TickBombMaxTicks = Mathf.Clamp(tickBombMaxTicks, 1, 18);
			if (TickBombMaxTicks < TickBombMinTicks)
			{
				int tickBombMinTicks2 = TickBombMinTicks;
				TickBombMinTicks = TickBombMaxTicks;
				TickBombMaxTicks = tickBombMinTicks2;
			}
			TouchCooldownSeconds = Mathf.Max(0f, touchCooldownSeconds);
		}

		public static bool IsCurrentBiomeAllowed()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)Singleton<MapHandler>.Instance == (Object)null)
				{
					return false;
				}
				return TickBiomeRules.IsAllowedTickBiome(Singleton<MapHandler>.Instance.GetCurrentBiome());
			}
			catch
			{
				return false;
			}
		}

		public static bool RollAttachChance()
		{
			return Random.value <= AttachChance;
		}

		private static bool RollTickBomb()
		{
			if (EnableTickBomb)
			{
				return Random.value <= TickBombChance;
			}
			return false;
		}

		private static int RollBurstTickCount(out bool isTickBomb)
		{
			isTickBomb = RollTickBomb();
			if (isTickBomb)
			{
				return Random.Range(TickBombMinTicks, TickBombMaxTicks + 1);
			}
			return 1;
		}

		public static string GetCurrentBiomeNameForLog()
		{
			return GetCurrentBiomeName();
		}

		public static void LogMapHandlerStateForDiagnostics(string context, bool force = false)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected I4, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Singleton<MapHandler>.Instance == (Object)null)
			{
				return;
			}
			int currentSegmentCacheKey = TickVegetationProbeService.GetCurrentSegmentCacheKey();
			if (!force && currentSegmentCacheKey == _lastLoggedMapHandlerKey)
			{
				return;
			}
			_lastLoggedMapHandlerKey = currentSegmentCacheKey;
			string currentSegmentName = GetCurrentSegmentName();
			string currentBiomeName = GetCurrentBiomeName();
			int num = (int)Singleton<MapHandler>.Instance.GetCurrentBiome();
			if (TickBiomeRules.IsAllowedTickBiome(Singleton<MapHandler>.Instance.GetCurrentBiome()))
			{
				ManualLogSource log = PEAKTicksPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[PEAK Ticks] MapHandler state (" + context + "): segment=" + currentSegmentName + ", biome=" + currentBiomeName + " (enum " + num + "), key=" + currentSegmentCacheKey + ", vegetation=allowed."));
				}
			}
			else
			{
				ManualLogSource log2 = PEAKTicksPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[PEAK Ticks] MapHandler state (" + context + "): segment=" + currentSegmentName + ", biome=" + currentBiomeName + " (enum " + num + "), key=" + currentSegmentCacheKey + ", vegetation=BLOCKED (not in AllowedTickBiomes)."));
				}
			}
		}

		public static Transform[] GetActiveRegistrationRoots()
		{
			if ((Object)(object)Singleton<MapHandler>.Instance == (Object)null || !IsCurrentBiomeAllowed())
			{
				return (Transform[])(object)new Transform[0];
			}
			if (IsSwampBiomeSegment())
			{
				return GetSwampGloomPropFolderRoots();
			}
			List<Transform> list = new List<Transform>(2);
			try
			{
				MapSegment currentMapSegment = MapHandler.CurrentMapSegment;
				AddRegistrationRoot(list, currentMapSegment.segmentParent);
				AddRegistrationRoot(list, currentMapSegment.segmentCampfire);
			}
			catch (Exception ex)
			{
				ManualLogSource log = PEAKTicksPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[PEAK Ticks] Failed to resolve active segment registration roots: " + ex.Message));
				}
			}
			return list.ToArray();
		}

		public static Transform[] GetSwampGloomPropFolderRoots()
		{
			List<Transform> list = new List<Transform>(GloomPropFolderNames.Length);
			CollectSwampGloomPropFolderRoots(list);
			return list.ToArray();
		}

		private static Transform FindSwampSegmentPropsTransform()
		{
			Transform val = FindMapTransform();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Stack<Transform> stack = new Stack<Transform>(128);
			stack.Push(val);
			while (stack.Count > 0)
			{
				Transform val2 = stack.Pop();
				for (int num = val2.childCount - 1; num >= 0; num--)
				{
					Transform child = val2.GetChild(num);
					if ((Object)(object)child != (Object)null)
					{
						stack.Push(child);
					}
				}
				if (string.Equals(((Object)val2).name, "Props", StringComparison.OrdinalIgnoreCase))
				{
					string vegetationHierarchyPath = GetVegetationHierarchyPath(val2);
					if (vegetationHierarchyPath.IndexOf("/Swamp_Segment/", StringComparison.OrdinalIgnoreCase) >= 0 && vegetationHierarchyPath.IndexOf("/Gloom/", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return val2;
					}
				}
			}
			return null;
		}

		private static void CollectSwampGloomPropFolderRoots(List<Transform> roots)
		{
			Transform val = FindSwampSegmentPropsTransform();
			if ((Object)(object)val == (Object)null)
			{
				ManualLogSource log = PEAKTicksPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"[PEAK Ticks] Swamp Props folder not found (expected Map/.../Gloom/Swamp_Segment/Props).");
				}
				return;
			}
			int num = 0;
			for (int i = 0; i < val.childCount; i++)
			{
				Transform child = val.GetChild(i);
				if (!((Object)(object)child == (Object)null) && ((Component)child).gameObject.activeInHierarchy && TryMatchGloomPropFolder(child, out var _))
				{
					AddRegistrationRoot(roots, ((Component)child).gameObject);
					num++;
				}
			}
			if (num == 0)
			{
				ManualLogSource log2 = PEAKTicksPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[PEAK Ticks] Swamp Props found at " + GetVegetationHierarchyPath(val) + " but Cattails/VenusLeaves/Vines folders are not ready yet."));
				}
				return;
			}
			ManualLogSource log3 = PEAKTicksPlugin.Log;
			if (log3 != null)
			{
				log3.LogInfo((object)("[PEAK Ticks] Swamp gloom prop folders: " + num + " under " + GetVegetationHierarchyPath(val) + "."));
			}
		}

		private static Transform FindMapTransform()
		{
			GameObject val = GameObject.Find("Map");
			if ((Object)(object)val != (Object)null)
			{
				return val.transform;
			}
			try
			{
				MapSegment currentMapSegment = MapHandler.CurrentMapSegment;
				Transform val2 = (((Object)(object)currentMapSegment.segmentParent != (Object)null) ? currentMapSegment.segmentParent.transform : null);
				while ((Object)(object)val2 != (Object)null)
				{
					if (string.Equals(((Object)val2).name, "Map", StringComparison.OrdinalIgnoreCase))
					{
						return val2;
					}
					val2 = val2.parent;
				}
			}
			catch
			{
			}
			return null;
		}

		public static IEnumerator RunSwampGloomSetupWithRetriesAsync()
		{
			float[] retryDelaysSeconds = new float[5] { 0f, 2f, 4f, 8f, 15f };
			for (int attempt = 0; attempt < retryDelaysSeconds.Length; attempt++)
			{
				if (attempt > 0)
				{
					yield return (object)new WaitForSeconds(retryDelaysSeconds[attempt]);
				}
				if (GetSwampGloomPropFolderRoots().Length == 0)
				{
					ManualLogSource log = PEAKTicksPlugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[PEAK Ticks] Swamp gloom setup retry " + (attempt + 1) + "/" + retryDelaysSeconds.Length + ": prop folders not ready."));
					}
					continue;
				}
				bool incrementalOnly = attempt > 0;
				yield return SetupVegetationProbesAsync(incrementalOnly);
				if (TickVegetationProbeService.TouchTargetCount < 50)
				{
					ManualLogSource log2 = PEAKTicksPlugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[PEAK Ticks] Swamp gloom setup retry " + (attempt + 1) + "/" + retryDelaysSeconds.Length + ": registered " + TickVegetationProbeService.TouchTargetCount + " targets so far."));
					}
					continue;
				}
				break;
			}
		}

		public static IEnumerator EnumerateSwampGloomPropDiscoveryAsync(Transform[] propFolderRoots, Action<VegetationDetectionCandidate> onCandidate, HashSet<int> seenSourceRootIds, Func<bool> shouldYield)
		{
			if (propFolderRoots == null || propFolderRoots.Length == 0 || onCandidate == null)
			{
				yield break;
			}
			foreach (Transform val in propFolderRoots)
			{
				if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
				{
					if (!TryMatchGloomPropFolder(val, out var folderName))
					{
						folderName = ((Object)val).name;
					}
					IEnumerator gloomPass = CollectGloomPropFolderCandidatesAsync(val, folderName, onCandidate, seenSourceRootIds, shouldYield);
					while (gloomPass.MoveNext())
					{
						yield return gloomPass.Current;
					}
				}
			}
		}

		private static void AddRegistrationRoot(List<Transform> roots, GameObject rootObject)
		{
			if ((Object)(object)rootObject == (Object)null || !rootObject.activeInHierarchy)
			{
				return;
			}
			Transform transform = rootObject.transform;
			for (int i = 0; i < roots.Count; i++)
			{
				if ((Object)(object)roots[i] == (Object)(object)transform)
				{
					return;
				}
			}
			roots.Add(transform);
		}

		public static void DestroyRegisteredPlantTriggers()
		{
			ClearModProbesInSegment();
		}

		public static void RequestSegmentVegetationRefresh()
		{
			int vegetationCacheKey = TickVegetationProbeService.GetVegetationCacheKey();
			int currentSegmentCacheKey = TickVegetationProbeService.GetCurrentSegmentCacheKey();
			if (vegetationCacheKey == _lastSegmentRefreshKey)
			{
				if (currentSegmentCacheKey != _lastMapSegmentKey)
				{
					_lastMapSegmentKey = currentSegmentCacheKey;
					if (IsSwampBiomeSegment())
					{
						if (TickVegetationProbeService.IsReadyForCurrentSegment())
						{
							ManualLogSource log = PEAKTicksPlugin.Log;
							if (log != null)
							{
								log.LogInfo((object)("[PEAK Ticks] Swamp segment change skipped incremental scan (" + TickVegetationProbeService.TouchTargetCount + " targets ready)."));
							}
						}
						else
						{
							PEAKTicksPlugin.ScheduleVegetationIncrementalSetup();
						}
						return;
					}
				}
				if (!TickVegetationProbeService.IsReadyForCurrentSegment() && !PEAKTicksPlugin.IsVegetationProbeSetupRunning())
				{
					PEAKTicksPlugin.ScheduleVegetationProbeSetup(forceRestart: false);
				}
			}
			else
			{
				_lastSegmentRefreshKey = vegetationCacheKey;
				_lastMapSegmentKey = currentSegmentCacheKey;
				LogMapHandlerStateForDiagnostics("segment refresh", force: true);
				ClearModProbesInSegment();
				TickVegetationProbeService.PrepareForSegmentChange(vegetationCacheKey);
				PEAKTicksPlugin.NotifySegmentProbeSetupScheduled(currentSegmentCacheKey);
				PEAKTicksPlugin.ScheduleVegetationProbeSetup();
			}
		}

		public static void LogVegetationTouchDetected(string sourceLabel, string method)
		{
			if (LogVegetationTouch)
			{
				ManualLogSource log = PEAKTicksPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[PEAK Ticks] Vegetation touch: " + sourceLabel + " (" + method + ")."));
				}
			}
		}

		public static IEnumerator SetupVegetationProbesAsync(bool incrementalOnly = false)
		{
			yield return TickVegetationProbeService.SetupProbesForCurrentSegmentAsync(incrementalOnly);
		}

		public static bool HostNeedsFoliageProbe(GameObject host)
		{
			return HostNeedsFoliageAssist(host);
		}

		public static bool IsBerryBushTouchTarget(GameObject sourceRoot, string sourceLabel)
		{
			if ((Object)(object)sourceRoot != (Object)null && (Object)(object)sourceRoot.GetComponent<BerryBush>() != (Object)null)
			{
				return true;
			}
			return sourceLabel?.StartsWith("BerryBush:", StringComparison.Ordinal) ?? false;
		}

		public static bool TryComputeFoliageBounds(GameObject host, float maxExtent, out Bounds bounds)
		{
			return TryComputeTagBounds(host, maxExtent, out bounds);
		}

		public static bool IsSwampBiomeSegment()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			try
			{
				return (Object)(object)Singleton<MapHandler>.Instance != (Object)null && (int)Singleton<MapHandler>.Instance.GetCurrentBiome() == 8;
			}
			catch
			{
				return false;
			}
		}

		public static bool ShouldRunDelayedVegetationPass()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			try
			{
				if ((Object)(object)Singleton<MapHandler>.Instance == (Object)null)
				{
					return false;
				}
				return (int)Singleton<MapHandler>.Instance.GetCurrentBiome() == 8;
			}
			catch
			{
				return false;
			}
		}

		public static IEnumerator EnumerateVegetationDiscoveryAsync(Transform[] roots, Action<VegetationDetectionCandidate> onCandidate, HashSet<int> seenSourceRootIds, Func<bool> shouldYield, bool spreadAndGloomOnly = false)
		{
			if (roots == null || roots.Length == 0 || onCandidate == null)
			{
				yield break;
			}
			Stack<Transform> stack = new Stack<Transform>(512);
			HashSet<int> processedSpreadGroupIds = new HashSet<int>();
			HashSet<int> processedGloomFolderIds = new HashSet<int>();
			foreach (Transform val in roots)
			{
				if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy)
				{
					stack.Push(val);
				}
			}
			while (stack.Count > 0)
			{
				Transform current = stack.Pop();
				if ((Object)(object)current == (Object)null || !((Component)current).gameObject.activeInHierarchy)
				{
					continue;
				}
				for (int num = current.childCount - 1; num >= 0; num--)
				{
					Transform child = current.GetChild(num);
					if ((Object)(object)child != (Object)null)
					{
						stack.Push(child);
					}
				}
				if (TryMatchGloomPropFolder(current, out var folderName))
				{
					int instanceID = ((Object)current).GetInstanceID();
					if (processedGloomFolderIds.Add(instanceID))
					{
						IEnumerator gloomPass = CollectGloomPropFolderCandidatesAsync(current, folderName, onCandidate, seenSourceRootIds, shouldYield);
						while (gloomPass.MoveNext())
						{
							yield return gloomPass.Current;
						}
					}
				}
				if (IsKeywordSpreadGroup(((Object)current).name, out var _) && IsAllowedAttachPath(current))
				{
					int instanceID2 = ((Object)current).GetInstanceID();
					if (processedSpreadGroupIds.Add(instanceID2))
					{
						IEnumerator gloomPass = CollectSpreadGroupCandidatesAsync(current, onCandidate, seenSourceRootIds, shouldYield);
						while (gloomPass.MoveNext())
						{
							yield return gloomPass.Current;
						}
					}
				}
				if (spreadAndGloomOnly)
				{
					if (shouldYield != null && shouldYield())
					{
						yield return null;
					}
					continue;
				}
				Renderer component = ((Component)current).GetComponent<Renderer>();
				if ((Object)(object)component != (Object)null && component.enabled)
				{
					if (TryBuildDetectionCandidate(((Component)component).gameObject, out var candidate))
					{
						TryEmitDetectionCandidate(candidate, seenSourceRootIds, onCandidate);
					}
					if (shouldYield != null && shouldYield())
					{
						yield return null;
					}
				}
				Collider component2 = ((Component)current).GetComponent<Collider>();
				if ((Object)(object)component2 != (Object)null && component2.enabled)
				{
					if (TryBuildDetectionCandidate(((Component)component2).gameObject, out var candidate2))
					{
						TryEmitDetectionCandidate(candidate2, seenSourceRootIds, onCandidate);
					}
					if (shouldYield != null && shouldYield())
					{
						yield return null;
					}
				}
			}
		}

		private static bool MatchesGloomPropFolderName(string transformName, string folderName)
		{
			if (string.IsNullOrEmpty(transformName) || string.IsNullOrEmpty(folderName))
			{
				return false;
			}
			if (string.Equals(transformName, folderName, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (string.Equals(folderName, "Vines", StringComparison.OrdinalIgnoreCase) && transformName.StartsWith("Vines", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return false;
		}

		private static bool TryMatchGloomPropFolder(Transform transform, out string folderName)
		{
			folderName = null;
			if ((Object)(object)transform == (Object)null)
			{
				return false;
			}
			for (int i = 0; i < GloomPropFolderNames.Length; i++)
			{
				string text = GloomPropFolderNames[i];
				if (MatchesGloomPropFolderName(((Object)transform).name, text))
				{
					string vegetationHierarchyPath = GetVegetationHierarchyPath(transform);
					if (vegetationHierarchyPath.IndexOf("/Props/", StringComparison.OrdinalIgnoreCase) >= 0 && (vegetationHierarchyPath.IndexOf("/Swamp_Segment/", StringComparison.OrdinalIgnoreCase) >= 0 || vegetationHierarchyPath.IndexOf("/Gloom/", StringComparison.OrdinalIgnoreCase) >= 0))
					{
						folderName = text;
						return true;
					}
				}
			}
			return false;
		}

		private static IEnumerator CollectGloomPropFolderCandidatesAsync(Transform folderTransform, string folderName, Action<VegetationDetectionCandidate> onCandidate, HashSet<int> seenSourceRootIds, Func<bool> shouldYield)
		{
			string sourceLabel = GetGloomPropSourceLabel(folderName);
			for (int childIndex = 0; childIndex < folderTransform.childCount; childIndex++)
			{
				Transform child = folderTransform.GetChild(childIndex);
				if ((Object)(object)child == (Object)null || !((Component)child).gameObject.activeInHierarchy || !IsAllowedAttachPath(child) || ShouldSkipSpreadClumpHost(((Component)child).gameObject))
				{
					continue;
				}
				if (IsKeywordSpreadGroup(((Object)child).name, out var _))
				{
					IEnumerator spreadPass = CollectSpreadGroupCandidatesAsync(child, onCandidate, seenSourceRootIds, shouldYield);
					while (spreadPass.MoveNext())
					{
						yield return spreadPass.Current;
					}
					continue;
				}
				TryEmitDetectionCandidate(BuildSpreadCandidate(((Component)child).gameObject, ((Component)child).gameObject, sourceLabel), seenSourceRootIds, onCandidate);
				if (shouldYield != null && shouldYield())
				{
					yield return null;
				}
			}
			IEnumerator deepPass = CollectGloomFolderDeepClumpsAsync(folderTransform, sourceLabel, onCandidate, seenSourceRootIds, shouldYield);
			while (deepPass.MoveNext())
			{
				yield return deepPass.Current;
			}
		}

		private static IEnumerator CollectGloomFolderDeepClumpsAsync(Transform folderTransform, string sourceLabel, Action<VegetationDetectionCandidate> onCandidate, HashSet<int> seenSourceRootIds, Func<bool> shouldYield)
		{
			Stack<Transform> stack = new Stack<Transform>();
			for (int i = 0; i < folderTransform.childCount; i++)
			{
				Transform child = folderTransform.GetChild(i);
				if ((Object)(object)child != (Object)null)
				{
					stack.Push(child);
				}
			}
			while (stack.Count > 0)
			{
				Transform val = stack.Pop();
				for (int num = val.childCount - 1; num >= 0; num--)
				{
					stack.Push(val.GetChild(num));
				}
				Renderer component = ((Component)val).GetComponent<Renderer>();
				if ((Object)(object)component == (Object)null || !IsAllowedAttachPath(((Component)component).transform))
				{
					continue;
				}
				Transform val2 = val;
				while ((Object)(object)val2.parent != (Object)null && (Object)(object)val2.parent != (Object)(object)folderTransform)
				{
					val2 = val2.parent;
				}
				if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.parent != (Object)(object)folderTransform) && !ShouldSkipSpreadClumpHost(((Component)val2).gameObject))
				{
					TryEmitDetectionCandidate(BuildSpreadCandidate(((Component)val2).gameObject, ((Component)val2).gameObject, sourceLabel), seenSourceRootIds, onCandidate);
					if (shouldYield != null && shouldYield())
					{
						yield return null;
					}
				}
			}
		}

		private static bool ShouldSkipSpreadClumpHost(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return true;
			}
			BerryBush component = gameObject.GetComponent<BerryBush>();
			if ((Object)(object)component != (Object)null && QualifiesBerryBush(component))
			{
				return true;
			}
			if ((Object)(object)gameObject.GetComponent<BerryVine>() != (Object)null || (Object)(object)gameObject.GetComponent<JungleVine>() != (Object)null)
			{
				return true;
			}
			if (TryEvaluateWhitelistedGameObject(gameObject, out var _, out var sourceLabel) && !IsBoundsOnlySourceLabel(sourceLabel))
			{
				return true;
			}
			return false;
		}

		private static IEnumerator CollectSpreadGroupCandidatesAsync(Transform groupTransform, Action<VegetationDetectionCandidate> onCandidate, HashSet<int> seenSourceRootIds, Func<bool> shouldYield)
		{
			if ((Object)(object)groupTransform == (Object)null || !((Component)groupTransform).gameObject.activeInHierarchy)
			{
				yield break;
			}
			if (!TryEvaluateWhitelistedGameObject(((Component)groupTransform).gameObject, out var _, out var sourceLabel))
			{
				sourceLabel = GetSpreadGroupSourceLabel(GetMatchedVegetationKeyword(((Object)groupTransform).name, SpreadGroupKeywords));
			}
			bool any = false;
			for (int childIndex = 0; childIndex < groupTransform.childCount; childIndex++)
			{
				Transform child = groupTransform.GetChild(childIndex);
				if (!((Object)(object)child == (Object)null) && ((Component)child).gameObject.activeInHierarchy && IsAllowedAttachPath(child) && !ShouldSkipSpreadClumpHost(((Component)child).gameObject))
				{
					if (TryEmitDetectionCandidate(BuildSpreadCandidate(((Component)child).gameObject, ((Component)child).gameObject, sourceLabel), seenSourceRootIds, onCandidate))
					{
						any = true;
					}
					if (shouldYield != null && shouldYield())
					{
						yield return null;
					}
				}
			}
			if (!any)
			{
				TryEmitDetectionCandidate(BuildSpreadCandidate(((Component)groupTransform).gameObject, ((Component)groupTransform).gameObject, sourceLabel), seenSourceRootIds, onCandidate);
			}
		}

		private static VegetationDetectionCandidate BuildSpreadCandidate(GameObject detectionHost, GameObject sourceRoot, string sourceLabel)
		{
			return new VegetationDetectionCandidate
			{
				DetectionHost = detectionHost,
				SourceRoot = sourceRoot,
				SourceLabel = sourceLabel,
				MaxBoundsExtent = 1.25f,
				BoundsOnly = true
			};
		}

		private static bool TryBuildDetectionCandidate(GameObject hitObject, out VegetationDetectionCandidate candidate)
		{
			candidate = default(VegetationDetectionCandidate);
			if ((Object)(object)hitObject == (Object)null || !hitObject.activeInHierarchy || !IsAllowedAttachPath(hitObject.transform))
			{
				return false;
			}
			if ((Object)(object)hitObject.GetComponentInParent<PeakTickVegetationScanner>() != (Object)null)
			{
				return false;
			}
			if (!TryEvaluateWhitelistedGameObject(hitObject, out var sourceRoot, out var sourceLabel))
			{
				return false;
			}
			if (!IsBoundsOnlySourceLabel(sourceLabel))
			{
				candidate = new VegetationDetectionCandidate
				{
					DetectionHost = sourceRoot,
					SourceRoot = sourceRoot,
					SourceLabel = sourceLabel,
					MaxBoundsExtent = 2.5f,
					BoundsOnly = false
				};
				return true;
			}
			Transform val = FindAncestorSpreadGroup(hitObject.transform);
			if ((Object)(object)val != (Object)null)
			{
				Renderer component = hitObject.GetComponent<Renderer>();
				GameObject val2 = (((Object)(object)component != (Object)null) ? ResolveFoliageTagHost(component, ((Component)val).gameObject) : sourceRoot);
				if (!TryEvaluateWhitelistedGameObject(val2, out sourceRoot, out sourceLabel))
				{
					sourceRoot = val2;
					sourceLabel = GetSpreadGroupSourceLabel(GetMatchedVegetationKeyword(((Object)val).name, SpreadGroupKeywords));
				}
				candidate = BuildSpreadCandidate(val2, sourceRoot, sourceLabel);
				return true;
			}
			candidate = new VegetationDetectionCandidate
			{
				DetectionHost = sourceRoot,
				SourceRoot = sourceRoot,
				SourceLabel = sourceLabel,
				MaxBoundsExtent = 2.5f,
				BoundsOnly = IsBoundsOnlySourceLabel(sourceLabel)
			};
			return true;
		}

		private static bool IsBoundsOnlySourceLabel(string sourceLabel)
		{
			if (string.IsNullOrEmpty(sourceLabel))
			{
				return false;
			}
			if (!sourceLabel.StartsWith("Group:", StringComparison.Ordinal) && !sourceLabel.StartsWith("Grass:", StringComparison.Ordinal))
			{
				return sourceLabel.StartsWith("Keyword:", StringComparison.Ordinal);
			}
			return true;
		}

		private static bool TryEmitDetectionCandidate(VegetationDetectionCandidate candidate, HashSet<int> seenSourceRootIds, Action<VegetationDetectionCandidate> onCandidate)
		{
			if ((Object)(object)candidate.SourceRoot == (Object)null || !candidate.SourceRoot.activeInHierarchy || (Object)(object)candidate.DetectionHost == (Object)null || !candidate.DetectionHost.activeInHierarchy)
			{
				return false;
			}
			if (!seenSourceRootIds.Add(((Object)candidate.SourceRoot).GetInstanceID()))
			{
				return false;
			}
			onCandidate(candidate);
			return true;
		}

		private static bool TryEvaluateNamedGloomPropTouch(GameObject hitObject, out GameObject sourceRoot, out string sourceLabel)
		{
			sourceRoot = null;
			sourceLabel = null;
			if ((Object)(object)hitObject == (Object)null)
			{
				return false;
			}
			string vegetationHierarchyPath = GetVegetationHierarchyPath(hitObject.transform);
			if (vegetationHierarchyPath.IndexOf("/Swamp_Segment/", StringComparison.OrdinalIgnoreCase) < 0 && vegetationHierarchyPath.IndexOf("/Gloom/", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			for (int i = 0; i < GloomPropFolderNames.Length; i++)
			{
				string text = GloomPropFolderNames[i];
				string value = "/Props/" + text;
				if (vegetationHierarchyPath.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					Transform val = FindNamedPropFolderAncestor(hitObject.transform, text);
					if (!((Object)(object)val == (Object)null) && IsAllowedAttachPath(val))
					{
						sourceRoot = ((Component)val).gameObject;
						sourceLabel = GetGloomPropSourceLabel(text);
						return true;
					}
				}
			}
			return false;
		}

		private static Transform FindNamedPropFolderAncestor(Transform start, string folderName)
		{
			Transform val = start;
			while ((Object)(object)val != (Object)null && IsActiveVegetationHierarchyTransform(val))
			{
				if (MatchesGloomPropFolderName(((Object)val).name, folderName))
				{
					return val;
				}
				val = val.parent;
			}
			return null;
		}

		private static string GetGloomPropSourceLabel(string folderName)
		{
			if (string.IsNullOrEmpty(folderName))
			{
				return "Group:vegetation";
			}
			if (string.Equals(folderName, "VenusLeaves", StringComparison.OrdinalIgnoreCase))
			{
				return "Keyword:venusleaves";
			}
			if (string.Equals(folderName, "Vines", StringComparison.OrdinalIgnoreCase))
			{
				return "Group:vine";
			}
			return "Group:" + folderName.ToLowerInvariant();
		}

		public static string FormatRegistrationRootsForLog(Transform[] roots)
		{
			if (roots == null || roots.Length == 0)
			{
				return "(none)";
			}
			List<string> list = new List<string>(roots.Length);
			foreach (Transform val in roots)
			{
				if (!((Object)(object)val == (Object)null))
				{
					list.Add(((Object)val).name + "@" + GetVegetationHierarchyPathForLog(val));
				}
			}
			if (list.Count != 0)
			{
				return string.Join("; ", list);
			}
			return "(none)";
		}

		public static string GetVegetationHierarchyPathForLog(Transform transform)
		{
			return GetVegetationHierarchyPath(transform);
		}

		private static GameObject ResolveFoliageTagHost(Renderer renderer, GameObject sourceRoot)
		{
			if ((Object)(object)renderer == (Object)null)
			{
				return sourceRoot;
			}
			GameObject gameObject = ((Component)renderer).gameObject;
			if ((Object)(object)sourceRoot == (Object)null)
			{
				return gameObject;
			}
			if (!IsKeywordSpreadGroup(((Object)sourceRoot).name, out var _))
			{
				return gameObject;
			}
			Transform val = ((Component)renderer).transform;
			while ((Object)(object)val != (Object)null && (Object)(object)val.parent != (Object)null && (Object)(object)((Component)val.parent).gameObject != (Object)(object)sourceRoot)
			{
				val = val.parent;
			}
			if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)(object)sourceRoot)
			{
				return ((Component)val).gameObject;
			}
			return gameObject;
		}

		private static bool IsAllowedAttachPath(Transform transform)
		{
			if ((Object)(object)transform == (Object)null)
			{
				return false;
			}
			if (TickBiomeRules.IsPathInAllowedTickBiome(GetVegetationHierarchyPath(transform)))
			{
				return true;
			}
			return IsUnderActiveRegistrationRoots(transform);
		}

		private static bool IsUnderActiveRegistrationRoots(Transform transform)
		{
			if ((Object)(object)transform == (Object)null || !IsCurrentBiomeAllowed())
			{
				return false;
			}
			Transform[] activeRegistrationRoots = GetActiveRegistrationRoots();
			foreach (Transform val in activeRegistrationRoots)
			{
				if (!((Object)(object)val == (Object)null) && ((Object)(object)transform == (Object)(object)val || transform.IsChildOf(val)))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsKeywordSpreadGroup(string objectName, out string matchedKeyword)
		{
			matchedKeyword = null;
			if (string.IsNullOrEmpty(objectName))
			{
				return false;
			}
			if (objectName.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			if (IsGenericVegetationMeshName(objectName))
			{
				return false;
			}
			matchedKeyword = GetMatchedVegetationKeyword(objectName, SpreadGroupKeywords);
			return matchedKeyword != null;
		}

		public static bool TryEvaluateWhitelistedVegetation(Collider collider, out GameObject sourceRoot, out string sourceLabel)
		{
			sourceRoot = null;
			sourceLabel = null;
			if ((Object)(object)collider == (Object)null || !collider.enabled || (Object)(object)((Component)collider).gameObject == (Object)null)
			{
				return false;
			}
			Character val = default(Character);
			if (CharacterRagdoll.TryGetCharacterFromCollider(collider, ref val))
			{
				return false;
			}
			return TryEvaluateWhitelistedGameObject(ResolveVegetationHitObject(((Component)collider).gameObject), out sourceRoot, out sourceLabel);
		}

		public static bool TryEvaluateWhitelistedGameObject(GameObject hitObject, out GameObject sourceRoot, out string sourceLabel)
		{
			sourceRoot = null;
			sourceLabel = null;
			if ((Object)(object)hitObject == (Object)null)
			{
				return false;
			}
			if ((Object)(object)hitObject.GetComponentInParent<Bugfix>() != (Object)null)
			{
				return false;
			}
			if (IsHarvestedFoodPickup(hitObject))
			{
				return false;
			}
			if (TryEvaluateNamedGloomPropTouch(hitObject, out sourceRoot, out sourceLabel))
			{
				return true;
			}
			if (TryResolveTickTriggerHost(hitObject, out var host))
			{
				sourceRoot = host;
				sourceLabel = "TickTriggerHost";
				return true;
			}
			if (TryEvaluateGrassTouch(hitObject, out var sourceRoot2, out var sourceLabel2))
			{
				sourceRoot = sourceRoot2;
				sourceLabel = sourceLabel2;
				return true;
			}
			if (TryEvaluateCapybaraTouch(hitObject, out var sourceRoot3, out var sourceLabel3))
			{
				sourceRoot = sourceRoot3;
				sourceLabel = sourceLabel3;
				return true;
			}
			BerryBush componentInParent = hitObject.GetComponentInParent<BerryBush>();
			if ((Object)(object)componentInParent != (Object)null && QualifiesBerryBush(componentInParent))
			{
				sourceRoot = ((Component)componentInParent).gameObject;
				sourceLabel = "BerryBush:" + ((object)Unsafe.As<SpawnPool, SpawnPool>(ref ((Spawner)componentInParent).spawnPool)/*cast due to .constrained prefix*/).ToString();
				return true;
			}
			BerryVine componentInParent2 = hitObject.GetComponentInParent<BerryVine>();
			if ((Object)(object)componentInParent2 != (Object)null && !IsExcludedVegetationText(GetCombinedVegetationText(((Component)componentInParent2).gameObject)))
			{
				sourceRoot = ((Component)componentInParent2).gameObject;
				sourceLabel = "Vine";
				return true;
			}
			JungleVine componentInParent3 = hitObject.GetComponentInParent<JungleVine>();
			if ((Object)(object)componentInParent3 != (Object)null)
			{
				string combinedVegetationText = GetCombinedVegetationText(((Component)componentInParent3).gameObject);
				if (!IsExcludedVegetationText(combinedVegetationText) && combinedVegetationText.Contains("vine"))
				{
					sourceRoot = ((Component)componentInParent3).gameObject;
					sourceLabel = "Vine";
					return true;
				}
			}
			string combinedVegetationText2 = GetCombinedVegetationText(hitObject);
			if (!MatchesVegetationKeyword(combinedVegetationText2))
			{
				return false;
			}
			Transform val = ResolveVegetationTriggerRoot(hitObject.transform, combinedVegetationText2);
			sourceRoot = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : hitObject);
			sourceLabel = GetVegetationKeywordSourceLabel(combinedVegetationText2);
			Transform val2 = FindOutermostKeywordNamedAncestor(hitObject.transform);
			if ((Object)(object)val2 != (Object)null)
			{
				string matchedVegetationKeyword = GetMatchedVegetationKeyword(((Object)val2).name, NamedVegetationTriggerKeywords);
				if (!string.IsNullOrEmpty(matchedVegetationKeyword))
				{
					sourceRoot = ((Component)val2).gameObject;
					sourceLabel = "Group:" + matchedVegetationKeyword;
				}
			}
			if ((Object)(object)sourceRoot == (Object)null || !IsAllowedAttachPath(sourceRoot.transform))
			{
				sourceRoot = null;
				sourceLabel = null;
				return false;
			}
			return true;
		}

		private static GameObject ResolveVegetationHitObject(GameObject hitObject)
		{
			if ((Object)(object)hitObject == (Object)null)
			{
				return null;
			}
			if (((Object)hitObject).name == "PEAKTicks_VegetationScanner" || PeakTickVegetationLegacyNames.IsLegacyModObject(((Object)hitObject).name))
			{
				return null;
			}
			if (((Object)hitObject).name == "PEAKTicks_VegetationTrigger")
			{
				Transform parent = hitObject.transform.parent;
				if (!((Object)(object)parent != (Object)null))
				{
					return null;
				}
				return ((Component)parent).gameObject;
			}
			TickTrigger val = hitObject.GetComponent<TickTrigger>();
			if ((Object)(object)val == (Object)null)
			{
				val = hitObject.GetComponentInParent<TickTrigger>();
			}
			if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform.parent != (Object)null)
			{
				return ((Component)((Component)val).transform.parent).gameObject;
			}
			return hitObject;
		}

		private static bool TryResolveTickTriggerHost(GameObject hitObject, out GameObject host)
		{
			host = null;
			if ((Object)(object)hitObject == (Object)null)
			{
				return false;
			}
			TickTrigger val = hitObject.GetComponent<TickTrigger>();
			if ((Object)(object)val == (Object)null)
			{
				val = hitObject.GetComponentInParent<TickTrigger>();
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			host = ResolveTickTriggerHost(val);
			if ((Object)(object)host == (Object)null || !IsAllowedAttachPath(host.transform))
			{
				host = null;
				return false;
			}
			return true;
		}

		internal static GameObject ResolveTickTriggerHost(TickTrigger trigger)
		{
			if ((Object)(object)trigger == (Object)null)
			{
				return null;
			}
			if ((Object)(object)((Component)trigger).transform.parent != (Object)null)
			{
				return ((Component)((Component)trigger).transform.parent).gameObject;
			}
			return ((Component)trigger).gameObject;
		}

		public static bool TryApplyVegetationBurstAttach(Character character, string sourceLabel, GameObject sourceObject = null)
		{
			if (!Enabled || (Object)(object)character == (Object)null || !character.IsLocal || (Object)(object)character.data == (Object)null || character.data.dead)
			{
				return false;
			}
			if (!TickModAuthorizationService.IsLocalVegetationActive)
			{
				return false;
			}
			if (!IsCurrentBiomeAllowed() || !CanStartVegetationBurst(character))
			{
				LogBurstSkipped(sourceLabel, (!IsCurrentBiomeAllowed()) ? "biome blocked" : "touch cooldown");
				return false;
			}
			if (!TickAttachService.CanAttachAnotherTick(character) || !RollAttachChance())
			{
				LogBurstSkipped(sourceLabel, (!TickAttachService.CanAttachAnotherTick(character)) ? "max ticks" : "chance roll");
				return false;
			}
			int vegetationBurstId = ++_vegetationBurstCounter;
			int num = TickAttachService.CountTicksOnCharacter(character);
			bool isTickBomb;
			int num2 = RollBurstTickCount(out isTickBomb);
			string attachSource = (isTickBomb ? (sourceLabel + " | tick bomb") : sourceLabel);
			string attachDetail = TickAttachService.FormatSourceObjectDetail(sourceObject);
			int num3 = 0;
			for (int i = 0; i < num2; i++)
			{
				if (!TickAttachService.CanAttachAnotherTick(character))
				{
					break;
				}
				if (!TickAttachService.TrySpawnAttachedTick(character, attachSource, attachDetail, out var _, vegetationBurstId))
				{
					LogBurstSkipped(sourceLabel, "spawn failed");
					break;
				}
				num3++;
			}
			if (num3 > 0)
			{
				MarkVegetationBurst(character);
			}
			else if (TickAttachService.CountTicksOnCharacter(character) > num)
			{
				MarkVegetationBurst(character);
			}
			if (num3 <= 0)
			{
				return TickAttachService.CountTicksOnCharacter(character) > num;
			}
			return true;
		}

		private static void LogBurstSkipped(string sourceLabel, string reason)
		{
			if (!string.IsNullOrEmpty(reason) && (!(_lastBurstSkipReason == reason) || !(Time.time < _nextBurstSkipLogTime)))
			{
				_lastBurstSkipReason = reason;
				_nextBurstSkipLogTime = Time.time + 8f;
				ManualLogSource log = PEAKTicksPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[PEAK Ticks] Vegetation attach skipped (" + reason + ") from " + (sourceLabel ?? "unknown") + "."));
				}
			}
		}

		public static void NotifyVegetationAttachSucceeded(Character character, int burstId)
		{
			if (!((Object)(object)character == (Object)null) && burstId > 0 && burstId != _lastCooldownAppliedBurstId)
			{
				_lastCooldownAppliedBurstId = burstId;
				MarkVegetationBurst(character);
			}
		}

		private static bool CanStartVegetationBurst(Character character)
		{
			if (TouchCooldownSeconds <= 0f)
			{
				return true;
			}
			if (((Object)character).GetInstanceID() != _burstCharacterId)
			{
				return true;
			}
			return Time.time >= _nextBurstAllowedTime;
		}

		public static float GetVegetationBurstCooldownRemaining(Character character)
		{
			if (TouchCooldownSeconds <= 0f || (Object)(object)character == (Object)null)
			{
				return 0f;
			}
			if (((Object)character).GetInstanceID() != _burstCharacterId)
			{
				return 0f;
			}
			return Mathf.Max(0f, _nextBurstAllowedTime - Time.time);
		}

		private static void MarkVegetationBurst(Character character)
		{
			_burstCharacterId = ((Object)character).GetInstanceID();
			_nextBurstAllowedTime = Time.time + TouchCooldownSeconds;
		}

		private static string GetCurrentSegmentName()
		{
			//IL_001a: 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)
			try
			{
				if ((Object)(object)Singleton<MapHandler>.Instance == (Object)null)
				{
					return "Unknown";
				}
				return ((object)Singleton<MapHandler>.Instance.GetCurrentSegment()/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
				return "Unknown";
			}
		}

		private static string GetCurrentBiomeName()
		{
			//IL_001a: 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)
			try
			{
				if ((Object)(object)Singleton<MapHandler>.Instance == (Object)null)
				{
					return "Unknown";
				}
				return ((object)Singleton<MapHandler>.Instance.GetCurrentBiome()/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
				return "Unknown";
			}
		}

		private static bool QualifiesKeywordNamedRoot(Transform transform)
		{
			if ((Object)(object)transform == (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)transform).GetComponent<BerryBush>() != (Object)null)
			{
				return false;
			}
			string name = ((Object)transform).name;
			if (name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			if (IsGenericVegetationMeshName(name))
			{
				return false;
			}
			return GetMatchedVegetationKeyword(name, NamedVegetationTriggerKeywords) != null;
		}

		private static bool IsGenericVegetationMeshName(string objectName)
		{
			if (string.IsNullOrEmpty(objectName))
			{
				return true;
			}
			string text = objectName.ToLowerInvariant();
			if (text == "mesh" || text.StartsWith("mesh ", StringComparison.Ordinal) || text.StartsWith("mesh(", StringComparison.Ordinal) || text.Contains("autolod") || text.Contains("_lod_") || text.Contains("lod_") || text.StartsWith("collider", StringComparison.Ordinal) || text == "peakticks_vegetationtrigger")
			{
				return true;
			}
			return false;
		}

		private static Transform FindOutermostKeywordNamedAncestor(Transform start)
		{
			if ((Object)(object)start == (Object)null)
			{
				return null;
			}
			Transform result = null;
			Transform val = start;
			while ((Object)(object)val != (Object)null && IsActiveVegetationHierarchyTransform(val))
			{
				if (QualifiesKeywordNamedRoot(val))
				{
					result = val;
				}
				val = val.parent;
			}
			return result;
		}

		private static Transform FindOutermostPathKeywordAncestor(Transform start, string combinedText)
		{
			if ((Object)(object)start == (Object)null)
			{
				return null;
			}
			string matchedVegetationKeyword = GetMatchedVegetationKeyword(combinedText, VegetationAttachKeywords);
			if (matchedVegetationKeyword == null)
			{
				return null;
			}
			Transform result = null;
			Transform val = start;
			while ((Object)(object)val != (Object)null && IsActiveVegetationHierarchyTransform(val))
			{
				if ((Object)(object)((Component)val).GetComponent<BerryBush>() != (Object)null)
				{
					val = val.parent;
					continue;
				}
				if (GetMatchedVegetationKeyword(GetVegetationHierarchyPath(val), VegetationAttachKeywords) == matchedVegetationKeyword)
				{
					result = val;
				}
				val = val.parent;
			}
			return result;
		}

		private static Transform ResolveVegetationTriggerRoot(Transform colliderTransform, string combinedText)
		{
			Transform val = FindOutermostKeywordNamedAncestor(colliderTransform);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			return FindOutermostPathKeywordAncestor(colliderTransform, combinedText);
		}

		private static bool IsActiveVegetationHierarchyTransform(Transform transform)
		{
			if ((Object)(object)transform != (Object)null)
			{
				return ((Component)transform).gameObject.activeInHierarchy;
			}
			return false;
		}

		private static string GetMatchedVegetationKeyword(string text, string[] keywords)
		{
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			string text2 = text.ToLowerInvariant();
			for (int i = 0; i < keywords.Length; i++)
			{
				if (text2.Contains(keywords[i]))
				{
					return keywords[i];
				}
			}
			return null;
		}

		private static string GetCombinedVegetationText(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return string.Empty;
			}
			return (((Object)gameObject).name + " " + GetVegetationHierarchyPath(gameObject.transform)).ToLowerInvariant();
		}

		private static bool IsExcludedVegetationText(string combined)
		{
			if (string.IsNullOrEmpty(combined))
			{
				return true;
			}
			if (IsExcludedAttachPath(combined))
			{
				return true;
			}
			if (!combined.Contains("luggage") && !combined.Contains("spawner") && !combined.Contains("/trees/") && !combined.Contains("treecrown") && !combined.Contains("palmtree") && !combined.Contains("palmmesh") && !combined.Contains("mushroom tree") && !combined.Contains("mushroom cluster") && !combined.Contains("jungle_thorn") && !combined.Contains("junglethorn") && !combined.Contains("peakticks_vegetationtrigger"))
			{
				return combined.Contains("ticktrigger");
			}
			return true;
		}

		private static bool MatchesVegetationKeyword(string combined)
		{
			if (IsExcludedVegetationText(combined))
			{
				return false;
			}
			return GetMatchedVegetationKeyword(combined, VegetationAttachKeywords) != null;
		}

		private static string GetVegetationKeywordSourceLabel(string combined)
		{
			string matchedVegetationKeyword = GetMatchedVegetationKeyword(combined, VegetationAttachKeywords);
			if (string.IsNullOrEmpty(matchedVegetationKeyword))
			{
				return "Keyword:vegetation";
			}
			return "Keyword:" + matchedVegetationKeyword;
		}

		private static bool TryEvaluateCapybaraTouch(GameObject hitObject, out GameObject sourceRoot, out string sourceLabel)
		{
			sourceRoot = null;
			sourceLabel = null;
			if ((Object)(object)hitObject == (Object)null)
			{
				return false;
			}
			string vegetationHierarchyPath = GetVegetationHierarchyPath(hitObject.transform);
			if (vegetationHierarchyPath.IndexOf("/Biome_3/", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			if (vegetationHierarchyPath.IndexOf("/Alpine/", StringComparison.OrdinalIgnoreCase) < 0 && vegetationHierarchyPath.IndexOf("/Mesa/", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			Transform val = null;
			Transform val2 = hitObject.transform;
			while ((Object)(object)val2 != (Object)null && IsActiveVegetationHierarchyTransform(val2))
			{
				if (((Object)val2).name.IndexOf("capybara", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					val = val2;
				}
				val2 = val2.parent;
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			sourceRoot = ((Component)val).gameObject;
			sourceLabel = "Capybara";
			return true;
		}

		private static bool TryEvaluateGrassTouch(GameObject hitObject, out GameObject sourceRoot, out string sourceLabel)
		{
			sourceRoot = null;
			sourceLabel = null;
			if ((Object)(object)hitObject == (Object)null)
			{
				return false;
			}
			Transform val = FindAncestorSpreadGroup(hitObject.transform);
			if ((Object)(object)val != (Object)null && ((Object)val).name.IndexOf("beachgrass", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				sourceRoot = ((Component)val).gameObject;
				sourceLabel = "Grass:beachgrass";
				return true;
			}
			if (TryEvaluateShoreSegmentGrassProp(hitObject, out sourceRoot, out sourceLabel))
			{
				return true;
			}
			string combinedVegetationText = GetCombinedVegetationText(hitObject);
			if (GetMatchedVegetationKeyword(combinedVegetationText, new string[1] { "beachgrass" }) == null || IsExcludedVegetationText(combinedVegetationText))
			{
				return false;
			}
			Transform val2 = ResolveVegetationTriggerRoot(hitObject.transform, combinedVegetationText);
			sourceRoot = (((Object)(object)val2 != (Object)null) ? ((Component)val2).gameObject : hitObject);
			sourceLabel = "Grass:beachgrass";
			return true;
		}

		private static bool TryEvaluateShoreSegmentGrassProp(GameObject hitObject, out GameObject sourceRoot, out string sourceLabel)
		{
			sourceRoot = null;
			sourceLabel = null;
			if ((Object)(object)hitObject == (Object)null)
			{
				return false;
			}
			string vegetationHierarchyPath = GetVegetationHierarchyPath(hitObject.transform);
			if (vegetationHierarchyPath.IndexOf("/Beach_Segment/", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			if (vegetationHierarchyPath.IndexOf("/WallProps/", StringComparison.OrdinalIgnoreCase) < 0 && vegetationHierarchyPath.IndexOf("/PlateauProps/", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			string combinedVegetationText = GetCombinedVegetationText(hitObject);
			if (GetMatchedVegetationKeyword(combinedVegetationText, new string[2] { "grass", "weed" }) == null || IsExcludedVegetationText(combinedVegetationText))
			{
				return false;
			}
			if (combinedVegetationText.Contains("palmtree") || combinedVegetationText.Contains("palmmesh") || combinedVegetationText.Contains("berrybush"))
			{
				return false;
			}
			Transform val = ResolveVegetationTriggerRoot(hitObject.transform, combinedVegetationText);
			sourceRoot = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : hitObject);
			sourceLabel = "Grass:shore";
			return true;
		}

		private static Transform FindAncestorSpreadGroup(Transform start)
		{
			Transform val = start;
			while ((Object)(object)val != (Object)null && IsActiveVegetationHierarchyTransform(val))
			{
				if (IsKeywordSpreadGroup(((Object)val).name, out var _))
				{
					return val;
				}
				val = val.parent;
			}
			return null;
		}

		private static string GetSpreadGroupSourceLabel(string matchedKeyword)
		{
			if (string.IsNullOrEmpty(matchedKeyword))
			{
				return "Group:vegetation";
			}
			return matchedKeyword switch
			{
				"beachgrass" => "Grass:beachgrass", 
				"fern" => "Group:fern", 
				"driftwood" => "Group:driftwood", 
				"vine" => "Group:vine", 
				"bushes" => "Group:bushes", 
				_ => "Group:" + matchedKeyword, 
			};
		}

		private static bool IsSpreadVegetationContainer(Transform transform)
		{
			if ((Object)(object)transform == (Object)null || transform.childCount == 0)
			{
				return false;
			}
			return ((Object)transform).name.ToLowerInvariant().Contains("beachgrass");
		}

		private static bool IsGrassSystemChunk(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return false;
			}
			if ((Object)(object)gameObject.GetComponent<GrassChunk>() != (Object)null)
			{
				return true;
			}
			return GetVegetationHierarchyPath(gameObject.transform).IndexOf("/GrassSystem/", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static bool HostNeedsFoliageAssist(GameObject host)
		{
			if ((Object)(object)host == (Object)null || !host.activeInHierarchy)
			{
				return false;
			}
			if (IsGrassSystemChunk(host))
			{
				return false;
			}
			Collider[] componentsInChildren = host.GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && val.enabled && !(((Object)((Component)val).gameObject).name == "PEAKTicks_VegetationTrigger") && !((Object)(object)((Component)val).GetComponentInParent<PeakTickVegetationScanner>() != (Object)null) && !val.isTrigger)
				{
					return false;
				}
			}
			return true;
		}

		private static bool BoundsQualifiesForTag(Bounds bounds)
		{
			//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)
			Vector3 size = ((Bounds)(ref bounds)).size;
			return ((Vector3)(ref size)).sqrMagnitude > 0.0001f;
		}

		private static Bounds ClampTagBounds(Bounds bounds, float maxExtent)
		{
			//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_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: 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_0039: 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_0045: Unknown result type (might be due to invalid IL or missing references)
			Vector3 extents = ((Bounds)(ref bounds)).extents;
			float num = Mathf.Max(extents.x, Mathf.Max(extents.y, extents.z));
			if (num <= maxExtent || num <= 0.0001f)
			{
				return bounds;
			}
			float num2 = maxExtent / num;
			((Bounds)(ref bounds)).extents = extents * num2;
			return bounds;
		}

		private static bool TryComputeTagBounds(GameObject tagHost, float maxExtent, out Bounds bounds)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			bounds = default(Bounds);
			if ((Object)(object)tagHost == (Object)null)
			{
				return false;
			}
			Renderer[] componentsInChildren = tagHost.GetComponentsInChildren<Renderer>(true);
			bool flag = false;
			foreach (Renderer val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					if (!flag)
					{
						bounds = val.bounds;
						flag = true;
					}
					else
					{
						((Bounds)(ref bounds)).Encapsulate(val.bounds);
					}
				}
			}
			if (!flag)
			{
				return false;
			}
			bounds = ClampTagBounds(bounds, maxExtent);
			return BoundsQualifiesForTag(bounds);
		}

		private static bool QualifiesBerryBush(BerryBush bush)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Invalid comparison between Unknown and I4
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)bush == (Object)null)
			{
				return false;
			}
			string vegetationHierarchyPath = GetVegetationHierarchyPath(((Component)bush).transform);
			if (IsExcludedAttachPath(vegetationHierarchyPath) || (int)((Spawner)bush).spawnPool == 33554432)
			{
				return false;
			}
			if (IsAllowedAttachSpawnPool(((Spawner)bush).spawnPool))
			{
				return true;
			}
			string name = ((Object)((Component)bush).gameObject).name;
			if (vegetationHierarchyPath.IndexOf("bushes", StringComparison.OrdinalIgnoreCase) < 0 && GetMatchedVegetationKeyword(name, new string[1] { "berrybush" }) == null)
			{
				return GetMatchedVegetationKeyword(vegetationHierarchyPath, new string[1] { "berrybush" }) != null;
			}
			return true;
		}

		private static bool IsHarvestedFoodPickup(GameObject gameObject)
		{
			string vegetationHierarchyPath = GetVegetationHierarchyPath(gameObject.transform);
			if (vegetationHierarchyPath.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			string text = vegetationHierarchyPath.ToLowerInvariant();
			if (text.Contains("apple berry") || text.Contains("pepper berry") || text.Contains("bananna berry") || text.Contains("berrynana") || text.Contains("cluster berry") || text.Contains("clusterberry") || text.Contains("pine berry") || text.Contains("kingberry") || text.Contains("raspberry") || text.Contains("shroomberry"))
			{
				return true;
			}
			if (text.Contains("mushroom chubby") || text.Contains("mushroom normie"))
			{
				return true;
			}
			if (text.Contains("mushroom cluster"))
			{
				return !text.Contains("mushroom tree");
			}
			return false;
		}

		private static bool IsExcludedAttachPath(string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				return true;
			}
			if (path.IndexOf("/Mallows/", StringComparison.OrdinalIgnoreCase) < 0 && path.IndexOf("CampfireFood", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return path.IndexOf("/crashed plane/", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static bool IsAllowedAttachSpawnPool(SpawnPool pool)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between I4 and Unknown
			for (int i = 0; i < AllowedAttachSpawnPools.Length; i++)
			{
				if ((int)AllowedAttachSpawnPools[i] == (int)pool)
				{
					return true;
				}
			}
			return false;
		}

		private static void ClearModProbesInSegment()
		{
			Transform[] activeRegistrationRoots = GetActiveRegistrationRoots();
			foreach (Transform val in activeRegistrationRoots)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Transform[] componentsInChildren = ((Component)val).GetComponentsInChildren<Transform>(true);
				foreach (Transform val2 in componentsInChildren)
				{
					if ((Object)(object)val2 != (Object)null && ((Object)val2).name == "PEAKTicks_VegetationTrigger")
					{
						Object.Destroy((Object)(object)((Component)val2).gameObject);
					}
				}
			}
		}

		private static string GetVegetationHierarchyPath(Transform transform)
		{
			if ((Object)(object)transform == (Object)null)
			{
				return string.Empty;
			}
			List<string> list = new List<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null)
			{
				list.Add(((Object)val).name);
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list);
		}
	}
	internal static class TickVegetationProbeService
	{
		private sealed class SegmentVegetationCache
		{
			public List<VegetationTouchTarget> TouchTargets = new List<VegetationTouchTarget>();

			public int ProbeCount;

			public int SpreadBoundsTargets;
		}

		private struct VegetationTouchTarget
		{
			public GameObject SourceRoot;

			public string SourceLabel;

			public Bounds WorldBounds;
		}

		public const string ProbeObjectName = "PEAKTicks_VegetationTrigger";

		private const int DiscoveryWorkBudgetPerFrame = 120;

		private const int ProbesInstalledPerFrame = 25;

		private const float MinProbeRadius = 0.12f;

		private const float MaxProbeRadius = 1.25f;

		private const float MaxBoundsAttachGap = 0.35f;

		private const float MaxSpreadProbeBoundsExtent = 1.25f;

		private static int _segmentKey = int.MinValue;

		private static int _probeCount;

		private static int _touchTargetCount;

		private static bool _setupCompleted;

		private static int _spreadBoundsTargets;

		private static readonly List<VegetationTouchTarget> TouchTargets = new List<VegetationTouchTarget>(4096);

		private static readonly Dictionary<int, SegmentVegetationCache> SegmentCaches = new Dictionary<int, SegmentVegetationCache>();

		public static int ProbeCount => _probeCount;

		public static int TouchTargetCount => _touchTargetCount;

		public static int SpreadBoundsTargetCount => _spreadBoundsTargets;

		public static bool IsReadyForCurrentSegment()
		{
			if (_segmentKey == GetVegetationCacheKey())
			{
				return _setupCompleted;
			}
			return false;
		}

		public static void Clear()
		{
			_segmentKey = int.MinValue;
			_probeCount = 0;
			_touchTargetCount = 0;
			_spreadBoundsTargets = 0;
			_setupCompleted = false;
			TouchTargets.Clear();
			SegmentCaches.Clear();
		}

		public static void PrepareForSegmentChange(int segmentKey)
		{
			_segmentKey = segmentKey;
			_probeCount = 0;
			_touchTargetCount = 0;
			_spreadBoundsTargets = 0;
			_setupCompleted = false;
			TouchTargets.Clear();
		}

		private static bool TryRestoreSegmentCache(int segmentKey)
		{
			if (!SegmentCaches.TryGetValue(segmentKey, out var value) || value.TouchTargets.Count == 0)
			{
				return false;
			}
			TouchTargets.Clear();
			int num = 0;
			for (int i = 0; i < value.TouchTargets.Count; i++)
			{
				VegetationTouchTarget item = value.TouchTargets[i];
				if (!((Object)(object)item.SourceRoot == (Object)null) && item.SourceRoot.activeInHierarchy)
				{
					TouchTargets.Add(item);
					num++;
				}
			}
			if (num == 0)
			{
				TouchTargets.Clear();
				SegmentCaches.Remove(segmentKey);
				return false;
			}
			_probeCount = value.ProbeCount;
			_touchTargetCount = TouchTargets.Count;
			_spreadBoundsTargets = value.SpreadBoundsTargets;
			_segmentKey = segmentKey;
			_setupCompleted = true;
			return true;
		}

		private static void SaveSegmentCache(int segmentKey)
		{
			SegmentVegetationCache segmentVegetationCache = new SegmentVegetationCache();
			segmentVegetationCache.TouchTargets.AddRange(TouchTargets);
			segmentVegetationCache.ProbeCount = _probeCount;
			segmentVegetationCache.SpreadBoundsTargets = _spreadBoundsTargets;
			SegmentCaches[segmentKey] = segmentVegetationCache;
		}

		public static IEnumerator SetupProbesForCurrentSegmentAsync(bool incrementalOnly = false)
		{
			int segmentKey = GetVegetationCacheKey();
			bool flag = IsSwampVegetationRegionKey(segmentKey);
			if (incrementalOnly && (!_setupCompleted || _segmentKey != segmentKey) && (!flag || _segmentKey != segmentKey))
			{
				incrementalOnly = false;
			}
			if (!TickVegetationAttachService.Enabled || !PhotonNetwork.InRoom)
			{
				yield break;
			}
			if (!TickVegetationAttachService.IsCurrentBiomeAllowed())
			{
				TickVegetationAttachService.LogMapHandlerStateForDiagnostics("probe registration skipped", force: true);
				yield break;
			}
			if (!incrementalOnly && TryRestoreSegmentCache(segmentKey))
			{
				ManualLogSource log = PEAKTicksPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[PEAK Ticks] Vegetation touch targets restored from cache: " + _touchTargetCount + " targets, " + _probeCount + " probes."));
				}
				yield break;
			}
			Transform[] roots = TickVegetationAttachService.GetActiveRegistrationRoots();
			if (roots.Length == 0)
			{
				ManualLogSource log2 = PEAKTicksPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)"[PEAK Ticks] Vegetation probe setup deferred (segment roots not ready).");
				}
				yield break;
			}
			if (!incrementalOnly)
			{
				_probeCount = 0;
				_touchTargetCount = 0;
				_spreadBoundsTargets = 0;
				_setupCompleted = false;
				TouchTargets.Clear();
			}
			HashSet<int> hashSet = new HashSet<int>();
			if (incrementalOnly)
			{
				for (int i = 0; i < TouchTargets.Count; i++)
				{
					GameObject sourceRoot = TouchTargets[i].SourceRoot;
					if ((Object)(object)sourceRoot != (Object)null)
					{
						hashSet.Add(((Object)sourceRoot).GetInstanceID());
					}
				}
			}
			int installed = (incrementalOnly ? _probeCount : 0);
			int registered = (incrementalOnly ? _touchTargetCount : 0);
			int spreadBoundsOnly = (incrementalOnly ? _spreadBoundsTargets : 0);
			int installedThisPass = 0;
			int registeredThisPass = 0;
			int spreadBoundsThisPass = 0;
			int discoveryWorkThisFrame = 0;
			IEnumerator discovery = ((!TickVegetationAttachService.IsSwampBiomeSegment()) ? TickVegetationAttachService.EnumerateVegetationDiscoveryAsync(roots, delegate(TickVegetationAttachService.VegetationDetectionCandidate candidate)
			{
				if (TryRegisterLogicalPlant(candidate))
				{
					registeredThisPass++;
					registered++;
					if (candidate.BoundsOnly)
					{
						spreadBoundsThisPass++;
						spreadBoundsOnly++;
					}
				}
				if (!candidate.BoundsOnly && TryInstallProbe(candidate.DetectionHost, candidate.MaxBoundsExtent))
				{
					installedThisPass++;
					installed++;
				}
			}, hashSet, delegate
			{
				discoveryWorkThisFrame++;
				if (discoveryWorkThisFrame >= 120)
				{
					discoveryWorkThisFrame = 0;
					return true;
				}
				return (installedThisPass + registeredThisPass) % 25 == 0;
			}, incrementalOnly) : TickVegetationAttachService.EnumerateSwampGloomPropDiscoveryAsync(roots, delegate(TickVegetationAttachService.VegetationDetectionCandidate candidate)
			{
				if (TryRegisterLogicalPlant(candidate))
				{
					registeredThisPass++;
					registered++;
					if (candidate.BoundsOnly)
					{
						spreadBoundsThisPass++;
						spreadBoundsOnly++;
					}
				}
			}, hashSet, delegate
			{
				discoveryWorkThisFrame++;
				if (discoveryWorkThisFrame >= 120)
				{
					discoveryWorkThisFrame = 0;
					return true;
				}
				return registeredThisPass % 25 == 0;
			}));
			while (discovery.MoveNext())
			{
				yield return discovery.Current;
			}
			_probeCount = installed;
			_touchTargetCount = registered;
			_spreadBoundsTargets = spreadBoundsOnly;
			_segmentKey = segmentKey;
			_setupCompleted = registered > 0 || installed > 0;
			if (_setupCompleted)
			{
				SaveSegmentCache(segmentKey);
			}
			else if (!incrementalOnly)
			{
				SegmentCaches.Remove(segmentKey);
			}
			if (registeredThisPass == 0 && installedThisPass == 0)
			{
				string text = (incrementalOnly ? "incremental" : "full");
				ManualLogSource log3 = PEAKTicksPlugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[PEAK Ticks] Vegetation touch setup empty (" + text + "; biome=" + TickVegetationAttachService.GetCurrentBiomeNameForLog() + "; roots=" + TickVegetationAttachService.FormatRegistrationRootsForLog(roots) + ")."));
				}
			}
			else if (registeredThisPass > 0 || installedThisPass > 0)
			{
				string text2 = (incrementalOnly ? "incremental" : "full");
				ManualLogSource log4 = PEAKTicksPlugin.Log;
				if (log4 != null)
				{
					log4.LogInfo((object)("[PEAK Ticks] Vegetation touch targets: " + registered + ", probes installed: " + installed + " (" + text2 + "; biome=" + TickVegetationAttachService.GetCurrentBiomeNameForLog() + "; regionKey=" + segmentKey + "; roots=" + TickVegetationAttachService.FormatRegistrationRootsForLog(roots) + "; new targets=" + registeredThisPass + ", new probes=" + installedThisPass + ", spread bounds-only=" + spreadBoundsOnly + ")."));
				}
			}
		}

		private static bool TryRegisterLogicalPlant(TickVegetationAttachService.VegetationDetectionCandidate candidate)
		{
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)candidate.SourceRoot == (Object)null || !candidate.SourceRoot.activeInHierarchy || (Object)(object)candidate.DetectionHost == (Object)null || !candidate.DetectionHost.activeInHierarchy)
			{
				return false;
			}
			if ((Object)(object)candidate.SourceRoot.GetComponentInParent<PeakTickVegetationScanner>() != (Object)null)
			{
				return false;
			}
			if (TickVegetationAttachService.IsBerryBushTouchTarget(candidate.SourceRoot, candidate.SourceLabel))
			{
				return false;
			}
			if (!candidate.BoundsOnly && !TickVegetationAttachService.HostNeedsFoliageProbe(candidate.DetectionHost))
			{
				return false;
			}
			if (!TickVegetationAttachService.TryComputeFoliageBounds(candidate.DetectionHost, candidate.MaxBoundsExtent, out var bounds))
			{
				return false;
			}
			TouchTargets.Add(new VegetationTouchTarget
			{
				SourceRoot = candidate.SourceRoot,
				SourceLabel = candidate.SourceLabel,
				WorldBounds = bounds
			});
			return true;
		}

		public static bool TryFindTouchTargetInDetectorBox(Transform boxTransform, Vector3 localCenter, Vector3 localHalfExtents, out GameObject sourceRoot, out string sourceLabel)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			sourceRoot = null;
			sourceLabel = null;
			if ((Object)(object)boxTransform == (Object)null || TouchTargets.Count == 0)
			{
				return false;
			}
			Vector3 val = boxTransform.TransformPoint(localCenter);
			float num = ((Vector3)(ref localHalfExtents)).magnitude + 0.35f;
			float num2 = float.MaxValue;
			VegetationTouchTarget vegetationTouchTarget = default(VegetationTouchTarget);
			bool flag = false;
			for (int i = 0; i < TouchTargets.Count; i++)
			{
				VegetationTouchTarget vegetationTouchTarget2 = TouchTargets[i];
				if ((Object)(object)vegetationTouchTarget2.SourceRoot == (Object)null || !vegetationTouchTarget2.SourceRoot.activeInHierarchy || TickVegetationAttachService.IsBerryBushTouchTarget(vegetationTouchTarget2.SourceRoot, vegetationTouchTarget2.SourceLabel))
				{
					continue;
				}
				Vector3 val2 = ((Bounds)(ref vegetationTouchTarget2.WorldBounds)).extents;
				float num3 = num + ((Vector3)(ref val2)).magnitude;
				val2 = ((Bounds)(ref vegetationTouchTarget2.WorldBounds)).center - val;
				if (!(((Vector3)(ref val2)).sqrMagnitude > num3 * num3) && BoundsIntersectDetectorBox(vegetationTouchTarget2.WorldBounds, boxTransform, localCenter, localHalfExtents))
				{
					float num4 = Vector3.Distance(val, ((Bounds)(ref vegetationTouchTarget2.WorldBounds)).ClosestPoint(val));
					if (!(num4 >= num2))
					{
						num2 = num4;
						vegetationTouchTarget = vegetationTouchTarget2;
						flag = true;
					}
				}
			}
			if (!flag)
			{
				return false;
			}
			sourceRoot = vegetationTouchTarget.SourceRoot;
			sourceLabel = vegetationTouchTarget.SourceLabel;
			return true;
		}

		private static bool BoundsIntersectDetectorBox(Bounds worldBounds, Transform boxTransform, Vector3 localCenter, Vector3 localHalfExtents)
		{
			//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_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: 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)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			Vector3 center = ((Bounds)(ref worldBounds)).center;
			Vector3 extents = ((Bounds)(ref worldBounds)).extents;
			for (int i = -1; i <= 1; i += 2)
			{
				for (int j = -1; j <= 1; j += 2)
				{
					for (int k = -1; k <= 1; k += 2)
					{
						if (IsWorldPointInsideDetectorBox(center + Vector3.Scale(extents, new Vector3((float)i, (float)j, (float)k)), boxTransform, localCenter, localHalfExtents))
						{
							return true;
						}
					}
				}
			}
			Vector3 val = boxTransform.TransformPoint(localCenter);
			return ((Bounds)(ref worldBounds)).Contains(val);
		}

		private static bool IsWorldPointInsideDetectorBox(Vector3 worldPoint, Transform boxTransform, Vector3 localCenter, Vector3 localHalfExtents)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0034: 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)
			Vector3 val = boxTransform.InverseTransformPoint(worldPoint) - localCenter;
			if (Mathf.Abs(val.x) <= localHalfExtents.x && Mathf.Abs(val.y) <= localHalfExtents.y)
			{
				return Mathf.Abs(val.z) <= localHalfExtents.z;
			}
			return false;
		}

		private static bool TryInstallProbe(GameObject host, float maxBoundsExtent)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: U