Decompiled source of Roguenarok WaveLab v0.1.3

BepInEx/plugins/WaveLab/Roguenarok.WaveLab.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Roguenarok.WaveLab")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+66087733cbceada476c9502a244f4a1829af82f0")]
[assembly: AssemblyProduct("Roguenarok.WaveLab")]
[assembly: AssemblyTitle("Roguenarok.WaveLab")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Roguenarok.WaveLab
{
	public sealed class Knob
	{
		public readonly string Key;

		public readonly string Group;

		public readonly string Name;

		public readonly string Help;

		public readonly float Default;

		public readonly float Min;

		public readonly float Max;

		public readonly float Step;

		public Knob(string group, string key, string name, float value, float min, float max, float step, string help)
		{
			Group = group;
			Key = key;
			Name = name;
			Default = value;
			Min = min;
			Max = max;
			Step = step;
			Help = help;
		}
	}
	public sealed class CreatureChoice
	{
		public string Prefab;

		public float Weight;

		public int FromWave;
	}
	public sealed class Settings
	{
		public static readonly Knob[] Knobs = new Knob[44]
		{
			new Knob("Ритм", "StartWave", "Начальная волна", 1f, 1f, 1000f, 1f, "Номер первой волны нового теста. С него уже считается рост сложности."),
			new Knob("Ритм", "FinalWave", "Последняя волна (0=до 10000)", 0f, 0f, 10000f, 1f, "После зачистки указанной волны тест завершается. Даже бесконечный режим ограничен 10000 волнами."),
			new Knob("Ритм", "Preparation", "Подготовка, секунд", 15f, 0f, 600f, 1f, "Задержка первой волны после Старт."),
			new Knob("Ритм", "WaitForClear", "Ждать зачистки: 0/1", 1f, 0f, 1f, 1f, "1: после гибели всех тестовых мобов начинается отдых. 0: волны приходят по интервалу даже при живых врагах."),
			new Knob("Ритм", "Rest", "Отдых после зачистки, сек", 45f, 0f, 600f, 1f, "Гарантированная пауза после зачистки. Не создаёт подкреплений."),
			new Knob("Ритм", "Interval", "Интервал волн, секунд", 90f, 5f, 600f, 1f, "Только при Ждать зачистки=0. Минимальная пауза от старта предыдущей волны. Очередь спавна должна закончиться перед новой волной."),
			new Knob("Ритм", "Timeout", "Лимит боя, сек (0=нет)", 0f, 0f, 1800f, 1f, "По истечении оставшиеся тестовые мобы удаляются без лута и начинается отдых. 0: ждать до зачистки."),
			new Knob("Ритм", "BatchSize", "Мобов за порцию", 2f, 1f, 16f, 1f, "Большую волну создаём порциями: меньше разовая нагрузка."),
			new Knob("Ритм", "BatchDelay", "Пауза порций, секунд", 0.8f, 0.1f, 10f, 0.1f, "Пауза между порциями внутри одной волны. Это не бесконечные подкрепления."),
			new Knob("Число", "BaseCount", "Базовое число врагов", 4f, 1f, 100f, 1f, "Число врагов для первого участника на волне 1."),
			new Knob("Число", "PerPlayer", "За дополнительного игрока", 2f, 0f, 30f, 1f, "Добавка за каждого живого участника после первого."),
			new Knob("Число", "CountGrowth", "Добавка за волну", 0.5f, 0f, 10f, 0.1f, "Линейный рост количества относительно волны 1."),
			new Knob("Число", "CountExponent", "Множитель числа за волну", 1f, 1f, 1.2f, 0.01f, "Дополнительный экспоненциальный рост: 1 отключает. Ограничен потолками."),
			new Knob("Число", "WaveCap", "Максимум в одной волне", 16f, 1f, 120f, 1f, "Общий потолок количества запланированных врагов волны."),
			new Knob("Число", "AliveCap", "Максимум живых тестовых", 48f, 1f, 160f, 1f, "Общий предел всех живых мобов WaveLab; обычные мобы не учитываются."),
			new Knob("Спавн", "RadiusMin", "Минимальный радиус, м", 35f, 5f, 150f, 1f, "Минимальное расстояние от выбранной точки спавна: игрок или центр арены."),
			new Knob("Спавн", "RadiusMax", "Максимальный радиус, м", 65f, 5f, 180f, 1f, "Верхний радиус. Реальное место ищется на сухой поверхности."),
			new Knob("Спавн", "PlayerSafety", "Не ближе к любому игроку, м", 20f, 0f, 100f, 1f, "Запрет появления рядом с любым живым игроком, в том числе вне тестовой команды."),
			new Knob("Спавн", "ArenaRadius", "Радиус участников, м", 200f, 20f, 2000f, 1f, "Участвуют живые игроки в этом радиусе от центра теста. Центр задаёт администратор своим местом при Старт."),
			new Knob("Спавн", "AroundArena", "Спавн от центра: 0/1", 0f, 0f, 1f, 1f, "0: около игроков по очереди. 1: кольцо вокруг неподвижного центра арены."),
			new Knob("Спавн", "Behind", "Предпочитать спину: 0/1", 1f, 0f, 1f, 1f, "Первые попытки спавна в секторе за спиной цели; затем круговой поиск."),
			new Knob("Спавн", "Slope", "Максимальный уклон, градусы", 35f, 0f, 70f, 1f, "Места круче указанного угла не подходят."),
			new Knob("Спавн", "Altitude", "Перепад высоты с целью, м", 20f, 1f, 100f, 1f, "Не создаёт врага на вершине или под обрывом далеко по высоте."),
			new Knob("Спавн", "WaterMargin", "Высота над водой, м", 0.5f, 0f, 10f, 0.1f, "Минимальное превышение земли над уровнем океана; подводный спавн запрещён."),
			new Knob("Спавн", "Attempts", "Попытки поиска места", 30f, 5f, 100f, 1f, "Если подходящее место не найдено, этот враг пропускается, без спавна под ногами."),
			new Knob("Сила", "Health", "Множитель здоровья", 1f, 0.1f, 10f, 0.1f, "Умножает нативное здоровье, уже учитывающее звёзды."),
			new Knob("Сила", "HealthGrowth", "Прирост HP за волну, %", 5f, 0f, 100f, 1f, "Линейная добавка к множителю HP за каждую волну после первой."),
			new Knob("Сила", "Damage", "Множитель урона", 1f, 0f, 10f, 0.1f, "Масштабирует исходящий урон тестового моба, не урон игрока."),
			new Knob("Сила", "DamageGrowth", "Прирост урона за волну, %", 2f, 0f, 100f, 1f, "Линейная добавка к множителю урона после первой волны."),
			new Knob("Сила", "Speed", "Множитель скорости", 1f, 0.25f, 3f, 0.05f, "Скорость ходьбы, бега и плавания. Не ускоряет анимацию атаки."),
			new Knob("Сила", "SpeedGrowth", "Прирост скорости за волну, %", 0f, 0f, 10f, 0.1f, "Линейная добавка к скорости за волну; итог ограничен 3x."),
			new Knob("Сила", "UnifiedFaction", "Общая фракция врагов: 0/1", 1f, 0f, 1f, 1f, "Объединяет тестовых мобов во фракцию ForestMonsters, чтобы разные виды не дрались друг с другом."),
			new Knob("Сила", "Hunt", "Преследовать игроков: 0/1", 1f, 0f, 1f, 1f, "Задаёт нативный режим охоты и начальную цель. Особенности AI конкретного существа остаются ванильными."),
			new Knob("Звёзды", "BaseStars", "Начальные звёзды", 0f, 0f, 5f, 1f, "0 звёзд соответствует уровню существа 1."),
			new Knob("Звёзды", "StarsEvery", "Волн за +1 звезду (0=нет)", 5f, 0f, 100f, 1f, "За каждые N волн после первой добавляется звезда. Например 5: волна 6 получает +1."),
			new Knob("Звёзды", "StarBonusChance", "Шанс ещё +1 звезды, %", 0f, 0f, 100f, 1f, "Случайная дополнительная звезда для каждого моба независимо."),
			new Knob("Звёзды", "MaxStars", "Предел звёзд", 2f, 0f, 5f, 1f, "Верхняя граница звёзд. Для >2 не у всех существ есть отдельный внешний вид."),
			new Knob("Награды", "InstantLoot", "Мгновенный дроп: 0/1", 1f, 0f, 1f, 1f, "1: предметы появляются при смерти; 0: ванильная задержка до исчезновения тела."),
			new Knob("Награды", "VanillaLoot", "Ванильный лут: 0/1", 0f, 0f, 1f, 1f, "Обычная таблица дропа только тестовых мобов. По умолчанию выключена, чтобы тест не превращался в фарм."),
			new Knob("Награды", "Gold", "Монет с обычного моба", 0f, 0f, 500f, 1f, "Монеты падают на землю при смерти. 0 отключает эту дополнительную награду."),
			new Knob("Награды", "GoldPerStar", "Множитель монет за звезду", 1.5f, 1f, 5f, 0.1f, "Итог: базовые монеты × множитель в степени числа звёзд; не более 5000."),
			new Knob("Награды", "AllowBosses", "Разрешить боссов: 0/1", 0f, 0f, 1f, 1f, "Включайте осознанно: убийство настоящего босса может открыть ванильную прогрессию мира."),
			new Knob("Награды", "DisableNativeRaids", "Отключить ванильные набеги: 0/1", 1f, 0f, 1f, 1f, "Не запускает новые случайные набеги во время теста. Уже начавшийся набег и спавнеры биомов сохраняются."),
			new Knob("Ритм", "Seed", "Seed случайности", 12345f, 1f, 1000000f, 1f, "Начальное состояние генератора случайности. Результат также зависит от движения команды и загруженной земли; это не повтор записи боя.")
		};

		public readonly Dictionary<string, float> Values = Knobs.ToDictionary((Knob k) => k.Key, (Knob k) => k.Default);

		public string Pool = "Greyling:70:1,Neck:30:1,Greydwarf:15:4,Greydwarf_Shaman:5:6";

		public float this[string key]
		{
			get
			{
				return Values[key];
			}
			set
			{
				Values[key] = value;
			}
		}

		public Settings Copy()
		{
			Settings settings = new Settings
			{
				Pool = Pool
			};
			Knob[] knobs = Knobs;
			foreach (Knob knob in knobs)
			{
				settings[knob.Key] = this[knob.Key];
			}
			return settings;
		}

		public void Validate()
		{
			Knob[] knobs = Knobs;
			foreach (Knob knob in knobs)
			{
				if (!Values.TryGetValue(knob.Key, out var value) || float.IsNaN(value) || float.IsInfinity(value) || value < knob.Min || value > knob.Max || (knob.Step == 1f && value != (float)(int)value))
				{
					throw new ArgumentException("Недопустимо: " + knob.Name);
				}
			}
			if (this["FinalWave"] > 0f && this["FinalWave"] < this["StartWave"])
			{
				throw new ArgumentException("Последняя волна раньше начальной.");
			}
			if (this["RadiusMin"] > this["RadiusMax"])
			{
				throw new ArgumentException("Минимальный радиус больше максимального.");
			}
			if (this["AroundArena"] < 0.5f && this["PlayerSafety"] > this["RadiusMax"])
			{
				throw new ArgumentException("Радиус безопасности больше радиуса появления у игрока.");
			}
			if (this["BaseStars"] > this["MaxStars"])
			{
				throw new ArgumentException("Начальные звёзды больше предела.");
			}
			if (!Choices().Any((CreatureChoice c) => (float)c.FromWave <= this["StartWave"]))
			{
				throw new ArgumentException("Нет доступного существа на первой волне.");
			}
		}

		public CreatureChoice[] Choices()
		{
			if (Pool == null || Pool.Length > 4096)
			{
				throw new ArgumentException("Слишком длинный список существ.");
			}
			string[] array = Pool.Split(',');
			if (array.Length < 1 || array.Length > 64)
			{
				throw new ArgumentException("Нужно 1–64 записи существ.");
			}
			return array.Select(delegate(string part)
			{
				string[] array2 = part.Trim().Split(':');
				if (array2.Length != 3 || array2[0].Length < 1 || array2[0].Length > 80 || array2[0].Any((char c) => !char.IsLetterOrDigit(c) && c != '_') || !float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || float.IsInfinity(result) || result <= 0f || result > 10000f || !int.TryParse(array2[2], out var result2) || result2 < 1 || result2 > 10000)
				{
					throw new ArgumentException("Состав: Prefab:вес:первая_волна, через запятую.");
				}
				return new CreatureChoice
				{
					Prefab = array2[0],
					Weight = result,
					FromWave = result2
				};
			}).ToArray();
		}

		public string Encode()
		{
			return string.Join("\n", Knobs.Select((Knob k) => k.Key + "=" + this[k.Key].ToString("R", CultureInfo.InvariantCulture))) + "\nPool=" + Pool;
		}

		public static Settings Decode(string text)
		{
			if (text == null || text.Length > 16000)
			{
				throw new ArgumentException("Слишком большой пресет.");
			}
			Settings settings = new Settings();
			HashSet<string> hashSet = new HashSet<string>();
			string[] array = text.Split('\n');
			foreach (string obj in array)
			{
				int num = obj.IndexOf('=');
				if (num < 1)
				{
					throw new ArgumentException("Повреждённый пресет.");
				}
				string text2 = obj.Substring(0, num);
				string text3 = obj.Substring(num + 1);
				if (!hashSet.Add(text2))
				{
					throw new ArgumentException("Повтор параметра.");
				}
				if (text2 == "Pool")
				{
					settings.Pool = text3;
					continue;
				}
				if (settings.Values.ContainsKey(text2) && float.TryParse(text3, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					settings[text2] = result;
					continue;
				}
				throw new ArgumentException("Неизвестный параметр: " + text2);
			}
			if (hashSet.Count != Knobs.Length + 1)
			{
				throw new ArgumentException("Неполный пресет.");
			}
			settings.Validate();
			return settings;
		}

		public int Count(int wave, int players)
		{
			return Math.Max(1, (int)Math.Min(this["WaveCap"], Math.Ceiling((double)(this["BaseCount"] + this["PerPlayer"] * (float)Math.Max(0, players - 1) + this["CountGrowth"] * (float)Math.Max(0, wave - 1)) * Math.Pow(this["CountExponent"], Math.Max(0, wave - 1)))));
		}

		public float Factor(string key, int wave, float cap)
		{
			return Math.Min(cap, this[key] * (1f + this[key + "Growth"] * (float)Math.Max(0, wave - 1) / 100f));
		}

		public int Stars(int wave, Random random)
		{
			return Math.Min((int)this["MaxStars"], (int)this["BaseStars"] + ((this["StarsEvery"] > 0f) ? (Math.Max(0, wave - 1) / (int)this["StarsEvery"]) : 0) + ((random.NextDouble() * 100.0 < (double)this["StarBonusChance"]) ? 1 : 0));
		}

		public string Pick(int wave, Random random)
		{
			CreatureChoice[] array = (from c in Choices()
				where c.FromWave <= wave
				select c).ToArray();
			double num = random.NextDouble() * (double)array.Sum((CreatureChoice c) => c.Weight);
			CreatureChoice[] array2 = array;
			foreach (CreatureChoice creatureChoice in array2)
			{
				num -= (double)creatureChoice.Weight;
				if (num < 0.0)
				{
					return creatureChoice.Prefab;
				}
			}
			return array.Last().Prefab;
		}
	}
	public sealed class SessionClock
	{
		public string Phase = "Остановлен";

		public bool Running;

		public bool Paused;

		public int Wave;

		public int Pending;

		public double Deadline;

		public void Start(double now, Settings s)
		{
			Running = true;
			Paused = false;
			Wave = (int)s["StartWave"] - 1;
			Pending = 0;
			Phase = "Подготовка";
			Deadline = now + (double)s["Preparation"];
		}

		public void Stop()
		{
			Running = (Paused = false);
			Pending = 0;
			Phase = "Остановлен";
		}

		public bool Due(double now)
		{
			if (Running && !Paused && Pending == 0 && Phase != "Бой")
			{
				return now >= Deadline;
			}
			return false;
		}

		public void Begin(double now, Settings s, int players)
		{
			Wave++;
			Pending = s.Count(Wave, players);
			Phase = "Бой";
			Deadline = now;
		}

		public void Rest(double now, Settings s)
		{
			Pending = 0;
			Phase = "Передышка";
			Deadline = now + (double)s["Rest"];
		}

		public bool AtEnd(Settings s)
		{
			return (float)Wave >= ((s["FinalWave"] > 0f) ? s["FinalWave"] : 10000f);
		}

		public void Shift(double elapsed)
		{
			Deadline += elapsed;
		}
	}
	[BepInPlugin("local.roguenarok.wavelab", "WaveLab", "0.1.3")]
	[BepInIncompatibility("local.roguenarok.foundation")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[Serializable]
		public sealed class ExportProfile
		{
			public string Format = "WaveLab-1";

			public string PluginVersion = "0.1.3";

			public string Creatures;

			public ExportParameter[] Parameters;
		}

		[Serializable]
		public sealed class ExportParameter
		{
			public string Key;

			public string Name;

			public string Description;

			public float Value;
		}

		private sealed class Target
		{
			public ZDOID Id;

			public Vector3 Position;

			public Vector3 Forward;

			public bool Alive;

			public Character Character;
		}

		public const string Id = "local.roguenarok.wavelab";

		public const string Version = "0.1.3";

		private const string Request = "WaveLab.Request.1";

		private const string Snapshot = "WaveLab.State.1";

		internal static Plugin Instance;

		private Harmony harmony;

		private Settings settings = new Settings();

		private Settings draft;

		private readonly SessionClock clock = new SessionClock();

		private readonly Dictionary<string, ConfigEntry<float>> entries = new Dictionary<string, ConfigEntry<float>>();

		private ConfigEntry<string> poolEntry;

		private ZNet net;

		private ZRpc server;

		private readonly HashSet<ZRpc> bound = new HashSet<ZRpc>();

		private readonly Dictionary<ZRpc, double> throttles = new Dictionary<ZRpc, double>();

		private long revision;

		private long draftRevision;

		private double nextNet;

		private double nextConfig;

		private DateTime fileStamp;

		private string lastApplied;

		private bool authorized;

		private string notice = "Tab — панель WaveLab";

		private int shownAlive;

		private int shownSkipped;

		private double shownDeadline;

		private bool awaitingApply;

		private readonly List<ZDOID> tracked = new List<ZDOID>();

		private Vector3 center;

		private bool recovered;

		private Random random = new Random(12345);

		private double lastTick;

		private double nextBatch;

		private double combatStart;

		private int skipped;

		private int spawnSerial;

		private GameObject ui;

		private RectTransform page;

		private TMP_Text info;

		private bool uiOpen;

		private string selectedGroup = "Ритм";

		private CursorLockMode oldLock;

		private bool oldVisible;

		private static double Now => Time.realtimeSinceStartupAsDouble;

		internal static bool BlockNativeRaids
		{
			get
			{
				Plugin instance = Instance;
				if (instance != null)
				{
					ZNet obj = instance.net;
					if (((obj != null) ? new bool?(obj.IsServer()) : ((bool?)null)) == true && Instance.clock.Running)
					{
						return Instance.settings["DisableNativeRaids"] >= 0.5f;
					}
				}
				return false;
			}
		}

		internal static bool InputCaptured => Instance?.uiOpen ?? false;

		private void Awake()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Expected O, but got Unknown
			Instance = this;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			Knob[] knobs = Settings.Knobs;
			foreach (Knob knob in knobs)
			{
				entries[knob.Key] = ((BaseUnityPlugin)this).Config.Bind<float>(knob.Group, knob.Key, knob.Default, new ConfigDescription(knob.Name + ". " + knob.Help, (AcceptableValueBase)(object)new AcceptableValueRange<float>(knob.Min, knob.Max), Array.Empty<object>()));
			}
			poolEntry = ((BaseUnityPlugin)this).Config.Bind<string>("Состав", "Creatures", settings.Pool, "Prefab:вес:первая_волна, через запятую. Вес — относительная частота, не процент. Пример: Greyling:70:1,Greydwarf:20:3,Greydwarf_Elite:5:6");
			bool flag = true;
			try
			{
				ReadConfig();
			}
			catch (Exception ex)
			{
				flag = false;
				((BaseUnityPlugin)this).Logger.LogError((object)("Invalid WaveLab config; safe defaults retained; original file preserved: " + ex.Message));
			}
			if (flag)
			{
				Persist(settings);
			}
			fileStamp = File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
			lastApplied = settings.Encode();
			draft = settings.Copy();
			harmony = new Harmony("local.roguenarok.wavelab");
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("WaveLab 0.1.3: standalone, no auto-start; Tab inventory for settings. Config: " + ((BaseUnityPlugin)this).Config.ConfigFilePath));
		}

		private void OnDestroy()
		{
			ZNet obj = net;
			if (obj != null && obj.IsServer())
			{
				ClearMobs();
			}
			CloseUi();
			Harmony obj2 = harmony;
			if (obj2 != null)
			{
				obj2.UnpatchSelf();
			}
			Instance = null;
		}

		private void Update()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)(object)net)
			{
				CloseUi();
				DestroyUi();
				net = instance;
				server = null;
				bound.Clear();
				throttles.Clear();
				clock.Stop();
				tracked.Clear();
				recovered = false;
				nextNet = (nextConfig = 0.0);
				lastTick = Now;
				draft = settings.Copy();
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null && uiOpen)
			{
				CloseUi();
			}
			if ((Object)(object)net == (Object)null)
			{
				return;
			}
			if (Chainloader.PluginInfos.ContainsKey("local.roguenarok.foundation"))
			{
				notice = "WaveLab нельзя запускать вместе с Roguenarok. Используйте отдельный профиль.";
				if (clock.Running)
				{
					clock.Stop();
				}
				return;
			}
			if (net.IsServer())
			{
				authorized = !net.IsDedicated();
				foreach (ZNetPeer item in from p in net.GetPeers()
					where !p.m_server && p.IsReady() && p.m_rpc.IsConnected()
					select p)
				{
					if (bound.Add(item.m_rpc))
					{
						ZNetPeer saved = item;
						item.m_rpc.Register<ZPackage>("WaveLab.Request.1", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage p)
						{
							Receive(saved, rpc, p);
						});
					}
				}
				ZRpc[] array = bound.Where((ZRpc r) => !r.IsConnected()).ToArray();
				foreach (ZRpc val in array)
				{
					bound.Remove(val);
					throttles.Remove(val);
				}
				if (Now >= nextConfig)
				{
					nextConfig = Now + 1.0;
					ReloadChangedConfig();
				}
				TickSession();
				if (Now >= nextNet)
				{
					nextNet = Now + 1.0;
					foreach (ZNetPeer item2 in from p in net.GetPeers()
						where !p.m_server && p.IsReady() && bound.Contains(p.m_rpc)
						select p)
					{
						SendState(item2);
					}
				}
			}
			else
			{
				ZNetPeer serverPeer = net.GetServerPeer();
				if (serverPeer?.m_rpc != server)
				{
					server = serverPeer?.m_rpc;
					authorized = false;
					if (server != null)
					{
						server.Register<ZPackage>("WaveLab.State.1", (Action<ZRpc, ZPackage>)ReceiveState);
					}
				}
				if (server != null && server.IsConnected() && Now >= nextNet)
				{
					nextNet = Now + 2.0;
					Send("sync");
				}
			}
			UpdateUiStatus();
		}

		private bool IsAdmin(ZNetPeer peer)
		{
			return (bool)AccessTools.Method(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null).Invoke(net, new object[2]
			{
				AccessTools.Field(typeof(ZNet), "m_adminList").GetValue(net),
				peer.m_socket.GetHostName()
			});
		}

		private void Receive(ZNetPeer peer, ZRpc rpc, ZPackage package)
		{
			ZNet obj = net;
			if (obj == null || !obj.IsServer() || net.GetPeer(peer.m_uid) != peer || peer.m_rpc != rpc || !peer.IsReady() || !rpc.IsConnected() || package.Size() > 20000 || (throttles.TryGetValue(rpc, out var value) && Now - value < 0.25))
			{
				return;
			}
			throttles[rpc] = Now;
			try
			{
				if (package.ReadString() != "0.1.3")
				{
					return;
				}
				string text = package.ReadString();
				long num = package.ReadLong();
				string payload = package.ReadString();
				if (text != "sync")
				{
					if (!IsAdmin(peer))
					{
						throw new InvalidOperationException("Нужны права администратора сервера.");
					}
					if (num != revision)
					{
						throw new InvalidOperationException("Настройки уже изменены. Нажмите «Обновить», затем повторите.");
					}
					Target actor = Players().FirstOrDefault((Target p) => p.Id == peer.m_characterID);
					Execute(text, payload, actor);
				}
			}
			catch (Exception ex)
			{
				notice = ex.Message;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Rejected WaveLab request: " + ex.Message));
			}
			SendState(peer, reply: true);
		}

		private void Send(string action, string payload = "")
		{
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			ZNet obj = net;
			if (obj != null && obj.IsServer())
			{
				if (net.IsDedicated())
				{
					return;
				}
				if (!(action == "apply") || draftRevision == revision)
				{
					try
					{
						Execute(action, payload, Players().FirstOrDefault((Target p) => (Object)(object)p.Character == (Object)(object)Player.m_localPlayer));
						return;
					}
					catch (Exception ex)
					{
						notice = ex.Message;
						return;
					}
				}
				notice = "Настройки изменились. Нажмите Обновить с сервера.";
			}
			else
			{
				ZRpc obj2 = server;
				if (obj2 == null || !obj2.IsConnected())
				{
					notice = "Нет соединения с сервером WaveLab.";
					return;
				}
				ZPackage val = new ZPackage();
				val.Write("0.1.3");
				val.Write(action);
				val.Write((action == "apply") ? draftRevision : revision);
				val.Write(payload);
				server.Invoke("WaveLab.Request.1", new object[1] { val });
			}
		}

		private void SendState(ZNetPeer peer, bool reply = false)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write("0.1.3");
			val.Write(IsAdmin(peer));
			val.Write(revision);
			val.Write(settings.Encode());
			val.Write(clock.Running);
			val.Write(clock.Paused);
			val.Write(clock.Wave);
			val.Write(clock.Pending);
			val.Write(clock.Phase);
			val.Write(Math.Max(0.0, ((clock.Phase == "Бой" && settings["WaitForClear"] < 0.5f) ? (combatStart + (double)settings["Interval"]) : clock.Deadline) - Now));
			val.Write(CountAlive());
			val.Write(skipped);
			val.Write((notice.Length > 400) ? notice.Substring(0, 400) : notice);
			val.Write(reply);
			peer.m_rpc.Invoke("WaveLab.State.1", new object[1] { val });
		}

		private void ReceiveState(ZRpc rpc, ZPackage package)
		{
			if (rpc != server)
			{
				return;
			}
			ZNet obj = net;
			if (((obj == null) ? null : obj.GetServerPeer()?.m_rpc) != rpc || package.Size() > 24000)
			{
				return;
			}
			try
			{
				if (package.ReadString() != "0.1.3")
				{
					return;
				}
				bool flag = package.ReadBool();
				long num = package.ReadLong();
				Settings settings = Settings.Decode(package.ReadString());
				bool running = package.ReadBool();
				bool paused = package.ReadBool();
				int num2 = package.ReadInt();
				int num3 = package.ReadInt();
				string text = package.ReadString();
				double num4 = package.ReadDouble();
				int num5 = package.ReadInt();
				int num6 = package.ReadInt();
				string text2 = package.ReadString();
				bool flag2 = package.ReadBool();
				if (num < 0 || num2 < 0 || num2 > 10000 || num3 < 0 || num3 > 120 || num5 < 0 || num5 > 160 || num6 < 0 || text.Length > 80 || text2.Length > 400 || double.IsNaN(num4) || double.IsInfinity(num4) || num4 < 0.0 || num4 > 1800.0)
				{
					return;
				}
				bool flag3 = num != revision;
				bool flag4 = authorized != flag;
				revision = num;
				authorized = flag;
				this.settings = settings;
				clock.Running = running;
				clock.Paused = paused;
				clock.Wave = num2;
				clock.Pending = num3;
				clock.Phase = text;
				shownDeadline = Now + num4;
				shownAlive = num5;
				shownSkipped = num6;
				notice = text2;
				if (!uiOpen)
				{
					draft = this.settings.Copy();
					draftRevision = revision;
				}
				else if (awaitingApply && flag2)
				{
					awaitingApply = false;
					if (flag3)
					{
						draft = this.settings.Copy();
						draftRevision = revision;
					}
					BuildPage();
				}
				else if (flag4)
				{
					draft = this.settings.Copy();
					draftRevision = revision;
					BuildPage();
				}
			}
			catch
			{
				notice = "Сервер прислал несовместимые данные WaveLab.";
			}
		}

		private void Execute(string action, string payload, Target actor)
		{
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			switch (action)
			{
			case "sync":
				return;
			case "apply":
			{
				Settings settings2 = Settings.Decode(payload);
				ValidatePrefabs(settings2);
				Persist(settings2);
				this.settings = settings2;
				draft = settings2.Copy();
				revision++;
				draftRevision = revision;
				notice = "Применено. Новые параметры спавна, звёзд и силы действуют на новых мобов; уже появившиеся сохраняют свои параметры.";
				awaitingApply = false;
				break;
			}
			case "start":
				if (actor == null || !actor.Alive)
				{
					throw new InvalidOperationException("Администратор должен находиться живым в мире.");
				}
				ValidatePrefabs(this.settings);
				ClearMobs();
				center = actor.Position;
				skipped = 0;
				random = new Random((int)this.settings["Seed"]);
				clock.Start(Now, this.settings);
				notice = "Тест запущен. Центр арены — место администратора при запуске.";
				break;
			case "pause":
				if (!clock.Running)
				{
					throw new InvalidOperationException("Сначала запустите тест.");
				}
				clock.Paused = !clock.Paused;
				notice = (clock.Paused ? "Пауза спавна и таймеров. Живые мобы продолжают бой." : "Продолжено.");
				break;
			case "next":
				if (clock.AtEnd(this.settings))
				{
					throw new InvalidOperationException("Достигнут предел волн; запустите новый тест.");
				}
				if (!clock.Running)
				{
					throw new InvalidOperationException("Сначала запустите тест.");
				}
				ClearMobs();
				clock.Pending = 0;
				clock.Paused = false;
				clock.Phase = "Передышка";
				clock.Deadline = Now;
				notice = "Следующая волна запускается сейчас.";
				break;
			case "stop":
				clock.Stop();
				ClearMobs();
				notice = "Тест остановлен, его живые мобы удалены без лута.";
				break;
			case "clear":
				ClearMobs();
				clock.Pending = 0;
				if (clock.Running)
				{
					clock.Rest(Now, this.settings);
				}
				notice = "Тестовые мобы удалены без лута.";
				break;
			case "reload":
			{
				Settings settings = this.settings;
				try
				{
					((BaseUnityPlugin)this).Config.Reload();
					ReadConfig();
					ValidatePrefabs(this.settings);
					lastApplied = this.settings.Encode();
					revision++;
					notice = "Конфиг перечитан.";
				}
				catch
				{
					this.settings = settings;
					throw;
				}
				break;
			}
			case "export":
			{
				string text = Path.Combine(Paths.ConfigPath, "WaveLab");
				Directory.CreateDirectory(text);
				string text2 = Path.Combine(text, "waves-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fff", CultureInfo.InvariantCulture) + ".json");
				File.WriteAllText(text2, ExportJson(this.settings));
				notice = "Экспорт на сервере: " + text2;
				break;
			}
			default:
				throw new ArgumentException("Неизвестная команда.");
			}
			if (net.IsServer() && !net.IsDedicated() && uiOpen)
			{
				BuildPage();
			}
		}

		private void ValidatePrefabs(Settings s)
		{
			s.Validate();
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				throw new InvalidOperationException("Мир ещё не загружен.");
			}
			CreatureChoice[] array = s.Choices();
			foreach (CreatureChoice creatureChoice in array)
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(creatureChoice.Prefab);
				Character val = ((prefab != null) ? prefab.GetComponent<Character>() : null);
				if ((Object)(object)val == (Object)null || val is Player || (Object)(object)prefab.GetComponent<MonsterAI>() == (Object)null)
				{
					throw new ArgumentException("Не найден боевой моб: " + creatureChoice.Prefab);
				}
				if (val.IsBoss() && s["AllowBosses"] < 0.5f)
				{
					throw new ArgumentException("Боссы запрещены: " + creatureChoice.Prefab);
				}
			}
		}

		private void ReadConfig()
		{
			Settings settings = new Settings
			{
				Pool = poolEntry.Value
			};
			Knob[] knobs = Settings.Knobs;
			foreach (Knob knob in knobs)
			{
				settings[knob.Key] = entries[knob.Key].Value;
			}
			settings.Validate();
			this.settings = settings;
		}

		private void Persist(Settings next)
		{
			//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_0025: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			string text = ((BaseUnityPlugin)this).Config.ConfigFilePath + ".tmp";
			try
			{
				ConfigFile val = new ConfigFile(text, false)
				{
					SaveOnConfigSet = false
				};
				Knob[] knobs = Settings.Knobs;
				foreach (Knob knob in knobs)
				{
					val.Bind<float>(knob.Group, knob.Key, next[knob.Key], new ConfigDescription(knob.Name + ". " + knob.Help, (AcceptableValueBase)(object)new AcceptableValueRange<float>(knob.Min, knob.Max), Array.Empty<object>()));
				}
				val.Bind<string>("Состав", "Creatures", next.Pool, "Prefab:вес:первая_волна, через запятую. Вес — относительная частота.");
				val.Save();
				if (File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
				{
					File.Replace(text, ((BaseUnityPlugin)this).Config.ConfigFilePath, null);
				}
				else
				{
					File.Move(text, ((BaseUnityPlugin)this).Config.ConfigFilePath);
				}
				knobs = Settings.Knobs;
				foreach (Knob knob2 in knobs)
				{
					entries[knob2.Key].Value = next[knob2.Key];
				}
				poolEntry.Value = next.Pool;
				fileStamp = File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
				lastApplied = next.Encode();
			}
			finally
			{
				if (File.Exists(text))
				{
					File.Delete(text);
				}
			}
		}

		private void ReloadChangedConfig()
		{
			DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
			if (lastWriteTimeUtc == fileStamp)
			{
				return;
			}
			fileStamp = lastWriteTimeUtc;
			Settings settings = this.settings;
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
				ReadConfig();
				ValidatePrefabs(this.settings);
				if (this.settings.Encode() != lastApplied)
				{
					lastApplied = this.settings.Encode();
					revision++;
					notice = "Конфиг обновлён с диска без перезапуска.";
				}
			}
			catch (Exception ex)
			{
				this.settings = settings;
				notice = "Конфиг отклонён: " + ex.Message;
				((BaseUnityPlugin)this).Logger.LogWarning((object)notice);
			}
		}

		private static Settings ImportJson(string text)
		{
			if (text == null || text.Length > 100000)
			{
				throw new ArgumentException("JSON слишком большой.");
			}
			ExportProfile exportProfile = JsonUtility.FromJson<ExportProfile>(text);
			if (exportProfile == null || exportProfile.Format != "WaveLab-1" || exportProfile.Parameters == null || exportProfile.Parameters.Length != Settings.Knobs.Length)
			{
				throw new ArgumentException("Нужен полный JSON-пресет WaveLab-1.");
			}
			Settings settings = new Settings
			{
				Pool = exportProfile.Creatures
			};
			HashSet<string> hashSet = new HashSet<string>();
			ExportParameter[] parameters = exportProfile.Parameters;
			foreach (ExportParameter exportParameter in parameters)
			{
				if (exportParameter == null || !settings.Values.ContainsKey(exportParameter.Key ?? "") || !hashSet.Add(exportParameter.Key))
				{
					throw new ArgumentException("Неизвестный или повторный параметр.");
				}
				settings[exportParameter.Key] = exportParameter.Value;
			}
			settings.Validate();
			return settings;
		}

		private static string ExportJson(Settings s)
		{
			return JsonUtility.ToJson((object)new ExportProfile
			{
				Creatures = s.Pool,
				Parameters = Settings.Knobs.Select((Knob k) => new ExportParameter
				{
					Key = k.Key,
					Name = k.Name,
					Description = k.Help,
					Value = s[k.Key]
				}).ToArray()
			}, true);
		}

		private static IEnumerable<ZDO> Objects()
		{
			if (ZDOMan.instance != null)
			{
				return ((Dictionary<ZDOID, ZDO>)AccessTools.Field(typeof(ZDOMan), "m_objectsByID").GetValue(ZDOMan.instance)).Values.ToArray();
			}
			return Array.Empty<ZDO>();
		}

		private List<Target> Players()
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			List<Target> list = new List<Target>();
			foreach (ZNetPeer item in from p in net.GetPeers()
				where !p.m_server && p.IsReady() && p.m_rpc.IsConnected()
				select p)
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val = ((instance != null) ? instance.GetZDO(item.m_characterID) : null);
				if (val != null)
				{
					Target obj = new Target
					{
						Id = item.m_characterID,
						Position = val.GetPosition(),
						Forward = val.GetRotation() * Vector3.forward,
						Alive = (!val.GetBool(ZDOVars.s_dead, false) && val.GetFloat(ZDOVars.s_health, 1f) > 0f)
					};
					ZNetScene instance2 = ZNetScene.instance;
					object character;
					if (instance2 == null)
					{
						character = null;
					}
					else
					{
						GameObject obj2 = instance2.FindInstance(item.m_characterID);
						character = ((obj2 != null) ? obj2.GetComponent<Character>() : null);
					}
					obj.Character = (Character)character;
					list.Add(obj);
				}
			}
			if (!net.IsDedicated() && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				Player localPlayer = Player.m_localPlayer;
				list.Add(new Target
				{
					Id = ((Character)localPlayer).GetZDOID(),
					Position = ((Component)localPlayer).transform.position,
					Forward = ((Component)localPlayer).transform.forward,
					Alive = !((Character)localPlayer).IsDead(),
					Character = (Character)(object)localPlayer
				});
			}
			return list;
		}

		private int CountAlive()
		{
			//IL_0014: 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_0026: 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)
			int num = 0;
			ZDOID[] array = tracked.ToArray();
			foreach (ZDOID val in array)
			{
				ZDOMan instance = ZDOMan.instance;
				ZDO val2 = ((instance != null) ? instance.GetZDO(val) : null);
				if (val2 == null)
				{
					tracked.Remove(val);
				}
				else if (val2.GetFloat(ZDOVars.s_health, 1f) > 0f)
				{
					num++;
				}
			}
			return num;
		}

		private void ClearMobs()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			ZNet obj = net;
			if (obj == null || !obj.IsServer() || ZDOMan.instance == null)
			{
				return;
			}
			ZDOID[] array = tracked.ToArray();
			foreach (ZDOID val in array)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(val);
				if (zDO != null && zDO.GetBool("wl_lab", false))
				{
					zDO.Set("wl_suppress", true);
					zDO.SetOwner(ZDOMan.GetSessionID());
					ZDOMan.instance.DestroyZDO(zDO);
				}
			}
			tracked.Clear();
		}

		private void TickSession()
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0356: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || ZDOMan.instance == null)
			{
				return;
			}
			if (!recovered)
			{
				recovered = true;
				foreach (ZDO item in from d in Objects()
					where d.GetBool("wl_lab", false)
					select d)
				{
					tracked.Add(item.m_uid);
				}
				ClearMobs();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"WaveLab: previous test mobs retired; test waits for explicit Start.");
			}
			double num = Math.Max(0.0, Now - lastTick);
			lastTick = Now;
			if (!clock.Running)
			{
				return;
			}
			List<Target> list = (from p in Players()
				where p.Alive
				select p).ToList();
			List<Target> list2 = list.Where(delegate(Target p)
			{
				//IL_0001: 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)
				Vector3 val2 = p.Position - center;
				return ((Vector3)(ref val2)).sqrMagnitude <= settings["ArenaRadius"] * settings["ArenaRadius"];
			}).ToList();
			if (clock.Paused || list2.Count == 0)
			{
				clock.Shift(num);
				combatStart += num;
				nextBatch += num;
				return;
			}
			if (clock.Due(Now))
			{
				clock.Begin(Now, settings, list2.Count);
				combatStart = Now;
				nextBatch = Now;
				spawnSerial = 0;
				notice = "Волна " + clock.Wave + ": запланировано " + clock.Pending + " врагов.";
			}
			if (clock.Phase != "Бой")
			{
				return;
			}
			if (settings["Timeout"] > 0f && Now - combatStart >= (double)settings["Timeout"])
			{
				ClearMobs();
				if (clock.AtEnd(settings))
				{
					clock.Stop();
				}
				else
				{
					clock.Rest(Now, settings);
				}
				notice = "Лимит боя: оставшиеся враги удалены без награды.";
				return;
			}
			if (clock.Pending > 0 && Now >= nextBatch)
			{
				nextBatch = Now + (double)settings["BatchDelay"];
				int val = (int)settings["AliveCap"] - CountAlive();
				int num2 = Math.Min(clock.Pending, Math.Min((int)settings["BatchSize"], val));
				Stopwatch stopwatch = Stopwatch.StartNew();
				for (int num3 = 0; num3 < num2; num3++)
				{
					if (stopwatch.ElapsedMilliseconds >= 12)
					{
						break;
					}
					Target target = list2[(spawnSerial + clock.Wave - 1) % list2.Count];
					if (TrySpawnPoint(target, list, stopwatch, out var point))
					{
						Spawn(target, point);
					}
					else
					{
						skipped++;
					}
					spawnSerial++;
					clock.Pending--;
				}
			}
			if (clock.Pending != 0)
			{
				return;
			}
			if (clock.AtEnd(settings))
			{
				if (CountAlive() == 0)
				{
					clock.Stop();
					notice = "Тест завершён: последняя волна зачищена.";
				}
			}
			else if (settings["WaitForClear"] >= 0.5f)
			{
				if (CountAlive() == 0)
				{
					clock.Rest(Now, settings);
				}
			}
			else if (Now - combatStart >= (double)settings["Interval"])
			{
				clock.Phase = "Интервал";
				clock.Deadline = Now;
			}
		}

		private bool TrySpawnPoint(Target target, List<Target> players, Stopwatch budget, out Vector3 point)
		{
			//IL_0002: 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_0020: 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_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_02be: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0320: Unknown result type (might be due to invalid IL or missing references)
			point = default(Vector3);
			Vector3 val = ((settings["AroundArena"] >= 0.5f) ? center : target.Position);
			float num3 = default(float);
			float num4 = default(float);
			float num5 = default(float);
			for (int i = 0; i < (int)settings["Attempts"] && budget.ElapsedMilliseconds < 12; i++)
			{
				double num = random.NextDouble() * Math.PI * 2.0;
				if (settings["Behind"] >= 0.5f && i < 8)
				{
					num = Math.Atan2(target.Forward.x, target.Forward.z) + Math.PI + (random.NextDouble() - 0.5) * Math.PI;
				}
				double num2 = (double)settings["RadiusMin"] + random.NextDouble() * (double)(settings["RadiusMax"] - settings["RadiusMin"]);
				Vector3 candidate = val + new Vector3((float)(Math.Sin(num) * num2), 0f, (float)(Math.Cos(num) * num2));
				if (players.Any(delegate(Target p)
				{
					//IL_002e: Unknown result type (might be due to invalid IL or missing references)
					//IL_0033: Unknown result type (might be due to invalid IL or missing references)
					Vector2 val2 = new Vector2(p.Position.x - candidate.x, p.Position.z - candidate.z);
					return ((Vector2)(ref val2)).sqrMagnitude < settings["PlayerSafety"] * settings["PlayerSafety"];
				}))
				{
					continue;
				}
				if (!ZoneSystem.instance.GetGroundHeight(candidate, ref num3))
				{
					AccessTools.Method(typeof(ZoneSystem), "PokeLocalZone", (Type[])null, (Type[])null).Invoke(ZoneSystem.instance, new object[1] { ZoneSystem.GetZone(candidate) });
					if (!ZoneSystem.instance.GetGroundHeight(candidate, ref num3))
					{
						continue;
					}
				}
				if (!(num3 < ZoneSystem.instance.m_waterLevel + settings["WaterMargin"]) && !(Math.Abs(num3 - target.Position.y) > settings["Altitude"]) && ZoneSystem.instance.GetGroundHeight(candidate + new Vector3(2f, 0f, 0f), ref num4) && ZoneSystem.instance.GetGroundHeight(candidate + new Vector3(0f, 0f, 2f), ref num5) && !(Math.Atan(Math.Sqrt((num4 - num3) * (num4 - num3) + (num5 - num3) * (num5 - num3)) / 2.0) * 180.0 / Math.PI > (double)settings["Slope"]))
				{
					candidate.y = num3 + 0.3f;
					if (!Physics.CheckCapsule(candidate + Vector3.up * 0.3f, candidate + Vector3.up * 1.4f, 0.45f, LayerMask.GetMask(new string[2] { "piece", "static_solid" })))
					{
						point = candidate;
						return true;
					}
				}
			}
			return false;
		}

		private void Spawn(Target target, Vector3 point)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			GameObject prefab = ZNetScene.instance.GetPrefab(settings.Pick(clock.Wave, random));
			if ((Object)(object)prefab == (Object)null)
			{
				skipped++;
				return;
			}
			GameObject val = Object.Instantiate<GameObject>(prefab, point, Quaternion.identity);
			ZNetView component = val.GetComponent<ZNetView>();
			ZDO val2 = ((component != null) ? component.GetZDO() : null);
			Character component2 = val.GetComponent<Character>();
			if (val2 == null || (Object)(object)component2 == (Object)null)
			{
				Object.Destroy((Object)(object)val);
				skipped++;
				return;
			}
			val2.Persistent = false;
			val2.Set("wl_lab", true);
			val2.Set("wl_unified", settings["UnifiedFaction"] >= 0.5f);
			val2.Set("wl_instant", settings["InstantLoot"] >= 0.5f);
			val2.Set("wl_vanilla", settings["VanillaLoot"] >= 0.5f);
			val2.Set("wl_speed", settings.Factor("Speed", clock.Wave, 3f));
			val2.Set("wl_damage", settings.Factor("Damage", clock.Wave, 100f));
			int num = settings.Stars(clock.Wave, random);
			val2.Set("wl_gold", (int)Math.Min(5000.0, Math.Round((double)settings["Gold"] * Math.Pow(settings["GoldPerStar"], num))));
			component2.SetLevel(num + 1);
			component2.SetMaxHealth(Math.Min(10000000f, component2.GetMaxHealth() * settings.Factor("Health", clock.Wave, 100f)));
			component2.SetHealth(component2.GetMaxHealth());
			val.AddComponent<LabSpeed>();
			tracked.Add(val2.m_uid);
			MonsterAI component3 = val.GetComponent<MonsterAI>();
			if ((Object)(object)component3 != (Object)null && settings["Hunt"] >= 0.5f)
			{
				val2.Set(ZDOVars.s_huntPlayer, true);
				((BaseAI)component3).SetHuntPlayer(true);
				AccessTools.Method(typeof(MonsterAI), "SetAlerted", (Type[])null, (Type[])null).Invoke(component3, new object[1] { true });
				if ((Object)(object)target.Character != (Object)null)
				{
					AccessTools.Method(typeof(MonsterAI), "SetTarget", (Type[])null, (Type[])null).Invoke(component3, new object[1] { target.Character });
				}
				AccessTools.Method(typeof(BaseAI), "SetTargetInfo", (Type[])null, (Type[])null).Invoke(component3, new object[1] { target.Id });
			}
		}

		private RectTransform Rect(string name, Transform parent, float x, float y, float width, float height)
		{
			//IL_0014: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: 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_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			RectTransform component = new GameObject(name, new Type[1] { typeof(RectTransform) })
			{
				layer = ((Component)parent).gameObject.layer
			}.GetComponent<RectTransform>();
			((Transform)component).SetParent(parent, false);
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0f, 1f);
			component.pivot = val;
			Vector2 anchorMin = (component.anchorMax = val);
			component.anchorMin = anchorMin;
			component.anchoredPosition = new Vector2(x, 0f - y);
			component.sizeDelta = new Vector2(width, height);
			return component;
		}

		private TMP_Text Label(string text, Transform parent, float x, float y, float width, float height, int size = 16)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_0081: 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)
			TMP_Text val = (TMP_Text)AccessTools.Field(typeof(InventoryGui), "m_recipeName").GetValue(InventoryGui.instance);
			TextMeshProUGUI obj = ((Component)Rect("Label", parent, x, y, width, height)).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = val.font;
			((TMP_Text)obj).fontSharedMaterial = ((TMP_Asset)val.font).material;
			((TMP_Text)obj).fontSize = size;
			((TMP_Text)obj).enableAutoSizing = false;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)257;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)3;
			((TMP_Text)obj).margin = Vector4.zero;
			((TMP_Text)obj).text = text;
			((Graphic)obj).color = Color.white;
			((Graphic)obj).raycastTarget = false;
			return (TMP_Text)(object)obj;
		}

		private Button Button(string text, Transform parent, float x, float y, float width, Action action)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: 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_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			Button val = Object.Instantiate<Button>((Button)AccessTools.Field(typeof(StoreGui), "m_buyButton").GetValue(StoreGui.instance), parent);
			RectTransform val2 = (RectTransform)((Component)val).transform;
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(0f, 1f);
			val2.pivot = val3;
			Vector2 anchorMin = (val2.anchorMax = val3);
			val2.anchorMin = anchorMin;
			val2.anchoredPosition = new Vector2(x, 0f - y);
			val2.sizeDelta = new Vector2(width, 34f);
			((Transform)val2).localScale = Vector3.one;
			((Component)val).gameObject.SetActive(true);
			((Selectable)val).interactable = true;
			val.onClick = new ButtonClickedEvent();
			((UnityEvent)val.onClick).AddListener((UnityAction)delegate
			{
				action();
			});
			TMP_Text[] componentsInChildren = ((Component)val).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text obj in componentsInChildren)
			{
				obj.text = text;
				obj.fontSize = 15f;
			}
			return val;
		}

		private InputField InputBox(string text, Transform parent, float x, float y, float width, Action<string> changed)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = Rect("Input", parent, x, y, width, 30f);
			Image val2 = ((Component)val).gameObject.AddComponent<Image>();
			((Graphic)val2).color = new Color(0.12f, 0.08f, 0.045f, 1f);
			InputField obj = ((Component)val).gameObject.AddComponent<InputField>();
			((Selectable)obj).targetGraphic = (Graphic)(object)val2;
			obj.characterLimit = 4096;
			Text val3 = ((Component)Rect("Text", (Transform)(object)val, 7f, 4f, width - 14f, 24f)).gameObject.AddComponent<Text>();
			val3.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
			val3.fontSize = 15;
			((Graphic)val3).color = Color.white;
			val3.supportRichText = false;
			obj.textComponent = val3;
			obj.text = text;
			((UnityEvent<string>)(object)obj.onEndEdit).AddListener((UnityAction<string>)delegate(string v)
			{
				changed(v);
			});
			((Selectable)obj).interactable = authorized;
			return obj;
		}

		private void ToggleUi()
		{
			//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)
			if (uiOpen)
			{
				CloseUi();
			}
			else if (!((Object)(object)InventoryGui.instance == (Object)null) && !((Object)(object)StoreGui.instance == (Object)null))
			{
				if ((Object)(object)ui == (Object)null)
				{
					BuildUi();
				}
				draft = settings.Copy();
				draftRevision = revision;
				oldLock = Cursor.lockState;
				oldVisible = Cursor.visible;
				uiOpen = true;
				ui.SetActive(true);
				BuildPage();
				Send("sync");
			}
		}

		private void CloseUi()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ui != (Object)null)
			{
				ui.SetActive(false);
			}
			if (uiOpen)
			{
				Cursor.lockState = oldLock;
				Cursor.visible = oldVisible;
				Input.ResetInputAxes();
			}
			uiOpen = false;
		}

		private void DestroyUi()
		{
			if ((Object)(object)ui != (Object)null)
			{
				Object.Destroy((Object)(object)ui);
			}
			ui = null;
			page = null;
		}

		private void LateUpdate()
		{
			int num;
			if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)InventoryGui.instance != (Object)null)
			{
				num = (InventoryGui.IsVisible() ? 1 : 0);
				if (num != 0 && (Object)(object)net != (Object)null && !uiOpen)
				{
					ToggleUi();
				}
			}
			else
			{
				num = 0;
			}
			if (num == 0 && uiOpen)
			{
				CloseUi();
			}
			if (uiOpen)
			{
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
				UpdateUiStatus();
			}
		}

		private void BuildUi()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0058: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = (RectTransform)AccessTools.Field(typeof(InventoryGui), "m_crafting").GetValue(InventoryGui.instance);
			RectTransform val2 = Rect("WaveLab", ((Transform)val).parent, 0f, 0f, 0f, 0f);
			val2.anchorMin = Vector2.zero;
			val2.anchorMax = Vector2.one;
			Vector2 offsetMin = (val2.offsetMax = Vector2.zero);
			val2.offsetMin = offsetMin;
			((Transform)val2).SetAsLastSibling();
			ui = ((Component)val2).gameObject;
			RectTransform val3 = Rect("Window", ui.transform, 20f, 400f, 1100f, 880f);
			((Transform)val3).localScale = Vector3.one * 0.65f;
			((Graphic)((Component)val3).gameObject.AddComponent<Image>()).color = new Color(0.075f, 0.045f, 0.025f, 0.97f);
			((Graphic)((Component)Rect("Accent", (Transform)(object)val3, 0f, 0f, 1100f, 3f)).gameObject.AddComponent<Image>()).color = new Color(0.9f, 0.65f, 0.22f);
			((Graphic)Label("WAVELAB · НАСТРОЙЩИК ВОЛН", (Transform)(object)val3, 22f, 17f, 1020f, 37f, 26)).color = new Color(1f, 0.8f, 0.35f);
			string[] array = new string[5] { "Старт", "Пауза / продолжить", "Следующая волна", "Очистить мобов", "Стоп + очистка" };
			string[] actions = new string[5] { "start", "pause", "next", "clear", "stop" };
			for (int i = 0; i < array.Length; i++)
			{
				int at = i;
				Button(array[i], (Transform)(object)val3, 22 + i * 207, 67f, 197f, delegate
				{
					Send(actions[at]);
				});
			}
			int num = 0;
			foreach (string item in Settings.Knobs.Select((Knob k) => k.Group).Distinct().Concat(new string[2] { "Состав", "Пресеты" }))
			{
				string g = item;
				Button(item, (Transform)(object)val3, 22f, 126 + num * 43, 175f, delegate
				{
					selectedGroup = g;
					BuildPage();
				});
				num++;
			}
			page = Rect("Page", (Transform)(object)val3, 215f, 120f, 860f, 600f);
			info = Label("", (Transform)(object)val3, 22f, 724f, 1050f, 93f, 15);
			Button("Применить на сервере", (Transform)(object)val3, 22f, 827f, 245f, delegate
			{
				try
				{
					draft.Validate();
					awaitingApply = true;
					Send("apply", draft.Encode());
				}
				catch (Exception ex)
				{
					notice = ex.Message;
				}
			});
			Button("Обновить с сервера", (Transform)(object)val3, 278f, 827f, 225f, delegate
			{
				draft = settings.Copy();
				draftRevision = revision;
				BuildPage();
				Send("sync");
			});
			Button("Копировать JSON", (Transform)(object)val3, 514f, 827f, 200f, delegate
			{
				try
				{
					draft.Validate();
					GUIUtility.systemCopyBuffer = ExportJson(draft);
					notice = "JSON скопирован. Это значения в панели; для применения нажмите Применить.";
				}
				catch (Exception ex)
				{
					notice = ex.Message;
				}
			});
			Button("Экспорт на сервере", (Transform)(object)val3, 725f, 827f, 215f, delegate
			{
				Send("export");
			});
			Button("Закрыть", (Transform)(object)val3, 951f, 827f, 125f, delegate
			{
				InventoryGui.instance.Hide();
			});
			ui.SetActive(false);
		}

		private void BuildPage()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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_0321: Unknown result type (might be due to invalid IL or missing references)
			//IL_042a: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_051b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0527: Unknown result type (might be due to invalid IL or missing references)
			//IL_0535: Unknown result type (might be due to invalid IL or missing references)
			//IL_053a: Unknown result type (might be due to invalid IL or missing references)
			//IL_053b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0542: Unknown result type (might be due to invalid IL or missing references)
			//IL_056b: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_05c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_05eb: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)page == (Object)null || draft == null)
			{
				return;
			}
			foreach (Transform item in (Transform)page)
			{
				((Component)item).gameObject.SetActive(false);
				Object.Destroy((Object)(object)((Component)item).gameObject);
			}
			((Graphic)Label(selectedGroup, (Transform)(object)page, 0f, 0f, 850f, 30f, 22)).color = new Color(1f, 0.8f, 0.35f);
			if (selectedGroup == "Состав")
			{
				Label("Формат: Prefab:вес:первая_волна, через запятую", (Transform)(object)page, 0f, 44f, 830f, 26f, 17);
				InputBox(draft.Pool, (Transform)(object)page, 0f, 80f, 830f, delegate(string value)
				{
					draft.Pool = value;
				});
				Label("Вес — относительная частота. Greyling:70:1,Neck:30:1 даёт примерно 70/30.\n\nДобавьте Greydwarf_Elite:5:6 — элитный грейдворф участвует с 6-й волны.\nТип существа и его звёзды настраиваются отдельно.\n\nДопускаются боевые существа с MonsterAI. Prefab проверяет сервер при применении.\nБоссы по умолчанию запрещены: их смерть может изменить прогрессию мира.\n\nОбычные спавнеры биома остаются: WaveLab считает и очищает только своих мобов.\nНа отдельном dedicated-сервере нужны DLL сервера и клиента, плюс adminlist.txt.\nЛокальная игра: хозяин мира имеет доступ автоматически.", (Transform)(object)page, 0f, 130f, 830f, 440f);
				return;
			}
			if (selectedGroup == "Пресеты")
			{
				string[] labels = new string[7] { "Луга", "Чёрный лес", "Болота", "Горы", "Равнины", "Туманные земли", "Пепельные земли" };
				string[] pools = new string[7] { "Greyling:70:1,Neck:30:1", "Greydwarf:70:1,Greydwarf_Shaman:15:3,Greydwarf_Elite:10:5,Troll:3:8", "Skeleton:35:1,Draugr:60:1,Draugr_Elite:8:5,Abomination:2:8", "Wolf:80:1,Fenring:15:4,StoneGolem:3:8", "Goblin:75:1,Deathsquito:10:2,GoblinShaman:10:4,GoblinBrute:5:7", "Seeker:80:1,Tick:15:2,SeekerBrute:8:6", "Charred_Melee:65:1,Charred_Archer:30:1,Morgen:5:7" };
				for (int num = 0; num < labels.Length; num++)
				{
					int at = num;
					Button(labels[num], (Transform)(object)page, 0f, 44 + num * 48, 300f, delegate
					{
						draft.Pool = pools[at];
						notice = "Выбран состав «" + labels[at] + "». Остальные крутилки сохранены. Нажмите Применить.";
					});
				}
				Button("Сброс всех крутилок", (Transform)(object)page, 350f, 44f, 350f, delegate
				{
					draft = new Settings();
					notice = "В панели значения по умолчанию. Сервер ещё не изменён.";
					BuildPage();
				});
				Button("Импорт JSON из буфера", (Transform)(object)page, 350f, 440f, 350f, delegate
				{
					try
					{
						draft = ImportJson(GUIUtility.systemCopyBuffer);
						notice = "Пресет загружен в панель. Нажмите Применить.";
						BuildPage();
					}
					catch (Exception ex)
					{
						notice = "Импорт отклонён: " + ex.Message;
					}
				});
				Label("Пресеты меняют только состав.\nКопировать JSON экспортирует значения панели.\nЭкспорт на сервере сохраняет уже применённые значения.\n\nПравки действуют на новых врагов.\nДля чистого сравнения нажмите Стоп, затем Старт.\nСеанс не запускается сам при перезапуске.\nНастройки сохраняются в BepInEx/config/local.roguenarok.wavelab.cfg.\nРедактирование cfg применяется автоматически.", (Transform)(object)page, 350f, 100f, 470f, 420f);
				return;
			}
			RectTransform val2 = Rect("Viewport", (Transform)(object)page, 0f, 40f, 850f, 555f);
			((Graphic)((Component)val2).gameObject.AddComponent<Image>()).color = new Color(0.08f, 0.055f, 0.035f, 0.4f);
			((Component)val2).gameObject.AddComponent<RectMask2D>();
			RectTransform val3 = Rect("Controls", (Transform)(object)val2, 10f, 0f, 825f, Settings.Knobs.Count((Knob knob) => knob.Group == selectedGroup) * 100);
			ScrollRect obj = ((Component)val2).gameObject.AddComponent<ScrollRect>();
			obj.viewport = val2;
			obj.content = val3;
			obj.horizontal = false;
			obj.movementType = (MovementType)2;
			obj.scrollSensitivity = 40f;
			int num2 = 0;
			foreach (Knob item2 in Settings.Knobs.Where((Knob knob) => knob.Group == selectedGroup))
			{
				Knob k = item2;
				float num3 = num2++ * 100;
				Image obj2 = ((Component)Rect("Setting", (Transform)(object)val3, 0f, num3, 805f, 96f)).gameObject.AddComponent<Image>();
				((Graphic)obj2).color = new Color(0.16f, 0.11f, 0.065f, 0.85f);
				((Graphic)obj2).raycastTarget = false;
				Label(k.Name, (Transform)(object)val3, 10f, num3 + 3f, 640f, 26f, 19);
				((Graphic)Label(k.Help, (Transform)(object)val3, 10f, num3 + 28f, 785f, 43f, 15)).color = new Color(0.87f, 0.8f, 0.66f);
				RectTransform val4 = Rect("Slider", (Transform)(object)val3, 10f, num3 + 76f, 620f, 14f);
				((Graphic)((Component)val4).gameObject.AddComponent<Image>()).color = new Color(0.22f, 0.15f, 0.07f);
				RectTransform val5 = Rect("Fill", (Transform)(object)val4, 0f, 0f, 630f, 14f);
				val5.anchorMin = Vector2.zero;
				val5.anchorMax = Vector2.one;
				Vector2 offsetMin = (val5.offsetMax = Vector2.zero);
				val5.offsetMin = offsetMin;
				Image obj3 = ((Component)val5).gameObject.AddComponent<Image>();
				((Graphic)obj3).raycastTarget = false;
				((Graphic)obj3).color = new Color(0.8f, 0.57f, 0.18f);
				RectTransform val6 = Rect("Handle", (Transform)(object)val4, 0f, -3f, 12f, 20f);
				val6.pivot = new Vector2(0.5f, 0.5f);
				val6.anchoredPosition = Vector2.zero;
				val6.sizeDelta = new Vector2(12f, 6f);
				((Graphic)((Component)val6).gameObject.AddComponent<Image>()).color = new Color(1f, 0.82f, 0.42f);
				Slider slider = ((Component)val4).gameObject.AddComponent<Slider>();
				slider.fillRect = val5;
				slider.handleRect = val6;
				((Selectable)slider).targetGraphic = (Graphic)(object)((Component)val6).GetComponent<Image>();
				slider.minValue = k.Min;
				slider.maxValue = k.Max;
				slider.wholeNumbers = k.Step == 1f;
				slider.value = draft[k.Key];
				((Selectable)slider).interactable = authorized;
				InputField field = null;
				field = InputBox(draft[k.Key].ToString("0.###", CultureInfo.InvariantCulture), (Transform)(object)val3, 654f, num3 + 69f, 140f, delegate(string value)
				{
					if (float.TryParse(value.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !float.IsNaN(result) && !float.IsInfinity(result) && result >= k.Min && result <= k.Max)
					{
						result = Mathf.Clamp(Mathf.Round(result / k.Step) * k.Step, k.Min, k.Max);
						draft[k.Key] = result;
						slider.SetValueWithoutNotify(result);
						field.SetTextWithoutNotify(result.ToString("0.###", CultureInfo.InvariantCulture));
					}
					else
					{
						notice = "Значение вне диапазона: " + k.Name;
						field.SetTextWithoutNotify(draft[k.Key].ToString(CultureInfo.InvariantCulture));
					}
				});
				((UnityEvent<float>)(object)slider.onValueChanged).AddListener((UnityAction<float>)delegate(float n)
				{
					n = Mathf.Clamp(Mathf.Round(n / k.Step) * k.Step, k.Min, k.Max);
					draft[k.Key] = n;
					field.SetTextWithoutNotify(n.ToString("0.###", CultureInfo.InvariantCulture));
				});
			}
		}

		private void UpdateUiStatus()
		{
			if (uiOpen && !((Object)(object)info == (Object)null))
			{
				bool flag = net.IsServer();
				int num = (flag ? CountAlive() : shownAlive);
				double a = (flag ? (clock.Deadline - Now) : (shownDeadline - Now));
				info.text = (authorized ? "АДМИНИСТРАТОР" : "ТОЛЬКО ПРОСМОТР · добавьте свой Steam ID в adminlist.txt") + " · " + clock.Phase + (clock.Paused ? " · ПАУЗА" : "") + " · волна " + clock.Wave + " · живых " + num + " · очередь " + clock.Pending + " · пропущено " + (flag ? skipped : shownSkipped) + "\n" + ((clock.Phase == "Подготовка" || clock.Phase == "Передышка") ? ("До волны: " + Math.Max(0.0, Math.Ceiling(a)) + " сек.\n") : "") + notice;
			}
		}
	}
	internal sealed class LabSpeed : MonoBehaviour
	{
		private void Start()
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			Character component = ((Component)this).GetComponent<Character>();
			ZNetView component2 = ((Component)this).GetComponent<ZNetView>();
			ZDO val = ((component2 != null) ? component2.GetZDO() : null);
			if ((Object)(object)component == (Object)null || val == null || !val.GetBool("wl_lab", false))
			{
				return;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(val.GetPrefab());
			Character val2 = ((prefab != null) ? prefab.GetComponent<Character>() : null);
			if (!((Object)(object)val2 == (Object)null))
			{
				if (val.GetBool("wl_unified", false))
				{
					component.m_faction = (Faction)2;
				}
				float num = Mathf.Clamp(val.GetFloat("wl_speed", 1f), 0.25f, 3f);
				component.m_speed = val2.m_speed * num;
				component.m_walkSpeed = val2.m_walkSpeed * num;
				component.m_runSpeed = val2.m_runSpeed * num;
				component.m_swimSpeed = val2.m_swimSpeed * num;
			}
		}
	}
	[HarmonyPatch(typeof(Character), "Start")]
	internal static class RemoteLabSpeedPatch
	{
		private static void Postfix(Character __instance)
		{
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (component != null)
			{
				ZDO zDO = component.GetZDO();
				if (((zDO != null) ? new bool?(zDO.GetBool("wl_lab", false)) : ((bool?)null)) == true && (Object)(object)((Component)__instance).GetComponent<LabSpeed>() == (Object)null)
				{
					((Component)__instance).gameObject.AddComponent<LabSpeed>();
				}
			}
		}
	}
	[HarmonyPatch(typeof(SEMan), "ModifyAttack")]
	internal static class LabDamagePatch
	{
		private static void Postfix(Character ___m_character, ref HitData __1)
		{
			object obj;
			if (___m_character == null)
			{
				obj = null;
			}
			else
			{
				ZNetView component = ((Component)___m_character).GetComponent<ZNetView>();
				obj = ((component != null) ? component.GetZDO() : null);
			}
			ZDO val = (ZDO)obj;
			if (val != null && val.GetBool("wl_lab", false) && __1 != null)
			{
				((DamageTypes)(ref __1.m_damage)).Modify(Mathf.Clamp(val.GetFloat("wl_damage", 1f), 0f, 100f));
			}
		}
	}
	[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
	internal static class LabLootPatch
	{
		private static bool Prefix(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result)
		{
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			ZDO val = ((component != null) ? component.GetZDO() : null);
			if (val == null || !val.GetBool("wl_lab", false) || (val.GetBool("wl_vanilla", false) && !val.GetBool("wl_suppress", false)))
			{
				return true;
			}
			__result = new List<KeyValuePair<GameObject, int>>();
			return false;
		}

		private static void Postfix(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result)
		{
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			ZDO val = ((component != null) ? component.GetZDO() : null);
			if (val != null && val.GetBool("wl_lab", false) && !val.GetBool("wl_suppress", false))
			{
				int num = val.GetInt("wl_gold", 0);
				ObjectDB instance = ObjectDB.instance;
				GameObject val2 = ((instance != null) ? instance.GetItemPrefab("Coins") : null);
				if (num > 0 && (Object)(object)val2 != (Object)null)
				{
					__result.Add(new KeyValuePair<GameObject, int>(val2, Math.Min(5000, num)));
				}
			}
		}
	}
	[HarmonyPatch(typeof(RandEventSystem), "UpdateRandomEvent")]
	internal static class LabNativeRaidPatch
	{
		private static bool Prefix()
		{
			return !Plugin.BlockNativeRaids;
		}
	}
	[HarmonyPatch(typeof(Ragdoll), "Setup")]
	internal static class LabInstantDropPatch
	{
		private static void Prefix(Ragdoll __instance, CharacterDrop __4)
		{
			object obj;
			if (__4 == null)
			{
				obj = null;
			}
			else
			{
				ZNetView component = ((Component)__4).GetComponent<ZNetView>();
				obj = ((component != null) ? component.GetZDO() : null);
			}
			ZDO val = (ZDO)obj;
			if (val != null && val.GetBool("wl_lab", false) && val.GetBool("wl_instant", false))
			{
				__instance.m_dropItems = false;
			}
		}
	}
}