Decompiled source of BountyHunt v0.2.0

plugins/BountyHunt.dll

Decompiled an hour 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 System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BountyHunt.Core;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Sansara.Admin;
using Sansara.Chronicle;
using Sansara.Clock;
using Sansara.Zones;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("BountyHunt")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+0a28a0863308e1422f11378b1be3f1937cd7a91c")]
[assembly: AssemblyProduct("BountyHunt")]
[assembly: AssemblyTitle("BountyHunt")]
[assembly: AssemblyVersion("0.2.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 Sansara.Clock
{
	public static class RealClock
	{
		private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

		public const double WorldClockLimit = 1000000000.0;

		public static double Now => (DateTime.UtcNow - Epoch).TotalSeconds;

		public static bool IsWorldTime(double t)
		{
			if (t > 0.0)
			{
				return t < 1000000000.0;
			}
			return false;
		}

		public static double FromWorld(double t, double worldNow, double realNow)
		{
			if (!IsWorldTime(t))
			{
				return t;
			}
			return realNow - (worldNow - t);
		}
	}
}
namespace Sansara.Zones
{
	public enum ZoneBlock
	{
		None,
		Wait,
		Keep
	}
	public static class ZoneReset
	{
		private enum Kind
		{
			Other,
			Player,
			Piece,
			Tombstone,
			ItemStand,
			Character
		}

		private static readonly Dictionary<int, Kind> Kinds = new Dictionary<int, Kind>();

		private static List<ZDO>[] _bySector;

		private static ZDOMan _bySectorOwner;

		private static HashSet<Vector2s> _generated;

		private static ZoneSystem _generatedOwner;

		private static HashSet<Vector2s> Generated
		{
			get
			{
				if (_generated == null || (Object)(object)_generatedOwner != (Object)(object)ZoneSystem.instance)
				{
					_generated = (HashSet<Vector2s>)(typeof(ZoneSystem).GetField("m_generatedZones", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ZoneSystem.instance));
					_generatedOwner = ZoneSystem.instance;
					if (_generated == null)
					{
						throw new MissingFieldException("ZoneSystem.m_generatedZones");
					}
				}
				return _generated;
			}
		}

		public static bool IsGenerated(Vector2s zone)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			return Generated.Contains(zone);
		}

		public static List<ZDO> Objects(Vector2s zone)
		{
			//IL_0060: 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 (_bySector == null || _bySectorOwner != ZDOMan.instance)
			{
				_bySector = (List<ZDO>[])typeof(ZDOMan).GetField("m_objectsBySector", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(ZDOMan.instance);
				_bySectorOwner = ZDOMan.instance;
				if (_bySector == null)
				{
					throw new MissingFieldException("ZDOMan.m_objectsBySector");
				}
			}
			uint sector = ZoneSystem.SectorToIndex(zone).Sector;
			List<ZDO> list = ((sector < _bySector.Length) ? _bySector[sector] : null);
			if (list == null)
			{
				return new List<ZDO>();
			}
			return new List<ZDO>(list);
		}

		public static List<Vector2s> ZonesInCircle(Vector3 center, float radius)
		{
			//IL_0013: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: 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_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: 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)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			List<Vector2s> list = new List<Vector2s>();
			radius = Mathf.Max(0f, radius);
			Vector2s zone = ZoneSystem.GetZone(new Vector3(center.x - radius, 0f, center.z - radius));
			Vector2s zone2 = ZoneSystem.GetZone(new Vector3(center.x + radius, 0f, center.z + radius));
			for (int i = zone.x; i <= zone2.x; i++)
			{
				for (int j = zone.y; j <= zone2.y; j++)
				{
					Vector3 zonePos = ZoneSystem.GetZonePos(new Vector2s(i, j));
					float num = Mathf.Max(0f, Mathf.Abs(center.x - zonePos.x) - 32f);
					float num2 = Mathf.Max(0f, Mathf.Abs(center.z - zonePos.z) - 32f);
					if (num * num + num2 * num2 <= radius * radius)
					{
						list.Add(new Vector2s(i, j));
					}
				}
			}
			return list;
		}

		public static void Destroy(ZDO zdo)
		{
			zdo.SetOwner(ZDOMan.GetSessionID());
			ZDOMan.instance.DestroyZDO(zdo);
		}

		public static bool IsPlayer(ZDO zdo)
		{
			return KindOf(zdo.GetPrefab()) == Kind.Player;
		}

		public static bool IsPlayerThing(ZDO zdo)
		{
			switch (KindOf(zdo.GetPrefab()))
			{
			case Kind.Piece:
				return zdo.GetLong(ZDOVars.s_creator, 0L) != 0;
			case Kind.Player:
			case Kind.Tombstone:
				return true;
			case Kind.ItemStand:
				return zdo.GetInt(ZDOVars.s_item, 0) != 0;
			case Kind.Character:
				return zdo.GetBool(ZDOVars.s_tamed, false);
			default:
				return false;
			}
		}

		public static bool AnyPlayerThing(IEnumerable<Vector2s> zones)
		{
			return zones.Any((Vector2s z) => Objects(z).Any(IsPlayerThing));
		}

		public static ZoneBlock Check(IEnumerable<Vector2s> zones, Func<ZDO, bool> waitFor, out ZDO blocker)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			ZoneBlock zoneBlock = ZoneBlock.None;
			blocker = null;
			foreach (Vector2s zone in zones)
			{
				foreach (ZDO item in Objects(zone))
				{
					ZoneBlock zoneBlock2 = BlockOf(item, waitFor);
					if (zoneBlock2 > zoneBlock)
					{
						zoneBlock = zoneBlock2;
						blocker = item;
						if (zoneBlock2 == ZoneBlock.Keep)
						{
							return zoneBlock;
						}
					}
				}
			}
			return zoneBlock;
		}

		private static ZoneBlock BlockOf(ZDO zdo, Func<ZDO, bool> waitFor)
		{
			switch (KindOf(zdo.GetPrefab()))
			{
			case Kind.Piece:
				if (zdo.GetLong(ZDOVars.s_creator, 0L) == 0L)
				{
					break;
				}
				goto IL_0059;
			case Kind.ItemStand:
				if (zdo.GetInt(ZDOVars.s_item, 0) == 0)
				{
					break;
				}
				goto IL_0059;
			case Kind.Character:
				if (!zdo.GetBool(ZDOVars.s_tamed, false))
				{
					break;
				}
				goto IL_0059;
			case Kind.Player:
			case Kind.Tombstone:
				{
					return ZoneBlock.Wait;
				}
				IL_0059:
				return ZoneBlock.Keep;
			}
			if (waitFor == null || !waitFor(zdo))
			{
				return ZoneBlock.None;
			}
			return ZoneBlock.Wait;
		}

		public static int DestroyAll(IEnumerable<Vector2s> zones, Func<ZDO, bool> keep = null)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			foreach (Vector2s zone in zones)
			{
				foreach (ZDO item in from z in Objects(zone)
					where !IsPlayer(z) && (keep == null || !keep(z))
					select z)
				{
					Destroy(item);
					num++;
				}
			}
			return num;
		}

		public static List<ZDO> Remaining(IEnumerable<Vector2s> zones, Func<ZDO, bool> keep = null)
		{
			return (from z in zones.SelectMany(Objects)
				where !IsPlayer(z) && (keep == null || !keep(z))
				select z).ToList();
		}

		public static void Unregister(Vector2s zone)
		{
			//IL_0005: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			Generated.Remove(zone);
			ZoneSystem instance = ZoneSystem.instance;
			if (instance.m_locationInstances.TryGetValue(zone, out var value))
			{
				value.m_placed = false;
				instance.m_locationInstances[zone] = value;
			}
		}

		public static string PrefabName(ZDO zdo)
		{
			GameObject prefab = ZNetScene.instance.GetPrefab(zdo.GetPrefab());
			return ((prefab != null) ? ((Object)prefab).name : null) ?? zdo.GetPrefab().ToString();
		}

		private static Kind KindOf(int hash)
		{
			if (!Kinds.TryGetValue(hash, out var value))
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(hash);
				value = ((!((Object)(object)prefab == (Object)null)) ? (((Object)(object)prefab.GetComponent<Player>() != (Object)null) ? Kind.Player : (((Object)(object)prefab.GetComponent<TombStone>() != (Object)null) ? Kind.Tombstone : (((Object)(object)prefab.GetComponent<ItemStand>() != (Object)null && (Object)(object)prefab.GetComponent<Piece>() == (Object)null) ? Kind.ItemStand : (((Object)(object)prefab.GetComponent<Piece>() != (Object)null) ? Kind.Piece : (((Object)(object)prefab.GetComponent<Character>() != (Object)null) ? Kind.Character : Kind.Other))))) : Kind.Other);
				Kinds[hash] = value;
			}
			return value;
		}
	}
}
namespace Sansara.Admin
{
	internal static class AdminChannel
	{
		private const float ScanSeconds = 1f;

		private const float DenialLogSeconds = 30f;

		private static ManualLogSource _log;

		private static bool _owner;

		private static bool _announced;

		private static float _nextScan;

		private static readonly HashSet<ZRpc> Registered = new HashSet<ZRpc>();

		private static readonly Dictionary<ZRpc, float> LastDenial = new Dictionary<ZRpc, float>();

		public static void Register(string module, string[] verbs, Func<string, string, string, string> handler, ManualLogSource log)
		{
			_log = log;
			if (AdminRegistry.Shared.Register(module, verbs, handler))
			{
				log.LogWarning((object)("Админ-канал: модуль " + module + " зарегистрирован повторно — обработчик заменён."));
			}
			if (AdminRegistry.Shared.ClaimOwner(typeof(AdminChannel).Assembly.GetName().Name))
			{
				_owner = true;
			}
		}

		public static void Tick()
		{
			if (!_owner || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || Time.realtimeSinceStartup < _nextScan)
			{
				return;
			}
			_nextScan = Time.realtimeSinceStartup + 1f;
			try
			{
				Scan();
			}
			catch (Exception ex)
			{
				_log.LogError((object)("Админ-канал: ошибка: " + ex));
			}
		}

		public static void CheckFile(string guid, string logPrefix, Func<string, string, string, string> handler)
		{
			string path = Path.Combine(Paths.ConfigPath, guid + ".command");
			if (!File.Exists(path))
			{
				return;
			}
			string text;
			try
			{
				text = File.ReadAllText(path).Trim();
				File.Delete(path);
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)(logPrefix + ": файл команды не прочитан: " + ex.Message));
				return;
			}
			_log.LogInfo((object)(logPrefix + ": команда «" + AdminProtocol.ForLog(text) + "»."));
			try
			{
				_log.LogInfo((object)(logPrefix + ": " + handler(text, "файл команд", "").Replace("\n", "; ")));
			}
			catch (Exception ex2)
			{
				_log.LogError((object)(logPrefix + ": ошибка команды: " + ex2));
			}
		}

		private static void Scan()
		{
			if (!_announced)
			{
				_announced = true;
				_log.LogInfo((object)("Админ-канал: владелец " + AdminRegistry.Shared.Owner + ", модули: " + string.Join(", ", AdminRegistry.Shared.Modules().ToArray()) + "."));
			}
			HashSet<ZRpc> live = new HashSet<ZRpc>();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer != null && peer.m_rpc != null)
				{
					live.Add(peer.m_rpc);
					if (Registered.Add(peer.m_rpc))
					{
						peer.m_rpc.Register<ZPackage>("Sansara_Admin", (Action<ZRpc, ZPackage>)OnRequest);
					}
				}
			}
			Registered.RemoveWhere((ZRpc r) => !live.Contains(r));
			foreach (ZRpc item in new List<ZRpc>(LastDenial.Keys))
			{
				if (!live.Contains(item))
				{
					LastDenial.Remove(item);
				}
			}
		}

		private static void OnRequest(ZRpc rpc, ZPackage pkg)
		{
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			int num = 0;
			AdminReply adminReply;
			try
			{
				int protocol = pkg.ReadInt();
				num = pkg.ReadInt();
				string module = pkg.ReadString();
				string line = pkg.ReadString();
				adminReply = Handle(rpc, protocol, module, line);
			}
			catch (Exception ex)
			{
				ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc);
				if (val == null || !val.IsReady())
				{
					return;
				}
				LogThrottled(rpc, "Админ-канал: неверный запрос от " + AdminProtocol.ForLog(val.m_playerName) + ": " + AdminProtocol.ForLog(ex.Message));
				adminReply = new AdminReply(4, "неверный запрос");
			}
			if (adminReply != null)
			{
				ZPackage val2 = new ZPackage();
				val2.Write(1);
				val2.Write(num);
				val2.Write(adminReply.Code);
				val2.Write(AdminProtocol.Truncate(adminReply.Text));
				rpc.Invoke("Sansara_AdminReply", new object[1] { val2 });
			}
		}

		private static void LogThrottled(ZRpc rpc, string message)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!LastDenial.TryGetValue(rpc, out var value) || realtimeSinceStartup - value >= 30f)
			{
				LastDenial[rpc] = realtimeSinceStartup;
				_log.LogWarning((object)message);
			}
		}

		private static AdminReply Handle(ZRpc rpc, int protocol, string module, string line)
		{
			ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc);
			if (val == null || !val.IsReady())
			{
				return null;
			}
			AdminReply adminReply = AdminProtocol.Check(protocol, module, line);
			if (adminReply != null)
			{
				return adminReply;
			}
			string text = AdminProtocol.ForLog(val.m_playerName);
			string text2 = AdminProtocol.ForLog(val.m_socket.GetHostName());
			string text3 = AdminProtocol.ForLog(AdminProtocol.Shown(module, line));
			bool flag = ((ZDOID)(ref val.m_characterID)).IsNone();
			if (flag || !ZNet.instance.IsAdmin(val.m_socket.GetHostName()))
			{
				LogThrottled(rpc, "Админ-канал: отказано " + text + " (" + text2 + "): " + text3);
				return new AdminReply(1, flag ? "отказано: персонаж не в мире (ещё не вошёл или погиб)" : "отказано: нужны права администратора");
			}
			Exception error;
			AdminReply adminReply2 = AdminRegistry.Shared.Dispatch(module, line, val.m_playerName, val.m_socket.GetHostName(), out error);
			if (error != null)
			{
				_log.LogError((object)("Админ-канал: " + text + " (" + text2 + ") → " + text3 + ": " + AdminProtocol.ForLog(error.Message) + Environment.NewLine + error.StackTrace));
			}
			else
			{
				_log.LogInfo((object)("Админ-канал: " + text + " (" + text2 + ") → " + text3 + ": " + AdminProtocol.ForLog(AdminProtocol.FirstLine(adminReply2.Text))));
			}
			return adminReply2;
		}
	}
	public sealed class AdminCommand
	{
		public string Verb = "";

		public readonly List<string> Args = new List<string>();

		public string Argument => string.Join(" ", Args);

		public static AdminCommand Parse(string text)
		{
			AdminCommand adminCommand = new AdminCommand();
			List<string> list = Tokens(text);
			if (list.Count == 0)
			{
				return adminCommand;
			}
			adminCommand.Verb = list[0].ToLowerInvariant();
			adminCommand.Args.AddRange(list.GetRange(1, list.Count - 1));
			return adminCommand;
		}

		public static List<string> Tokens(string text)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			bool flag2 = false;
			string text2 = text ?? "";
			foreach (char c in text2)
			{
				if (c == '"')
				{
					flag = !flag;
					flag2 = true;
				}
				else if (!flag && char.IsWhiteSpace(c))
				{
					if (flag2)
					{
						list.Add(stringBuilder.ToString());
						stringBuilder.Clear();
						flag2 = false;
					}
				}
				else
				{
					stringBuilder.Append(c);
					flag2 = true;
				}
			}
			if (flag2)
			{
				list.Add(stringBuilder.ToString());
			}
			return list;
		}

		public static bool TryId(string argument, out string id)
		{
			string text = (argument ?? "").Trim();
			id = ((text.Length > 1 && text[0] == '@') ? text.Substring(1).Trim() : null);
			if (id == "")
			{
				id = null;
			}
			return id != null;
		}
	}
	public sealed class AdminReply
	{
		public readonly int Code;

		public readonly string Text;

		public AdminReply(int code, string text)
		{
			Code = code;
			Text = text ?? "";
		}
	}
	public static class AdminProtocol
	{
		public const int Version = 1;

		public const string RequestRpc = "Sansara_Admin";

		public const string ReplyRpc = "Sansara_AdminReply";

		public const int MaxLine = 256;

		public const int MaxReply = 4000;

		public const int Done = 0;

		public const int Denied = 1;

		public const int NoModule = 2;

		public const int Failed = 3;

		public const int Invalid = 4;

		public const int MaxLogText = 300;

		public static string Truncate(string text)
		{
			string text2 = text ?? "";
			if (text2.Length > 4000)
			{
				return text2.Substring(0, 3999) + "…";
			}
			return text2;
		}

		public static AdminReply Check(int protocol, string module, string line)
		{
			if (protocol != 1)
			{
				return new AdminReply(4, $"неверный запрос: обновите Sansara Admin (протокол сервера {1})");
			}
			if (module == null || line == null)
			{
				return new AdminReply(4, "неверный запрос");
			}
			if (Shown(module, line).Length > 256)
			{
				return new AdminReply(4, $"строка длиннее {256} символов");
			}
			return null;
		}

		public static string Shown(string module, string line)
		{
			string text = (module ?? "").Trim();
			if (!(text == ""))
			{
				return (text + " " + (line ?? "").Trim()).Trim();
			}
			return "help";
		}

		public static string ForLog(string s)
		{
			char[] array = (s ?? "").ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (char.IsControl(array[i]))
				{
					array[i] = ' ';
				}
			}
			if (array.Length > 300)
			{
				return new string(array, 0, 300);
			}
			return new string(array);
		}

		public static string FirstLine(string text)
		{
			string text2 = (text ?? "").Replace("\r", "");
			int num = text2.IndexOf('\n');
			if (num >= 0)
			{
				return text2.Substring(0, num);
			}
			return text2;
		}

		public static string HelpLine(string module, IEnumerable<string> verbs)
		{
			return module + ": " + string.Join(", ", verbs);
		}

		public static Dictionary<string, string[]> ParseHelp(string text)
		{
			Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(StringComparer.Ordinal);
			string[] array = (text ?? "").Replace("\r", "").Split(new char[1] { '\n' });
			foreach (string text2 in array)
			{
				int num = text2.IndexOf(':');
				if (num <= 0)
				{
					continue;
				}
				string text3 = text2.Substring(0, num).Trim();
				if (IsName(text3))
				{
					dictionary[text3] = (from v in text2.Substring(num + 1).Split(new char[1] { ',' })
						select v.Trim()).Where(IsName).ToArray();
				}
			}
			return dictionary;
		}

		public static bool IsName(string s)
		{
			if (!string.IsNullOrEmpty(s))
			{
				return s.All((char c) => c >= 'a' && c <= 'z');
			}
			return false;
		}

		public static string Unknown(string verb, IEnumerable<string> verbs)
		{
			return (string.IsNullOrEmpty(verb) ? "укажите действие" : ("неизвестное действие «" + verb + "»")) + "; есть: " + string.Join(", ", verbs);
		}
	}
	public sealed class AdminRegistry
	{
		public const string SharedKey = "sansara.Admin.v1";

		public static readonly AdminRegistry Shared = new AdminRegistry("sansara.Admin.v1");

		private readonly string _key;

		private Dictionary<string, Tuple<string[], Func<string, string, string, string>>> Table
		{
			get
			{
				object data = AppDomain.CurrentDomain.GetData(_key);
				if (data == null)
				{
					Dictionary<string, Tuple<string[], Func<string, string, string, string>>> dictionary = new Dictionary<string, Tuple<string[], Func<string, string, string, string>>>(StringComparer.Ordinal);
					AppDomain.CurrentDomain.SetData(_key, dictionary);
					return dictionary;
				}
				return (data as Dictionary<string, Tuple<string[], Func<string, string, string, string>>>) ?? throw new InvalidOperationException("данные " + _key + " другого формата");
			}
		}

		public string Owner => (AppDomain.CurrentDomain.GetData(_key + ".owner") as string) ?? "";

		public AdminRegistry(string key)
		{
			_key = key;
		}

		public bool Register(string module, string[] verbs, Func<string, string, string, string> handler)
		{
			string text = (module ?? "").Trim().ToLowerInvariant();
			if (!AdminProtocol.IsName(text))
			{
				throw new ArgumentException("имя модуля — латинские буквы: " + module);
			}
			string[] array = verbs ?? new string[0];
			foreach (string text2 in array)
			{
				if (!AdminProtocol.IsName(text2))
				{
					throw new ArgumentException("глагол — латинские буквы: " + text2);
				}
			}
			if (handler == null)
			{
				throw new ArgumentNullException("handler");
			}
			Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table;
			bool result = table.ContainsKey(text);
			table[text] = Tuple.Create(verbs ?? new string[0], handler);
			return result;
		}

		public bool ClaimOwner(string assembly)
		{
			if (string.IsNullOrEmpty(assembly))
			{
				throw new ArgumentException("пустое имя сборки", "assembly");
			}
			string name = _key + ".owner";
			if (!(AppDomain.CurrentDomain.GetData(name) is string text))
			{
				AppDomain.CurrentDomain.SetData(name, assembly);
				return true;
			}
			return text == assembly;
		}

		public List<string> Modules()
		{
			return Table.Keys.OrderBy<string, string>((string k) => k, StringComparer.Ordinal).ToList();
		}

		public string HelpText()
		{
			Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table;
			if (table.Count != 0)
			{
				return string.Join("\n", from k in Modules()
					select AdminProtocol.HelpLine(k, table[k].Item1));
			}
			return "на сервере нет модулей Sansara Admin";
		}

		public AdminReply Dispatch(string module, string line, string adminName, string adminId, out Exception error)
		{
			error = null;
			try
			{
				string text = (module ?? "").Trim().ToLowerInvariant();
				if (text == "")
				{
					return new AdminReply(0, AdminProtocol.Truncate(HelpText()));
				}
				Dictionary<string, Tuple<string[], Func<string, string, string, string>>> table = Table;
				if (!table.TryGetValue(text, out var value))
				{
					return new AdminReply(2, "модуля " + text + " нет на сервере; есть: " + ((table.Count == 0) ? "ничего" : string.Join(", ", Modules())));
				}
				string text2 = value.Item2((line ?? "").Trim(), adminName ?? "", adminId ?? "");
				return new AdminReply(0, AdminProtocol.Truncate(string.IsNullOrEmpty(text2) ? "готово" : text2));
			}
			catch (Exception ex)
			{
				return new AdminReply(3, AdminProtocol.Truncate("ошибка: " + (error = ex).Message));
			}
		}
	}
	public static class AdminUi
	{
		public const string Verb = "ui";

		public const string Head = "ui=1";

		public const string NoteKey = "note";

		public const string MoreKey = "more";

		private const int Reserve = 16;

		public static string Field(object value)
		{
			char[] array = ((!(value is bool)) ? (Convert.ToString(value, CultureInfo.InvariantCulture) ?? "") : (((bool)value) ? "1" : "0")).ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] == ';')
				{
					array[i] = ',';
				}
				else if (char.IsControl(array[i]))
				{
					array[i] = ' ';
				}
			}
			return new string(array).Trim();
		}

		public static string Row(string key, params object[] fields)
		{
			if (!AdminProtocol.IsName(key))
			{
				throw new ArgumentException("ключ строки ui — латинские буквы в нижнем регистре: " + key);
			}
			return key + "=" + string.Join(";", (fields ?? new object[0]).Select(Field));
		}

		public static string Note(string text)
		{
			return Text(new string[1] { Row("note", text) });
		}

		public static string Text(IEnumerable<string> rows)
		{
			List<string> list = (rows ?? Enumerable.Empty<string>()).ToList();
			StringBuilder stringBuilder = new StringBuilder("ui=1");
			for (int i = 0; i < list.Count; i++)
			{
				if (stringBuilder.Length + 1 + list[i].Length > 3984)
				{
					stringBuilder.Append('\n').Append("more").Append('=')
						.Append((list.Count - i).ToString(CultureInfo.InvariantCulture));
					break;
				}
				stringBuilder.Append('\n').Append(list[i]);
			}
			return stringBuilder.ToString();
		}
	}
}
namespace Sansara.Chronicle
{
	public sealed class ChroniclePlayer
	{
		public long Id { get; }

		public string Name { get; }

		public ChroniclePlayer(long id, string name)
		{
			Id = id;
			Name = name ?? "";
		}
	}
	public sealed class ChronicleEntry
	{
		public double Time;

		public string Date = "";

		public string Kind = "";

		public string Id = "";

		public string Result = "";

		public List<ChroniclePlayer> Players = new List<ChroniclePlayer>();

		public const string Victory = "victory";

		public bool IsVictory
		{
			get
			{
				if (!(Result == "victory"))
				{
					return Result == "found";
				}
				return true;
			}
		}

		public static string Clean(string s)
		{
			return new string((s ?? "").Where((char c) => c != '\t' && c != '\n' && c != '\r' && c != '|' && c != '=').ToArray()).Trim();
		}

		public string ToLine()
		{
			string text = string.Join("|", Players.Select((ChroniclePlayer p) => p.Id.ToString(CultureInfo.InvariantCulture) + "=" + Clean(p.Name)));
			return string.Join("\t", Time.ToString("R", CultureInfo.InvariantCulture), Clean(Date), Clean(Kind), Clean(Id), Clean(Result), text);
		}

		public static ChronicleEntry Parse(string line)
		{
			if (string.IsNullOrWhiteSpace(line) || line.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal))
			{
				return null;
			}
			string[] array = line.TrimEnd(new char[1] { '\r' }).Split(new char[1] { '\t' });
			if (array.Length < 5 || !double.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || array[2].Trim() == "" || array[3].Trim() == "")
			{
				return null;
			}
			ChronicleEntry chronicleEntry = new ChronicleEntry
			{
				Time = result,
				Date = array[1].Trim(),
				Kind = array[2].Trim(),
				Id = array[3].Trim(),
				Result = array[4].Trim()
			};
			if (array.Length > 5)
			{
				string[] array2 = array[5].Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text in array2)
				{
					int num = text.IndexOf('=');
					long.TryParse((num > 0) ? text.Substring(0, num) : "0", NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2);
					chronicleEntry.Players.Add(new ChroniclePlayer(result2, (num >= 0) ? text.Substring(num + 1) : text));
				}
			}
			return chronicleEntry;
		}

		public static List<ChronicleEntry> ParseAll(string text, out int bad)
		{
			List<ChronicleEntry> list = new List<ChronicleEntry>();
			bad = 0;
			string[] array = (text ?? "").Split(new char[1] { '\n' });
			foreach (string text2 in array)
			{
				ChronicleEntry chronicleEntry = Parse(text2);
				if (chronicleEntry != null)
				{
					list.Add(chronicleEntry);
				}
				else if (!string.IsNullOrWhiteSpace(text2) && !text2.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal))
				{
					bad++;
				}
			}
			return list;
		}
	}
	public static class ChronicleFile
	{
		public static string PathFor(string configDir, string world)
		{
			string text = string.Concat((world ?? "world").Split(Path.GetInvalidFileNameChars()));
			return Path.Combine(configDir, "sansara.Chronicle", text + ".tsv");
		}

		public static void Append(string path, ChronicleEntry entry)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			File.AppendAllText(path, entry.ToLine() + "\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		}

		public static List<ChronicleEntry> Read(string path, out int bad)
		{
			bad = 0;
			if (!File.Exists(path))
			{
				return new List<ChronicleEntry>();
			}
			return ChronicleEntry.ParseAll(File.ReadAllText(path), out bad);
		}
	}
	public sealed class FirstVictory
	{
		public string Id;

		public string Date;

		public List<string> Names = new List<string>();

		public int Count;
	}
	public sealed class Leader
	{
		public string Name;

		public int Count;
	}
	public static class ChronicleStats
	{
		public static Dictionary<string, FirstVictory> Firsts(IEnumerable<ChronicleEntry> all, string kind)
		{
			Dictionary<string, FirstVictory> dictionary = new Dictionary<string, FirstVictory>();
			foreach (ChronicleEntry item in from e in all
				where e.Kind == kind && e.IsVictory
				orderby e.Time
				select e)
			{
				if (!dictionary.TryGetValue(item.Id, out var value))
				{
					value = new FirstVictory
					{
						Id = item.Id,
						Date = item.Date,
						Names = (from p in item.Players
							select p.Name into n
							where n != ""
							select n).Distinct().ToList()
					};
					dictionary[item.Id] = value;
				}
				value.Count++;
			}
			return dictionary;
		}

		public static List<Leader> Leaders(IEnumerable<ChronicleEntry> all, string kind)
		{
			Dictionary<long, int> dictionary = new Dictionary<long, int>();
			Dictionary<long, string> names = new Dictionary<long, string>();
			foreach (ChronicleEntry item in from e in all
				where e.Kind == kind && e.IsVictory
				orderby e.Time
				select e)
			{
				foreach (ChroniclePlayer item2 in from p in item.Players
					group p by (p.Id == 0L) ? p.Name : p.Id.ToString() into g
					select g.First())
				{
					long key = ((item2.Id != 0L) ? item2.Id : item2.Name.GetHashCode());
					dictionary[key] = ((!dictionary.TryGetValue(key, out var value)) ? 1 : (value + 1));
					if (item2.Name != "")
					{
						names[key] = item2.Name;
					}
				}
			}
			string value2;
			return (from kv in dictionary
				select new Leader
				{
					Name = (names.TryGetValue(kv.Key, out value2) ? value2 : "?"),
					Count = kv.Value
				} into l
				orderby l.Count descending
				select l).ThenBy<Leader, string>((Leader l) => l.Name, StringComparer.Ordinal).ToList();
		}

		public static string Names(IList<string> names, int max)
		{
			if (names.Count == 0)
			{
				return "";
			}
			for (int num = names.Count; num >= 1; num--)
			{
				string text = string.Join(", ", names.Take(num)) + ((num < names.Count) ? $" +{names.Count - num}" : "");
				if (text.Length <= max)
				{
					return text;
				}
			}
			string text2 = names[0];
			if (text2.Length <= max)
			{
				return text2;
			}
			return text2.Substring(0, Math.Max(1, max - 1)) + "…";
		}
	}
}
namespace BountyHunt
{
	internal static class Hunt
	{
		private struct Camp
		{
			public string Location;

			public Vector3 Pos;
		}

		private const double KillWindow = 30.0;

		private const double FightGrace = 1800.0;

		private static readonly Random Rng = new Random();

		private static readonly Dictionary<long, string> Names = new Dictionary<long, string>();

		private static readonly Dictionary<long, ZDOID> LastCharacter = new Dictionary<long, ZDOID>();

		private static readonly Dictionary<long, long> PeerPlayer = new Dictionary<long, long>();

		private static string _path;

		private static HuntState _state;

		private static double _lastNearAt;

		private static float _nextPing;

		private static string _lastWait = "";

		private static string _onlyPlayer;

		private static string _onlyId;

		private static bool _struck;

		private static bool _woundLogged;

		internal static readonly string[] Verbs = new string[5] { "status", "help", "start", "siege", "stop" };

		private const string HelpText = "status — идёт ли охота или осада, где, когда следующая; лагеря в очереди восстановления\nstart [игрок|@ID] — добыча сейчас (у этого игрока или у случайного на поверхности)\nsiege [игрок|@ID] — осада лагеря сейчас (в кольце [Siege] от этого игрока или от случайного)\nsiege x,z — осада в лагере, ближайшем к точке (игроки не нужны)\nstop — тихо убрать добычу или существ осады (лагерь встанет в очередь восстановления); следующая через IntervalHours";

		private const int SiegeTier = 5;

		private const double StrikeWindow = 30.0;

		private const float ResetWaitSeconds = 30f;

		private const float TempleKeepOff = 300f;

		private const int SearchZones = 4;

		private const string TreasureTagKey = "sansara.Treasure.tag";

		private static bool _forceSiege;

		private static bool _noSiege;

		private static double _lastStrikeAt;

		private static float _strikePrint = float.NaN;

		private static float _nextSiegePing;

		private static string _busyPath;

		private static string _busyText;

		private static Vector3? _temple;

		private static bool _templeChecked;

		private static readonly HashSet<ZDOID> OwnDestroyed = new HashSet<ZDOID>();

		private static readonly Dictionary<SiegeRestore, float> Resetting = new Dictionary<SiegeRestore, float>();

		private static readonly Dictionary<SiegeRestore, List<Vector2s>> ResetZones = new Dictionary<SiegeRestore, List<Vector2s>>();

		private static readonly Dictionary<SiegeRestore, string> WaitLogged = new Dictionary<SiegeRestore, string>();

		private static Vector3 SiegeCenter => new Vector3((float)_state.X, 0f, (float)_state.Z);

		public static void Load()
		{
			string text = Path.Combine(Paths.ConfigPath, "sansara.BountyHunt");
			Directory.CreateDirectory(text);
			string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			_path = Path.Combine(text, text2 + ".hunt");
			_state = (File.Exists(_path) ? HuntState.Parse(File.ReadAllText(_path)) : new HuntState());
			double now = World.Now;
			if (_state.NextAt <= 0.0 || _state.NextAt > now + (double)Plugin.IntervalHours.Value * 3600.0 + 60.0)
			{
				_state.NextAt = now + Math.Min(3600.0, (double)Plugin.IntervalHours.Value * 3600.0);
				Save();
			}
			ZDOMan instance = ZDOMan.instance;
			instance.m_onZDODestroyed = (Action<ZDO>)Delegate.Remove(instance.m_onZDODestroyed, new Action<ZDO>(OnDestroyed));
			ZDOMan instance2 = ZDOMan.instance;
			instance2.m_onZDODestroyed = (Action<ZDO>)Delegate.Combine(instance2.m_onZDODestroyed, new Action<ZDO>(OnDestroyed));
			Plugin.Log.LogInfo((object)(_state.Active ? string.Format("Охота: идёт {0} «{1}» ({2}), осталось {3:0} мин.", _state.IsSiege ? "осада" : "охота на", _state.Name, _state.Leader, (_state.ExpiresAt - now) / 60.0) : $"Охота: следующая добыча через {(_state.NextAt - now) / 60.0:0} мин."));
			LoadSiege();
		}

		private static void Save()
		{
			File.WriteAllText(_path, _state.Serialize());
		}

		public static void Tick()
		{
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: 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_01ff: Unknown result type (might be due to invalid IL or missing references)
			double now = World.Now;
			TickRestores();
			WriteBusy();
			if (_state.Active && _state.IsSiege)
			{
				TickSiege(now);
				return;
			}
			if (!_state.Active)
			{
				if (now >= _state.NextAt)
				{
					TrySpawn(now);
				}
				return;
			}
			ZDO val = Leader();
			if (val == null)
			{
				bool flag = HuntPlanner.IsKill(now, _lastNearAt, 30.0, _struck);
				Plugin.Log.LogInfo((object)(flag ? ("Охота: «" + _state.Name + "» убит.") : ("Охота: «" + _state.Name + "» пропал без охотников рядом.")));
				Finish(now, flag);
				return;
			}
			Vector3 position = val.GetPosition();
			bool flag2 = HuntPlanner.Wounded(val.GetFloat(ZDOVars.s_health, -1f), val.GetFloat(ZDOVars.s_maxHealth, 0f));
			if (flag2 && !_woundLogged)
			{
				_woundLogged = true;
				Plugin.Log.LogInfo((object)("Охота: «" + _state.Name + "» ранен — " + Health(val) + ", ближайший игрок в " + Nearest(position) + "."));
			}
			Watch(now, position, Plugin.FightRadius.Value, Plugin.ShotRadius.Value, flag2, deathForfeits: true);
			Strikes(val);
			if (HuntPlanner.Expired(now, _state.ExpiresAt, now - _lastNearAt <= 30.0, 1800.0))
			{
				Plugin.Log.LogInfo((object)("Охота: срок «" + _state.Name + "» вышел — добыча убрана."));
				RemoveQuarry(val, position);
				Finish(now, kill: false);
			}
			else if (Time.time >= _nextPing)
			{
				_nextPing = Time.time + Mathf.Max(1f, Plugin.PingSeconds.Value);
				World.Ping(position, HuntTexts.PingName(_state.Name));
			}
		}

		private static void Strikes(ZDO leader)
		{
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			foreach (World.PlayerInfo item in from p in World.Players()
				where p.PlayerId != 0
				select p)
			{
				int s_attackers = ZDOVars.s_attackers;
				if (leader.GetBool(s_attackers + item.Peer.m_playerName, false))
				{
					_struck = true;
					Names[item.PlayerId] = item.Peer.m_playerName;
					if (_state.Participants.Add(item.PlayerId))
					{
						flag = true;
						Plugin.Log.LogInfo((object)$"Охота: охотник {item.PlayerId} ({item.Peer.m_playerName}) ранил главаря, в {World.DistanceXZ(item.Pos, leader.GetPosition()):0} м.");
					}
				}
			}
			if (flag)
			{
				Save();
			}
		}

		private static void OnDestroyed(ZDO zdo)
		{
			//IL_0006: 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)
			try
			{
				bool own = OwnDestroyed.Remove(zdo.m_uid);
				if (_state != null && _state.Active)
				{
					if (_state.IsSiege)
					{
						OnSiegeDestroyed(zdo, own);
					}
					else if (!(zdo.GetString("sansara.BountyHunt.tag", "") != "L" + Tag(_state.StartedAt)))
					{
						Plugin.Log.LogInfo((object)($"Охота: объект «{_state.Name}» уничтожен (владелец {zdo.GetOwner()}, {Health(zdo)}, ударивших игроков " + $"{zdo.GetInt(ZDOVars.s_attackers, 0)}, ближайший игрок в {Nearest(zdo.GetPosition())})."));
						Strikes(zdo);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Охота: ошибка при гибели главаря: " + ex));
			}
		}

		private static string Health(ZDO zdo)
		{
			float num = zdo.GetFloat(ZDOVars.s_maxHealth, 0f);
			float num2 = zdo.GetFloat(ZDOVars.s_health, -1f);
			if (!(num <= 0f))
			{
				return $"здоровье {((num2 < 0f) ? num : num2):0}/{num:0}";
			}
			return "здоровье неизвестно (у клиентов не загружен)";
		}

		private static string Nearest(Vector3 pos)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			List<World.PlayerInfo> list = World.Players();
			if (list.Count != 0)
			{
				return $"{list.Min((World.PlayerInfo p) => World.DistanceXZ(p.Pos, pos)):0} м";
			}
			return "— (никого)";
		}

		private static string Tag(double startedAt)
		{
			return ((long)startedAt).ToString(CultureInfo.InvariantCulture);
		}

		private static ZDO Leader()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(_state.LeaderUser, _state.LeaderId));
			if (zDO != null && zDO.GetString("sansara.BountyHunt.tag", "") == "L" + Tag(_state.StartedAt))
			{
				return zDO;
			}
			if (_state.X == 0.0 && _state.Z == 0.0)
			{
				return null;
			}
			zDO = World.Tagged(new Vector3((float)_state.X, 0f, (float)_state.Z), 2, "sansara.BountyHunt.tag", "L" + Tag(_state.StartedAt)).FirstOrDefault();
			if (zDO != null)
			{
				_state.LeaderUser = ((ZDOID)(ref zDO.m_uid)).UserID;
				_state.LeaderId = ((ZDOID)(ref zDO.m_uid)).ID;
				Save();
				Plugin.Log.LogInfo((object)$"Охота: «{_state.Name}» найден после перезапуска ({zDO.m_uid}).");
			}
			return zDO;
		}

		private static void RemoveQuarry(ZDO leader, Vector3 pos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			HashSet<ZDO> hashSet = new HashSet<ZDO>(World.Tagged(pos, 2, "sansara.BountyHunt.tag", "R" + Tag(_state.StartedAt)));
			foreach (KeyValuePair<long, uint> item in _state.Retinue)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(item.Key, item.Value));
				if (zDO != null && zDO.GetString("sansara.BountyHunt.tag", "") == "R" + Tag(_state.StartedAt))
				{
					hashSet.Add(zDO);
				}
			}
			if (leader != null)
			{
				hashSet.Add(leader);
			}
			foreach (ZDO item2 in hashSet)
			{
				World.Destroy(item2);
			}
		}

		public static void CheckCommand()
		{
			string path = Path.Combine(Paths.ConfigPath, "sansara.BountyHunt.command");
			if (File.Exists(path))
			{
				string text;
				try
				{
					text = File.ReadAllText(path).Trim();
					File.Delete(path);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Охота: файл команды не прочитан: " + ex.Message));
					return;
				}
				Plugin.Log.LogInfo((object)("Охота: команда «" + text + "»."));
				Plugin.Log.LogInfo((object)("Охота: " + Handle(text, "файл команд", "").Replace("\n", "; ")));
			}
		}

		public static string Handle(string line, string admin, string adminId)
		{
			AdminCommand adminCommand = AdminCommand.Parse(line);
			switch (adminCommand.Verb)
			{
			case "help":
				return "status — идёт ли охота или осада, где, когда следующая; лагеря в очереди восстановления\nstart [игрок|@ID] — добыча сейчас (у этого игрока или у случайного на поверхности)\nsiege [игрок|@ID] — осада лагеря сейчас (в кольце [Siege] от этого игрока или от случайного)\nsiege x,z — осада в лагере, ближайшем к точке (игроки не нужны)\nstop — тихо убрать добычу или существ осады (лагерь встанет в очередь восстановления); следующая через IntervalHours";
			case "status":
				return Status();
			case "start":
			case "siege":
			case "stop":
				if (!Plugin.Enabled.Value)
				{
					return "модуль выключен в настройках (Enabled = false)";
				}
				if (_state == null)
				{
					return "охота ещё не запущена: мир загружается";
				}
				if (!(adminCommand.Verb == "start"))
				{
					if (!(adminCommand.Verb == "siege"))
					{
						return Stop();
					}
					return StartSiege(adminCommand.Argument);
				}
				return Start(adminCommand.Argument);
			default:
				return AdminProtocol.Unknown(adminCommand.Verb, Verbs);
			}
		}

		private static string Status()
		{
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			string text = (Plugin.Enabled.Value ? "" : "выключена в настройках; ");
			if (_state == null)
			{
				if (Plugin.Enabled.Value)
				{
					return "охота ещё не запущена: мир загружается";
				}
				return "модуль выключен в настройках (Enabled = false)";
			}
			double now = World.Now;
			string text2 = RestoreStatus();
			if (!_state.Active)
			{
				return $"{text}охоты нет; следующая через {Math.Max(0.0, (_state.NextAt - now) / 60.0):0} мин" + text2;
			}
			if (_state.IsSiege)
			{
				return text + SiegeStatus(now) + text2;
			}
			ZDO val = Leader();
			Vector3 p = (Vector3)((val != null) ? val.GetPosition() : new Vector3((float)_state.X, 0f, (float)_state.Z));
			return text + "идёт охота на «" + _state.Name + "» (" + _state.Leader + "), " + ((val != null) ? "главарь в " : "главарь не найден, место ") + Round(p) + "\n" + $"осталось {Math.Max(0.0, (_state.ExpiresAt - now) / 60.0):0} мин, охотников {_state.Participants.Count}, погибли {_state.Dead.Count}" + text2;
		}

		private static string Start(string who)
		{
			if (_state.Active)
			{
				return "уже идёт " + (_state.IsSiege ? "осада" : "охота на") + " «" + _state.Name + "» — сначала stop";
			}
			double now = World.Now;
			double nextAt = _state.NextAt;
			if (AdminCommand.TryId(who, out var id))
			{
				_onlyId = id;
			}
			else
			{
				_onlyPlayer = ((who == "") ? null : who);
			}
			_noSiege = true;
			try
			{
				TrySpawn(now);
			}
			finally
			{
				_noSiege = false;
				_onlyPlayer = null;
				_onlyId = null;
			}
			if (!_state.Active)
			{
				_state.NextAt = nextAt;
				Save();
				return "добыча не появилась: " + _lastWait + "; расписание не изменено";
			}
			return $"«{_state.Name}» ({_state.Leader}) в ({_state.X:0}, {_state.Z:0}), срок {Mathf.Max(0.05f, Plugin.LifetimeHours.Value):0.##} ч";
		}

		private static string Stop()
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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)
			if (!_state.Active)
			{
				return "охоты сейчас нет";
			}
			double now = World.Now;
			if (_state.IsSiege)
			{
				return StopSiege(now);
			}
			ZDO val = Leader();
			Vector3 pos = (Vector3)((val != null) ? val.GetPosition() : new Vector3((float)_state.X, 0f, (float)_state.Z));
			RemoveQuarry(val, pos);
			string name = _state.Name;
			Plugin.Log.LogInfo((object)("Охота: «" + name + "» убран по команде."));
			_state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0);
			_lastNearAt = 0.0;
			_struck = false;
			_woundLogged = false;
			Save();
			return $"«{name}» убран; следующая через {Plugin.IntervalHours.Value:0.##} ч";
		}

		private static void Watch(double now, Vector3 pos, float fightRadius, float shotRadius, bool wounded, bool deathForfeits)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			List<World.PlayerInfo> source = World.Players();
			foreach (World.PlayerInfo item in source.Where((World.PlayerInfo p) => p.PlayerId != 0))
			{
				float num = World.DistanceXZ(item.Pos, pos);
				if (HuntPlanner.Engaged(num, fightRadius, shotRadius, wounded))
				{
					_lastNearAt = now;
					Names[item.PlayerId] = item.Peer.m_playerName;
					if (_state.Participants.Add(item.PlayerId))
					{
						flag = true;
						Plugin.Log.LogInfo((object)string.Format("Охота: охотник {0} ({1}), в {2:0} м{3}{4}.", item.PlayerId, item.Peer.m_playerName, num, wounded ? ", главарь ранен" : "", _state.IsSiege ? " от центра лагеря" : ""));
					}
				}
			}
			HashSet<long> peerUids = new HashSet<long>();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				peerUids.Add(peer.m_uid);
				World.PlayerInfo playerInfo = source.FirstOrDefault((World.PlayerInfo p) => p.Peer != null && p.Peer.m_uid == peer.m_uid);
				if (playerInfo.Peer != null && playerInfo.PlayerId != 0L)
				{
					PeerPlayer[peer.m_uid] = playerInfo.PlayerId;
				}
				if (PeerPlayer.TryGetValue(peer.m_uid, out var value) && _state.Participants.Contains(value))
				{
					if (LastCharacter.TryGetValue(peer.m_uid, out var value2) && !((ZDOID)(ref value2)).IsNone() && value2 != peer.m_characterID && _state.Dead.Add(value))
					{
						flag = true;
						Plugin.Log.LogInfo((object)string.Format("Охота: охотник {0} погиб{1}.", value, deathForfeits ? " — без награды" : ""));
					}
					LastCharacter[peer.m_uid] = peer.m_characterID;
				}
			}
			foreach (long item2 in LastCharacter.Keys.Where((long uid) => !peerUids.Contains(uid)).ToList())
			{
				LastCharacter.Remove(item2);
				PeerPlayer.Remove(item2);
			}
			if (flag)
			{
				Save();
			}
		}

		private static void Wait(double now, double seconds, string why)
		{
			_state.NextAt = now + seconds;
			if (why != _lastWait)
			{
				_lastWait = why;
				Plugin.Log.LogInfo((object)("Охота: добыча откладывается — " + why + "."));
			}
		}

		private static void TrySpawn(double now)
		{
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			if (World.RaidRunning)
			{
				Wait(now, 60.0, "идёт набег");
				return;
			}
			List<World.PlayerInfo> list = World.Players();
			bool forceSiege = _forceSiege;
			List<World.PlayerInfo> list2 = list.Where((World.PlayerInfo p) => p.PlayerId != 0L && World.OnSurface(p.Pos) && !World.NearTrial(p.Pos) && (_onlyPlayer == null || string.Equals(p.Peer.m_playerName, _onlyPlayer, StringComparison.OrdinalIgnoreCase)) && (_onlyId == null || p.Peer.m_socket.GetHostName() == _onlyId)).ToList();
			if (list2.Count == 0)
			{
				Wait(now, 60.0, "нет игроков на поверхности мира");
				return;
			}
			int num = HuntPlanner.OpenTier(new HashSet<string>(ZoneSystem.instance.GetGlobalKeys()));
			if ((forceSiege || (!_noSiege && num >= 5 && Rng.NextDouble() < (double)Plugin.SiegeChance.Value)) && (TrySiege(now, list2, list) || forceSiege))
			{
				return;
			}
			if (Plugin.Targets.Count == 0)
			{
				Wait(now, 3600.0, "каталог пуст");
				return;
			}
			foreach (World.PlayerInfo item in list2.OrderBy((World.PlayerInfo _) => Rng.Next()).Take(3))
			{
				foreach (int item2 in HuntPlanner.TierOrder(BountyCatalog.TierOf(World.BiomeAt(item.Pos)), num, Rng))
				{
					BountyTarget bountyTarget = HuntPlanner.Pick(Plugin.Targets, item2, Rng);
					if (bountyTarget != null && FindPlace(item.Pos, bountyTarget, list, out var place))
					{
						Spawn(now, bountyTarget, place, item);
						return;
					}
				}
			}
			Wait(now, 300.0, "не нашлось места");
		}

		private static bool FindPlace(Vector3 center, BountyTarget target, List<World.PlayerInfo> players, out Vector3 place)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			float min = Mathf.Max(50f, Plugin.MinDistance.Value);
			float num = Mathf.Max(min + 50f, Plugin.MaxDistance.Value);
			for (int i = 0; i < 80; i++)
			{
				HuntPlanner.RingPoint(Rng, center.x, center.z, min, num, out var x, out var z);
				Vector3 p = new Vector3((float)x, 0f, (float)z);
				if (BountyCatalog.TierOf(World.BiomeAt(p)) == target.Tier && World.DryLand(p.x, p.z, out var height))
				{
					p.y = height + 0.5f;
					if (!players.Any((World.PlayerInfo o) => World.DistanceXZ(o.Pos, p) < min * 0.5f) && !World.NearPlayerThings(p) && !World.NearTrial(p) && !NearBusy(p))
					{
						place = p;
						return true;
					}
				}
			}
			place = Vector3.zero;
			return false;
		}

		private static void Spawn(double now, BountyTarget target, Vector3 place, World.PlayerInfo near)
		{
			//IL_0026: 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_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_02da: Unknown result type (might be due to invalid IL or missing references)
			double num = SiegePlanner.UniqueStart(now, Math.Max(_state.LastStart, _state.StartedAt));
			ZDO val = World.CreateCreature(target.Leader, place, Plugin.LeaderLevel.Value, "L" + Tag(num), target.Name);
			List<KeyValuePair<long, uint>> list = new List<KeyValuePair<long, uint>>();
			for (int i = 0; i < target.RetinueCount; i++)
			{
				float num2 = ((float)i + 0.5f) * 2f * (float)Math.PI / (float)target.RetinueCount;
				float num3 = place.x + 5f * Mathf.Cos(num2);
				float num4 = place.z + 5f * Mathf.Sin(num2);
				ZDO val2 = World.CreateCreature(target.Retinue, new Vector3(num3, WorldGenerator.instance.GetHeight(num3, num4) + 0.5f, num4), Plugin.RetinueLevel.Value, "R" + Tag(num));
				list.Add(new KeyValuePair<long, uint>(((ZDOID)(ref val2.m_uid)).UserID, ((ZDOID)(ref val2.m_uid)).ID));
			}
			double num5 = Mathf.Max(0.05f, Plugin.LifetimeHours.Value);
			_state = new HuntState
			{
				NextAt = _state.NextAt,
				Active = true,
				Name = target.Name,
				Leader = target.Leader,
				Tier = target.Tier,
				LeaderUser = ((ZDOID)(ref val.m_uid)).UserID,
				LeaderId = ((ZDOID)(ref val.m_uid)).ID,
				Retinue = list,
				StartedAt = num,
				ExpiresAt = now + num5 * 3600.0,
				X = place.x,
				Z = place.z,
				LastStart = num,
				Restores = _state.Restores
			};
			_lastNearAt = 0.0;
			_struck = false;
			_woundLogged = false;
			_nextPing = 0f;
			_lastWait = "";
			Save();
			Plugin.Log.LogInfo((object)($"Охота: «{target.Name}» ({target.Leader} ур.{Plugin.LeaderLevel.Value}, свита {target.RetinueCount} {target.Retinue}) " + $"в {Round(place)} ({target.Biome}), в {World.DistanceXZ(near.Pos, place):0} м от {near.Peer.m_playerName}; срок {num5:0.##} ч."));
			string text = HuntTexts.Spotted(target, num5);
			World.MessageAll(text);
			World.Chat(place, text);
		}

		private static void Finish(double now, bool kill)
		{
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			List<long> list = (kill ? HuntPlanner.Winners(_state.Participants, _state.Dead) : new List<long>());
			List<long> list2 = new List<long>();
			if (kill)
			{
				List<World.PlayerInfo> source = World.Players();
				foreach (long id in list)
				{
					try
					{
						bool num = Rewards.Give(_state.Tier, _state.StartedAt, id);
						if (num)
						{
							list2.Add(id);
						}
						World.PlayerInfo playerInfo = source.FirstOrDefault((World.PlayerInfo o) => o.PlayerId == id);
						if (!num && playerInfo.Peer != null)
						{
							World.Message(playerInfo.Peer.m_uid, "Охота: добыча взята, но награду выдать не удалось — сообщите администратору.");
						}
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)$"Охота: награда игроку {id}: {arg}");
					}
				}
				string value;
				string text = HuntTexts.Taken(_state.Name, list.Select((long key) => (!Names.TryGetValue(key, out value)) ? "" : value));
				World.MessageAll(text);
				World.Chat(Vector3.zero, text);
			}
			else
			{
				string text2 = HuntTexts.Escaped(_state.Name);
				World.MessageAll(text2);
				World.Chat(Vector3.zero, text2);
			}
			Plugin.Log.LogInfo((object)("Охота: итог «" + _state.Name + "» — " + (kill ? "взята" : "ушла") + "; охотники " + string.Join(", ", _state.Participants) + "; погибли " + string.Join(", ", _state.Dead) + "; награждены " + string.Join(", ", list2) + "."));
			WriteChronicle(now, kill, kill ? list : _state.Participants.ToList());
			_state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0);
			_lastNearAt = 0.0;
			_struck = false;
			_woundLogged = false;
			Save();
			Plugin.Log.LogInfo((object)$"Охота: следующая добыча через {Plugin.IntervalHours.Value:0.##} ч.");
		}

		private static void WriteChronicle(double now, bool kill, List<long> players)
		{
			try
			{
				string value;
				ChronicleEntry entry = new ChronicleEntry
				{
					Time = now,
					Date = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
					Kind = "hunt",
					Id = _state.Leader,
					Result = (kill ? "victory" : "escaped"),
					Players = players.Select((long id) => new ChroniclePlayer(id, Names.TryGetValue(id, out value) ? value : "")).ToList()
				};
				ChronicleFile.Append(ChronicleFile.PathFor(Paths.ConfigPath, ZNet.instance.GetWorldName()), entry);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Охота: летопись не записана: " + ex.Message));
			}
		}

		private static string Round(Vector3 p)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			return $"({p.x:0}, {p.y:0}, {p.z:0})";
		}

		private static string SiegeTag(double startedAt)
		{
			return "S" + Tag(startedAt);
		}

		private static bool IsTreasure(ZDO zdo)
		{
			return zdo.GetString("sansara.Treasure.tag", "") != "";
		}

		private static void LoadSiege()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			string text = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			_busyPath = Path.Combine(Paths.ConfigPath, "sansara.BountyHunt", text + ".busy.txt");
			_busyText = (File.Exists(_busyPath) ? SafeRead(_busyPath) : "");
			if (_state.Active && _state.IsSiege)
			{
				List<ZDO> list = World.Tagged(SiegeCenter, 4, "sansara.BountyHunt.tag", SiegeTag(_state.StartedAt));
				_state.Mobs = list.Select((ZDO z) => new KeyValuePair<long, uint>(((ZDOID)(ref z.m_uid)).UserID, ((ZDOID)(ref z.m_uid)).ID)).ToList();
				Save();
				Plugin.Log.LogInfo((object)($"Охота: осада «{_state.Name}» — после перезапуска найдено {list.Count} из {HuntTexts.Creatures(_state.Total)}, " + $"погибло до перезапуска {_state.Killed}."));
				if (list.Count == 0 && _state.Total > 0)
				{
					Plugin.Log.LogWarning((object)("Охота: осада «" + _state.Name + "» — существ осады в мире нет (откат мира или стёрты); осада окончена без победы и наград."));
					QueueRestore(new List<KeyValuePair<long, uint>>());
					_state = _state.Next(World.Now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0);
					Save();
				}
			}
			foreach (SiegeRestore restore in _state.Restores)
			{
				Plugin.Log.LogInfo((object)("Охота: лагерь " + Zones(restore.Zones) + " ждёт восстановления" + ((restore.After > RealClock.Now) ? $" (через {(restore.After - RealClock.Now) / 60.0:0} мин)" : "") + "."));
			}
			WriteBusy();
		}

		private static string SafeRead(string path)
		{
			try
			{
				return File.ReadAllText(path);
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static string StartSiege(string argument)
		{
			if (_state.Active)
			{
				return "уже идёт " + (_state.IsSiege ? "осада" : "охота на") + " «" + _state.Name + "» — сначала stop";
			}
			double now = World.Now;
			double nextAt = _state.NextAt;
			if (SiegePlanner.TryParsePoint(argument, out var x, out var z))
			{
				string text = SiegeAt(now, x, z);
				if (text != null)
				{
					return "осада не началась: " + text + "; расписание не изменено";
				}
			}
			else
			{
				if (AdminCommand.TryId(argument, out var id))
				{
					_onlyId = id;
				}
				else
				{
					_onlyPlayer = ((argument == "") ? null : argument);
				}
				_forceSiege = true;
				try
				{
					TrySpawn(now);
				}
				finally
				{
					_forceSiege = false;
					_onlyPlayer = null;
					_onlyId = null;
				}
				if (!_state.Active)
				{
					_state.NextAt = nextAt;
					Save();
					return "осада не началась: " + _lastWait + "; расписание не изменено";
				}
			}
			return $"осада «{_state.Name}» ({_state.Location}) в ({_state.X:0}, {_state.Z:0}): {HuntTexts.Creatures(_state.Total)}, " + $"метка {SiegeTag(_state.StartedAt)}, срок {Mathf.Max(0.05f, Plugin.SiegeLifetimeHours.Value):0.##} ч";
		}

		private static bool TrySiege(double now, List<World.PlayerInfo> players, List<World.PlayerInfo> all)
		{
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			if (!_forceSiege && HuntPlanner.OpenTier(new HashSet<string>(ZoneSystem.instance.GetGlobalKeys())) < 5)
			{
				Wait(now, 60.0, "Равнины ещё не открыты — осады нет");
				return false;
			}
			List<Camp> list = Camps();
			if (list.Count == 0)
			{
				Wait(now, 300.0, "в мире нет лагерей " + Plugin.SiegeLocations.Value);
				return false;
			}
			float min = Mathf.Max(50f, Plugin.SiegeMinDistance.Value);
			float max = Mathf.Max(min + 50f, Plugin.SiegeMaxDistance.Value);
			foreach (World.PlayerInfo player in players.OrderBy((World.PlayerInfo _) => Rng.Next()).Take(3))
			{
				foreach (Camp item in (from _ in list.Where(delegate(Camp c)
					{
						//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)
						float num = World.DistanceXZ(player.Pos, c.Pos);
						return num >= min && num <= max;
					})
					orderby Rng.Next()
					select _).ToList().Take(20))
				{
					if (Unsafe(item, all, min * 0.5f) == null)
					{
						string text = SpawnSiege(now, item, $"в {World.DistanceXZ(player.Pos, item.Pos):0} м от {player.Peer.m_playerName}");
						if (text == null)
						{
							return true;
						}
						Wait(now, 300.0, text);
						return false;
					}
				}
			}
			Wait(now, 300.0, "не нашлось лагеря для осады");
			return false;
		}

		private static string SiegeAt(double now, double x, double z)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: 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_0090: 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)
			Vector3 point = new Vector3((float)x, 0f, (float)z);
			List<Camp> list = (from c in Camps()
				orderby World.DistanceXZ(c.Pos, point)
				select c).ToList();
			if (list.Count == 0)
			{
				return "в мире нет лагерей " + Plugin.SiegeLocations.Value;
			}
			string text = null;
			foreach (Camp item in list.Take(10))
			{
				string text2 = Unsafe(item, null, 0f);
				if (text2 == null)
				{
					return SpawnSiege(now, item, $"в {World.DistanceXZ(item.Pos, point):0} м от точки ({x:0}, {z:0})");
				}
				text = text ?? ("ближайший лагерь " + Round(item.Pos) + " не подходит — " + text2);
			}
			return text;
		}

		private static List<Camp> Camps()
		{
			//IL_004e: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = new HashSet<string>(Plugin.SiegeLocations.Value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries), StringComparer.OrdinalIgnoreCase);
			List<Camp> list = new List<Camp>();
			foreach (KeyValuePair<Vector2s, LocationInstance> locationInstance in ZoneSystem.instance.m_locationInstances)
			{
				string text = LocationName(locationInstance.Value);
				if (text != null && hashSet.Contains(text))
				{
					list.Add(new Camp
					{
						Location = text,
						Pos = locationInstance.Value.m_position
					});
				}
			}
			return list;
		}

		private static string LocationName(LocationInstance instance)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			ZoneLocation location = instance.m_location;
			if (location == null)
			{
				return null;
			}
			if (string.IsNullOrEmpty(location.m_prefabName))
			{
				return location.m_prefab.Name;
			}
			return location.m_prefabName;
		}

		private static string Unsafe(Camp camp, List<World.PlayerInfo> players, float keepOff)
		{
			//IL_001a: 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_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (World.NearPlayerThings(camp.Pos))
			{
				return "рядом вещи игроков";
			}
			if (World.NearTrial(camp.Pos))
			{
				return "рядом испытание Mine Portal";
			}
			Vector3? val = StartTemple();
			if (val.HasValue && World.DistanceXZ(val.Value, camp.Pos) < 300f)
			{
				return "рядом стартовый алтарь";
			}
			if (NearBusy(camp.Pos))
			{
				return "лагерь ждёт восстановления";
			}
			if (players != null && players.Any((World.PlayerInfo p) => World.DistanceXZ(p.Pos, camp.Pos) < keepOff))
			{
				return "рядом игроки";
			}
			return null;
		}

		private static Vector3? StartTemple()
		{
			//IL_0029: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			if (!_templeChecked)
			{
				_templeChecked = true;
				foreach (KeyValuePair<Vector2s, LocationInstance> locationInstance in ZoneSystem.instance.m_locationInstances)
				{
					if (LocationName(locationInstance.Value) == "StartTemple")
					{
						_temple = locationInstance.Value.m_position;
						break;
					}
				}
			}
			return _temple;
		}

		private static string SpawnSiege(double now, Camp camp, string where)
		{
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0427: Unknown result type (might be due to invalid IL or missing references)
			//IL_044c: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			List<KeyValuePair<string, int>> list2 = SiegePlanner.ParseMix(Plugin.SiegeMix.Value, list);
			list.ForEach(delegate(string e)
			{
				Plugin.Log.LogWarning((object)("Охота: Siege.Mix — " + e));
			});
			foreach (KeyValuePair<string, int> item in list2.Where((KeyValuePair<string, int> m) => (Object)(object)ZNetScene.instance.GetPrefab(m.Key) == (Object)null).ToList())
			{
				Plugin.Log.LogWarning((object)("Охота: существа " + item.Key + " нет в игре — в осаде пропущено."));
				list2.Remove(item);
			}
			if (list2.Count == 0)
			{
				return "состав осады (Siege.Mix) пуст";
			}
			int count = SiegePlanner.Count(Plugin.SiegeMinCount.Value, Plugin.SiegeMaxCount.Value, Rng);
			List<string> list3 = SiegePlanner.Compose(list2, count, Rng);
			double num = SiegePlanner.UniqueStart(now, Math.Max(_state.LastStart, _state.StartedAt));
			string text = SiegeTag(num);
			int num2 = Mathf.Max(1, Plugin.SiegeLevel.Value);
			float num3 = Mathf.Max(8f, Plugin.SiegeSpreadRadius.Value);
			List<KeyValuePair<long, uint>> list4 = new List<KeyValuePair<long, uint>>();
			foreach (string item2 in list3)
			{
				HuntPlanner.RingPoint(Rng, camp.Pos.x, camp.Pos.z, 6.0, num3, out var x, out var z);
				float height = WorldGenerator.instance.GetHeight((float)x, (float)z);
				ZDO val = World.CreateCreature(item2, new Vector3((float)x, height + 1f, (float)z), num2, text);
				list4.Add(new KeyValuePair<long, uint>(((ZDOID)(ref val.m_uid)).UserID, ((ZDOID)(ref val.m_uid)).ID));
			}
			double num4 = Mathf.Max(0.05f, Plugin.SiegeLifetimeHours.Value);
			string text2 = SiegePlanner.CampName(camp.Location);
			_state = new HuntState
			{
				NextAt = _state.NextAt,
				Active = true,
				Kind = "siege",
				Name = text2,
				Leader = camp.Location,
				Location = camp.Location,
				Tier = 5,
				StartedAt = num,
				ExpiresAt = now + num4 * 3600.0,
				X = camp.Pos.x,
				Z = camp.Pos.z,
				Mobs = list4,
				Total = list4.Count,
				Restores = _state.Restores,
				LastStart = num
			};
			_lastNearAt = 0.0;
			_lastStrikeAt = 0.0;
			_strikePrint = float.NaN;
			_nextSiegePing = 0f;
			_lastWait = "";
			Save();
			WriteBusy();
			string text3 = string.Join(", ", from c in list3
				group c by c into g
				select $"{g.Key} ×{g.Count()}");
			Plugin.Log.LogInfo((object)($"Охота: осада «{text2}» ({camp.Location}) в {Round(camp.Pos)}, {where}: {HuntTexts.Creatures(list4.Count)} ур.{num2} ({text3}), " + $"метка {text}, зоны {Zones(SiegePlanner.ZonesInCircle(camp.Pos.x, camp.Pos.z, Plugin.SiegeRestoreRadius.Value))}; срок {num4:0.##} ч."));
			string text4 = HuntTexts.SiegeStarted(list4.Count, Math.Max(5, BountyCatalog.TierOf(World.BiomeAt(camp.Pos))), num4);
			World.MessageAll(text4);
			World.Chat(camp.Pos, text4);
			return null;
		}

		private static void TickSiege(double now)
		{
			//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_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			Vector3 siegeCenter = SiegeCenter;
			List<ZDO> list = SiegeMobs();
			int num = Math.Min(_state.Killed, _state.Total);
			Watch(now, siegeCenter, Plugin.SiegeFightRadius.Value, 0f, wounded: false, Plugin.SiegeDeathForfeits.Value);
			SiegeMarks(list);
			float num2 = (float)(list.Count + num) + list.Sum((ZDO z) => z.GetFloat(ZDOVars.s_health, -1f) + (float)z.GetInt(ZDOVars.s_attackers, 0));
			if (!float.IsNaN(_strikePrint) && Math.Abs(num2 - _strikePrint) > 0.01f)
			{
				_lastStrikeAt = now;
			}
			_strikePrint = num2;
			if (!_state.HalfAnnounced && list.Count > 0 && SiegePlanner.HalfDown(num, _state.Total))
			{
				_state.HalfAnnounced = true;
				Save();
				Plugin.Log.LogInfo((object)$"Охота: осада — погибло {num} из {_state.Total}.");
				string text = HuntTexts.SiegeHalf(list.Count);
				World.MessageAll(text);
				World.Chat(siegeCenter, text);
			}
			int count = SiegeWinners().Count;
			if (SiegePlanner.Won(num, _state.Total, Plugin.SiegeWinShare.Value, count))
			{
				Plugin.Log.LogInfo((object)$"Охота: осада — погибло {num} из {_state.Total} (нужно {SiegePlanner.Needed(_state.Total, Plugin.SiegeWinShare.Value)}), охотников {count}: победа.");
				FinishSiege(now, won: true, list);
				return;
			}
			if (list.Count == 0)
			{
				Plugin.Log.LogInfo((object)$"Охота: осада — живых существ не осталось, погибло {num} из {_state.Total}, охотников {count}: не победа.");
				FinishSiege(now, won: false, list);
				return;
			}
			bool fighting = now - _lastNearAt <= 30.0 || now - _lastStrikeAt <= 30.0;
			if (HuntPlanner.Expired(now, _state.ExpiresAt, fighting, 1800.0))
			{
				Plugin.Log.LogInfo((object)$"Охота: срок осады вышел — погибло {num} из {_state.Total}, оставшиеся убраны.");
				FinishSiege(now, won: false, list);
			}
			else if (Time.time >= _nextSiegePing)
			{
				_nextSiegePing = Time.time + Mathf.Max(1f, Plugin.PingSeconds.Value);
				World.Ping(siegeCenter, HuntTexts.SiegePingName(_state.Name));
			}
		}

		private static List<ZDO> SiegeMobs()
		{
			return ByIds(_state.Mobs, SiegeTag(_state.StartedAt));
		}

		private static List<ZDO> ByIds(IEnumerable<KeyValuePair<long, uint>> ids, string tag)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			List<ZDO> list = new List<ZDO>();
			foreach (KeyValuePair<long, uint> id in ids)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(id.Key, id.Value));
				if (zDO != null && !OwnDestroyed.Contains(zDO.m_uid) && zDO.GetString("sansara.BountyHunt.tag", "") == tag)
				{
					list.Add(zDO);
				}
			}
			return list;
		}

		private static List<long> SiegeWinners()
		{
			if (!Plugin.SiegeDeathForfeits.Value)
			{
				return _state.Participants.ToList();
			}
			return HuntPlanner.Winners(_state.Participants, _state.Dead);
		}

		private static void SiegeMarks(IList<ZDO> mobs)
		{
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			if (mobs.Count == 0)
			{
				return;
			}
			bool flag = false;
			foreach (World.PlayerInfo item in from p in World.Players()
				where p.PlayerId != 0
				select p)
			{
				int s_attackers = ZDOVars.s_attackers;
				string key = s_attackers + item.Peer.m_playerName;
				ZDO val = ((IEnumerable<ZDO>)mobs).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => z.GetBool(key, false)));
				if (val != null)
				{
					Names[item.PlayerId] = item.Peer.m_playerName;
					if (_state.Participants.Add(item.PlayerId))
					{
						flag = true;
						_lastStrikeAt = World.Now;
						Plugin.Log.LogInfo((object)$"Охота: охотник {item.PlayerId} ({item.Peer.m_playerName}) ранил существо осады, в {World.DistanceXZ(item.Pos, val.GetPosition()):0} м.");
					}
				}
			}
			if (flag)
			{
				Save();
			}
		}

		private static void OnSiegeDestroyed(ZDO zdo, bool own)
		{
			if (!own && !(zdo.GetString("sansara.BountyHunt.tag", "") != SiegeTag(_state.StartedAt)))
			{
				_state.Killed = Math.Min(_state.Total, _state.Killed + 1);
				Save();
				Plugin.Log.LogInfo((object)($"Охота: существо осады {ZoneReset.PrefabName(zdo)} погибло — {_state.Killed} из {_state.Total} " + $"(ударивших игроков {zdo.GetInt(ZDOVars.s_attackers, 0)})."));
				SiegeMarks(new List<ZDO> { zdo });
			}
		}

		private static void FinishSiege(double now, bool won, List<ZDO> mobs)
		{
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			List<long> list = (won ? SiegeWinners() : new List<long>());
			List<long> list2 = new List<long>();
			if (won)
			{
				List<World.PlayerInfo> source = World.Players();
				foreach (long id in list)
				{
					try
					{
						bool num = Rewards.Give(5, _state.StartedAt, id, Math.Max(1, Plugin.SiegeRewardCount.Value));
						if (num)
						{
							list2.Add(id);
						}
						World.PlayerInfo playerInfo = source.FirstOrDefault((World.PlayerInfo o) => o.PlayerId == id);
						if (!num && playerInfo.Peer != null)
						{
							World.Message(playerInfo.Peer.m_uid, "Охота: лагерь взят, но награду выдать не удалось — сообщите администратору.");
						}
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)$"Охота: награда игроку {id}: {arg}");
					}
				}
			}
			string value;
			string text = (won ? HuntTexts.SiegeTaken(_state.Name, list.Select((long key) => (!Names.TryGetValue(key, out value)) ? "" : value)) : HuntTexts.SiegeHeld(_state.Name));
			World.MessageAll(text);
			World.Chat(Vector3.zero, text);
			Plugin.Log.LogInfo((object)(string.Format("Охота: итог осады «{0}» — {1}; погибло {2} из {3}, живых {4}; ", _state.Name, won ? "взят" : "устоял", _state.Killed, _state.Total, mobs.Count) + "охотники " + string.Join(", ", _state.Participants) + "; погибли " + string.Join(", ", _state.Dead) + "; награждены " + string.Join(", ", list2) + "."));
			WriteChronicle(now, won, won ? list : _state.Participants.ToList());
			List<KeyValuePair<long, uint>> survivors = new List<KeyValuePair<long, uint>>();
			if (won)
			{
				survivors = mobs.Select((ZDO z) => new KeyValuePair<long, uint>(((ZDOID)(ref z.m_uid)).UserID, ((ZDOID)(ref z.m_uid)).ID)).ToList();
			}
			else
			{
				RemoveSiegeMobs(SiegeCenter, SiegeTag(_state.StartedAt), mobs);
			}
			QueueRestore(survivors);
			_state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0);
			_lastNearAt = 0.0;
			_lastStrikeAt = 0.0;
			Save();
			WriteBusy();
			Plugin.Log.LogInfo((object)$"Охота: следующая добыча через {Plugin.IntervalHours.Value:0.##} ч.");
		}

		private static string StopSiege(double now)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			int n = RemoveSiegeMobs(SiegeCenter, SiegeTag(_state.StartedAt), SiegeMobs());
			string name = _state.Name;
			Plugin.Log.LogInfo((object)("Охота: осада «" + name + "» снята по команде, убрано " + HuntTexts.Creatures(n) + "."));
			QueueRestore(new List<KeyValuePair<long, uint>>());
			_state = _state.Next(now + Math.Max(0.05, Plugin.IntervalHours.Value) * 3600.0);
			_lastNearAt = 0.0;
			_lastStrikeAt = 0.0;
			Save();
			WriteBusy();
			return $"осада «{name}» снята, убрано {HuntTexts.Creatures(n)}; лагерь восстановится через {Plugin.SiegeRestoreDelayMinutes.Value:0.##} мин, " + $"когда рядом не будет игроков; следующая охота через {Plugin.IntervalHours.Value:0.##} ч";
		}

		private static int RemoveSiegeMobs(Vector3 center, string tag, IEnumerable<ZDO> known)
		{
			//IL_0057: 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)
			HashSet<ZDO> hashSet = new HashSet<ZDO>();
			foreach (ZDO item in known ?? Enumerable.Empty<ZDO>())
			{
				if (item != null && item.GetString("sansara.BountyHunt.tag", "") == tag)
				{
					hashSet.Add(item);
				}
			}
			hashSet.UnionWith(World.Tagged(center, 4, "sansara.BountyHunt.tag", tag));
			int num = 0;
			foreach (ZDO item2 in hashSet)
			{
				if (OwnDestroyed.Add(item2.m_uid))
				{
					World.Destroy(item2);
					num++;
				}
			}
			if (OwnDestroyed.Count > 20000)
			{
				OwnDestroyed.Clear();
			}
			return num;
		}

		private static void QueueRestore(List<KeyValuePair<long, uint>> survivors)
		{
			SiegeRestore siegeRestore = new SiegeRestore
			{
				After = RealClock.Now + (double)Math.Max(0f, Plugin.SiegeRestoreDelayMinutes.Value) * 60.0,
				X = _state.X,
				Z = _state.Z,
				Tag = SiegeTag(_state.StartedAt),
				Location = _state.Location,
				Zones = SiegePlanner.ZonesInCircle(_state.X, _state.Z, Mathf.Max(1f, Plugin.SiegeRestoreRadius.Value)),
				Mobs = survivors
			};
			_state.Restores.Add(siegeRestore);
			Plugin.Log.LogInfo((object)($"Охота: лагерь {Zones(siegeRestore.Zones)} ждёт восстановления через {Plugin.SiegeRestoreDelayMinutes.Value:0.##} мин" + ((survivors.Count > 0) ? $"; выживших {survivors.Count} уберёт восстановление." : ".")));
		}

		private static void TickRestores()
		{
			//IL_0330: Unknown result type (might be due to invalid IL or missing references)
			//IL_033c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: 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_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0286: Unknown result type (might be due to invalid IL or missing references)
			if (_state.Restores.Count == 0)
			{
				return;
			}
			List<ZonePoint> list = null;
			foreach (SiegeRestore item in _state.Restores.ToList())
			{
				try
				{
					if (Resetting.ContainsKey(item))
					{
						CompleteRestore(item);
						continue;
					}
					list = list ?? (from p in World.Players()
						select SiegePlanner.ZoneOf(p.Pos.x, p.Pos.z)).ToList();
					List<Vector2s> generated = item.Zones.Select(ToZone).Where(ZoneReset.IsGenerated).ToList();
					ZDO blocker = null;
					switch (SiegePlanner.Decide(RealClock.Now, item, list, Mathf.Max(0, Plugin.SiegeRestoreClearZones.Value), () => ZoneReset.Check(generated, IsTreasure, out blocker) switch
					{
						ZoneBlock.Keep => RestoreBlock.Keep, 
						ZoneBlock.Wait => RestoreBlock.Wait, 
						_ => RestoreBlock.None, 
					}))
					{
					case RestoreStep.WaitPlayers:
						LogWait(item, "players", "Охота: восстановление лагеря " + Zones(item.Zones) + " ждёт, пока рядом нет игроков.");
						break;
					case RestoreStep.WaitThings:
					{
						string text = ((blocker == null) ? "?" : (IsTreasure(blocker) ? "клад" : ZoneReset.PrefabName(blocker)));
						string reason = "things:" + text;
						string[] obj3 = new string[7]
						{
							"Охота: восстановление лагеря ",
							Zones(item.Zones),
							" ждёт — в его зонах ",
							text,
							" (",
							null,
							null
						};
						ZDO obj4 = blocker;
						obj3[5] = Round((obj4 != null) ? obj4.GetPosition() : Vector3.zero);
						obj3[6] = "); попробую позже.";
						LogWait(item, reason, string.Concat(obj3));
						break;
					}
					case RestoreStep.Cancel:
					{
						int n = RemoveSiegeMobs(new Vector3((float)item.X, 0f, (float)item.Z), item.Tag, ByIds(item.Mobs, item.Tag));
						ManualLogSource log = Plugin.Log;
						string[] obj = new string[9]
						{
							"Охота: восстановление лагеря ",
							Zones(item.Zones),
							" отменено — в его зонах вещи игроков (",
							(blocker == null) ? "?" : ZoneReset.PrefabName(blocker),
							" в ",
							null,
							null,
							null,
							null
						};
						ZDO obj2 = blocker;
						obj[5] = Round((obj2 != null) ? obj2.GetPosition() : Vector3.zero);
						obj[6] = "); убрано ";
						obj[7] = HuntTexts.Creatures(n);
						obj[8] = " осады.";
						log.LogWarning((object)string.Concat(obj));
						DropRestore(item);
						break;
					}
					case RestoreStep.Reset:
						StartRestore(item, generated);
						break;
					}
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Охота: ошибка восстановления лагеря {Zones(item.Zones)} — снят с очереди: {arg}");
					if (ResetZones.TryGetValue(item, out var value))
					{
						foreach (Vector2s item2 in value)
						{
							try
							{
								ZoneReset.Unregister(item2);
							}
							catch (Exception ex)
							{
								Plugin.Log.LogError((object)$"Охота: зона {item2.x},{item2.y} не снята с учёта: {ex.Message}");
							}
						}
					}
					DropRestore(item);
				}
			}
		}

		private static void LogWait(SiegeRestore r, string reason, string text)
		{
			if (!WaitLogged.TryGetValue(r, out var value) || value != reason)
			{
				WaitLogged[r] = reason;
				Plugin.Log.LogInfo((object)text);
			}
		}

		private static void StartRestore(SiegeRestore r, List<Vector2s> generated)
		{
			//IL_0013: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = new Vector3((float)r.X, 0f, (float)r.Z);
			int num = RemoveSiegeMobs(val, r.Tag, ByIds(r.Mobs, r.Tag));
			string text = ((num > 0) ? $", существ осады {num}" : "");
			if (!ZoneReset.IsGenerated(ZoneSystem.GetZone(val)) || generated.Count == 0)
			{
				Plugin.Log.LogInfo((object)("Охота: лагерь " + Zones(r.Zones) + " ещё не строился — восстанавливать нечего" + text + "."));
				DropRestore(r);
			}
			else
			{
				ResetZones[r] = generated;
				Resetting[r] = Time.time;
				int num2 = ZoneReset.DestroyAll(generated, IsTreasure);
				Plugin.Log.LogInfo((object)$"Охота: восстановление лагеря {Zones(r.Zones)} — зон {generated.Count}, удалено объектов {num2}{text}.");
			}
		}

		private static void CompleteRestore(SiegeRestore r)
		{
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			List<Vector2s> list = ResetZones[r];
			List<ZDO> list2 = ZoneReset.Remaining(list, IsTreasure);
			if (list2.Count > 0 && Time.time - Resetting[r] < 30f)
			{
				foreach (ZDO item in list2)
				{
					ZoneReset.Destroy(item);
				}
				return;
			}
			if (list2.Count > 0)
			{
				string arg = string.Join(", ", from g in list2.GroupBy(ZoneReset.PrefabName)
					select $"{g.Key} ×{g.Count()}");
				Plugin.Log.LogWarning((object)$"Охота: после очистки лагеря осталось объектов {list2.Count}: {arg}.");
			}
			foreach (Vector2s item2 in list)
			{
				ZoneReset.Unregister(item2);
			}
			Plugin.Log.LogInfo((object)("Охота: лагерь восстановлен — зоны " + string.Join("; ", list.Select((Vector2s z) => $"{z.x},{z.y}")) + "; заново построится при следующем визите."));
			DropRestore(r);
		}

		private static void DropRestore(SiegeRestore r)
		{
			Resetting.Remove(r);
			ResetZones.Remove(r);
			WaitLogged.Remove(r);
			_state.Restores.Remove(r);
			Save();
			WriteBusy();
		}

		private static Vector2s ToZone(ZonePoint z)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			return new Vector2s(z.X, z.Y);
		}

		private static string Zones(IEnumerable<ZonePoint> zones)
		{
			return string.Join("; ", zones.Select((ZonePoint z) => z.ToString()));
		}

		private static List<ZonePoint> BusyZones()
		{
			List<ZonePoint> list = new List<ZonePoint>();
			if (_state == null)
			{
				return list;
			}
			if (_state.Active && _state.IsSiege)
			{
				list.AddRange(SiegePlanner.ZonesInCircle(_state.X, _state.Z, Mathf.Max(1f, Plugin.SiegeRestoreRadius.Value)));
			}
			foreach (SiegeRestore restore in _state.Restores)
			{
				list.AddRange(restore.Zones);
			}
			return list;
		}

		private static bool NearBusy(Vector3 pos)
		{
			//IL_0000: 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)
			return SiegePlanner.NearBusy(SiegePlanner.ZoneOf(pos.x, pos.z), BusyZones(), 1);
		}

		private static void WriteBusy()
		{
			if (_busyPath == null)
			{
				return;
			}
			string text = SiegePlanner.BusyText(BusyZones());
			if (text == _busyText)
			{
				return;
			}
			try
			{
				if (text == "")
				{
					File.Delete(_busyPath);
				}
				else
				{
					File.WriteAllText(_busyPath, text);
				}
				_busyText = text;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Охота: файл занятых зон не записан: " + ex.Message));
			}
		}

		private static string SiegeStatus(double now)
		{
			List<ZDO> list = SiegeMobs();
			return $"идёт осада «{_state.Name}» ({_state.Location}) в ({_state.X:0}, {_state.Z:0}), метка {SiegeTag(_state.StartedAt)}\n" + $"живых {list.Count} из {_state.Total}, погибло {_state.Killed} (для победы {SiegePlanner.Needed(_state.Total, Plugin.SiegeWinShare.Value)}), " + $"охотников {_state.Participants.Count}, осталось {Math.Max(0.0, (_state.ExpiresAt - now) / 60.0):0} мин";
		}

		private static string RestoreStatus()
		{
			if (_state.Restores.Count == 0)
			{
				return "";
			}
			double now = RealClock.Now;
			string value;
			return string.Concat(_state.Restores.Select((SiegeRestore r) => $"\nждёт восстановления: {SiegePlanner.CampName(r.Location)} ({r.X:0}, {r.Z:0}), зоны {Zones(r.Zones)} — " + (Resetting.ContainsKey(r) ? "идёт очистка" : ((r.After > now) ? $"через {(r.After - now) / 60.0:0} мин" : ((WaitLogged.TryGetValue(r, out value) && value.StartsWith("things:", StringComparison.Ordinal)) ? ("ждёт: в зонах " + value.Substring(7)) : "когда рядом не будет игроков")))));
		}
	}
	[BepInPlugin("sansara.BountyHunt", "Bounty Hunt", "0.2.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.BountyHunt";

		public const string Name = "Bounty Hunt";

		public const string Version = "0.2.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> IntervalHours;

		internal static ConfigEntry<float> LifetimeHours;

		internal static ConfigEntry<float> MinDistance;

		internal static ConfigEntry<float> MaxDistance;

		internal static ConfigEntry<float> FightRadius;

		internal static ConfigEntry<float> ShotRadius;

		internal static ConfigEntry<int> LeaderLevel;

		internal static ConfigEntry<int> RetinueLevel;

		internal static ConfigEntry<float> PingSeconds;

		internal static ConfigEntry<string> Catalog;

		internal static readonly ConfigEntry<string>[] RewardPool = new ConfigEntry<string>[9];

		internal static readonly ConfigEntry<int>[] RewardCount = new ConfigEntry<int>[9];

		internal static List<BountyTarget> Targets = new List<BountyTarget>();

		internal static ConfigEntry<float> SiegeChance;

		internal static ConfigEntry<string> SiegeLocations;

		internal static ConfigEntry<float> SiegeMinDistance;

		internal static ConfigEntry<float> SiegeMaxDistance;

		internal static ConfigEntry<int> SiegeMinCount;

		internal static ConfigEntry<int> SiegeMaxCount;

		internal static ConfigEntry<string> SiegeMix;

		internal static ConfigEntry<int> SiegeLevel;

		internal static ConfigEntry<float> SiegeSpreadRadius;

		internal static ConfigEntry<float> SiegeWinShare;

		internal static ConfigEntry<float> SiegeFightRadius;

		internal static ConfigEntry<bool> SiegeDeathForfeits;

		internal static ConfigEntry<float> SiegeLifetimeHours;

		internal static ConfigEntry<int> SiegeRewardCount;

		internal static ConfigEntry<float> SiegeRestoreRadius;

		internal static ConfigEntry<float> SiegeRestoreDelayMinutes;

		internal static ConfigEntry<int> SiegeRestoreClearZones;

		private float _nextTick;

		private bool _started;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Охота за головами.");
			IntervalHours = ((BaseUnityPlugin)this).Config.Bind<float>("General", "IntervalHours", 6f, "Через сколько часов работы сервера после прошлой охоты появляется новая добыча.");
			LifetimeHours = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LifetimeHours", 2f, "Сколько часов добыча ждёт охотников; потом уходит (если с ней не идёт бой).");
			MinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MinDistance", 600f, "Ближе всего к выбранному игроку, м.");
			MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MaxDistance", 1500f, "Дальше всего от выбранного игрока, м.");
			FightRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FightRadius", 60f, "Игроки в этом радиусе от главаря — участники охоты (награда, если не погибли), м.");
			ShotRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ShotRadius", 150f, "Пока главарь ранен, участниками считаются и игроки в этом радиусе (стрелки издалека), м; не меньше FightRadius.");
			LeaderLevel = ((BaseUnityPlugin)this).Config.Bind<int>("General", "LeaderLevel", 4, "Уровень главаря (1 — без звёзд, 3 — две звезды, 4 — сильнее двух звёзд).");
			RetinueLevel = ((BaseUnityPlugin)this).Config.Bind<int>("General", "RetinueLevel", 2, "Уровень свиты.");
			PingSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PingSeconds", 4f, "Как часто сервер ставит пинг на карте в точке главаря, с (пинг живёт 5 с).");
			Catalog = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Catalog", "Meadows:Вепрь-великан:Boar:Neck:3, BlackForest:Старый тролль:Troll:Greydwarf_Elite:2, Swamp:Вожак драугров:Draugr_Elite:Draugr:3, Mountain:Вожак-фенринг:Fenring:Wolf:4, Plains:Вождь фулингов:GoblinBrute:Goblin:4, Mistlands:Матка искателей:SeekerBrute:Seeker:3, AshLands:Обугленный воевода:Charred_Melee:Charred_Archer:3", "Биом:Имя:Главарь:Свита:Количество через запятую (биомы Meadows … AshLands, DeepNorth) — из чего выбирается добыча.");
			for (int i = 1; i <= 8; i++)
			{
				RewardPool[i] = ((BaseUnityPlugin)this).Config.Bind<string>($"Tier{i}", "RewardPool", "", "Награды Event Rewards за добычу этого уровня через запятую; пусто — без награды.");
				RewardCount[i] = ((BaseUnityPlugin)this).Config.Bind<int>($"Tier{i}", "RewardCount", 1, "Наград каждому охотнику.");
			}
			SiegeChance = ((BaseUnityPlugin)this).Config.Bind<float>("Siege", "Chance", 0.25f, "Вероятность, что подошедшая по времени охота будет осадой лагеря (если Равнины открыты и нашёлся лагерь); 0 — осад нет.");
			SiegeLocations = ((BaseUnityPlugin)this).Config.Bind<string>("Siege", "Locations", "GoblinCamp2", "Локации-лагеря для осады через запятую (имена префабов).");
			SiegeMinDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Siege", "MinDistance", 600f, "Ближе всего лагерь к выбранному игроку, м; других игроков ближе половины — нет.");
			SiegeMaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Siege", "MaxDistance", 2000f, "Дальше всего лагерь от выбранного игрока, м.");
			SiegeMinCount = ((BaseUnityPlugin)this).Config.Bind<int>("Siege", "MinCount", 30, "Наименьшее число существ осады.");
			SiegeMaxCount = ((BaseUnityPlugin)this).Config.Bind<int>("Siege", "MaxCount", 50, "Наибольшее число существ осады.");
			SiegeMix = ((BaseUnityPlugin)this).Config.Bind<string>("Siege", "Mix", "GoblinBrute:2,GoblinShaman:1,Goblin:3", "Состав осады: Существо:вес через запятую; существа, которых нет в игре, пропускаются.");
			SiegeLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Siege", "Level", 3, "Уровень суще