Decompiled source of Treasure v0.1.6

plugins/Treasure.dll

Decompiled 4 hours 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 HarmonyLib;
using Microsoft.CodeAnalysis;
using Sansara.Admin;
using Sansara.Chronicle;
using Sansara.Clock;
using Splatform;
using Treasure.Core;
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("Treasure")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.6.0")]
[assembly: AssemblyInformationalVersion("0.1.6+ae0a16433674e458d86a7b9f14512aae571a955f")]
[assembly: AssemblyProduct("Treasure")]
[assembly: AssemblyTitle("Treasure")]
[assembly: AssemblyVersion("0.1.6.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.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));
			}
		}

		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));
			}
		}
	}
}
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.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 Treasure
{
	internal static class Hoard
	{
		private const float WorldRadius = 9800f;

		private static readonly Random Rng = new Random();

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

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

		private static string _path;

		private static TreasureState _state;

		private static float _nextSign;

		private static bool _signMissingReported;

		private static string _lastWait = "";

		internal static readonly string[] Verbs = new string[5] { "status", "help", "hide", "rehide", "remove" };

		private const string HelpText = "status — спрятан ли клад, где сундук, загадка, когда следующий\nhide — спрятать клад сейчас, если его нет\nrehide — убрать текущий клад и сразу спрятать новый\nremove — тихо убрать клад; следующий через IntervalDays";

		public static void Load()
		{
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			string text = Path.Combine(Paths.ConfigPath, "sansara.Treasure");
			Directory.CreateDirectory(text);
			string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			_path = Path.Combine(text, text2 + ".treasure");
			_state = (File.Exists(_path) ? TreasureState.Parse(File.ReadAllText(_path)) : new TreasureState());
			if (RealClock.IsWorldTime(_state.NextAt) || RealClock.IsWorldTime(_state.ExpiresAt))
			{
				double timeSeconds = ZNet.instance.GetTimeSeconds();
				double now = RealClock.Now;
				_state.NextAt = RealClock.FromWorld(_state.NextAt, timeSeconds, now);
				_state.ExpiresAt = RealClock.FromWorld(_state.ExpiresAt, timeSeconds, now);
				Save();
				Plugin.Log.LogInfo((object)"Клад: сроки переведены на реальное время.");
			}
			double now2 = World.Now;
			if (_state.NextAt <= 0.0)
			{
				_state.NextAt = now2 + Math.Max(0.01, Plugin.FirstDelayHours.Value) * 3600.0;
				Save();
			}
			if (_state.Active && Riddle.IsOld(_state.Riddle))
			{
				Vector3 val = default(Vector3);
				((Vector3)(ref val))..ctor((float)_state.X, (float)_state.Y, (float)_state.Z);
				string riddle = _state.Riddle;
				_state.Riddle = (World.Landmark(val, Plugin.LandmarkRadius.Value, Plugin.LandmarkTable, out var name, out var at) ? Riddle.Text(_state.Tier, name, at.x, at.z, val.x, val.z) : Riddle.Text(_state.Tier, null, 0.0, 0.0, val.x, val.z));
				Save();
				Plugin.Log.LogInfo((object)("Клад: загадка дополнена — «" + riddle + "» → «" + _state.Riddle + "»."));
			}
			List<string> list = World.UnnamedLocations(Plugin.LandmarkTable);
			Plugin.Log.LogInfo((object)($"Клад: ориентиров в таблице {Plugin.LandmarkTable.Count}, строк добычи {Plugin.LootLines.Count}; " + string.Format("локации мира без названия ({0}): {1}.", list.Count, string.Join(", ", list.Take(60)))));
			Plugin.Log.LogInfo((object)(_state.Active ? $"Клад: спрятан ({Biomes.Names[_state.Tier]}), «{_state.Riddle}», осталось {(_state.ExpiresAt - now2) / 86400.0:0.#} дн." : $"Клад: следующий через {(_state.NextAt - now2) / 3600.0:0.#} ч."));
		}

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

		public static void Tick()
		{
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			double now = World.Now;
			if (Time.time >= _nextSign)
			{
				_nextSign = Time.time + 10f;
				UpdateSign(now);
			}
			if (!_state.Active)
			{
				if (now >= _state.NextAt)
				{
					TryHide(now);
				}
				return;
			}
			ZDO val = Chest();
			long playerId;
			string name;
			long peerUid;
			if (val == null)
			{
				Plugin.Log.LogWarning((object)"Клад: сундука клада больше нет в мире (убран не модом) — клад пропал.");
				End(now, "Клад пропал. Новый — через неделю.", "Клад пропал · " + Today());
			}
			else if ((val.GetInt(ZDOVars.s_inUse, 0) == 1 || Signature(World.Items(val)) != _state.Sig) && World.Finder(val, out playerId, out name, out peerUid))
			{
				Found(now, playerId, name, peerUid);
			}
			else if (now >= _state.ExpiresAt)
			{
				World.Destroy(val);
				Plugin.Log.LogInfo((object)"Клад: срок вышел, клад не найден — сундук убран.");
				End(now, "Клад так и не нашли — он пропал.", "Не найден · " + Today());
			}
			else
			{
				Heat(now, new Vector3((float)_state.X, (float)_state.Y, (float)_state.Z));
			}
		}

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

		private static ZDO Chest()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(_state.ChestUser, _state.ChestId));
			if (zDO != null && zDO.GetString("sansara.Treasure.tag", "") == Tag(_state.HiddenAt))
			{
				return zDO;
			}
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor((float)_state.X, (float)_state.Y, (float)_state.Z);
			zDO = World.Tagged(val, 1, "sansara.Treasure.tag", Tag(_state.HiddenAt)).FirstOrDefault() ?? World.ChestAt(val, Plugin.ChestPrefab.Value);
			if (zDO != null)
			{
				_state.ChestUser = ((ZDOID)(ref zDO.m_uid)).UserID;
				_state.ChestId = ((ZDOID)(ref zDO.m_uid)).ID;
				if (zDO.GetString("sansara.Treasure.tag", "") == "")
				{
					zDO.Set("sansara.Treasure.tag", Tag(_state.HiddenAt));
				}
				Save();
				Plugin.Log.LogInfo((object)$"Клад: сундук найден на месте после перезапуска ({zDO.m_uid}).");
			}
			return zDO;
		}

		public static void CheckCommand()
		{
			string path = Path.Combine(Paths.ConfigPath, "sansara.Treasure.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 — спрятан ли клад, где сундук, загадка, когда следующий\nhide — спрятать клад сейчас, если его нет\nrehide — убрать текущий клад и сразу спрятать новый\nremove — тихо убрать клад; следующий через IntervalDays";
			case "status":
				return Status();
			case "hide":
			case "rehide":
			case "remove":
				if (!Plugin.Enabled.Value)
				{
					return "модуль выключен в настройках (Enabled = false)";
				}
				if (_state != null)
				{
					return Command(adminCommand.Verb);
				}
				return "клад ещё не запущен: мир загружается";
			default:
				return AdminProtocol.Unknown(adminCommand.Verb, Verbs);
			}
		}

		private static string Status()
		{
			//IL_0099: 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;
			if (_state.Active)
			{
				Vector3 p = default(Vector3);
				((Vector3)(ref p))..ctor((float)_state.X, (float)_state.Y, (float)_state.Z);
				return $"{text}спрятан ({Biomes.Names[_state.Tier]}), сундук в {Round(p)}, пропадёт через {Math.Max(0.0, (_state.ExpiresAt - now) / 86400.0):0.#} дн.\n" + "загадка: " + _state.Riddle;
			}
			string text2 = $"{text}не спрятан; следующий через {Math.Max(0.0, (_state.NextAt - now) / 3600.0):0.#} ч";
			if (!(_state.Last != ""))
			{
				return text2;
			}
			return text2 + "\nпрошлый: " + _state.Last;
		}

		private static string Command(string verb)
		{
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			double now = World.Now;
			if (verb == "hide" && _state.Active)
			{
				return "клад уже спрятан — для нового используйте rehide";
			}
			string text = "";
			if (verb == "rehide" || verb == "remove")
			{
				if (_state.Active)
				{
					ZDO val = Chest();
					if (val != null)
					{
						World.Destroy(val);
					}
					text = ((val != null) ? "сундук удалён" : "сундука не было");
					Plugin.Log.LogInfo((object)("Клад: прежний клад убран по команде (" + text + ")."));
				}
				else
				{
					text = "клада не было";
				}
				_state = new TreasureState
				{
					NextAt = now + Math.Max(0.01, Plugin.IntervalDays.Value) * 86400.0,
					Last = _state.Last
				};
				Save();
				_nextSign = 0f;
				if (verb == "remove")
				{
					return $"клад убран ({text}); следующий через {Plugin.IntervalDays.Value:0.##} дн.";
				}
			}
			double nextAt = _state.NextAt;
			TryHide(now);
			if (!_state.Active)
			{
				_state.NextAt = nextAt;
				Save();
				return "клад не спрятан: " + _lastWait + "; следующий — по расписанию" + ((text != "") ? (" (прежний: " + text + ")") : "");
			}
			Vector3 p = default(Vector3);
			((Vector3)(ref p))..ctor((float)_state.X, (float)_state.Y, (float)_state.Z);
			return "спрятан (" + Biomes.Names[_state.Tier] + "), сундук в " + Round(p) + "\nзагадка: " + _state.Riddle;
		}

		private static void Heat(double now, Vector3 pos)
		{
			//IL_003c: 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)
			foreach (World.PlayerInfo item in from p in World.Players()
				where p.PlayerId != 0
				select p)
			{
				int num = Treasure.Core.Heat.Level(World.DistanceXZ(item.Pos, pos), Plugin.WarmRadius.Value, Plugin.HotRadius.Value, Plugin.CloseRadius.Value);
				int value;
				int lastLevel = (LastLevel.TryGetValue(item.PlayerId, out value) ? value : 0);
				double value2;
				double lastAt = (LastTold.TryGetValue(item.PlayerId, out value2) ? value2 : 0.0);
				if (Treasure.Core.Heat.ShouldTell(num, lastLevel, now, lastAt, Plugin.HeatCooldownSeconds.Value))
				{
					World.Message(item.Peer.m_uid, Treasure.Core.Heat.Texts[num]);
					LastTold[item.PlayerId] = now;
				}
				LastLevel[item.PlayerId] = num;
			}
		}

		private static void Found(double now, long playerId, string name, long peerUid)
		{
			int num = Biomes.OpenTier(new HashSet<string>(ZoneSystem.instance.GetGlobalKeys()));
			bool flag = Rewards.Give(num, _state.HiddenAt, playerId);
			Plugin.Log.LogInfo((object)string.Format("Клад: найден — {0} ({1}), уровень наград {2}{3}.", name, playerId, num, flag ? ", награды поставлены" : ", наград нет"));
			if (!flag)
			{
				World.Message(peerUid, "Клад ваш! Награду выдать не удалось — сообщите администратору.");
			}
			WriteChronicle(now, playerId, name);
			End(now, "Клад найден: " + name + "!", "Найден: " + name + " · " + Today());
		}

		private static void End(double now, string message, string last)
		{
			World.MessageAll(message);
			World.Chat(message);
			_state = new TreasureState
			{
				NextAt = now + Math.Max(0.01, Plugin.IntervalDays.Value) * 86400.0,
				Last = last
			};
			LastLevel.Clear();
			LastTold.Clear();
			Save();
			_nextSign = 0f;
			Plugin.Log.LogInfo((object)$"Клад: следующий через {Plugin.IntervalDays.Value:0.##} дн.");
		}

		private static void UpdateSign(double now)
		{
			ZDO val = World.HintSign();
			if (val == null)
			{
				if (!_signMissingReported)
				{
					_signMissingReported = true;
					Plugin.Log.LogInfo((object)"Клад: табличка Чертога славы (Portal Hall 0.2.0+, роль treasure.sign) не найдена — загадка только в чате.");
				}
				return;
			}
			string text;
			if (_state.Active)
			{
				text = Riddle.ForSign(_state.Riddle);
			}
			else
			{
				double num = Math.Max(0.0, (_state.NextAt - now) / 86400.0);
				string text2 = ((num < 1.0) ? "Новый клад — скоро" : $"Новый клад — через {Math.Ceiling(num):0} дн.");
				text = ((_state.Last != "") ? (_state.Last + "\n" + text2) : text2);
			}
			World.SetSign(val, text);
		}

		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 TryHide(double now)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: 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_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Unknown result type (might be due to invalid IL or missing references)
			//IL_0308: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_035e: Unknown result type (might be due to invalid IL or missing references)
			//IL_036c: Unknown result type (might be due to invalid IL or missing references)
			//IL_037a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0422: Unknown result type (might be due to invalid IL or missing references)
			//IL_049e: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: 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)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			int num = Biomes.OpenTier(new HashSet<string>(ZoneSystem.instance.GetGlobalKeys()));
			int num2 = Biomes.PickTier(num, Plugin.MaxBiomeTier.Value, Rng);
			List<World.PlayerInfo> list = World.Players();
			Vector3 val = Vector3.zero;
			string name = null;
			Vector3 at = Vector3.zero;
			bool flag = false;
			Vector3 val2 = Vector3.zero;
			float num3 = 9800f;
			if (Plugin.SearchRadius.Value > 0f)
			{
				if (list.Count == 0)
				{
					Wait(now, 60.0, "нет игроков (SearchRadius > 0)");
					return;
				}
				val2 = list[Rng.Next(list.Count)].Pos;
				num3 = Plugin.SearchRadius.Value;
			}
			for (int i = 0; i < 4000 && !flag; i++)
			{
				double num4 = (double)num3 * Math.Sqrt(Rng.NextDouble());
				double num5 = Rng.NextDouble() * 2.0 * Math.PI;
				Vector3 p = new Vector3(val2.x + (float)(num4 * Math.Cos(num5)), 0f, val2.z + (float)(num4 * Math.Sin(num5)));
				int num6 = Biomes.TierOf(World.BiomeAt(p));
				bool num7;
				if (!(Plugin.SearchRadius.Value > 0f))
				{
					num7 = num6 != num2;
				}
				else
				{
					if (num6 < 1)
					{
						continue;
					}
					num7 = num6 > Math.Min(num, Plugin.MaxBiomeTier.Value);
				}
				if (!num7 && World.DryLand(p.x, p.z, out var height))
				{
					p.y = height;
					if (!list.Any((World.PlayerInfo o) => World.DistanceXZ(o.Pos, p) < Plugin.PlayerClearance.Value) && !World.NearPlayerThings(p) && !World.NearTrial(p) && (World.Landmark(p, Plugin.LandmarkRadius.Value, Plugin.LandmarkTable, out name, out at) || i >= 3000))
					{
						val = p;
						num2 = num6;
						flag = true;
					}
				}
			}
			if (!flag)
			{
				Wait(now, 600.0, "не нашлось места в биоме " + Biomes.Names[num2]);
				return;
			}
			List<KeyValuePair<string, int>> list2 = Loot.Roll(Plugin.LootLines, num2, Rng);
			Vector3 val3 = val + Vector3.down * Mathf.Max(0f, Plugin.ChestDepth.Value);
			int added;
			ZDO val4 = World.CreateChest(Plugin.ChestPrefab.Value, val3, list2, Tag(now), out added);
			if (val4 == null)
			{
				Wait(now, 3600.0, "префаб " + Plugin.ChestPrefab.Value + " не сундук");
				return;
			}
			string text = ((name != null) ? Riddle.Text(num2, name, at.x, at.z, val.x, val.z) : Riddle.Text(num2, null, 0.0, 0.0, val.x, val.z));
			_state = new TreasureState
			{
				NextAt = _state.NextAt,
				Active = true,
				ChestUser = ((ZDOID)(ref val4.m_uid)).UserID,
				ChestId = ((ZDOID)(ref val4.m_uid)).ID,
				X = val3.x,
				Y = val3.y,
				Z = val3.z,
				Tier = num2,
				HiddenAt = now,
				ExpiresAt = now + Math.Max(0.01, Plugin.LifetimeDays.Value) * 86400.0,
				Riddle = text,
				Sig = Signature(World.Items(val4)),
				Last = _state.Last
			};
			_lastWait = "";
			Save();
			_nextSign = 0f;
			Plugin.Log.LogInfo((object)(string.Format("Клад: спрятан в {0} ({1}), предметов {2} ({3}); ", Round(val3), Biomes.Names[num2], added, string.Join(", ", list2.Select((KeyValuePair<string, int> l) => l.Key + "×" + l.Value))) + "загадка «" + text + "»" + ((name != null) ? $", ориентир в {Round(at)} ({World.DistanceXZ(at, val):0} м)." : ".")));
			World.MessageAll("Спрятан новый клад! Загадка — в чате и в Чертоге славы.");
			World.Chat("Загадка клада: " + Riddle.ForChat(text) + ".");
		}

		private static void WriteChronicle(double now, long playerId, string name)
		{
			try
			{
				ChronicleEntry entry = new ChronicleEntry
				{
					Time = ZNet.instance.GetTimeSeconds(),
					Date = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
					Kind = "treasure",
					Id = Biomes.Names[_state.Tier],
					Result = "victory",
					Players = new List<ChroniclePlayer>
					{
						new ChroniclePlayer(playerId, name)
					}
				};
				ChronicleFile.Append(ChronicleFile.PathFor(Paths.ConfigPath, ZNet.instance.GetWorldName()), entry);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Клад: летопись не записана: " + ex.Message));
			}
		}

		private static string Signature(byte[] bytes)
		{
			if (bytes == null)
			{
				return "0:0";
			}
			uint num = 2166136261u;
			foreach (byte b in bytes)
			{
				num = (num ^ b) * 16777619;
			}
			return bytes.Length.ToString(CultureInfo.InvariantCulture) + ":" + num.ToString("x8", CultureInfo.InvariantCulture);
		}

		private static string Today()
		{
			return DateTime.Now.ToString("dd.MM", CultureInfo.InvariantCulture);
		}

		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})";
		}
	}
	[BepInPlugin("sansara.Treasure", "Treasure", "0.1.6")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.Treasure";

		public const string Name = "Treasure";

		public const string Version = "0.1.6";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> IntervalDays;

		internal static ConfigEntry<float> LifetimeDays;

		internal static ConfigEntry<float> FirstDelayHours;

		internal static ConfigEntry<string> ChestPrefab;

		internal static ConfigEntry<float> ChestDepth;

		internal static ConfigEntry<int> MaxBiomeTier;

		internal static ConfigEntry<float> LandmarkRadius;

		internal static ConfigEntry<float> PlayerClearance;

		internal static ConfigEntry<float> SearchRadius;

		internal static ConfigEntry<float> WarmRadius;

		internal static ConfigEntry<float> HotRadius;

		internal static ConfigEntry<float> CloseRadius;

		internal static ConfigEntry<float> HeatCooldownSeconds;

		internal static ConfigEntry<string> LootText;

		internal static ConfigEntry<string> LandmarksText;

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

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

		internal static List<LootLine> LootLines = new List<LootLine>();

		internal static List<KeyValuePair<string, string>> LandmarkTable = new List<KeyValuePair<string, string>>();

		private float _nextTick;

		private bool _started;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Клады.");
			IntervalDays = ((BaseUnityPlugin)this).Config.Bind<float>("General", "IntervalDays", 7f, "Через сколько дней (работы сервера) после находки или пропажи прячется новый клад.");
			LifetimeDays = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LifetimeDays", 7f, "Сколько дней клад ждёт; не найден — пропадает.");
			FirstDelayHours = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FirstDelayHours", 1f, "Через сколько часов после первого запуска мода прячется первый клад.");
			ChestPrefab = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ChestPrefab", "TreasureChest_meadows_buried", "Сундук клада (префаб с Container; закопанный клад игры виден Вишбону).");
			ChestDepth = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ChestDepth", 0.3f, "Насколько сундук ниже поверхности земли, м.");
			MaxBiomeTier = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxBiomeTier", 7, "Самый поздний биом для клада (1 Луга … 7 Пепельные земли); выбирается из открытых, поздние чаще.");
			LandmarkRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LandmarkRadius", 600f, "Ориентир для загадки ищется в этом радиусе от клада, м; нет — «от центра мира».");
			PlayerClearance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PlayerClearance", 300f, "Клад не прячется ближе этого к игрокам, м.");
			SearchRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SearchRadius", 0f, "0 — клад прячется по всему миру; больше 0 — не дальше этого от случайного игрока в игре, м (для проверок).");
			WarmRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Heat", "WarmRadius", 300f, "«Тепло», м.");
			HotRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Heat", "HotRadius", 100f, "«Горячо», м.");
			CloseRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Heat", "CloseRadius", 30f, "«Совсем рядом», м.");
			HeatCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Heat", "CooldownSeconds", 30f, "Не чаще раза в столько секунд на игрока (если не стало ближе).");
			LootText = ((BaseUnityPlugin)this).Config.Bind<string>("Loot", "Items", "0:Coins:200:400, 0:Ruby:2:4, 0:AmberPearl:2:5, 0:Amber:3:6, 1:DeerHide:10:20, 2:Copper:20:40, 2:Tin:20:30, 3:Iron:20:40, 3:SilverNecklace:1:2, 4:Silver:20:40, 4:SilverNecklace:2:3, 5:BlackMetal:20:40, 5:SilverNecklace:2:4, 6:Eitr:10:20, 6:SilverNecklace:3:5, 7:FlametalNew:10:20, 7:SilverNecklace:3:5", "уровень:Предмет:от:до через запятую; уровень 0 — в любом кладе, N — в кладе биома уровня N.");
			LandmarksText = ((BaseUnityPlugin)this).Config.Bind<string>("Riddle", "Landmarks", "StartTemple=древнего святилища, Eikthyrnir=алтаря Эйктюра, GDKing=алтаря Древнего, Bonemass=алтаря Массы костей, Dragonqueen=алтаря Модер, GoblinKing=алтаря Яглута, Mistlands_DvergrBossEntrance=логова Королевы, FaderLocation=алтаря Фадера, Vendor_BlackForest=лавки Халдора, Hildir_camp=лагеря Хильдир, BogWitch=хижины ведьмы, SunkenCrypt=затопленного склепа, Crypt=склепа, TrollCave=пещеры тролля, StoneTowerRuins=каменной башни, StoneHenge=каменного круга, ShipWreck=обломков корабля, WoodHouse=заброшенного дома, WoodFarm=заброшенной фермы, WoodVillage=заброшенной деревни, MountainCave=горной пещеры, DrakeNest=гнезда дрейков, AbandonedLogCabin=бревенчатой хижины, GoblinCamp=лагеря фулингов, TarPit=смоляной ямы, Mistlands_DvergrTownEntrance=поселения двергов, Mistlands_Giant=останков великана, Mistlands_Harbour=гавани двергов, Runestone=рунного камня, Dolmen=дольмена, CharredFortress=крепости обугленных, MorgenHole=норы моргена, Ruin=руин, BearCave=берлоги, AshlandRuins=пепельных руин, CharredRuins=обугленных руин, CharredTowerRuins=обугленной башни, FortressRuins=руин крепости, GoblinHut=хижины фулингов, Greydwarf_camp=стоянки карликов, Grave=могилы, MountainGrave=горной могилы, MountainWell=горного колодца, Hildir_cave=пещеры Хильдир, Hildir_crypt=склепа Хильдир, Hildir_plainsfortress=крепости Хильдир, InfestedTree=заражённого дерева, LumberCamp=лесопилки, Mistlands_Excavation=раскопок двергов, Mistlands_GuardTower=башни двергов, Mistlands_Lighthouse=маяка двергов, Mistlands_Statue=статуи двергов, Mistlands_Swords=мечей великана, Mistlands_Viaduct=виадука двергов, Mistlands_RockSpire=скального шпиля, IcePond=ледяного пруда, StoneCircle=каменного круга, StoneHouse=каменного дома, StoneTower=каменной башни, SwampHut=болотной хижины, SulfurArch=серной арки, PlaceofMystery=таинственного места, Mistlands_RoadPost=дорожного столба, DrakeLorestone=камня дрейков", "ПрефиксЛокации=название в родительном падеже («от склепа») через запятую.");
			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", 2, "Наград нашедшему.");
			}
			AdminChannel.Register("treasure", Hoard.Verbs, Hoard.Handle, Log);
			Log.LogInfo((object)"Treasure 0.1.6 загружен.");
		}

		private void Update()
		{
			AdminChannel.Tick();
			if (!Enabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || WorldGenerator.instance == null || Time.time < _nextTick)
			{
				return;
			}
			_nextTick = Time.time + 1f;
			try
			{
				if (!_started)
				{
					_started = true;
					List<string> list = new List<string>();
					LootLines = Loot.Parse(LootText.Value, list);
					LandmarkTable = Landmarks.Parse(LandmarksText.Value, list);
					list.ForEach(delegate(string e)
					{
						Log.LogWarning((object)("Клад: настройки — " + e));
					});
					Hoard.Load();
				}
				Hoard.CheckCommand();
				Hoard.Tick();
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Клад: ошибка: " + ex));
			}
		}
	}
	internal static class Rewards
	{
		private static readonly Random Rng = new Random();

		private static MethodInfo _enqueue;

		private static bool _checked;

		public static bool Give(int tier, double startedAt, long playerId)
		{
			if (tier < 1 || tier > 8)
			{
				return false;
			}
			string[] array = Plugin.RewardPool[tier].Value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				Plugin.Log.LogWarning((object)$"Клад: награда игроку {playerId} не выдана — RewardPool уровня {tier} пуст.");
				return false;
			}
			MethodInfo methodInfo = Api();
			if (methodInfo == null)
			{
				Plugin.Log.LogError((object)$"Клад: награда игроку {playerId} не выдана — нет Event Rewards (ExternalRewards.Enqueue).");
				return false;
			}
			bool result = false;
			for (int i = 1; i <= Math.Max(1, Plugin.RewardCount[tier].Value); i++)
			{
				string text = array[Rng.Next(array.Length)];
				string text2 = $"treasure:{(long)startedAt}" + ((i > 1) ? $"/{i}" : "");
				string text3 = (string)methodInfo.Invoke(null, new object[3] { playerId, text, text2 });
				if (text3 != null)
				{
					Plugin.Log.LogError((object)$"Клад: награда {text} игроку {playerId} не выдана: {text3}.");
					continue;
				}
				result = true;
				Plugin.Log.LogInfo((object)$"Клад: игроку {playerId} поставлена награда {text} ({text2}).");
			}
			return result;
		}

		private static MethodInfo Api()
		{
			if (!_checked)
			{
				_checked = true;
				Type type = AccessTools.TypeByName("EventRewards.ExternalRewards");
				_enqueue = ((type == null) ? null : AccessTools.Method(type, "Enqueue", new Type[3]
				{
					typeof(long),
					typeof(string),
					typeof(string)
				}, (Type[])null));
			}
			return _enqueue;
		}
	}
	internal static class World
	{
		private enum Kind
		{
			Other,
			Player,
			Piece,
			Tombstone,
			ItemStand,
			Character,
			Terrain
		}

		internal struct PlayerInfo
		{
			public ZNetPeer Peer;

			public long PlayerId;

			public Vector3 Pos;
		}

		private const string RoleKey = "sansara.PortalHall.role";

		public const string TagKey = "sansara.Treasure.tag";

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

		private static readonly List<ZDO> Found = new List<ZDO>();

		private static List<ZDO>[] _bySector;

		private static FieldInfo _ewpSkip;

		private static bool _ewpChecked;

		private static readonly PlatformUserID ServerUser = new PlatformUserID("Steam_0");

		public static double Now => RealClock.Now;

		private static FieldInfo EwpSkip
		{
			get
			{
				if (!_ewpChecked)
				{
					_ewpChecked = true;
					Type type = AccessTools.TypeByName("ExpandWorld.Prefab.HandleCreated");
					_ewpSkip = ((type == null) ? null : AccessTools.Field(type, "Skip"));
				}
				return _ewpSkip;
			}
		}

		public static List<PlayerInfo> Players()
		{
			//IL_0033: 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)
			List<PlayerInfo> list = new List<PlayerInfo>();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				ZDO val = (((ZDOID)(ref peer.m_characterID)).IsNone() ? null : ZDOMan.instance.GetZDO(peer.m_characterID));
				if (val != null)
				{
					list.Add(new PlayerInfo
					{
						Peer = peer,
						PlayerId = val.GetLong(ZDOVars.s_playerID, 0L),
						Pos = val.GetPosition()
					});
				}
			}
			return list;
		}

		public static float DistanceXZ(Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return Utils.DistanceXZ(a, b);
		}

		public static string BiomeAt(Vector3 pos)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			return ((object)WorldGenerator.instance.GetBiome(pos)/*cast due to .constrained prefix*/).ToString();
		}

		public static bool DryLand(float x, float z, out float height)
		{
			height = WorldGenerator.instance.GetHeight(x, z);
			float num = ZoneSystem.instance.m_waterLevel + 2f;
			if (height < num)
			{
				return false;
			}
			for (int i = 0; i < 8; i++)
			{
				float num2 = (float)i * (float)Math.PI / 4f;
				if (WorldGenerator.instance.GetHeight(x + 15f * Mathf.Cos(num2), z + 15f * Mathf.Sin(num2)) < num)
				{
					return false;
				}
			}
			return true;
		}

		public static bool NearPlayerThings(Vector3 pos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			Vector2s zone = ZoneSystem.GetZone(pos);
			for (int i = -1; i <= 1; i++)
			{
				for (int j = -1; j <= 1; j++)
				{
					foreach (ZDO item in ZoneObjects(new Vector2s(zone.x + i, zone.y + j)))
					{
						switch (KindOf(item.GetPrefab()))
						{
						case Kind.Piece:
							if (item.GetLong(ZDOVars.s_creator, 0L) == 0L)
							{
								break;
							}
							goto case Kind.Player;
						case Kind.Player:
						case Kind.Tombstone:
						case Kind.Terrain:
							return true;
						case Kind.ItemStand:
							if (item.GetInt(ZDOVars.s_item, 0) != 0)
							{
								return true;
							}
							break;
						case Kind.Character:
							if (item.GetBool(ZDOVars.s_tamed, false))
							{
								return true;
							}
							break;
						}
					}
				}
			}
			return false;
		}

		public static bool NearTrial(Vector3 pos)
		{
			//IL_003f: 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_0045: 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_009b: Unknown result type (might be due to invalid IL or missing references)
			string text = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			string path = Path.Combine(Paths.ConfigPath, "sansara.MinePortal", text + ".slots.txt");
			if (!File.Exists(path))
			{
				return false;
			}
			Vector2s zone = ZoneSystem.GetZone(pos);
			string[] array = File.ReadAllLines(path);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length == 3 && int.TryParse(array2[1], out var result) && int.TryParse(array2[2], out var result2) && Math.Abs(zone.x - result) <= 6 && Math.Abs(zone.y - result2) <= 6)
				{
					return true;
				}
			}
			return false;
		}

		public static bool Landmark(Vector3 pos, float radius, List<KeyValuePair<string, string>> table, out string name, out Vector3 at)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: 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_0090: Unknown result type (might be due to invalid IL or missing references)
			name = null;
			at = Vector3.zero;
			float num = radius;
			foreach (KeyValuePair<Vector2s, LocationInstance> locationInstance in ZoneSystem.instance.m_locationInstances)
			{
				string text = locationInstance.Value.m_location?.m_prefab.Name;
				if (text == null)
				{
					continue;
				}
				float num2 = DistanceXZ(locationInstance.Value.m_position, pos);
				if (!(num2 > num) && !(num2 < 20f))
				{
					string text2 = Landmarks.NameOf(table, text);
					if (text2 != null)
					{
						num = num2;
						name = text2;
						at = locationInstance.Value.m_position;
					}
				}
			}
			return name != null;
		}

		public static List<string> UnnamedLocations(List<KeyValuePair<string, string>> table)
		{
			return (from n in (from l in ZoneSystem.instance.m_locationInstances.Values
					select l.m_location?.m_prefab.Name into n
					where n != null
					select n).Distinct()
				where Landmarks.NameOf(table, n) == null
				orderby n
				select n).ToList();
		}

		private static List<ZDO> ZoneObjects(Vector2s zone)
		{
			//IL_002f: 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)
			if (_bySector == null)
			{
				_bySector = (List<ZDO>[])AccessTools.Field(typeof(ZDOMan), "m_objectsBySector").GetValue(ZDOMan.instance);
			}
			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);
		}

		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<TerrainComp>() != (Object)null) ? Kind.Terrain : (((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;
		}

		public static ZDO CreateChest(string prefabName, Vector3 pos, List<KeyValuePair<string, int>> items, string tag, out int added)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Expected O, but got Unknown
			added = 0;
			GameObject prefab = ZNetScene.instance.GetPrefab(prefabName);
			Container val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Container>() : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Inventory val2 = new Inventory("treasure", (Sprite)null, val.m_width, val.m_height);
			foreach (KeyValuePair<string, int> item in items)
			{
				GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(item.Key);
				if ((Object)(object)itemPrefab == (Object)null || (Object)(object)itemPrefab.GetComponent<ItemDrop>() == (Object)null)
				{
					Plugin.Log.LogWarning((object)("Клад: предмета " + item.Key + " нет в игре — пропущен."));
					continue;
				}
				int num = item.Value;
				while (num > 0)
				{
					int num2 = Mathf.Min(num, itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxStackSize);
					if (!val2.AddItem(itemPrefab, num2))
					{
						Plugin.Log.LogWarning((object)$"Клад: сундук полон — {item.Key} ×{num} не поместились.");
						break;
					}
					num -= num2;
					added += num2;
				}
			}
			ZNetView component = prefab.GetComponent<ZNetView>();
			int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name);
			object obj = EwpSkip?.GetValue(null);
			ZDO val3;
			try
			{
				EwpSkip?.SetValue(null, true);
				val3 = ZDOMan.instance.CreateNewZDO(pos, stableHashCode);
			}
			finally
			{
				if (obj != null)
				{
					EwpSkip.SetValue(null, obj);
				}
			}
			val3.Persistent = component.m_persistent;
			val3.Type = component.m_type;
			val3.Distant = component.m_distant;
			val3.SetPrefab(stableHashCode);
			val3.SetRotation(Quaternion.Euler(0f, Random.Range(0f, 360f), 0f));
			ZPackage val4 = new ZPackage();
			val2.Save(val4);
			val3.Set(ZDOVars.s_items, val4.GetArray());
			val3.Set(ZDOVars.s_addedDefaultItems, true);
			val3.Set("sansara.Treasure.tag", tag);
			return val3;
		}

		public static byte[] Items(ZDO chest)
		{
			return chest.GetByteArray(ZDOVars.s_items, (byte[])null);
		}

		public static List<ZDO> Tagged(Vector3 around, int zones, string key, string value)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			Found.Clear();
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(around), new SimulationDistance(zones, 0, true), Found, (List<ZDO>)null);
			return Found.Where((ZDO z) => z.GetString(key, "") == value).ToList();
		}

		public static ZDO ChestAt(Vector3 pos, string prefabName)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			int hash = StringExtensionMethods.GetStableHashCode(prefabName);
			Found.Clear();
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(pos), new SimulationDistance(1, 0, true), Found, (List<ZDO>)null);
			return (from z in Found
				where z.GetPrefab() == hash && Vector3.Distance(z.GetPosition(), pos) < 2f
				orderby Vector3.Distance(z.GetPosition(), pos)
				select z).FirstOrDefault();
		}

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

		public static bool Finder(ZDO chest, out long playerId, out string name, out long peerUid)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			playerId = 0L;
			name = "";
			peerUid = 0L;
			float num = 10f;
			foreach (PlayerInfo item in Players())
			{
				if (item.Peer.m_uid == chest.GetOwner())
				{
					playerId = item.PlayerId;
					name = item.Peer.m_playerName;
					peerUid = item.Peer.m_uid;
					return playerId != 0;
				}
				float num2 = Vector3.Distance(item.Pos, chest.GetPosition());
				if (num2 <= num)
				{
					num = num2;
					playerId = item.PlayerId;
					name = item.Peer.m_playerName;
					peerUid = item.Peer.m_uid;
				}
			}
			return playerId != 0;
		}

		public static ZDO HintSign()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(10820f, 0f, 300f);
			Found.Clear();
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(val), new SimulationDistance(1, 0, true), Found, (List<ZDO>)null);
			return ((IEnumerable<ZDO>)Found).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => z.GetString("sansara.PortalHall.role", "") == "treasure.sign"));
		}

		public static void SetSign(ZDO sign, string text)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			text = "<color=white>" + text + "</color>";
			if (!(sign.GetString(ZDOVars.s_text, "") == text))
			{
				sign.SetOwner(ZDOMan.GetSessionID());
				sign.Set(ZDOVars.s_text, text);
				ZDOMan.instance.ForceSendZDO(sign.m_uid);
			}
		}

		public static void MessageAll(string text)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text });
		}

		public static void Message(long peerUid, string text)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(peerUid, "ShowMessage", new object[2] { 2, text });
		}

		public static void Chat(string text)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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_0046: Expected O, but got Unknown
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ChatMessage", new object[4]
			{
				Vector3.zero,
				1,
				(object)new UserInfo
				{
					Name = "Клад",
					UserId = ServerUser
				},
				text
			});
		}
	}
}
namespace Treasure.Core
{
	public static class Biomes
	{
		public const int MaxTier = 8;

		public static readonly string[] Names = new string[9] { "", "Meadows", "BlackForest", "Swamp", "Mountain", "Plains", "Mistlands", "AshLands", "DeepNorth" };

		public static readonly string[] Russian = new string[9] { "", "Луга", "Чёрный лес", "Болото", "Горы", "Равнины", "Туманные земли", "Пепельные земли", "Дальний Север" };

		public static readonly string[] OpeningKeys = new string[8] { null, "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader" };

		public static int TierOf(string biome)
		{
			for (int i = 1; i <= 8; i++)
			{
				if (string.Equals(Names[i], biome, StringComparison.OrdinalIgnoreCase))
				{
					return i;
				}
			}
			return 0;
		}

		public static int OpenTier(ICollection<string> worldKeys)
		{
			int result = 1;
			for (int i = 2; i <= 8; i++)
			{
				if (worldKeys.Contains(OpeningKeys[i - 1]))
				{
					result = i;
				}
			}
			return result;
		}

		public static int PickTier(int open, int maxTier, Random rng)
		{
			int num = Math.Max(1, Math.Min(open, maxTier));
			int maxValue = num * (num + 1) / 2;
			int num2 = rng.Next(maxValue);
			for (int i = 1; i <= num; i++)
			{
				num2 -= i;
				if (num2 < 0)
				{
					return i;
				}
			}
			return num;
		}
	}
	public sealed class LootLine
	{
		public int Tier;

		public string Item = "";

		public int Min;

		public int Max;
	}
	public static class Loot
	{
		public const string Default = "0:Coins:200:400, 0:Ruby:2:4, 0:AmberPearl:2:5, 0:Amber:3:6, 1:DeerHide:10:20, 2:Copper:20:40, 2:Tin:20:30, 3:Iron:20:40, 3:SilverNecklace:1:2, 4:Silver:20:40, 4:SilverNecklace:2:3, 5:BlackMetal:20:40, 5:SilverNecklace:2:4, 6:Eitr:10:20, 6:SilverNecklace:3:5, 7:FlametalNew:10:20, 7:SilverNecklace:3:5";

		public static List<LootLine> Parse(string text, List<string> errors)
		{
			List<LootLine> list = new List<LootLine>();
			string[] array = (text ?? "").Split(new char[3] { ',', ';', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0)
				{
					string[] array2 = (from s in text2.Split(new char[1] { ':' })
						select s.Trim()).ToArray();
					int result = 0;
					int result2 = 0;
					int result3 = 0;
					if (array2.Length != 4 || !int.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out result) || result < 0 || result > 8 || array2[1] == "" || !int.TryParse(array2[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out result2) || !int.TryParse(array2[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out result3) || result2 < 1 || result3 < result2)
					{
						errors.Add("строка «" + text2 + "» не разобрана (нужно уровень:Предмет:от:до)");
						continue;
					}
					list.Add(new LootLine
					{
						Tier = result,
						Item = array2[1],
						Min = result2,
						Max = result3
					});
				}
			}
			return list;
		}

		public static List<KeyValuePair<string, int>> Roll(List<LootLine> lines, int tier, Random rng)
		{
			return (from l in lines
				where l.Tier == 0 || l.Tier == tier
				select new KeyValuePair<string, int>(l.Item, rng.Next(l.Min, l.Max + 1))).ToList();
		}
	}
	public static class Landmarks
	{
		public const string Default = "StartTemple=древнего святилища, Eikthyrnir=алтаря Эйктюра, GDKing=алтаря Древнего, Bonemass=алтаря Массы костей, Dragonqueen=алтаря Модер, GoblinKing=алтаря Яглута, Mistlands_DvergrBossEntrance=логова Королевы, FaderLocation=алтаря Фадера, Vendor_BlackForest=лавки Халдора, Hildir_camp=лагеря Хильдир, BogWitch=хижины ведьмы, SunkenCrypt=затопленного склепа, Crypt=склепа, TrollCave=пещеры тролля, StoneTowerRuins=каменной башни, StoneHenge=каменного круга, ShipWreck=обломков корабля, WoodHouse=заброшенного дома, WoodFarm=заброшенной фермы, WoodVillage=заброшенной деревни, MountainCave=горной пещеры, DrakeNest=гнезда дрейков, AbandonedLogCabin=бревенчатой хижины, GoblinCamp=лагеря фулингов, TarPit=смоляной ямы, Mistlands_DvergrTownEntrance=поселения двергов, Mistlands_Giant=останков великана, Mistlands_Harbour=гавани двергов, Runestone=рунного камня, Dolmen=дольмена, CharredFortress=крепости обугленных, MorgenHole=норы моргена, Ruin=руин, BearCave=берлоги, AshlandRuins=пепельных руин, CharredRuins=обугленных руин, CharredTowerRuins=обугленной башни, FortressRuins=руин крепости, GoblinHut=хижины фулингов, Greydwarf_camp=стоянки карликов, Grave=могилы, MountainGrave=горной могилы, MountainWell=горного колодца, Hildir_cave=пещеры Хильдир, Hildir_crypt=склепа Хильдир, Hildir_plainsfortress=крепости Хильдир, InfestedTree=заражённого дерева, LumberCamp=лесопилки, Mistlands_Excavation=раскопок двергов, Mistlands_GuardTower=башни двергов, Mistlands_Lighthouse=маяка двергов, Mistlands_Statue=статуи двергов, Mistlands_Swords=мечей великана, Mistlands_Viaduct=виадука двергов, Mistlands_RockSpire=скального шпиля, IcePond=ледяного пруда, StoneCircle=каменного круга, StoneHouse=каменного дома, StoneTower=каменной башни, SwampHut=болотной хижины, SulfurArch=серной арки, PlaceofMystery=таинственного места, Mistlands_RoadPost=дорожного столба, DrakeLorestone=камня дрейков";

		public static List<KeyValuePair<string, string>> Parse(string text, List<string> errors)
		{
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
			string[] array = (text ?? "").Split(new char[3] { ',', ';', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				int num = text2.IndexOf('=');
				if (text2.Length != 0)
				{
					if (num <= 0 || num == text2.Length - 1)
					{
						errors.Add("строка «" + text2 + "» не разобрана (нужно Префикс=название)");
					}
					else
					{
						list.Add(new KeyValuePair<string, string>(text2.Substring(0, num).Trim(), text2.Substring(num + 1).Trim()));
					}
				}
			}
			return list;
		}

		public static string NameOf(List<KeyValuePair<string, string>> table, string prefab)
		{
			string result = null;
			int num = 0;
			foreach (KeyValuePair<string, string> item in table)
			{
				if (prefab.StartsWith(item.Key, StringComparison.OrdinalIgnoreCase) && item.Key.Length > num)
				{
					result = item.Value;
					num = item.Key.Length;
				}
			}
			return result;
		}
	}
	public static class Riddle
	{
		private static readonly string[] Directions = new string[8] { "к востоку", "к северо-востоку", "к северу", "к северо-западу", "к западу", "к юго-западу", "к югу", "к юго-востоку" };

		public const string Separator = " | ";

		public static string Direction(double fromX, double fromZ, double toX, double toZ)
		{
			int num = (int)Math.Round((Math.Atan2(toZ - fromZ, toX - fromX) * 180.0 / Math.PI % 360.0 + 360.0) % 360.0 / 45.0) % 8;
			return Directions[num];
		}

		public static int RoundDistance(double meters)
		{
			if (!(meters < 100.0))
			{
				return (int)(Math.Round(meters / 50.0) * 50.0);
			}
			return (int)Math.Max(10.0, Math.Round(meters / 10.0) * 10.0);
		}

		public static string Text(int tier, string landmark, double markX, double markZ, double toX, double toZ)
		{
			double num = Math.Sqrt(toX * toX + toZ * toZ) / 1000.0;
			string text = Biomes.Russian[tier] + ": " + num.ToString("0.0", CultureInfo.InvariantCulture).Replace('.', ',') + " км " + Direction(0.0, 0.0, toX, toZ) + " от центра";
			if (landmark != null)
			{
				double meters = Math.Sqrt((toX - markX) * (toX - markX) + (toZ - markZ) * (toZ - markZ));
				text += string.Format("{0}{1} м {2} от {3}", " | ", RoundDistance(meters), Direction(markX, markZ, toX, toZ), landmark);
			}
			return text;
		}

		public static string ForSign(string riddle)
		{
			return riddle.Replace(" | ", "\n");
		}

		public static string ForChat(string riddle)
		{
			return riddle.Replace(" | ", "; ");
		}

		public static bool IsOld(string riddle)
		{
			return !riddle.Contains(" от центра");
		}
	}
	public static class Heat
	{
		public static readonly string[] Texts = new string[4] { "", "Клад: тепло…", "Клад: горячо!", "Клад: совсем рядом!" };

		public static int Level(double distance, double warm, double hot, double close)
		{
			if (!(distance <= close))
			{
				if (!(distance <= hot))
				{
					return (distance <= warm) ? 1 : 0;
				}
				return 2;
			}
			return 3;
		}

		public static bool ShouldTell(int level, int lastLevel, double now, double lastAt, double cooldown)
		{
			if (level > 0)
			{
				if (level <= lastLevel)
				{
					return now - lastAt >= cooldown;
				}
				return true;
			}
			return false;
		}
	}
	public sealed class TreasureState
	{
		public double NextAt;

		public bool Active;

		public long ChestUser;

		public uint ChestId;

		public double X;

		public double Y;

		public double Z;

		public int Tier;

		public double HiddenAt;

		public double ExpiresAt;

		public string Riddle = "";

		public string Sig = "";

		public string Last = "";

		public string Serialize()
		{
			StringBuilder sb = new StringBuilder();
			Line("next", D(NextAt));
			if (Last != "")
			{
				Line("last", Last);
			}
			if (Active)
			{
				Line("chest", ChestUser.ToString(CultureInfo.InvariantCulture) + ":" + ChestId.ToString(CultureInfo.InvariantCulture));
				Line("pos", D(X) + " " + D(Y) + " " + D(Z));
				Line("tier", Tier.ToString(CultureInfo.InvariantCulture));
				Line("hidden", D(HiddenAt));
				Line("expires", D(ExpiresAt));
				Line("riddle", Riddle);
				Line("sig", Sig);
			}
			return sb.ToString();
			static string D(double v)
			{
				return v.ToString("R", CultureInfo.InvariantCulture);
			}
			void Line(string key, string value)
			{
				sb.Append(key).Append('\t').Append(value.Replace("\t", " ").Replace("\n", " "))
					.Append('\n');
			}
		}

		public static TreasureState Parse(string text)
		{
			TreasureState treasureState = new TreasureState();
			string[] array = (text ?? "").Split(new char[1] { '\n' });
			foreach (string text2 in array)
			{
				int num = text2.IndexOf('\t');
				if (num <= 0)
				{
					continue;
				}
				string text3 = text2.Substring(0, num);
				string text4 = text2.Substring(num + 1).TrimEnd(new char[1] { '\r' });
				switch (text3)
				{
				case "next":
					treasureState.NextAt = Num(text4);
					break;
				case "last":
					treasureState.Last = text4;
					break;
				case "chest":
				{
					string[] array3 = text4.Split(new char[1] { ':' });
					if (array3.Length == 2 && long.TryParse(array3[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && uint.TryParse(array3[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
					{
						treasureState.ChestUser = result;
						treasureState.ChestId = result2;
						treasureState.Active = true;
					}
					break;
				}
				case "pos":
				{
					string[] array2 = text4.Split(new char[1] { ' ' });
					if (array2.Length == 3)
					{
						treasureState.X = Num(array2[0]);
						treasureState.Y = Num(array2[1]);
						treasureState.Z = Num(array2[2]);
					}
					break;
				}
				case "tier":
					int.TryParse(text4, NumberStyles.Integer, CultureInfo.InvariantCulture, out treasureState.Tier);
					break;
				case "hidden":
					treasureState.HiddenAt = Num(text4);
					break;
				case "expires":
					treasureState.ExpiresAt = Num(text4);
					break;
				case "riddle":
					treasureState.Riddle = text4;
					break;
				case "sig":
					treasureState.Sig = text4;
					break;
				}
			}
			return treasureState;
		}

		private static double Num(string text)
		{
			if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return 0.0;
			}
			return result;
		}
	}
}