Decompiled source of RaidCall v0.4.1

plugins/RaidCall.dll

Decompiled 5 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 RaidCall.Core;
using Sansara.Admin;
using Sansara.Chronicle;
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("RaidCall")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.1.0")]
[assembly: AssemblyInformationalVersion("0.4.1+ee6a46e381b50add1bc5a1ee15a3276bde7e4e27")]
[assembly: AssemblyProduct("RaidCall")]
[assembly: AssemblyTitle("RaidCall")]
[assembly: AssemblyVersion("0.4.1.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));
			}
		}

		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 RaidCall
{
	internal static class ActiveFile
	{
		private const string StatePrefix = "state:";

		private static string _path;

		public static ZDOID Current { get; private set; } = ZDOID.None;

		public static AttemptState State { get; private set; }

		public static void Load()
		{
			//IL_0045: 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)
			string text = Path.Combine(Paths.ConfigPath, "sansara.RaidCall");
			Directory.CreateDirectory(text);
			string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			_path = Path.Combine(text, text2 + ".active.txt");
			Current = ZDOID.None;
			State = null;
			if (!File.Exists(_path))
			{
				return;
			}
			string text3 = File.ReadAllText(_path).Trim();
			if (text3.StartsWith("state:"))
			{
				State = AttemptState.Parse(text3.Substring("state:".Length));
				if (State == null)
				{
					Plugin.Log.LogWarning((object)"Зов набега: состояние попытки по команде не прочитано — забыто.");
				}
				return;
			}
			string[] array = text3.Split(new char[1] { ':' });
			if (array.Length == 2 && long.TryParse(array[0], out var result) && uint.TryParse(array[1], out var result2))
			{
				Current = new ZDOID(result, result2);
			}
		}

		public static void Set(ZDOID id)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			Current = id;
			State = null;
			File.WriteAllText(_path, ((ZDOID)(ref id)).IsNone() ? "" : $"{((ZDOID)(ref id)).UserID}:{((ZDOID)(ref id)).ID}");
		}

		public static void SetState(AttemptState state)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			Current = ZDOID.None;
			State = state;
			File.WriteAllText(_path, "state:" + state.Serialize());
		}
	}
	internal static class Caller
	{
		public static void Scan()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: 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)
			string b = Plugin.Keyword.Value.Trim();
			List<World.PlayerInfo> list = World.Players();
			HashSet<ZDOID> hashSet = new HashSet<ZDOID>();
			foreach (World.PlayerInfo item in list)
			{
				foreach (ZDO item2 in World.SignsNear(item.Pos, 32f))
				{
					if (hashSet.Add(item2.m_uid) && !RaidRun.IsActiveSign(item2.m_uid) && string.Equals(World.SignText(item2).Trim(), b, StringComparison.OrdinalIgnoreCase))
					{
						Handle(item2, list);
					}
				}
			}
		}

		private static void Handle(ZDO sign, List<World.PlayerInfo> players)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: 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)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = sign.GetPosition();
			ZDO val = World.StandWithItemNear(position, Plugin.StandRadius.Value);
			string text = ((val != null) ? World.ItemOnStand(val) : "");
			Plugin.Table.TryGetValue(text, out var value);
			RandomEvent val2 = ((value != null) ? World.EventTemplate(value.Event) : null);
			Vector3 val3 = ((val != null) ? val.GetPosition() : position);
			long num = World.CallerOf(sign, players);
			CallFacts f = new CallFacts
			{
				HasTrophy = (val != null),
				TrophyKnown = (value != null),
				EventAvailable = (val2 != null),
				Busy = RaidRun.Busy,
				Now = World.Now,
				LastDoneAt = LastDoneNear(val3),
				CooldownSeconds = (double)Plugin.CooldownMinutes.Value * 60.0,
				ForeignWard = WardRules.IsForeign(num, World.WardsAround(val3)),
				BiomeFits = (val2 != null && CallRules.BiomeFits(World.BiomeAt(val3), World.SpawnBiomes(val2)))
			};
			Refusal refusal = CallRules.Check(f);
			if (refusal == Refusal.None && !World.TakeItem(val, text))
			{
				refusal = Refusal.TakeFailed;
			}
			if (refusal != Refusal.None)
			{
				World.SetSignText(sign, Texts.RefusalText(refusal, CallRules.CooldownLeft(f)));
				Plugin.Log.LogInfo((object)$"Зов набега: отказ {refusal} у {val3}, игрок {num}, трофей «{text}».");
			}
			else
			{
				AttemptState state = new AttemptState
				{
					Stage = Stage.Countdown,
					Event = value.Event,
					Tier = value.Tier,
					Trophy = text,
					Caller = num,
					PosX = val3.x,
					PosY = val3.y,
					PosZ = val3.z,
					CountdownAt = World.Now
				};
				RaidRun.Begin(sign, state);
			}
		}

		private static double LastDoneNear(Vector3 pos)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			double num = -1.0;
			foreach (ZDO item in World.SignsNear(pos, Plugin.CooldownRadius.Value))
			{
				AttemptState attemptState = World.ReadState(item);
				if (attemptState != null && attemptState.Stage == Stage.Done && attemptState.DoneAt > num)
				{
					num = attemptState.DoneAt;
				}
			}
			return num;
		}
	}
	[BepInPlugin("sansara.RaidCall", "Raid Call", "0.4.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.RaidCall";

		public const string Name = "Raid Call";

		public const string Version = "0.4.1";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> Keyword;

		internal static ConfigEntry<float> StandRadius;

		internal static ConfigEntry<int> CountdownSeconds;

		internal static ConfigEntry<int> CooldownMinutes;

		internal static ConfigEntry<float> CooldownRadius;

		internal static ConfigEntry<int> AbandonMinutes;

		internal static ConfigEntry<string> Raids;

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

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

		internal static Dictionary<string, RaidEntry> Table = new Dictionary<string, RaidEntry>();

		private float _nextTick;

		internal static bool Started;

		private float _validateAt;

		private bool _validated;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Мод работает.");
			Keyword = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Keyword", "набег", "Текст таблички для вызова набега (без учёта регистра и пробелов по краям).");
			StandRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StandRadius", 3f, "Наибольшее расстояние от таблички до подставки с трофеем, м.");
			CountdownSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("General", "CountdownSeconds", 60, "Время на подготовку перед набегом, с.");
			CooldownMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("General", "CooldownMinutes", 15, "Перезарядка места после набега, реальные минуты.");
			CooldownRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CooldownRadius", 100f, "Радиус места для перезарядки, м.");
			AbandonMinutes = ((BaseUnityPlugin)this).Config.Bind<int>("General", "AbandonMinutes", 10, "Сколько набег может стоять без игроков в зоне, пока его не снимут.");
			Raids = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Raids", "TrophyBoar:army_eikthyr:1, TrophyNeck:army_eikthyr:1, TrophyDeer:army_eikthyr:1, TrophyEikthyr:army_eikthyr:2, TrophyGreydwarf:army_theelder:2, TrophyGreydwarfBrute:army_theelder:2, TrophyGreydwarfShaman:army_theelder:2, TrophyTheElder:army_theelder:3, TrophyForestTroll:foresttrolls:2, TrophyBjorn:foresttrolls:2, TrophySkeleton:skeletons:2, TrophyGhost:ghosts:2, TrophySkeletonHildir:hildirboss1:4, TrophyDraugr:army_bonemass:3, TrophyDraugrFem:army_bonemass:3, TrophyDraugrElite:army_bonemass:3, TrophyAbomination:army_bonemass:3, TrophyKvastur:army_bonemass:4, TrophyBonemass:army_bonemass:4, TrophyBlob:blobs:3, TrophyLeech:blobs:3, TrophySkeletonPoison:skeletons:3, TrophyWraith:ghosts:3, TrophySurtling:surtlings:3, TrophyHatchling:army_moder:4, TrophySGolem:army_moder:4, TrophySerpent:army_moder:4, TrophyDragonQueen:army_moder:5, TrophyWolf:wolves:4, TrophyFenring:wolves:4, TrophyUlv:bats:4, TrophyCultist:bats:4, TrophyCultist_Hildir:hildirboss2:4, TrophyGoblin:army_goblin:5, TrophyGoblinBrute:army_goblin:5, TrophyGoblinShaman:army_goblin:5, TrophyLox:army_goblin:5, TrophyGoblinKing:army_goblin:6, TrophyDeathsquito:bats:5, TrophyGrowth:blobs:5, TrophyBjornUndead:foresttrolls:5, TrophyGoblinBruteBrosBrute:hildirboss3:5, TrophyGoblinBruteBrosShaman:hildirboss3:5, TrophyGjall:army_gjall:6, TrophyTick:army_gjall:6, TrophyHare:army_gjall:6, TrophySeeker:army_seekers:6, TrophySeekerBrute:army_seekers:6, TrophyDvergr:army_seekers:6, TrophySeekerQueen:army_seekers:7, TrophyCharredMelee:army_charred:7, TrophyCharredArcher:army_charred:7, TrophyAsksvin:army_charred:7, TrophyVolture:army_charred:7, TrophyBonemawSerpent:army_charred:7, TrophyCharredMage:army_charredspawners:7, TrophyMorgen:army_charredspawners:7, TrophyFallenValkyrie:army_charredspawners:7, TrophyBlob_Lava:blobs:7, TrophyFader:army_charredspawners:8, TrophyJotunWarrior:army_jotuns:8, TrophyJotunWitch:army_jotuns:8, TrophyFrostTroll:army_jotuns:8, TrophyBarka:army_jotuns:8, TrophyWrithan:army_jotuns:8, TrophyMole:army_elakingar:8, TrophyElaking:army_elakingar:8, TrophyMoose:army_elakingar:8, TrophySeal:army_elakingar:8, TrophyDeerWhite:army_elakingar:8, TrophyBlob_Frost:blobs:8, TrophyBlob_Morkhalla:blobs:8", "Трофей:набег:уровень (1 Луга … 8 Дальний Север) через запятую.");
			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, "Наград каждому выжившему участнику.");
			}
			AdminChannel.Register("raid", RaidAdmin.Verbs, RaidAdmin.Handle, Log);
			Log.LogInfo((object)"Raid Call 0.4.1 загружен.");
		}

		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)ObjectDB.instance == (Object)null || (Object)(object)RandEventSystem.instance == (Object)null || WorldGenerator.instance == null || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.LocationsGenerated || Time.time < _nextTick)
			{
				return;
			}
			_nextTick = Time.time + 1f;
			if (!Started)
			{
				Started = true;
				_validateAt = Time.time + 30f;
				LoadTable();
				World.Init();
				ActiveFile.Load();
				RaidRun.Restore();
			}
			if (!_validated && Time.time >= _validateAt)
			{
				_validated = true;
				Validate();
			}
			AdminChannel.CheckFile("sansara.RaidCall", "Зов набега", RaidAdmin.Handle);
			try
			{
				RaidRun.Tick();
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Зов набега: ошибка такта попытки: " + ex));
			}
			try
			{
				Caller.Scan();
			}
			catch (Exception ex2)
			{
				Log.LogError((object)("Зов набега: ошибка поиска табличек: " + ex2));
			}
		}

		private static void LoadTable()
		{
			List<string> list = new List<string>();
			Table = RaidTable.Parse(Raids.Value, list);
			foreach (string item in list)
			{
				Log.LogWarning((object)("Зов набега: Raids — " + item));
			}
			Log.LogInfo((object)$"Зов набега: трофеев в таблице {Table.Count}, набегов {Table.Values.Select((RaidEntry e) => e.Event).Distinct().Count()}.");
		}

		private static void Validate()
		{
			foreach (RaidEntry value in Table.Values)
			{
				if ((Object)(object)ObjectDB.instance.GetItemPrefab(value.Trophy) == (Object)null)
				{
					Log.LogWarning((object)("Зов набега: трофея " + value.Trophy + " нет в игре — строка не сработает."));
				}
				if (!RandEventSystem.instance.HaveEvent(value.Event))
				{
					Log.LogWarning((object)("Зов набега: набега " + value.Event + " нет в игре или он выключен — строка не сработает."));
				}
			}
			Log.LogInfo((object)"Зов набега: таблица сверена с игрой.");
		}
	}
	internal static class RaidAdmin
	{
		internal static readonly string[] Verbs = new string[5] { "status", "help", "start", "stop", "ui" };

		private const string HelpText = "status — занято ли место, активная попытка (набег, уровень, трофей, кто позвал, место, отсчёт или время набега, участники и погибшие), трофеев в таблице\nstart <набег|трофей> [Имя|\"Имя с пробелами\"|@ID] — набег у игрока (без игрока — у вас) без таблички и трофея: отсчёт, награды выжившим по уровню, без перезарядки места; из файла команд игрок обязателен\nstop — снять активную попытку с итогом «брошено», без наград (идущий набег игры сбрасывается)\nui — данные для окна Sansara Admin (машинный ответ): идёт ли попытка, набеги таблицы с уровнями и трофеями";

		private const string Loading = "набеги ещё не запущены: мир загружается";

		public static string Handle(string line, string admin, string adminId)
		{
			AdminCommand adminCommand = AdminCommand.Parse(line);
			switch (adminCommand.Verb)
			{
			case "help":
				return "status — занято ли место, активная попытка (набег, уровень, трофей, кто позвал, место, отсчёт или время набега, участники и погибшие), трофеев в таблице\nstart <набег|трофей> [Имя|\"Имя с пробелами\"|@ID] — набег у игрока (без игрока — у вас) без таблички и трофея: отсчёт, награды выжившим по уровню, без перезарядки места; из файла команд игрок обязателен\nstop — снять активную попытку с итогом «брошено», без наград (идущий набег игры сбрасывается)\nui — данные для окна Sansara Admin (машинный ответ): идёт ли попытка, набеги таблицы с уровнями и трофеями";
			case "status":
				return Status();
			case "ui":
				if (!Plugin.Started)
				{
					return AdminUi.Note("набеги ещё не запущены: мир загружается");
				}
				return RaidUi.Text(Plugin.Table, (string e) => World.EventTemplate(e) != null, RaidRun.Busy);
			case "start":
			case "stop":
				if (!Plugin.Enabled.Value)
				{
					return "модуль выключен в настройках (Enabled = false)";
				}
				if (!Plugin.Started)
				{
					return "набеги ещё не запущены: мир загружается";
				}
				if (!(adminCommand.Verb == "start"))
				{
					return RaidRun.Stop(admin);
				}
				return Start(adminCommand, admin, adminId);
			default:
				return AdminProtocol.Unknown(adminCommand.Verb, Verbs);
			}
		}

		private static string Start(AdminCommand c, string admin, string adminId)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: 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_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			if (c.Args.Count == 0)
			{
				return "укажите набег или трофей: start <набег|трофей> [Имя|@ID]";
			}
			string argument = c.Args[0];
			string text = string.Join(" ", c.Args.Skip(1)).Trim();
			StartChoice startChoice = AdminStart.Resolve(argument, Plugin.Table);
			RandomEvent val = ((startChoice != null) ? World.EventTemplate(startChoice.Event) : null);
			bool flag = string.IsNullOrEmpty(adminId);
			World.PlayerInfo? playerInfo = FindPlayer(text, flag ? null : adminId);
			Vector3 val2 = playerInfo?.Pos ?? Vector3.zero;
			StartRefusal startRefusal = AdminStart.Check(new StartFacts
			{
				Resolved = (startChoice != null),
				EventEnabled = (val != null),
				Busy = RaidRun.Busy,
				FromFile = flag,
				PlayerGiven = (text != ""),
				PlayerFound = playerInfo.HasValue,
				PlayerY = val2.y,
				BiomeFits = (val != null && playerInfo.HasValue && CallRules.BiomeFits(World.BiomeAt(val2), World.SpawnBiomes(val)))
			});
			if (startRefusal != StartRefusal.None)
			{
				return AdminStart.RefusalText(startRefusal, argument, text);
			}
			AttemptState state = new AttemptState
			{
				Stage = Stage.Countdown,
				Event = startChoice.Event,
				Tier = startChoice.Tier,
				Trophy = startChoice.Trophy,
				Caller = playerInfo.Value.PlayerId,
				PosX = val2.x,
				PosY = val2.y,
				PosZ = val2.z,
				CountdownAt = World.Now,
				ByCommand = true
			};
			string playerName = playerInfo.Value.Peer.m_playerName;
			Plugin.Log.LogInfo((object)("Зов набега: набег " + startChoice.Event + " у игрока " + playerName + " по команде (" + AdminProtocol.ForLog(admin) + ")."));
			RaidRun.Begin(null, state);
			return AdminStart.Started(startChoice.Event, startChoice.Tier, playerName, val2.x, val2.z, Plugin.CountdownSeconds.Value);
		}

		private static World.PlayerInfo? FindPlayer(string who, string adminId)
		{
			List<World.PlayerInfo> list = (from p in World.Players()
				where p.PlayerId != 0
				select p).ToList();
			Func<World.PlayerInfo, bool> func;
			if (who != "")
			{
				func = ((!AdminCommand.TryId(who, out var id)) ? ((Func<World.PlayerInfo, bool>)((World.PlayerInfo p) => string.Equals(p.Peer.m_playerName, who, StringComparison.OrdinalIgnoreCase))) : ((Func<World.PlayerInfo, bool>)((World.PlayerInfo p) => p.Peer.m_socket.GetHostName() == id)));
			}
			else
			{
				if (adminId == null)
				{
					return null;
				}
				func = (World.PlayerInfo p) => p.Peer.m_socket.GetHostName() == adminId;
			}
			foreach (World.PlayerInfo item in list)
			{
				if (func(item))
				{
					return item;
				}
			}
			return null;
		}

		private static string Status()
		{
			string text = (Plugin.Enabled.Value ? "" : "выключен в настройках; ");
			if (!Plugin.Started)
			{
				return text + "набеги ещё не запущены: мир загружается";
			}
			AttemptState current = RaidRun.Current;
			RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent();
			bool flag = current != null && current.Stage == Stage.Running && currentRandomEvent != null && currentRandomEvent.m_name == current.Event;
			return text + RaidReport.Status(current, World.Now, Plugin.CountdownSeconds.Value, flag ? currentRandomEvent.m_time : 0f, flag ? currentRandomEvent.m_duration : 0f, (currentRandomEvent != null && !flag) ? currentRandomEvent.m_name : "", Plugin.Table.Count);
		}
	}
	internal static class RaidRun
	{
		private static ZDOID _sign = ZDOID.None;

		private static AttemptState _state;

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

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

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

		private static bool _waitLogged;

		private static double _emptySince = -1.0;

		private static double _lastTime;

		private static double _duration;

		private static int _lastShown = -1;

		public static bool Busy
		{
			get
			{
				if (_state == null)
				{
					return RandEventSystem.instance.GetCurrentRandomEvent() != null;
				}
				return true;
			}
		}

		internal static AttemptState Current => _state;

		private static Vector3 Pos => new Vector3(_state.PosX, _state.PosY, _state.PosZ);

		public static bool IsActiveSign(ZDOID id)
		{
			//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)
			if (_state != null)
			{
				return id == _sign;
			}
			return false;
		}

		internal static string Stop(string admin)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			if (_state == null)
			{
				return "попытки набега сейчас нет";
			}
			string text = _state.Event;
			bool flag = _state.Stage == Stage.Running;
			RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent();
			bool flag2 = false;
			if (flag && currentRandomEvent != null && currentRandomEvent.m_name == text)
			{
				RandEventSystem.instance.ResetRandomEvent();
				flag2 = true;
			}
			Plugin.Log.LogInfo((object)("Зов набега: попытка " + text + " снята по команде (" + AdminProtocol.ForLog(admin) + ")."));
			ZDO val = (((ZDOID)(ref _sign)).IsNone() ? null : ZDOMan.instance.GetZDO(_sign));
			if (val == null && !_state.ByCommand)
			{
				Clear();
				return "набег " + text + " снят; табличка не найдена — итог не записан";
			}
			Finish(val, Outcome.Abandoned);
			return "набег " + text + " снят с итогом «брошено», награды не выдаются" + (flag2 ? "; набег игры сброшен, его враги остаются в мире" : (flag ? "; набега игры уже не было" : "; набег не успел начаться"));
		}

		public static void Begin(ZDO sign, AttemptState state)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: 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_0056: 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_00bf: Unknown result type (might be due to invalid IL or missing references)
			_sign = sign?.m_uid ?? ZDOID.None;
			_state = state;
			_lastShown = -1;
			Save(sign);
			if (sign != null)
			{
				ActiveFile.Set(_sign);
			}
			string title = Texts.Title(state.Event);
			Show(sign, Texts.Countdown(title, Plugin.CountdownSeconds.Value));
			World.MessageNear(Pos, 96f, Texts.StartMessage(title, Plugin.CountdownSeconds.Value));
			Plugin.Log.LogInfo((object)($"Зов набега: вызов {state.Event} (уровень {state.Tier}, трофей {state.Trophy}) " + (state.ByCommand ? "по команде у игрока" : "игроком") + $" {state.Caller} у {Pos}, отсчёт {Plugin.CountdownSeconds.Value} с."));
		}

		private static void Save(ZDO sign)
		{
			if (sign != null)
			{
				World.WriteState(sign, _state);
			}
			else
			{
				ActiveFile.SetState(_state);
			}
		}

		private static void Show(ZDO sign, string text)
		{
			if (sign != null)
			{
				World.SetSignText(sign, text);
			}
		}

		public unsafe static void Restore()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			ZDO val = null;
			AttemptState attemptState = ActiveFile.State;
			string text = "по команде";
			if (attemptState == null)
			{
				ZDOID current = ActiveFile.Current;
				if (((ZDOID)(ref current)).IsNone())
				{
					return;
				}
				current = ActiveFile.Current;
				text = ((object)(*(ZDOID*)(&current))/*cast due to .constrained prefix*/).ToString();
				val = ZDOMan.instance.GetZDO(ActiveFile.Current);
				attemptState = ((val != null) ? World.ReadState(val) : null);
			}
			if (attemptState == null || attemptState.Stage == Stage.Done)
			{
				Plugin.Log.LogWarning((object)("Зов набега: активная попытка " + text + " не найдена или уже завершена — забыта."));
				ActiveFile.Set(ZDOID.None);
				return;
			}
			if (attemptState.Tier < 1 || attemptState.Tier > 8)
			{
				Plugin.Log.LogWarning((object)$"Зов набега: у попытки {text} уровень {attemptState.Tier} вне 1..{8} — забыта.");
				ActiveFile.Set(ZDOID.None);
				return;
			}
			_sign = val?.m_uid ?? ZDOID.None;
			_state = attemptState;
			if (attemptState.Stage == Stage.Countdown)
			{
				attemptState.CountdownAt = World.Now;
				Save(val);
				Plugin.Log.LogInfo((object)("Зов набега: восстановлен отсчёт " + attemptState.Event + " — начат заново."));
				return;
			}
			RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent();
			if (currentRandomEvent == null || currentRandomEvent.m_name != attemptState.Event)
			{
				Plugin.Log.LogInfo((object)("Зов набега: набег " + attemptState.Event + " не восстановлен игрой после перезапуска."));
				Finish(val, Outcome.Disrupted);
				return;
			}
			_duration = currentRandomEvent.m_duration;
			_lastTime = currentRandomEvent.m_time;
			Plugin.Log.LogInfo((object)$"Зов набега: восстановлен набег {attemptState.Event}, время {currentRandomEvent.m_time:0}/{currentRandomEvent.m_duration:0} с, участников {attemptState.Participants.Count}.");
		}

		public static void Tick()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (_state != null)
			{
				ZDO val = (((ZDOID)(ref _sign)).IsNone() ? null : ZDOMan.instance.GetZDO(_sign));
				if (val == null && !_state.ByCommand)
				{
					Plugin.Log.LogWarning((object)((_state.Stage == Stage.Countdown) ? ("Зов набега: табличка разрушена до начала набега — набег " + _state.Event + " отменён.") : ("Зов набега: табличка попытки " + _state.Event + " разрушена — итог не засчитывается, набег доигрывается сам.")));
					Clear();
				}
				else if (_state.Stage == Stage.Countdown)
				{
					TickCountdown(val);
				}
				else
				{
					TickRunning(val);
				}
			}
		}

		private static void TickCountdown(ZDO sign)
		{
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			int num = Mathf.CeilToInt((float)(_state.CountdownAt + (double)Plugin.CountdownSeconds.Value - World.Now));
			string title = Texts.Title(_state.Event);
			if (num > 0)
			{
				if (num != _lastShown && (num % 10 == 0 || num <= 5))
				{
					_lastShown = num;
					Show(sign, Texts.Countdown(title, num));
					if (sign == null && (num == 30 || num == 10))
					{
						World.MessageNear(Pos, 96f, Texts.Countdown(title, num));
					}
				}
				return;
			}
			if (RandEventSystem.instance.GetCurrentRandomEvent() != null)
			{
				if (!_waitLogged)
				{
					_waitLogged = true;
					Plugin.Log.LogInfo((object)("Зов набега: идёт чужой набег — " + _state.Event + " ждёт его конца."));
				}
				return;
			}
			_waitLogged = false;
			RandEventSystem.instance.SetRandomEventByName(_state.Event, Pos);
			RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent();
			if (currentRandomEvent == null || currentRandomEvent.m_name != _state.Event)
			{
				Plugin.Log.LogError((object)("Зов набега: игра не запустила набег " + _state.Event + "."));
				Finish(sign, Outcome.Disrupted);
				return;
			}
			_state.Stage = Stage.Running;
			_state.StartedAt = World.Now;
			_duration = currentRandomEvent.m_duration;
			_lastTime = 0.0;
			_emptySince = -1.0;
			LastCharacter.Clear();
			PeerPlayer.Clear();
			Names.Clear();
			Save(sign);
			Show(sign, Texts.Running(title));
			if (sign == null)
			{
				World.MessageNear(Pos, 96f, Texts.Running(title));
			}
			Plugin.Log.LogInfo((object)$"Зов набега: набег {_state.Event} начался у {Pos}, длительность {currentRandomEvent.m_duration:0} с.");
		}

		private static void TickRunning(ZDO sign)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent();
			if (currentRandomEvent == null || currentRandomEvent.m_name != _state.Event)
			{
				Finish(sign, (currentRandomEvent != null) ? Outcome.Disrupted : RaidOutcome.OnEventGone(_lastTime, _duration));
				return;
			}
			_lastTime = currentRandomEvent.m_time;
			bool flag = false;
			bool flag2 = false;
			List<World.PlayerInfo> list = World.Players();
			foreach (World.PlayerInfo item in list)
			{
				if (item.PlayerId != 0L)
				{
					bool flag3 = World.DistanceXZ(item.Pos, currentRandomEvent.m_pos) < currentRandomEvent.m_eventRange;
					flag2 = flag2 || flag3;
					if (flag3 || _state.Participants.Contains(item.PlayerId))
					{
						Names[item.PlayerId] = item.Peer.m_playerName;
					}
					if (flag3 && _state.Participants.Add(item.PlayerId))
					{
						flag = true;
						Plugin.Log.LogInfo((object)$"Зов набега: участник {item.PlayerId} ({item.Peer.m_playerName}).");
					}
				}
			}
			HashSet<long> peerUids = new HashSet<long>();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				peerUids.Add(peer.m_uid);
				World.PlayerInfo playerInfo = list.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)$"Зов набега: участник {value} погиб — без награды.");
					}
					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(sign);
			}
			double now = World.Now;
			_emptySince = (flag2 ? (-1.0) : ((_emptySince < 0.0) ? now : _emptySince));
			if (RaidOutcome.IsAbandoned(_emptySince, now, (double)Plugin.AbandonMinutes.Value * 60.0))
			{
				RandEventSystem.instance.ResetRandomEvent();
				Finish(sign, Outcome.Abandoned);
			}
		}

		private static void Finish(ZDO sign, Outcome outcome)
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string title = Texts.Title(_state.Event);
				_state.Stage = Stage.Done;
				_state.DoneAt = World.Now;
				_state.Result = outcome;
				List<long> list = ((outcome == Outcome.Victory) ? RaidOutcome.Winners(_state.Participants, _state.Dead) : new List<long>());
				foreach (long item in list.Where((long id) => !_state.Rewarded.Contains(id)))
				{
					try
					{
						if (Rewards.Give(_sign, _state, item))
						{
							_state.Rewarded.Add(item);
						}
					}
					catch (Exception ex)
					{
						Plugin.Log.LogError((object)($"Зов набега: ошибка выдачи награды игроку {item}: " + ex));
					}
				}
				Save(sign);
				Show(sign, Texts.Result(outcome, title));
				foreach (World.PlayerInfo item2 in from p in World.Players()
					where _state.Participants.Contains(p.PlayerId)
					select p)
				{
					World.Message(item2.Peer.m_uid, Texts.ResultMessage(outcome, title, list.Contains(item2.PlayerId), _state.Rewarded.Contains(item2.PlayerId)));
				}
				Plugin.Log.LogInfo((object)string.Format("Зов набега: итог {0} — {1}; участники {2}; погибли {3}; награждены {4}.", _state.Event, outcome, string.Join(", ", _state.Participants), string.Join(", ", _state.Dead), string.Join(", ", _state.Rewarded)));
				WriteChronicle(outcome, (outcome == Outcome.Victory) ? list : _state.Participants.ToList());
			}
			finally
			{
				Clear();
			}
		}

		private static void WriteChronicle(Outcome outcome, List<long> players)
		{
			try
			{
				string value;
				ChronicleEntry entry = new ChronicleEntry
				{
					Time = World.Now,
					Date = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
					Kind = "raid",
					Id = _state.Event,
					Result = outcome switch
					{
						Outcome.Abandoned => "abandoned", 
						Outcome.Victory => "victory", 
						_ => "disrupted", 
					},
					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.LogError((object)("Зов набега: ошибка записи в летопись: " + ex));
			}
		}

		private static void Clear()
		{
			//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)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			_state = null;
			_sign = ZDOID.None;
			LastCharacter.Clear();
			PeerPlayer.Clear();
			ActiveFile.Set(ZDOID.None);
		}
	}
	internal static class Rewards
	{
		private static readonly Random Rng = new Random();

		private static MethodInfo _enqueue;

		private static bool _checked;

		public static bool Give(ZDOID sign, AttemptState state, long playerId)
		{
			if (state.Tier < 1 || state.Tier > 8)
			{
				Plugin.Log.LogWarning((object)$"Зов набега: награда игроку {playerId} не выдана — уровень {state.Tier} вне 1..{8}.");
				return false;
			}
			string[] array = Plugin.RewardPool[state.Tier].Value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				Plugin.Log.LogWarning((object)$"Зов набега: награда игроку {playerId} не выдана — RewardPool уровня {state.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[state.Tier].Value); i++)
			{
				string text = array[Rng.Next(array.Length)];
				string text2 = RewardReason.Of(((ZDOID)(ref sign)).IsNone() ? null : $"{((ZDOID)(ref sign)).UserID}:{((ZDOID)(ref sign)).ID}", state.StartedAt, 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
	{
		internal struct PlayerInfo
		{
			public ZNetPeer Peer;

			public ZDO Character;

			public long PlayerId;

			public Vector3 Pos;
		}

		internal const string StateKey = "raidcall_state";

		private static readonly HashSet<int> SignPrefabs = new HashSet<int>();

		private static readonly HashSet<int> StandPrefabs = new HashSet<int>();

		private static readonly Dictionary<int, float> WardRadius = new Dictionary<int, float>();

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

		public static double Now => ZNet.instance.GetTimeSeconds();

		public static void Init()
		{
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name);
				if ((Object)(object)prefab.GetComponent<Sign>() != (Object)null)
				{
					SignPrefabs.Add(stableHashCode);
				}
				if ((Object)(object)prefab.GetComponent<ItemStand>() != (Object)null)
				{
					StandPrefabs.Add(stableHashCode);
				}
				PrivateArea component = prefab.GetComponent<PrivateArea>();
				if ((Object)(object)component != (Object)null)
				{
					WardRadius[stableHashCode] = component.m_radius;
				}
			}
			Plugin.Log.LogInfo((object)$"Зов набега: префабов табличек {SignPrefabs.Count}, подставок {StandPrefabs.Count}, вардов {WardRadius.Count}.");
		}

		public static List<PlayerInfo> Players()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: 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,
						Character = val,
						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);
		}

		private static List<ZDO> Near(Vector3 pos, float radius, HashSet<int> prefabs)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			Found.Clear();
			int num = Mathf.CeilToInt(radius / 64f);
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(pos), new SimulationDistance(num, 0, true), Found, (List<ZDO>)null);
			return Found.Where((ZDO z) => prefabs.Contains(z.GetPrefab()) && Vector3.Distance(z.GetPosition(), pos) <= radius).ToList();
		}

		public static List<ZDO> SignsNear(Vector3 pos, float radius)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Near(pos, radius, SignPrefabs);
		}

		public static ZDO StandWithItemNear(Vector3 pos, float radius)
		{
			//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_000e: Unknown result type (might be due to invalid IL or missing references)
			return (from z in Near(pos, radius, StandPrefabs)
				where z.GetInt(ZDOVars.s_item, 0) != 0
				orderby Vector3.Distance(z.GetPosition(), pos)
				select z).FirstOrDefault();
		}

		public static string ItemOnStand(ZDO stand)
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(stand.GetInt(ZDOVars.s_item, 0));
			if (!((Object)(object)itemPrefab != (Object)null))
			{
				return "";
			}
			return ((Object)itemPrefab).name;
		}

		public static List<WardInfo> WardsAround(Vector3 pos)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			float radius = ((WardRadius.Count > 0) ? WardRadius.Values.Max() : 32f);
			List<WardInfo> list = new List<WardInfo>();
			foreach (ZDO item in Near(pos, radius, new HashSet<int>(WardRadius.Keys)))
			{
				List<long> list2 = new List<long>();
				int num = item.GetInt(ZDOVars.s_permitted, 0);
				for (int i = 0; i < num; i++)
				{
					list2.Add(item.GetLong("pu_id" + i, 0L));
				}
				bool inside = DistanceXZ(item.GetPosition(), pos) <= WardRadius[item.GetPrefab()];
				list.Add(new WardInfo(item.GetBool(ZDOVars.s_enabled, false), inside, item.GetLong(ZDOVars.s_creator, 0L), list2));
			}
			return list;
		}

		public static int 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_000c: Expected I4, but got Unknown
			return (int)WorldGenerator.instance.GetBiome(pos);
		}

		public static RandomEvent EventTemplate(string name)
		{
			return ((IEnumerable<RandomEvent>)RandEventSystem.instance.m_events).FirstOrDefault((Func<RandomEvent, bool>)((RandomEvent e) => e.m_name == name && e.m_enabled));
		}

		public static IEnumerable<int> SpawnBiomes(RandomEvent ev)
		{
			return from s in ev.m_spawn
				where s.m_enabled
				select (int)s.m_biome;
		}

		public static long CallerOf(ZDO sign, List<PlayerInfo> players)
		{
			long owner = sign.GetOwner();
			foreach (PlayerInfo player in players)
			{
				if (player.Peer.m_uid == owner)
				{
					return player.PlayerId;
				}
			}
			PlayerInfo playerInfo = (from p in players
				where Vector3.Distance(p.Pos, sign.GetPosition()) <= 10f
				orderby Vector3.Distance(p.Pos, sign.GetPosition())
				select p).FirstOrDefault();
			if (playerInfo.Peer == null)
			{
				return 0L;
			}
			return playerInfo.PlayerId;
		}

		public static void SetSignText(ZDO sign, string text)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			sign.SetOwner(ZDOMan.GetSessionID());
			sign.Set(ZDOVars.s_text, text);
			ZDOMan.instance.ForceSendZDO(sign.m_uid);
		}

		public static string SignText(ZDO sign)
		{
			return sign.GetString(ZDOVars.s_text, "");
		}

		public static AttemptState ReadState(ZDO sign)
		{
			return AttemptState.Parse(sign.GetString("raidcall_state", ""));
		}

		public static void WriteState(ZDO sign, AttemptState state)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			sign.SetOwner(ZDOMan.GetSessionID());
			sign.Set("raidcall_state", state.Serialize());
			ZDOMan.instance.ForceSendZDO(sign.m_uid);
		}

		public static bool TakeItem(ZDO stand, string expectedPrefab)
		{
			//IL_003b: 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)
			if (ItemOnStand(stand) != expectedPrefab)
			{
				return false;
			}
			stand.SetOwner(ZDOMan.GetSessionID());
			stand.Set(ZDOVars.s_item, 0, false);
			stand.Set(ZDOVars.s_type, 0, false);
			ZDOMan.instance.ForceSendZDO(stand.m_uid);
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, stand.m_uid, "SetVisualItem", new object[4] { 0, 0, 0, 0 });
			return true;
		}

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

		public static void MessageNear(Vector3 pos, float radius, string text)
		{
			//IL_0016: 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)
			foreach (PlayerInfo item in Players())
			{
				if (DistanceXZ(item.Pos, pos) <= radius)
				{
					Message(item.Peer.m_uid, text);
				}
			}
		}
	}
}
namespace RaidCall.Core
{
	public enum StartRefusal
	{
		None,
		NotInTable,
		EventDisabled,
		Busy,
		NoPlayerFromFile,
		PlayerNotFound,
		Underground,
		WrongBiome
	}
	public sealed class StartChoice
	{
		public string Event = "";

		public int Tier;

		public string Trophy = "";
	}
	public struct StartFacts
	{
		public bool Resolved;

		public bool EventEnabled;

		public bool Busy;

		public bool FromFile;

		public bool PlayerGiven;

		public bool PlayerFound;

		public float PlayerY;

		public bool BiomeFits;
	}
	public static class AdminStart
	{
		public const float DungeonHeight = 3000f;

		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		public static StartChoice Resolve(string argument, IDictionary<string, RaidEntry> table)
		{
			string a = (argument ?? "").Trim();
			if (a == "")
			{
				return null;
			}
			RaidEntry raidEntry = table.Values.FirstOrDefault((RaidEntry e) => string.Equals(e.Trophy, a, StringComparison.OrdinalIgnoreCase));
			if (raidEntry != null)
			{
				return new StartChoice
				{
					Event = raidEntry.Event,
					Tier = raidEntry.Tier,
					Trophy = raidEntry.Trophy
				};
			}
			List<RaidEntry> list = table.Values.Where((RaidEntry e) => string.Equals(e.Event, a, StringComparison.OrdinalIgnoreCase)).ToList();
			if (list.Count != 0)
			{
				return new StartChoice
				{
					Event = list[0].Event,
					Tier = list.Min((RaidEntry e) => e.Tier)
				};
			}
			return null;
		}

		public static StartRefusal Check(StartFacts f)
		{
			if (!f.Resolved)
			{
				return StartRefusal.NotInTable;
			}
			if (!f.EventEnabled)
			{
				return StartRefusal.EventDisabled;
			}
			if (f.Busy)
			{
				return StartRefusal.Busy;
			}
			if (f.FromFile && !f.PlayerGiven)
			{
				return StartRefusal.NoPlayerFromFile;
			}
			if (!f.PlayerFound)
			{
				return StartRefusal.PlayerNotFound;
			}
			if (f.PlayerY >= 3000f)
			{
				return StartRefusal.Underground;
			}
			if (!f.BiomeFits)
			{
				return StartRefusal.WrongBiome;
			}
			return StartRefusal.None;
		}

		public static string RefusalText(StartRefusal r, string argument, string player)
		{
			switch (r)
			{
			case StartRefusal.NotInTable:
				return "«" + argument + "» — нет такого набега или трофея в таблице Raids";
			case StartRefusal.EventDisabled:
				return "этого набега нет в игре или он выключен";
			case StartRefusal.Busy:
				return "уже идёт попытка или набег игры — сначала status/stop";
			case StartRefusal.NoPlayerFromFile:
				return "из файла команд укажите игрока: start <набег|трофей> <Имя|@ID>";
			case StartRefusal.PlayerNotFound:
				if (!(player == ""))
				{
					return "игрок «" + player + "» не найден в игре";
				}
				return "вы не в игре: укажите игрока";
			case StartRefusal.Underground:
				return "игрок в подземелье: набег начинается только на поверхности";
			case StartRefusal.WrongBiome:
				return "враги этого набега не приходят в биом игрока";
			default:
				return "набег не начат";
			}
		}

		public static string Started(string eventName, int tier, string player, float x, float z, int seconds)
		{
			return "набег " + Texts.Title(eventName) + " (" + eventName + "), уровень " + tier.ToString(Inv) + ", у игрока " + player + " в (" + x.ToString("0", Inv) + "; " + z.ToString("0", Inv) + "), отсчёт " + seconds.ToString(Inv) + " с";
		}
	}
	public static class RewardReason
	{
		public static string Of(string signKey, double startedAt, int n)
		{
			return "raid:" + (signKey ?? "cmd") + ":" + ((long)startedAt).ToString(CultureInfo.InvariantCulture) + ((n > 1) ? ("/" + n.ToString(CultureInfo.InvariantCulture)) : "");
		}
	}
	public enum Stage
	{
		Countdown,
		Running,
		Done
	}
	public sealed class AttemptState
	{
		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		public Stage Stage;

		public string Event = "";

		public int Tier;

		public string Trophy = "";

		public long Caller;

		public float PosX;

		public float PosY;

		public float PosZ;

		public double CountdownAt;

		public double StartedAt = -1.0;

		public double DoneAt = -1.0;

		public Outcome? Result;

		public bool ByCommand;

		public HashSet<long> Participants = new HashSet<long>();

		public HashSet<long> Dead = new HashSet<long>();

		public HashSet<long> Rewarded = new HashSet<long>();

		public string Serialize()
		{
			List<string> list = new List<string>();
			list.Add("stage=" + Stage);
			list.Add("event=" + Event);
			list.Add("tier=" + Tier.ToString(Inv));
			list.Add("trophy=" + Trophy);
			list.Add("caller=" + Caller.ToString(Inv));
			list.Add("pos=" + string.Join(",", PosX.ToString("R", Inv), PosY.ToString("R", Inv), PosZ.ToString("R", Inv)));
			list.Add("countdown=" + CountdownAt.ToString("R", Inv));
			list.Add("started=" + StartedAt.ToString("R", Inv));
			list.Add("done=" + DoneAt.ToString("R", Inv));
			list.Add("result=" + Result);
			list.Add("members=" + Ids(Participants));
			list.Add("dead=" + Ids(Dead));
			list.Add("rewarded=" + Ids(Rewarded));
			list.Add("cmd=" + (ByCommand ? "1" : "0"));
			List<string> values = list;
			return string.Join(";", values);
		}

		public static AttemptState Parse(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			string[] array = text.Split(new char[1] { ';' });
			foreach (string text2 in array)
			{
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					dictionary[text2.Substring(0, num)] = text2.Substring(num + 1);
				}
			}
			if (!dictionary.TryGetValue("stage", out var value) || !Enum.TryParse<Stage>(value, out var result) || !dictionary.TryGetValue("event", out var value2) || value2 == "")
			{
				return null;
			}
			try
			{
				AttemptState attemptState = new AttemptState
				{
					Stage = result,
					Event = value2
				};
				attemptState.Tier = int.Parse(Get(dictionary, "tier", "0"), Inv);
				attemptState.Trophy = Get(dictionary, "trophy", "");
				attemptState.Caller = long.Parse(Get(dictionary, "caller", "0"), Inv);
				string[] array2 = Get(dictionary, "pos", "0,0,0").Split(new char[1] { ',' });
				attemptState.PosX = float.Parse(array2[0], Inv);
				attemptState.PosY = float.Parse(array2[1], Inv);
				attemptState.PosZ = float.Parse(array2[2], Inv);
				attemptState.CountdownAt = double.Parse(Get(dictionary, "countdown", "0"), Inv);
				attemptState.StartedAt = double.Parse(Get(dictionary, "started", "-1"), Inv);
				attemptState.DoneAt = double.Parse(Get(dictionary, "done", "-1"), Inv);
				string text3 = Get(dictionary, "result", "");
				attemptState.Result = ((text3 == "") ? ((Outcome?)null) : new Outcome?((Outcome)Enum.Parse(typeof(Outcome), text3)));
				attemptState.Participants = ParseIds(Get(dictionary, "members", ""));
				attemptState.Dead = ParseIds(Get(dictionary, "dead", ""));
				attemptState.Rewarded = ParseIds(Get(dictionary, "rewarded", ""));
				attemptState.ByCommand = Get(dictionary, "cmd", "0") == "1";
				return attemptState;
			}
			catch (Exception ex) when (ex is FormatException || ex is OverflowException || ex is ArgumentException || ex is IndexOutOfRangeException)
			{
				return null;
			}
		}

		private static string Get(Dictionary<string, string> map, string key, string fallback)
		{
			if (!map.TryGetValue(key, out var value))
			{
				return fallback;
			}
			return value;
		}

		private static string Ids(IEnumerable<long> ids)
		{
			return string.Join(",", ids.Select((long i) => i.ToString(Inv)));
		}

		private static HashSet<long> ParseIds(string text)
		{
			return new HashSet<long>(from i in text.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select long.Parse(i, Inv));
		}
	}
	public enum Refusal
	{
		None,
		NoTrophy,
		UnknownTrophy,
		EventUnavailable,
		Busy,
		Cooldown,
		ForeignWard,
		WrongBiome,
		TakeFailed
	}
	public struct CallFacts
	{
		public bool HasTrophy;

		public bool TrophyKnown;

		public bool EventAvailable;

		public bool Busy;

		public double Now;

		public double LastDoneAt;

		public double CooldownSeconds;

		public bool ForeignWard;

		public bool BiomeFits;
	}
	public static class CallRules
	{
		public static Refusal Check(CallFacts f)
		{
			if (!f.HasTrophy)
			{
				return Refusal.NoTrophy;
			}
			if (!f.TrophyKnown)
			{
				return Refusal.UnknownTrophy;
			}
			if (!f.EventAvailable)
			{
				return Refusal.EventUnavailable;
			}
			if (f.Busy)
			{
				return Refusal.Busy;
			}
			if (CooldownLeft(f) > 0.0)
			{
				return Refusal.Cooldown;
			}
			if (f.ForeignWard)
			{
				return Refusal.ForeignWard;
			}
			if (!f.BiomeFits)
			{
				return Refusal.WrongBiome;
			}
			return Refusal.None;
		}

		public static double CooldownLeft(CallFacts f)
		{
			if (!(f.LastDoneAt < 0.0))
			{
				return Math.Max(0.0, f.LastDoneAt + f.CooldownSeconds - f.Now);
			}
			return 0.0;
		}

		public static bool BiomeFits(int placeBiome, IEnumerable<int> spawnBiomes)
		{
			return spawnBiomes.Any((int b) => (b & placeBiome) != 0);
		}
	}
	public enum Outcome
	{
		Victory,
		Disrupted,
		Abandoned
	}
	public static class RaidOutcome
	{
		public const double EndTolerance = 2.5;

		public static Outcome OnEventGone(double lastTime, double duration)
		{
			if (!(duration > 0.0) || !(lastTime >= duration - 2.5))
			{
				return Outcome.Disrupted;
			}
			return Outcome.Victory;
		}

		public static bool IsAbandoned(double emptySince, double now, double abandonSeconds)
		{
			if (emptySince >= 0.0)
			{
				return now - emptySince >= abandonSeconds;
			}
			return false;
		}

		public static List<long> Winners(IEnumerable<long> participants, ICollection<long> dead)
		{
			return participants.Where((long p) => !dead.Contains(p)).Distinct().ToList();
		}
	}
	public static class RaidReport
	{
		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		public static string Status(AttemptState state, double now, int countdownSeconds, double eventTime, double eventDuration, string otherEvent, int tableSize)
		{
			string text = "трофеев в таблице " + tableSize.ToString(Inv);
			bool flag = !string.IsNullOrEmpty(otherEvent);
			if (state == null)
			{
				return (flag ? ("занято: попытки нет, идёт набег игры " + otherEvent) : "свободно: попытки нет") + "; " + text;
			}
			string text2 = ((state.Trophy == "") ? "без трофея" : ("трофей " + state.Trophy));
			string text3 = (state.ByCommand ? "по команде у игрока " : "позвал игрок ") + state.Caller.ToString(Inv);
			string text4 = "занято: " + Texts.Title(state.Event) + " (" + state.Event + "), уровень " + state.Tier.ToString(Inv) + ", " + text2 + ", " + text3 + ", место (" + state.PosX.ToString("0", Inv) + "; " + state.PosY.ToString("0", Inv) + "; " + state.PosZ.ToString("0", Inv) + ")";
			string text5 = ((state.Stage == Stage.Countdown) ? ("отсчёт: до начала " + Math.Max(0.0, Math.Ceiling(state.CountdownAt + (double)countdownSeconds - now)).ToString("0", Inv) + " с" + (flag ? (", ждёт конца набега игры " + otherEvent) : "")) : ("идёт: " + eventTime.ToString("0", Inv) + " из " + eventDuration.ToString("0", Inv) + " с"));
			return text4 + "\n" + text5 + "; участники " + state.Participants.Count.ToString(Inv) + ", погибли " + state.Dead.Count.ToString(Inv) + "; " + text;
		}
	}
	public sealed class RaidEntry
	{
		public string Trophy { get; }

		public string Event { get; }

		public int Tier { get; }

		public RaidEntry(string trophy, string eventName, int tier)
		{
			Trophy = trophy;
			Event = eventName;
			Tier = tier;
		}
	}
	public static class RaidTable
	{
		public const int MaxTier = 8;

		public const string Default = "TrophyBoar:army_eikthyr:1, TrophyNeck:army_eikthyr:1, TrophyDeer:army_eikthyr:1, TrophyEikthyr:army_eikthyr:2, TrophyGreydwarf:army_theelder:2, TrophyGreydwarfBrute:army_theelder:2, TrophyGreydwarfShaman:army_theelder:2, TrophyTheElder:army_theelder:3, TrophyForestTroll:foresttrolls:2, TrophyBjorn:foresttrolls:2, TrophySkeleton:skeletons:2, TrophyGhost:ghosts:2, TrophySkeletonHildir:hildirboss1:4, TrophyDraugr:army_bonemass:3, TrophyDraugrFem:army_bonemass:3, TrophyDraugrElite:army_bonemass:3, TrophyAbomination:army_bonemass:3, TrophyKvastur:army_bonemass:4, TrophyBonemass:army_bonemass:4, TrophyBlob:blobs:3, TrophyLeech:blobs:3, TrophySkeletonPoison:skeletons:3, TrophyWraith:ghosts:3, TrophySurtling:surtlings:3, TrophyHatchling:army_moder:4, TrophySGolem:army_moder:4, TrophySerpent:army_moder:4, TrophyDragonQueen:army_moder:5, TrophyWolf:wolves:4, TrophyFenring:wolves:4, TrophyUlv:bats:4, TrophyCultist:bats:4, TrophyCultist_Hildir:hildirboss2:4, TrophyGoblin:army_goblin:5, TrophyGoblinBrute:army_goblin:5, TrophyGoblinShaman:army_goblin:5, TrophyLox:army_goblin:5, TrophyGoblinKing:army_goblin:6, TrophyDeathsquito:bats:5, TrophyGrowth:blobs:5, TrophyBjornUndead:foresttrolls:5, TrophyGoblinBruteBrosBrute:hildirboss3:5, TrophyGoblinBruteBrosShaman:hildirboss3:5, TrophyGjall:army_gjall:6, TrophyTick:army_gjall:6, TrophyHare:army_gjall:6, TrophySeeker:army_seekers:6, TrophySeekerBrute:army_seekers:6, TrophyDvergr:army_seekers:6, TrophySeekerQueen:army_seekers:7, TrophyCharredMelee:army_charred:7, TrophyCharredArcher:army_charred:7, TrophyAsksvin:army_charred:7, TrophyVolture:army_charred:7, TrophyBonemawSerpent:army_charred:7, TrophyCharredMage:army_charredspawners:7, TrophyMorgen:army_charredspawners:7, TrophyFallenValkyrie:army_charredspawners:7, TrophyBlob_Lava:blobs:7, TrophyFader:army_charredspawners:8, TrophyJotunWarrior:army_jotuns:8, TrophyJotunWitch:army_jotuns:8, TrophyFrostTroll:army_jotuns:8, TrophyBarka:army_jotuns:8, TrophyWrithan:army_jotuns:8, TrophyMole:army_elakingar:8, TrophyElaking:army_elakingar:8, TrophyMoose:army_elakingar:8, TrophySeal:army_elakingar:8, TrophyDeerWhite:army_elakingar:8, TrophyBlob_Frost:blobs:8, TrophyBlob_Morkhalla:blobs:8";

		public static Dictionary<string, RaidEntry> Parse(string text, List<string> errors)
		{
			Dictionary<string, RaidEntry> dictionary = new Dictionary<string, RaidEntry>(StringComparer.OrdinalIgnoreCase);
			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)
				{
					continue;
				}
				string[] array2 = text2.Split(new char[1] { ':' });
				if (array2.Length != 3 || array2[0].Trim() == "" || array2[1].Trim() == "" || !int.TryParse(array2[2].Trim(), out var result) || result < 1 || result > 8)
				{
					errors.Add($"строка «{text2}» пропущена: нужно Трофей:набег:уровень 1–{8}");
					continue;
				}
				string text3 = array2[0].Trim();
				if (dictionary.ContainsKey(text3))
				{
					errors.Add("трофей " + text3 + " указан повторно, оставлена первая строка");
				}
				else
				{
					dictionary[text3] = new RaidEntry(text3, array2[1].Trim(), result);
				}
			}
			return dictionary;
		}
	}
	public static class RaidUi
	{
		public static string Text(IDictionary<string, RaidEntry> table, Func<string, bool> enabled, bool busy)
		{
			List<string> list = new List<string>();
			list.Add(AdminUi.Row("busy", busy));
			List<string> list2 = list;
			foreach (IGrouping<string, RaidEntry> item in (from g in table.Values.GroupBy<RaidEntry, string>((RaidEntry e) => e.Event, StringComparer.Ordinal)
				orderby g.Min((RaidEntry e) => e.Tier)
				select g).ThenBy<IGrouping<string, RaidEntry>, string>((IGrouping<string, RaidEntry> g) => g.Key, StringComparer.Ordinal))
			{
				string text = string.Join(",", from e in item
					group e by e.Tier into t
					orderby t.Key
					select t.Key.ToString(CultureInfo.InvariantCulture) + ":" + t.Select((RaidEntry e) => e.Trophy).OrderBy<string, string>((string s) => s, StringComparer.Ordinal).First());
				list2.Add(AdminUi.Row("raid", item.Key, enabled(item.Key), item.Min((RaidEntry e) => e.Tier), text));
			}
			return AdminUi.Text(list2);
		}
	}
	public static class Texts
	{
		public const int SignLimit = 50;

		private static readonly Dictionary<string, string> Titles = new Dictionary<string, string>
		{
			["army_eikthyr"] = "Свита Эйктюра",
			["army_theelder"] = "Грейдворфы",
			["foresttrolls"] = "Тролли",
			["army_bonemass"] = "Драугры",
			["blobs"] = "Слизни",
			["skeletons"] = "Скелеты",
			["ghosts"] = "Призраки",
			["surtlings"] = "Суртлинги",
			["army_moder"] = "Дракончики",
			["wolves"] = "Волки",
			["bats"] = "Летучие мыши",
			["hildirboss1"] = "Бренна",
			["hildirboss2"] = "Гейрафа",
			["hildirboss3"] = "Зил и Тангр",
			["army_goblin"] = "Фулинги",
			["army_gjall"] = "Гьяллы",
			["army_seekers"] = "Искатели",
			["army_charred"] = "Обугленные",
			["army_charredspawners"] = "Камни обугленных",
			["army_jotuns"] = "Ётуны",
			["army_elakingar"] = "Элакинги"
		};

		public static string Title(string eventName)
		{
			if (!Titles.TryGetValue(eventName, out var value))
			{
				return eventName;
			}
			return value;
		}

		public static string Countdown(string title, int seconds)
		{
			return $"Набег: {title} через {seconds} с";
		}

		public static string Running(string title)
		{
			return "Набег: " + title + "! Держитесь";
		}

		public static string Result(Outcome outcome, string title)
		{
			return outcome switch
			{
				Outcome.Victory => "Выстояли: " + title, 
				Outcome.Abandoned => "Набег брошен: " + title, 
				_ => "Набег сорван: " + title, 
			};
		}

		public static string RefusalText(Refusal refusal, double cooldownLeftSeconds)
		{
			return refusal switch
			{
				Refusal.NoTrophy => "Повесьте трофей на подставку рядом", 
				Refusal.UnknownTrophy => "Этот трофей не зовёт набег", 
				Refusal.EventUnavailable => "Этот набег сейчас недоступен", 
				Refusal.Busy => "Сейчас идёт другой набег", 
				Refusal.Cooldown => $"Место отдыхает ещё {(int)Math.Ceiling(cooldownLeftSeconds / 60.0)} мин", 
				Refusal.ForeignWard => "Чужой вард: здесь звать нельзя", 
				Refusal.WrongBiome => "Эти враги сюда не приходят", 
				Refusal.TakeFailed => "Не удалось взять трофей", 
				_ => "Набег нельзя позвать", 
			};
		}

		public static string StartMessage(string title, int seconds)
		{
			return $"Зов набега: {title} через {seconds} с. Готовьтесь!";
		}

		public static string ResultMessage(Outcome outcome, string title, bool survived, bool rewarded)
		{
			switch (outcome)
			{
			case Outcome.Victory:
				if (!survived)
				{
					return "Выстояли: " + title + "! Павшим награды нет.";
				}
				if (!rewarded)
				{
					return "Выстояли: " + title + "!";
				}
				return "Выстояли: " + title + "! Награда ждёт.";
			case Outcome.Abandoned:
				return "Набег брошен: " + title + ". Награды нет.";
			default:
				return "Набег сорван: " + title + ". Награды нет.";
			}
		}
	}
	public sealed class WardInfo
	{
		public bool Enabled { get; }

		public bool Inside { get; }

		public long Creator { get; }

		public HashSet<long> Permitted { get; }

		public WardInfo(bool enabled, bool inside, long creator, IEnumerable<long> permitted)
		{
			Enabled = enabled;
			Inside = inside;
			Creator = creator;
			Permitted = new HashSet<long>(permitted ?? Enumerable.Empty<long>());
		}
	}
	public static class WardRules
	{
		public static bool IsForeign(long caller, IEnumerable<WardInfo> wards)
		{
			return wards.Any((WardInfo w) => w.Enabled && w.Inside && (caller == 0L || (w.Creator != caller && !w.Permitted.Contains(caller))));
		}
	}
}