Decompiled source of Forest Wave by vladosso v3.1.0

plugins/ForestWave/ForestWave.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.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("vladosso")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 vladosso")]
[assembly: AssemblyFileVersion("3.1.0.0")]
[assembly: AssemblyInformationalVersion("3.1.0")]
[assembly: AssemblyProduct("Forest Wave by vladosso")]
[assembly: AssemblyTitle("ForestWave")]
[assembly: AssemblyVersion("3.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ForestWave
{
	public static class Localizer
	{
		public const string Author = "vladosso";

		public const string Product = "Forest Wave by vladosso";

		public static readonly IReadOnlyDictionary<string, string> English = new Dictionary<string, string>
		{
			["ВЫКЛ"] = "OFF",
			["ГОТОВ"] = "ARMED",
			["СТОП"] = "STOPPED",
			["ВОЛНА"] = "FELLING",
			["ПАУЗА ФИЗИКИ"] = "PHYSICS PAUSE",
			["БРЁВНА → ДРЕВЕСИНА"] = "PROCESSING LOGS",
			["СБОР"] = "COLLECTING",
			["ГОТОВО"] = "DONE",
			["аварийная остановка"] = "emergency stop",
			["режим выключен"] = "mode disabled",
			["конфигурация обновлена"] = "configuration reloaded",
			["смерть, телепорт или смена сессии"] = "death, teleport or session change",
			["остановлено"] = "stopped",
			["ошибка; см. LogOutput.log"] = "error; see LogOutput.log",
			["перегрузка: уменьшите масштаб"] = "overloaded: choose a smaller preset",
			["плотная область: уменьшите радиус"] = "dense area: reduce radius",
			["оставшиеся брёвна можно дорубить вручную"] = "remaining logs can be cut manually",
			["волна завершена"] = "wave completed",
			["выгрузка"] = "plugin unloading",
			["ТЕСТ: следующий лимит {0}"] = "SMALL: next limit {0}",
			["СРЕДНИЙ: следующий лимит {0}"] = "MEDIUM: next limit {0}",
			["БОЛЬШОЙ: следующий лимит {0}"] = "LARGE: next limit {0}",
			["Срублено: {0} / {1}   Брёвна: {2}"] = "Felled: {0} / {1}   Logs: {2}",
			["{0}: режим  {1}: СТОП  {2}: масштаб"] = "{0}: toggle  {1}: STOP  {2}: preset",
			["{0}: язык RU / EN"] = "{0}: language RU / EN",
			["Радиус соседства, метры."] = "Distance between neighbouring trees, metres.",
			["Пауза между деревьями, секунды."] = "Delay between trees, seconds.",
			["Лимит деревьев мода, включая первое. Естественные столкновения могут срубить дополнительные деревья."] = "Tree limit including the first tree. Natural collisions can fell extra trees.",
			["Минимальное время физического падения до распила. Движущиеся брёвна ждут дольше."] = "Minimum time before processing fallen logs. Moving logs wait longer.",
			["Интервал штатного разрушения успокоившихся брёвен."] = "Interval between processing settled logs using normal game damage.",
			["Волна ждёт, если движущихся загруженных брёвен не меньше этого числа."] = "Pause new felling when this many loaded logs are moving.",
			["Порог сглаженного FPS для паузы новых разрушений."] = "Smoothed FPS threshold for pausing new felling.",
			["Остановка после длительной перегрузки."] = "Stop after this many seconds of sustained overload.",
			["Распиливать только брёвна волны после падения штатным уроном, получая обычные ресурсы."] = "Process only this wave's settled logs using normal damage to produce resources.",
			["После волны подбирать её древесину в пределах 100 м, соблюдая вес и вместимость. Остатки остаются на земле."] = "Collect this wave's wood within 100 metres, respecting weight and inventory capacity. Leave excess wood on the ground.",
			["Индикатор режима и счётчик."] = "Show mode status and tree counter.",
			["Включить/выключить; выключение отменяет работу."] = "Toggle mode. Disabling cancels the current wave.",
			["Отменить очередь и распил. Уже падающие брёвна продолжают движение."] = "Cancel felling and log processing. Falling logs keep moving.",
			["Следующий пресет, когда волна не запущена."] = "Next preset while no wave is running.",
			["Скрыть/показать индикатор."] = "Show or hide the indicator.",
			["Перечитать конфигурацию в состоянии покоя."] = "Reload configuration while idle.",
			["Переключить русский и английский язык."] = "Switch between Russian and English.",
			["Auto: язык игры; для остальных языков английский. Russian / English: выбрать вручную."] = "Auto: follow the game language, with English for unsupported languages. Russian / English: select manually."
		};

		public static bool IsRussian(string setting, string gameLanguage)
		{
			if (string.Equals(setting, "Russian", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (string.Equals(setting, "English", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			return string.Equals(gameLanguage, "Russian", StringComparison.OrdinalIgnoreCase);
		}

		public static string Get(string key, bool russian)
		{
			if (!russian && English.TryGetValue(key, out var value))
			{
				return value;
			}
			return key;
		}

		public static string Format(string key, bool russian, params object[] args)
		{
			return string.Format(CultureInfo.InvariantCulture, Get(key, russian), args);
		}

		public static string Description(string russian)
		{
			return russian + " / " + Get(russian, russian: false);
		}
	}
	[BepInPlugin("ru.forestwave.valheim", "Forest Wave by vladosso", "3.1.0")]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(TreeBase), "Damage")]
		private static class AxePatch
		{
			private static bool Prefix(TreeBase __instance, HitData hit)
			{
				if ((Object)(object)Instance != (Object)null && Instance.IsSeed(__instance, hit))
				{
					Instance.Begin(__instance, hit);
					return false;
				}
				return true;
			}
		}

		private sealed class LogRecord
		{
			public TreeLog Log;

			public Rigidbody Body;

			public float Born;
		}

		[HarmonyPatch(typeof(TreeLog), "Awake")]
		private static class LogSpawnPatch
		{
			private static void Postfix(TreeLog __instance)
			{
				Instance?.Track(__instance);
				((Component)__instance).gameObject.AddComponent<LogLifetime>().Log = __instance;
			}
		}

		private sealed class LogLifetime : MonoBehaviour
		{
			internal TreeLog Log;

			private void OnDestroy()
			{
				if ((Object)(object)Instance != (Object)null)
				{
					Instance.liveLogs.Remove(Log);
					Instance.logs.Remove(Log);
				}
			}
		}

		[HarmonyPatch(typeof(TreeLog), "Destroy")]
		private static class LogChildrenPatch
		{
			private static void Prefix(TreeLog __instance, out int __state)
			{
				__state = Instance?.capture ?? 0;
				if ((Object)(object)Instance != (Object)null && Instance.busy && Instance.logs.ContainsKey(__instance))
				{
					Instance.capture = Instance.runId;
				}
			}

			private static Exception Finalizer(Exception __exception, int __state)
			{
				if ((Object)(object)Instance != (Object)null)
				{
					Instance.capture = __state;
				}
				return __exception;
			}
		}

		[HarmonyPatch(typeof(ItemDrop), "Awake")]
		private static class DropPatch
		{
			private static void Postfix(ItemDrop __instance)
			{
				if ((Object)(object)Instance != (Object)null && Instance.busy && Instance.capture != 0)
				{
					Instance.drops.Add(__instance);
				}
			}
		}

		public const string Id = "ru.forestwave.valheim";

		internal static Plugin Instance;

		private readonly WaveQueue<TreeBase> wave = new WaveQueue<TreeBase>();

		private readonly Collider[] nearby = (Collider[])(object)new Collider[2048];

		private readonly Dictionary<TreeBase, Vector3> from = new Dictionary<TreeBase, Vector3>();

		private Harmony harmony;

		private ConfigEntry<float> radius;

		private ConfigEntry<float> delay;

		private ConfigEntry<float> fallTime;

		private ConfigEntry<float> logDelay;

		private ConfigEntry<float> minFps;

		private ConfigEntry<float> maxPause;

		private ConfigEntry<int> maximum;

		private ConfigEntry<int> movingLimit;

		private ConfigEntry<bool> processLogs;

		private ConfigEntry<bool> collect;

		private ConfigEntry<bool> indicator;

		private ConfigEntry<KeyboardShortcut> toggleKey;

		private ConfigEntry<KeyboardShortcut> stopKey;

		private ConfigEntry<KeyboardShortcut> presetKey;

		private ConfigEntry<KeyboardShortcut> hudKey;

		private ConfigEntry<KeyboardShortcut> reloadKey;

		private ConfigEntry<KeyboardShortcut> languageKey;

		private ConfigEntry<string> language;

		private int reasonArgument;

		private bool armed;

		private bool injecting;

		private bool busy;

		private int capture;

		private int runId;

		private int preset;

		private int moving;

		private int splitCount;

		private int collected;

		private float frameAverage = 1f / 60f;

		private float nextLog;

		private float nextLoadCheck;

		private float runRadius;

		private float runDelay;

		private Coroutine routine;

		private string status = "ВЫКЛ";

		private string lastReason = "";

		private GUIStyle label;

		private readonly Dictionary<TreeLog, LogRecord> logs = new Dictionary<TreeLog, LogRecord>();

		private readonly HashSet<TreeLog> liveLogs = new HashSet<TreeLog>();

		private readonly HashSet<ItemDrop> drops = new HashSet<ItemDrop>();

		private bool Russian
		{
			get
			{
				string setting = language?.Value;
				Localization instance = Localization.instance;
				return Localizer.IsRussian(setting, (instance != null) ? instance.GetSelectedLanguage() : null);
			}
		}

		public int Felled => wave.Felled;

		public bool Running => busy;

		public int SplitCount => splitCount;

		public int Collected => collected;

		public bool Armed => armed;

		private string Text(string key, params object[] args)
		{
			return Localizer.Format(key, Russian, args);
		}

		private void Awake()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Expected O, but got Unknown
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Expected O, but got Unknown
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Expected O, but got Unknown
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Expected O, but got Unknown
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Expected O, but got Unknown
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Expected O, but got Unknown
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Expected O, but got Unknown
			//IL_0288: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Expected O, but got Unknown
			//IL_0324: Unknown result type (might be due to invalid IL or missing references)
			//IL_0358: Unknown result type (might be due to invalid IL or missing references)
			//IL_038c: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_044d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0457: Expected O, but got Unknown
			Instance = this;
			language = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "Language", "Auto", new ConfigDescription(Localizer.Description("Auto: язык игры; для остальных языков английский. Russian / English: выбрать вручную."), (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Auto", "Russian", "English" }), Array.Empty<object>()));
			languageKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "ToggleLanguage", new KeyboardShortcut((KeyCode)286, Array.Empty<KeyCode>()), Localizer.Description("Переключить русский и английский язык."));
			radius = ((BaseUnityPlugin)this).Config.Bind<float>("Wave", "Radius", 12f, new ConfigDescription(Localizer.Description("Радиус соседства, метры."), (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 30f), Array.Empty<object>()));
			delay = ((BaseUnityPlugin)this).Config.Bind<float>("Wave", "Delay", 0.65f, new ConfigDescription(Localizer.Description("Пауза между деревьями, секунды."), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.15f, 5f), Array.Empty<object>()));
			maximum = ((BaseUnityPlugin)this).Config.Bind<int>("Wave", "MaxTrees", 8, new ConfigDescription(Localizer.Description("Лимит деревьев мода, включая первое. Естественные столкновения могут срубить дополнительные деревья."), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 300), Array.Empty<object>()));
			fallTime = ((BaseUnityPlugin)this).Config.Bind<float>("Physics", "MinimumFallSeconds", 8f, new ConfigDescription(Localizer.Description("Минимальное время физического падения до распила. Движущиеся брёвна ждут дольше."), (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>()));
			logDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Physics", "LogBreakInterval", 0.35f, new ConfigDescription(Localizer.Description("Интервал штатного разрушения успокоившихся брёвен."), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.15f, 2f), Array.Empty<object>()));
			movingLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Physics", "MaxMovingLogs", 14, new ConfigDescription(Localizer.Description("Волна ждёт, если движущихся загруженных брёвен не меньше этого числа."), (AcceptableValueBase)(object)new AcceptableValueRange<int>(4, 40), Array.Empty<object>()));
			minFps = ((BaseUnityPlugin)this).Config.Bind<float>("Physics", "MinimumFPS", 25f, new ConfigDescription(Localizer.Description("Порог сглаженного FPS для паузы новых разрушений."), (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 60f), Array.Empty<object>()));
			maxPause = ((BaseUnityPlugin)this).Config.Bind<float>("Physics", "MaximumPauseSeconds", 30f, new ConfigDescription(Localizer.Description("Остановка после длительной перегрузки."), (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 120f), Array.Empty<object>()));
			processLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Wood", "ProcessSettledLogs", true, Localizer.Description("Распиливать только брёвна волны после падения штатным уроном, получая обычные ресурсы."));
			collect = ((BaseUnityPlugin)this).Config.Bind<bool>("Wood", "CollectAfterWave", false, Localizer.Description("После волны подбирать её древесину в пределах 100 м, соблюдая вес и вместимость. Остатки остаются на земле."));
			indicator = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "ShowIndicator", true, Localizer.Description("Индикатор режима и счётчик."));
			toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "Toggle", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), Localizer.Description("Включить/выключить; выключение отменяет работу."));
			stopKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "EmergencyStop", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), Localizer.Description("Отменить очередь и распил. Уже падающие брёвна продолжают движение."));
			presetKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "CyclePreset", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), Localizer.Description("Следующий пресет, когда волна не запущена."));
			hudKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "ToggleIndicator", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), Localizer.Description("Скрыть/показать индикатор."));
			reloadKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "ReloadConfig", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), Localizer.Description("Перечитать конфигурацию в состоянии покоя."));
			if (Version.GetVersionString(false) != "1.0.12")
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Unsupported Valheim " + Version.GetVersionString(false) + "; Forest Wave by vladosso disabled pending revalidation."));
				((Behaviour)this).enabled = false;
			}
			else
			{
				harmony = new Harmony("ru.forestwave.valheim");
				harmony.PatchAll(typeof(Plugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Forest Wave by vladosso loaded; API target Valheim " + Version.GetVersionString(false)));
			}
		}

		private bool CanRun()
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && !((Character)Player.m_localPlayer).IsDead() && !((Character)Player.m_localPlayer).IsTeleporting() && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.IsSinglePlayer && ZNet.instance.IsServer())
			{
				return ZNet.instance.GetPeerConnections() == 0;
			}
			return false;
		}

		private static bool Typing()
		{
			if (!Console.IsVisible() && (!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()) && !TextInput.IsVisible() && !Menu.IsVisible())
			{
				return InventoryGui.IsVisible();
			}
			return true;
		}

		private void Update()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: 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_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			frameAverage = Mathf.Lerp(frameAverage, Mathf.Clamp(Time.unscaledDeltaTime, 0.001f, 0.5f), 0.025f);
			KeyboardShortcut value = stopKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				armed = false;
				StopWave("аварийная остановка");
			}
			if (!Typing())
			{
				value = toggleKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					armed = !armed;
					if (!armed)
					{
						StopWave("режим выключен");
					}
					status = (armed ? "ГОТОВ" : "ВЫКЛ");
				}
				value = presetKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown() && !busy)
				{
					SetPreset((preset + 1) % 3);
				}
				value = hudKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					indicator.Value = !indicator.Value;
				}
				value = languageKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					language.Value = (Russian ? "English" : "Russian");
					((BaseUnityPlugin)this).Config.Save();
				}
				value = reloadKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown() && !busy)
				{
					((BaseUnityPlugin)this).Config.Reload();
					lastReason = "конфигурация обновлена";
				}
			}
			if (busy && !CanRun())
			{
				armed = false;
				StopWave("смерть, телепорт или смена сессии");
			}
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				armed = false;
			}
		}

		public void SetPreset(int index)
		{
			if (!busy)
			{
				preset = Mathf.Clamp(index, 0, 2);
				radius.Value = (new float[3] { 12f, 14f, 16f })[preset];
				delay.Value = (new float[3] { 0.65f, 0.5f, 0.4f })[preset];
				maximum.Value = (new int[3] { 8, 60, 200 })[preset];
				lastReason = (new string[3] { "ТЕСТ: следующий лимит {0}", "СРЕДНИЙ: следующий лимит {0}", "БОЛЬШОЙ: следующий лимит {0}" })[preset];
				reasonArgument = maximum.Value;
				((BaseUnityPlugin)this).Config.Save();
			}
		}

		public void StopWave()
		{
			StopWave("остановлено");
		}

		private void StopWave(string reason)
		{
			if (routine != null)
			{
				((MonoBehaviour)this).StopCoroutine(routine);
			}
			routine = null;
			busy = false;
			wave.Stop();
			from.Clear();
			logs.Clear();
			drops.Clear();
			Array.Clear(nearby, 0, nearby.Length);
			capture = 0;
			injecting = false;
			status = "СТОП";
			lastReason = reason;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("STOP; felled=" + Felled + "; reason=" + Text(reason)));
		}

		private bool IsSeed(TreeBase tree, HitData hit)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Invalid comparison between Unknown and I4
			if (!((Behaviour)this).enabled || !armed || busy || injecting || !CanRun() || !Object.op_Implicit((Object)(object)tree.m_logPrefab))
			{
				return false;
			}
			ZNetView component = ((Component)tree).GetComponent<ZNetView>();
			Player localPlayer = Player.m_localPlayer;
			ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon();
			if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.IsOwner() && (Object)(object)hit.GetAttacker() == (Object)(object)localPlayer && currentWeapon != null && (int)currentWeapon.m_shared.m_skillType == 7)
			{
				return hit.m_damage.m_chop > 0f;
			}
			return false;
		}

		private void Begin(TreeBase tree, HitData hit)
		{
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			busy = true;
			runId++;
			splitCount = 0;
			collected = 0;
			nextLog = 0f;
			lastReason = "";
			wave.Start(tree, Mathf.Clamp(maximum.Value, 1, 300));
			runRadius = Mathf.Clamp(radius.Value, 3f, 30f);
			runDelay = Mathf.Clamp(delay.Value, 0.15f, 5f);
			from.Clear();
			logs.Clear();
			drops.Clear();
			status = "ВОЛНА";
			routine = ((MonoBehaviour)this).StartCoroutine(Guarded(Run(tree, hit.m_dir)));
			((BaseUnityPlugin)this).Logger.LogInfo((object)("START run=" + runId + " radius=" + runRadius + " delay=" + runDelay + " limit=" + wave.Limit));
		}

		private IEnumerator Guarded(IEnumerator work)
		{
			while (true)
			{
				object current;
				try
				{
					if (!work.MoveNext())
					{
						break;
					}
					current = work.Current;
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)ex);
					armed = false;
					StopWave("ошибка; см. LogOutput.log");
					break;
				}
				yield return current;
			}
		}

		private void MeasureLoad()
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			if (Time.unscaledTime < nextLoadCheck)
			{
				return;
			}
			nextLoadCheck = Time.unscaledTime + 0.5f;
			moving = 0;
			foreach (TreeLog liveLog in liveLogs)
			{
				if (!Object.op_Implicit((Object)(object)liveLog))
				{
					continue;
				}
				Rigidbody component = ((Component)liveLog).GetComponent<Rigidbody>();
				if (!Object.op_Implicit((Object)(object)component) || component.IsSleeping())
				{
					continue;
				}
				Vector3 val = component.linearVelocity;
				if (!(((Vector3)(ref val)).sqrMagnitude > 0.09f))
				{
					val = component.angularVelocity;
					if (!(((Vector3)(ref val)).sqrMagnitude > 0.09f))
					{
						continue;
					}
				}
				moving++;
			}
		}

		private bool Overloaded()
		{
			MeasureLoad();
			if (!(frameAverage > 1f / minFps.Value) && moving < movingLimit.Value)
			{
				return logs.Count >= 80;
			}
			return true;
		}

		private IEnumerator Run(TreeBase seed, Vector3 direction)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			yield return null;
			TreeBase current = seed;
			float nextTree = Time.time;
			float stalledSince = -1f;
			while (wave.Active && CanRun())
			{
				ProcessOneLog();
				if (Overloaded())
				{
					status = "ПАУЗА ФИЗИКИ";
					if (stalledSince < 0f)
					{
						stalledSince = Time.unscaledTime;
					}
					if (Time.unscaledTime - stalledSince > maxPause.Value)
					{
						StopWave("перегрузка: уменьшите масштаб");
						yield break;
					}
					yield return null;
					continue;
				}
				stalledSince = -1f;
				status = "ВОЛНА";
				if (Time.time < nextTree || Time.timeScale == 0f)
				{
					yield return null;
					continue;
				}
				if (Object.op_Implicit((Object)(object)current) && ((Component)current).gameObject.activeInHierarchy)
				{
					ZNetView view = ((Component)current).GetComponent<ZNetView>();
					if (Object.op_Implicit((Object)(object)view) && view.IsValid() && view.IsOwner())
					{
						Vector3 position = ((Component)current).transform.position;
						int count = Physics.OverlapSphereNonAlloc(position, runRadius, nearby, -1, (QueryTriggerInteraction)1);
						if (count == nearby.Length)
						{
							StopWave("плотная область: уменьшите радиус");
							yield break;
						}
						List<TreeBase> candidates = new List<TreeBase>();
						HashSet<TreeBase> unique = new HashSet<TreeBase>();
						Vector3 val3;
						for (int i = 0; i < count; i++)
						{
							Collider val = nearby[i];
							nearby[i] = null;
							TreeBase val2 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponentInParent<TreeBase>() : null);
							if (Object.op_Implicit((Object)(object)val2) && (Object)(object)val2 != (Object)(object)current && Object.op_Implicit((Object)(object)val2.m_logPrefab) && unique.Add(val2))
							{
								val3 = position - ((Component)val2).transform.position;
								if (((Vector3)(ref val3)).sqrMagnitude <= runRadius * runRadius)
								{
									candidates.Add(val2);
								}
							}
							if (i % 64 == 63)
							{
								yield return null;
							}
						}
						candidates.RemoveAll((TreeBase x) => !Object.op_Implicit((Object)(object)x) || !((Component)x).gameObject.activeInHierarchy);
						candidates.Sort(delegate(TreeBase a, TreeBase b)
						{
							//IL_0006: Unknown result type (might be due to invalid IL or missing references)
							//IL_000c: Unknown result type (might be due to invalid IL or missing references)
							//IL_0011: Unknown result type (might be due to invalid IL or missing references)
							//IL_0016: Unknown result type (might be due to invalid IL or missing references)
							//IL_0027: 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_0037: Unknown result type (might be due to invalid IL or missing references)
							Vector3 val4 = ((Component)a).transform.position - position;
							float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude;
							val4 = ((Component)b).transform.position - position;
							return sqrMagnitude.CompareTo(((Vector3)(ref val4)).sqrMagnitude);
						});
						if (from.TryGetValue(current, out var value))
						{
							val3 = position - value;
							direction = ((Vector3)(ref val3)).normalized;
						}
						direction.y = 0f;
						if (((Vector3)(ref direction)).sqrMagnitude < 0.01f)
						{
							direction = Vector3.forward;
						}
						if (Object.op_Implicit((Object)(object)current) && Object.op_Implicit((Object)(object)view) && view.IsValid() && view.IsOwner() && CanRun())
						{
							injecting = true;
							capture = runId;
							try
							{
								current.Damage(MakeHit(position, direction));
							}
							finally
							{
								injecting = false;
								capture = 0;
							}
							if (!Object.op_Implicit((Object)(object)current) || !((Component)current).gameObject.activeInHierarchy || (Object.op_Implicit((Object)(object)view) && view.IsValid() && view.GetZDO().GetFloat(ZDOVars.s_health, 1f) <= 0f))
							{
								wave.ConfirmFelled();
								foreach (TreeBase item in candidates)
								{
									if (Object.op_Implicit((Object)(object)item) && wave.Offer(item))
									{
										from[item] = position;
									}
								}
								((BaseUnityPlugin)this).Logger.LogInfo((object)("FELLED run=" + runId + " count=" + Felled + "/" + wave.Limit));
							}
						}
						nextTree = Time.time + runDelay;
					}
				}
				if (!wave.TryTake(out current))
				{
					break;
				}
				yield return null;
			}
			wave.Stop();
			from.Clear();
			float drainStart = Time.time;
			while (processLogs.Value && logs.Count > 0 && CanRun() && Time.time - drainStart < 90f)
			{
				status = "БРЁВНА → ДРЕВЕСИНА";
				ProcessOneLog();
				yield return null;
			}
			if (collect.Value && CanRun())
			{
				status = "СБОР";
				List<ItemDrop> list = new List<ItemDrop>(drops);
				foreach (ItemDrop item2 in list)
				{
					if (!CanRun())
					{
						break;
					}
					if (Object.op_Implicit((Object)(object)item2) && IsWood(item2) && Vector3.Distance(((Component)item2).transform.position, ((Component)Player.m_localPlayer).transform.position) <= 100f)
					{
						Player localPlayer = Player.m_localPlayer;
						int stack = item2.m_itemData.m_stack;
						if (((Humanoid)localPlayer).GetInventory().GetTotalWeight() + item2.m_itemData.GetWeight(-1) <= localPlayer.GetMaxCarryWeight() && ((Humanoid)localPlayer).Pickup(((Component)item2).gameObject, false, false))
						{
							collected += stack;
						}
					}
					yield return null;
				}
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("DONE run=" + runId + " felled=" + Felled + " logBreaks=" + splitCount + " collected=" + collected + " remainingLogs=" + logs.Count));
			lastReason = ((logs.Count > 0) ? "оставшиеся брёвна можно дорубить вручную" : "волна завершена");
			logs.Clear();
			drops.Clear();
			busy = false;
			routine = null;
			status = "ГОТОВО";
		}

		internal static HitData MakeHit(Vector3 point, Vector3 direction)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			HitData val = new HitData
			{
				m_point = point + Vector3.up,
				m_dir = direction,
				m_toolTier = 100
			};
			val.m_damage.m_chop = 10000000f;
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				val.SetAttacker((Character)(object)Player.m_localPlayer);
			}
			return val;
		}

		private void OnGUI()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			if (((Behaviour)this).enabled && indicator.Value && Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				if (label == null)
				{
					label = new GUIStyle(GUI.skin.label)
					{
						fontSize = 14,
						wordWrap = true
					};
				}
				float num = Mathf.Max(8, Screen.width - 468);
				float num2 = Mathf.Max(8, Screen.height - 186);
				GUI.Box(new Rect(num, num2, 450f, 132f), "");
				Color color = GUI.color;
				GUI.color = (Color)(armed ? new Color(0.65f, 1f, 0.6f) : Color.white);
				GUI.Label(new Rect(num + 10f, num2 + 6f, 430f, 120f), "FOREST WAVE • vladosso • " + Text(status) + "\n" + Text("Срублено: {0} / {1}   Брёвна: {2}", Felled, (wave.Limit > 0) ? wave.Limit : maximum.Value, logs.Count) + "\n" + Text("{0}: режим  {1}: СТОП  {2}: масштаб", toggleKey.Value, stopKey.Value, presetKey.Value) + "\n" + Text("{0}: язык RU / EN", languageKey.Value) + "\n" + Text(lastReason, reasonArgument), label);
				GUI.color = color;
			}
		}

		private void OnDestroy()
		{
			StopWave("выгрузка");
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			liveLogs.Clear();
			Instance = null;
		}

		private static bool IsWood(ItemDrop drop)
		{
			string name = drop.m_itemData.m_shared.m_name;
			switch (name)
			{
			default:
				return name == "$item_blackwood";
			case "$item_wood":
			case "$item_roundlog":
			case "$item_finewood":
			case "$item_yggdrasilwood":
				return true;
			}
		}

		private void ProcessOneLog()
		{
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < nextLog || !processLogs.Value || !CanRun() || frameAverage > 1f / minFps.Value)
			{
				return;
			}
			nextLog = Time.time + logDelay.Value;
			TreeLog val = null;
			List<TreeLog> list = new List<TreeLog>();
			foreach (KeyValuePair<TreeLog, LogRecord> log in logs)
			{
				LogRecord value = log.Value;
				if (!Object.op_Implicit((Object)(object)value.Log) || !((Component)value.Log).gameObject.activeInHierarchy)
				{
					list.Add(log.Key);
				}
				else
				{
					if (Time.time - value.Born < fallTime.Value || !Object.op_Implicit((Object)(object)value.Body))
					{
						continue;
					}
					if (!value.Body.IsSleeping())
					{
						Vector3 val2 = value.Body.linearVelocity;
						if (((Vector3)(ref val2)).sqrMagnitude > 0.16f)
						{
							continue;
						}
						val2 = value.Body.angularVelocity;
						if (((Vector3)(ref val2)).sqrMagnitude > 0.16f)
						{
							continue;
						}
					}
					ZNetView component = ((Component)value.Log).GetComponent<ZNetView>();
					if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.IsOwner() && !Object.op_Implicit((Object)(object)val))
					{
						val = value.Log;
					}
				}
			}
			foreach (TreeLog item in list)
			{
				logs.Remove(item);
			}
			if (Object.op_Implicit((Object)(object)val))
			{
				capture = runId;
				try
				{
					val.Damage(MakeHit(((Component)val).transform.position, Vector3.zero));
				}
				finally
				{
					capture = 0;
				}
				ZNetView val3 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponent<ZNetView>() : null);
				if (!Object.op_Implicit((Object)(object)val) || !((Component)val).gameObject.activeInHierarchy || !Object.op_Implicit((Object)(object)val3) || !val3.IsValid())
				{
					logs.Remove(val);
					splitCount++;
				}
			}
		}

		private void Track(TreeLog log)
		{
			liveLogs.Add(log);
			if (capture == runId && capture != 0 && busy)
			{
				logs[log] = new LogRecord
				{
					Log = log,
					Body = ((Component)log).GetComponent<Rigidbody>(),
					Born = Time.time
				};
			}
		}
	}
	public sealed class WaveQueue<T>
	{
		private readonly Queue<T> queue = new Queue<T>();

		private readonly HashSet<T> seen = new HashSet<T>();

		public int Limit { get; private set; }

		public int Felled { get; private set; }

		public bool Active { get; private set; }

		public int Pending => queue.Count;

		public int Visited => seen.Count;

		public void Start(T root, int limit)
		{
			if (Active)
			{
				throw new InvalidOperationException("A wave is already running");
			}
			queue.Clear();
			seen.Clear();
			seen.Add(root);
			Limit = Math.Max(1, Math.Min(300, limit));
			Felled = 0;
			Active = true;
		}

		public bool Offer(T item)
		{
			if (!Active || Felled + queue.Count >= Limit || !seen.Add(item))
			{
				return false;
			}
			queue.Enqueue(item);
			return true;
		}

		public bool TryTake(out T item)
		{
			if (Active && Felled < Limit && queue.Count > 0)
			{
				item = queue.Dequeue();
				return true;
			}
			item = default(T);
			return false;
		}

		public void ConfirmFelled()
		{
			if (Active && Felled < Limit)
			{
				Felled++;
			}
		}

		public void Stop()
		{
			Active = false;
			queue.Clear();
		}

		public void Reset()
		{
			Stop();
			seen.Clear();
			Felled = 0;
			Limit = 0;
		}
	}
}