Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Forest Wave by vladosso v3.1.0
plugins/ForestWave/ForestWave.dll
Decompiled a month agousing 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; } } }