Decompiled source of SansaraPath v0.6.1

plugins/SansaraPath.dll

Decompiled a day ago
using System;
using System.Collections;
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 Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Sansara.Admin;
using Sansara.Chronicle;
using Sansara.Deeds;
using SansaraPath.Core;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[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("SansaraPath")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.6.1.0")]
[assembly: AssemblyInformationalVersion("0.6.1+60a4ae76ed7918200cbfb333ad77d944d474136c")]
[assembly: AssemblyProduct("SansaraPath")]
[assembly: AssemblyTitle("SansaraPath")]
[assembly: AssemblyVersion("0.6.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.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 Sansara.Deeds
{
	public static class DeedBus
	{
		public const string Key = "sansara.Deeds.v1";

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

		public static bool Subscribe(string owner, Action<string, string, int, string, long[], string[]> handler)
		{
			try
			{
				Table[owner] = handler ?? throw new ArgumentNullException("handler");
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		public static bool Unsubscribe(string owner)
		{
			try
			{
				Table.Remove(owner);
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		public static int Publish(string kind, string id, int tier, string result, long[] playerIds, string[] playerNames, Action<string> log)
		{
			if (playerIds == null || playerNames == null || playerIds.Length == 0 || playerIds.Length != playerNames.Length)
			{
				return 0;
			}
			KeyValuePair<string, Action<string, string, int, string, long[], string[]>>[] array;
			try
			{
				array = Table.ToArray();
			}
			catch (Exception ex)
			{
				log?.Invoke("Шина подвигов: " + ex.Message);
				return 0;
			}
			KeyValuePair<string, Action<string, string, int, string, long[], string[]>>[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				KeyValuePair<string, Action<string, string, int, string, long[], string[]>> keyValuePair = array2[i];
				try
				{
					keyValuePair.Value(kind, id, tier, result, playerIds, playerNames);
				}
				catch (Exception arg)
				{
					log?.Invoke($"Шина подвигов: подписчик {keyValuePair.Key}: {arg}");
				}
			}
			return array.Length;
		}
	}
}
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 SansaraPath
{
	internal static class ClientSide
	{
		private sealed class Bill
		{
			public int Id;

			public string Token = "";

			public int Coins;

			public long Paid;
		}

		private sealed class Confirmation
		{
			public float At;

			public bool Payout;

			public string Arg;
		}

		private sealed class PendingReply
		{
			public string Stamp;

			public Grade Grade;

			public Affix Affix;

			public float Value;

			public Affix Affix2;

			public float Value2;

			public string Name = "";
		}

		internal const string BillsKey = "sansara.Path.bills";

		private const float ConfirmDelaySeconds = 0.5f;

		private static float _payoutNoRoomAt = -1000f;

		private static bool _billsResent;

		private static readonly List<Confirmation> Confirmations = new List<Confirmation>();

		private static Player _runesPlayer;

		private static ProfileView _runesProfile;

		private static string _runesWanted = "";

		private static string _runesApplied = "";

		internal static readonly Dictionary<long, string> TitlesById = new Dictionary<long, string>();

		internal static readonly Dictionary<string, string> TitlesBySender = new Dictionary<string, string>(StringComparer.Ordinal);

		private static float _noRoomMessageAt = -1000f;

		private static readonly HashSet<string> SealWarned = new HashSet<string>(StringComparer.Ordinal);

		private static ZRpc _server;

		private static readonly HashSet<string> WornSent = new HashSet<string>(StringComparer.Ordinal);

		private static readonly MethodInfo InventoryChanged = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null);

		internal static ProfileView Profile;

		private const float ProfileRetrySeconds = 5f;

		private static float _profileRequestedAt = -1000f;

		private static readonly Dictionary<string, string> SentSeals = new Dictionary<string, string>(StringComparer.Ordinal);

		internal static readonly HashSet<string> RejectedSeals = new HashSet<string>(StringComparer.Ordinal);

		private const int PendingCap = 50;

		private static readonly Dictionary<string, PendingReply> Pending = new Dictionary<string, PendingReply>(StringComparer.Ordinal);

		private static string _forgeBonusText;

		private static int[] _forgeBonusByRank = PathSettings.DefaultForgeBonusByRank;

		internal static bool Connected
		{
			get
			{
				if (_server != null && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer())
				{
					return ZNet.instance.GetServerPeer()?.m_rpc == _server;
				}
				return false;
			}
		}

		internal static void Notify(string text)
		{
			MessageHud instance = MessageHud.instance;
			if (instance != null)
			{
				instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
		}

		private static string CoinName()
		{
			GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Coins") : null);
			ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val2 != (Object)null))
			{
				return null;
			}
			return val2.m_itemData.m_shared.m_name;
		}

		internal static int CoinsInBag()
		{
			Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
			string text = CoinName();
			if (val != null && text != null)
			{
				return val.CountItems(text, -1, true);
			}
			return 0;
		}

		internal static bool HasBill(int id)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !localPlayer.m_customData.TryGetValue("sansara.Path.bills", out var value) || string.IsNullOrEmpty(value))
			{
				return false;
			}
			for (int i = 0; i < value.Length; i++)
			{
				int num = 0;
				bool flag = false;
				for (; i < value.Length && value[i] >= '0' && value[i] <= '9'; i++)
				{
					num = num * 10 + (value[i] - 48);
					flag = true;
				}
				if (flag && num == id && i < value.Length && value[i] == ':')
				{
					return true;
				}
				for (; i < value.Length && value[i] != ','; i++)
				{
				}
			}
			return false;
		}

		private static List<Bill> ReadBills(Player player)
		{
			List<Bill> list = new List<Bill>();
			if (!player.m_customData.TryGetValue("sansara.Path.bills", out var value) || string.IsNullOrEmpty(value))
			{
				return list;
			}
			string[] array = value.Split(new char[1] { ',' });
			foreach (string text in array)
			{
				string[] array2 = text.Split(new char[1] { ':' });
				if (array2.Length == 4 && int.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0 && array2[1] != "" && int.TryParse(array2[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) && long.TryParse(array2[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3))
				{
					list.Add(new Bill
					{
						Id = result,
						Token = array2[1],
						Coins = result2,
						Paid = result3
					});
				}
				else if (text != "")
				{
					Plugin.Log.LogWarning((object)("Путь: непонятная запись счёта «" + text + "» — пропущена"));
				}
			}
			return list;
		}

		private static void WriteBills(Player player, List<Bill> bills)
		{
			if (bills.Count == 0)
			{
				player.m_customData.Remove("sansara.Path.bills");
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (Bill bill in bills)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append(bill.Id.ToString(CultureInfo.InvariantCulture)).Append(':').Append(bill.Token)
					.Append(':')
					.Append(bill.Coins.ToString(CultureInfo.InvariantCulture))
					.Append(':')
					.Append(bill.Paid.ToString(CultureInfo.InvariantCulture));
			}
			player.m_customData["sansara.Path.bills"] = stringBuilder.ToString();
		}

		private static void TickBills()
		{
			if (_billsResent || !Connected)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			_billsResent = true;
			if (!localPlayer.m_customData.ContainsKey("sansara.Path.bills"))
			{
				return;
			}
			List<Bill> list = ReadBills(localPlayer);
			long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			int count = list.Count;
			list.RemoveAll((Bill b) => now - b.Paid > 2678400);
			if (list.Count != count)
			{
				Plugin.Log.LogWarning((object)$"Путь: забыто старых счетов без ответа сервера: {count - list.Count}");
			}
			WriteBills(localPlayer, list);
			foreach (Bill item in list)
			{
				QueuePaid(item.Id, item.Token);
				Plugin.Log.LogInfo((object)$"Путь: повтор оплаты заказа №{item.Id} ({item.Coins} монет)");
			}
		}

		private static void QueuePaid(int id, string token)
		{
			Confirmations.Add(new Confirmation
			{
				At = Time.realtimeSinceStartup + 0.5f,
				Arg = MasterOrders.PaidArg(id, token)
			});
		}

		private static void QueuePayoutTaken(string uid)
		{
			Confirmations.Add(new Confirmation
			{
				At = Time.realtimeSinceStartup + 0.5f,
				Payout = true,
				Arg = uid
			});
		}

		private static void TickConfirmations()
		{
			if (Confirmations.Count == 0)
			{
				return;
			}
			if (!Connected)
			{
				Confirmations.Clear();
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			while (Confirmations.Count > 0 && Confirmations[0].At <= realtimeSinceStartup)
			{
				Confirmation confirmation = Confirmations[0];
				Confirmations.RemoveAt(0);
				if (confirmation.Payout)
				{
					_server.Invoke("SansaraPath_PayoutTaken", new object[1] { confirmation.Arg });
				}
				else
				{
					Request("order-paid", confirmation.Arg);
				}
			}
		}

		private static void OnOrderBill(ZRpc rpc, int id, string token, int coins)
		{
			try
			{
				string arg = id.ToString(CultureInfo.InvariantCulture);
				Player localPlayer = Player.m_localPlayer;
				Inventory val = (((Object)(object)localPlayer != (Object)null) ? ((Humanoid)localPlayer).GetInventory() : null);
				string text = CoinName();
				if (val == null || text == null || coins <= 0 || id <= 0 || string.IsNullOrEmpty(token) || token.IndexOf(':') >= 0 || token.IndexOf(',') >= 0)
				{
					Request("order-cancel", arg);
					return;
				}
				List<Bill> list = null;
				if (HasBill(id))
				{
					list = ReadBills(localPlayer);
					if (list.Exists((Bill b) => b.Id == id && b.Token == token))
					{
						QueuePaid(id, token);
						return;
					}
					int num = list.RemoveAll((Bill b) => b.Id == id);
					if (num > 0)
					{
						Plugin.Log.LogWarning((object)$"Путь: старый счёт заказа №{id} с другой меткой забыт ({num}): сервер его не примет");
						WriteBills(localPlayer, list);
					}
				}
				int num2 = val.CountItems(text, -1, true);
				if (num2 < coins)
				{
					Notify($"Не хватает монет на заказ №{id}: нужно {coins}, в сумке {num2}.");
					Request("order-cancel", arg);
					return;
				}
				val.RemoveItem(text, coins, -1, true);
				if (list == null)
				{
					list = ReadBills(localPlayer);
				}
				list.Add(new Bill
				{
					Id = id,
					Token = token,
					Coins = coins,
					Paid = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
				});
				WriteBills(localPlayer, list);
				QueuePaid(id, token);
				Plugin.Log.LogInfo((object)$"Путь: залог {coins} монет за заказ №{id}");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: оплата заказа: " + ex));
			}
		}

		private static void OnOrderPaidSeen(ZRpc rpc, int id, string token)
		{
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if (!((Object)(object)localPlayer == (Object)null) && HasBill(id))
				{
					List<Bill> list = ReadBills(localPlayer);
					if (list.RemoveAll((Bill b) => b.Id == id && b.Token == (token ?? "")) > 0)
					{
						WriteBills(localPlayer, list);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: ответ на оплату заказа: " + ex));
			}
		}

		private static void OnPayout(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				string text = pkg.ReadString();
				int num = pkg.ReadInt();
				int num2 = pkg.ReadInt();
				bool refund = pkg.ReadBool();
				Player localPlayer = Player.m_localPlayer;
				Inventory val = (((Object)(object)localPlayer != (Object)null) ? ((Humanoid)localPlayer).GetInventory() : null);
				GameObject val2 = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Coins") : null);
				if (val == null || (Object)(object)val2 == (Object)null || string.IsNullOrEmpty(text) || num <= 0)
				{
					return;
				}
				localPlayer.m_customData.TryGetValue("sansara.Path.payouts", out var value);
				if (MasterOrders.HasReceived(value, text))
				{
					QueuePayoutTaken(text);
				}
				else if (!val.CanAddItem(val2, num))
				{
					if (Time.realtimeSinceStartup - _payoutNoRoomAt > 60f)
					{
						_payoutNoRoomAt = Time.realtimeSinceStartup;
						Notify(PathTexts.PayoutNoRoom(num2, num));
					}
				}
				else
				{
					val.AddItem("Coins", num, 1, 0, 0L, "", false, false);
					localPlayer.m_customData["sansara.Path.payouts"] = MasterOrders.AddReceived(value, text, 100);
					Notify(PathTexts.PayoutReceived(num2, num, refund));
					QueuePayoutTaken(text);
					Plugin.Log.LogInfo((object)$"Путь: выплата {text}: +{num} монет за заказ №{num2}");
					Request("profile", "");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: выплата: " + ex));
			}
		}

		private static void TickRunes()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)RunesEffect.Template == (Object)null)
			{
				_runesPlayer = null;
				return;
			}
			if (_runesProfile != Profile)
			{
				_runesProfile = Profile;
				_runesWanted = RunesEffect.KeyOf(Profile);
			}
			string text = (Connected ? _runesWanted : "");
			SEMan sEMan = ((Character)localPlayer).GetSEMan();
			bool flag = sEMan.HaveStatusEffect(RunesEffect.Hash);
			if (localPlayer != _runesPlayer || !(text == _runesApplied) || flag != (text != ""))
			{
				_runesPlayer = localPlayer;
				_runesApplied = text;
				RunesEffect.Apply(sEMan, (text == "") ? null : Profile);
				if (text != "")
				{
					Plugin.Log.LogInfo((object)("Путь: руны судьбы: " + text));
				}
			}
		}

		private static void OnTitles(ZRpc rpc, string text)
		{
			try
			{
				TitlesById.Clear();
				TitlesBySender.Clear();
				string[] array = (text ?? "").Split(new char[1] { '\n' });
				for (int i = 0; i < array.Length; i++)
				{
					string[] array2 = array[i].TrimEnd(new char[1] { '\r' }).Split(new char[1] { '\t' });
					if (array2.Length >= 4 && !(array2[3] == ""))
					{
						if (long.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result != 0L)
						{
							TitlesById[result] = array2[3];
						}
						if (array2[1] != "" && array2[2] != "")
						{
							TitlesBySender[SansaraPath.Core.Profile.KeyOf(array2[1], array2[2])] = array2[3];
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: титулы: " + ex));
			}
		}

		internal unsafe static string TitleOfSender(PlatformUserID user, string name)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (TitlesBySender.Count == 0 || string.IsNullOrEmpty(name))
			{
				return null;
			}
			if (!string.IsNullOrEmpty(user.m_userID) && TitlesBySender.TryGetValue(SansaraPath.Core.Profile.KeyOf(user.m_userID, name), out var value))
			{
				return value;
			}
			if (!((PlatformUserID)(ref user)).IsValid || !TitlesBySender.TryGetValue(SansaraPath.Core.Profile.KeyOf(((object)(*(PlatformUserID*)(&user))/*cast due to .constrained prefix*/).ToString(), name), out value))
			{
				return null;
			}
			return value;
		}

		private static void OnSeal(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				string uid = pkg.ReadString();
				int tier = pkg.ReadInt();
				string text = pkg.ReadString();
				Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
				if (val == null)
				{
					return;
				}
				if (val.GetAllItems().Any((ItemData i) => ItemKeys.GetSeal(i.m_customData) == uid))
				{
					_server.Invoke("SansaraPath_SealTaken", new object[1] { uid });
					return;
				}
				GameObject val2 = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(text) : null);
				ItemDrop val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val3 == (Object)null || val3.m_itemData.m_shared.m_maxStackSize != 1)
				{
					if (SealWarned.Add(text))
					{
						Plugin.Log.LogWarning((object)("Путь: SealPrefab «" + text + "» не найден или стакается — печать не создана"));
					}
					return;
				}
				ItemData val4 = val.AddItem(text, 1, 1, 0, 0L, "", false, false);
				if (val4 == null)
				{
					if (Time.realtimeSinceStartup - _noRoomMessageAt > 60f)
					{
						_noRoomMessageAt = Time.realtimeSinceStartup;
						MessageHud instance = MessageHud.instance;
						if (instance != null)
						{
							instance.ShowMessage((MessageType)1, PathTexts.SealNoRoom(tier), 0, (Sprite)null, false, true);
						}
					}
				}
				else
				{
					val4.m_customData["sansara.Path.seal"] = uid;
					val4.m_customData["sansara.Path.sealTier"] = tier.ToString(CultureInfo.InvariantCulture);
					MarkChanged(val);
					_server.Invoke("SansaraPath_SealTaken", new object[1] { uid });
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: печать: " + ex));
			}
		}

		internal static string SealFor(int tier)
		{
			Inventory obj = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
			ItemData val = ((obj != null) ? ((IEnumerable<ItemData>)obj.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)delegate(ItemData i)
			{
				string seal = ItemKeys.GetSeal(i.m_customData);
				return seal != null && !RejectedSeals.Contains(seal) && ItemKeys.GetSealTier(i.m_customData) == tier;
			}) : null);
			if (val == null)
			{
				return null;
			}
			return ItemKeys.GetSeal(val.m_customData);
		}

		internal static bool HasTrophy(string prefab)
		{
			Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
			if (!string.IsNullOrEmpty(prefab) && val != null)
			{
				return val.GetAllItems().Any((ItemData i) => (Object)(object)i.m_dropPrefab != (Object)null && ((Object)i.m_dropPrefab).name == prefab);
			}
			return false;
		}

		private static void RemoveTrophy(string prefab)
		{
			if (!string.IsNullOrEmpty(prefab))
			{
				Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
				ItemData val2 = ((val != null) ? ((IEnumerable<ItemData>)val.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => (Object)(object)i.m_dropPrefab != (Object)null && ((Object)i.m_dropPrefab).name == prefab)) : null);
				if (val2 != null)
				{
					val.RemoveItem(val2, 1);
					return;
				}
				ManualLogSource log = Plugin.Log;
				string text = prefab;
				Player localPlayer = Player.m_localPlayer;
				log.LogWarning((object)("Путь: трофей " + text + " для Шедевра не найден в инвентаре игрока " + ((localPlayer != null) ? localPlayer.GetPlayerName() : null)));
			}
		}

		private static void RemoveSeal(string uid)
		{
			if (!string.IsNullOrEmpty(uid))
			{
				Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
				ItemData val2 = ((val != null) ? ((IEnumerable<ItemData>)val.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => ItemKeys.GetSeal(i.m_customData) == uid)) : null);
				if (val2 != null)
				{
					val.RemoveItem(val2);
				}
			}
		}

		public static void Register(ZNetPeer serverPeer)
		{
			_server = serverPeer.m_rpc;
			WornSent.Clear();
			Pending.Clear();
			SentSeals.Clear();
			RejectedSeals.Clear();
			Profile = null;
			_profileRequestedAt = -1000f;
			_billsResent = false;
			Confirmations.Clear();
			TitlesById.Clear();
			TitlesBySender.Clear();
			_server.Register<ZPackage>("SansaraPath_Crafted", (Action<ZRpc, ZPackage>)OnCrafted);
			_server.Register<string>("SansaraPath_Profile", (Action<ZRpc, string>)OnProfile);
			_server.Register<string>("SansaraPath_Message", (Action<ZRpc, string>)OnMessage);
			_server.Register<ZPackage>("SansaraPath_Seal", (Action<ZRpc, ZPackage>)OnSeal);
			_server.Register<string>("SansaraPath_Titles", (Action<ZRpc, string>)OnTitles);
			_server.Register<int, string, int>("SansaraPath_OrderBill", (Action<ZRpc, int, string, int>)OnOrderBill);
			_server.Register<int, string>("SansaraPath_OrderPaidSeen", (Action<ZRpc, int, string>)OnOrderPaidSeen);
			_server.Register<ZPackage>("SansaraPath_Payout", (Action<ZRpc, ZPackage>)OnPayout);
		}

		public static void SendCraft(string uid, string prefab, string sealUid, string masterpiece)
		{
			if (!Connected)
			{
				return;
			}
			if (!string.IsNullOrEmpty(sealUid) && !string.IsNullOrEmpty(uid))
			{
				if (SentSeals.Count >= 50)
				{
					SentSeals.Clear();
				}
				SentSeals[uid] = sealUid;
			}
			_server.Invoke("SansaraPath_Craft", new object[4]
			{
				uid,
				prefab,
				sealUid ?? "",
				masterpiece ?? ""
			});
		}

		public static void SendUpgrade(string prefab, int levels)
		{
			if (Connected)
			{
				_server.Invoke("SansaraPath_Upgrade", new object[2] { prefab, levels });
			}
		}

		public static void SendStack(string prefab, int batches, int tier)
		{
			if (Connected)
			{
				_server.Invoke("SansaraPath_Stack", new object[3] { prefab, batches, tier });
			}
		}

		public static void SendWorn(string uid)
		{
			if (Connected && WornSent.Add(uid))
			{
				_server.Invoke("SansaraPath_Worn", new object[1] { uid });
			}
		}

		public static void Request(string action, string arg)
		{
			if (Connected)
			{
				_server.Invoke("SansaraPath_Request", new object[2]
				{
					action,
					arg ?? ""
				});
			}
		}

		public static void Tick()
		{
			TickRunes();
			TickConfirmations();
			TickBills();
			if (Profile == null && !((Object)(object)Player.m_localPlayer == (Object)null) && Connected && !(Time.realtimeSinceStartup - _profileRequestedAt < 5f))
			{
				_profileRequestedAt = Time.realtimeSinceStartup;
				Request("profile", "auto");
			}
		}

		internal static float ForgeBonus(string prefab)
		{
			ProfileView profile = Profile;
			if (profile == null || !Connected || !PluginConfig.CraftEnabled(profile.Craft) || !ItemInfo.TryPrefab(prefab, out var data))
			{
				return 0f;
			}
			return Masterpieces.ForgeBonus(profile.Craft, profile.Rank, ItemInfo.KindOf(data), ForgeBonusByRank());
		}

		private static int[] ForgeBonusByRank()
		{
			string text = PluginConfig.ForgeBonusByRank.Value ?? "";
			if (text != _forgeBonusText)
			{
				int[] array = PathSettings.ParseInts(text, PathSettings.DefaultForgeBonusByRank, "ForgeBonusByRank", new List<string>());
				if (array.Length != PathSettings.DefaultForgeBonusByRank.Length || array.Any((int b) => b < 0 || b > 50))
				{
					array = PathSettings.DefaultForgeBonusByRank;
				}
				_forgeBonusByRank = array;
				_forgeBonusText = text;
			}
			return _forgeBonusByRank;
		}

		public static void MarkChanged(Inventory inventory)
		{
			if (inventory != null)
			{
				InventoryChanged?.Invoke(inventory, null);
			}
		}

		private static void OnCrafted(ZRpc rpc, ZPackage pkg)
		{
			try
			{
				string uid = pkg.ReadString();
				string stamp = pkg.ReadString();
				Grade grade = Grades.Parse(pkg.ReadString());
				Affix affix = Affixes.Parse(pkg.ReadString());
				float.TryParse(pkg.ReadString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result);
				string text = pkg.ReadString();
				Affix affix2 = Affixes.Parse(pkg.ReadString());
				float.TryParse(pkg.ReadString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2);
				string name = pkg.ReadString();
				string prefab = pkg.ReadString();
				if (grade == Grade.Masterpiece && Profile != null)
				{
					Profile.MasterpieceReady = "";
				}
				if (SentSeals.TryGetValue(uid, out var value))
				{
					SentSeals.Remove(uid);
					if (string.IsNullOrEmpty(text))
					{
						RejectedSeals.Add(value);
					}
				}
				Inventory val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetInventory() : null);
				ItemData val2 = ((val != null) ? ((IEnumerable<ItemData>)val.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => ItemKeys.GetUid(i.m_customData) == uid)) : null);
				if (val2 == null)
				{
					if (Pending.Count >= 50)
					{
						Pending.Remove(Pending.Keys.First());
					}
					Pending[uid] = new PendingReply
					{
						Stamp = stamp,
						Grade = grade,
						Affix = affix,
						Value = result,
						Affix2 = affix2,
						Value2 = result2,
						Name = name
					};
					Plugin.Log.LogWarning((object)("Путь: изделие " + uid + " не найдено в инвентаре — ответ отложен до первого показа"));
					RemoveSeal(text);
					RemoveTrophy(prefab);
					Request("profile", "");
				}
				else
				{
					Pending.Remove(uid);
					WriteKeys(val2, stamp, grade, affix, result, affix2, result2, name);
					ItemStats.FillDurability(val2);
					MarkChanged(val);
					RemoveSeal(text);
					RemoveTrophy(prefab);
					Request("profile", "");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: ответ на создание: " + ex));
			}
		}

		public static void TryApplyPending(ItemData item)
		{
			if (item != null && Pending.Count != 0 && !ItemKeys.HasStamp(item.m_customData))
			{
				string uid = ItemKeys.GetUid(item.m_customData);
				if (uid != null && Pending.TryGetValue(uid, out var value))
				{
					Pending.Remove(uid);
					WriteKeys(item, value.Stamp, value.Grade, value.Affix, value.Value, value.Affix2, value.Value2, value.Name);
					ItemStats.FillDurability(item);
					Player localPlayer = Player.m_localPlayer;
					MarkChanged((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
				}
			}
		}

		private static void WriteKeys(ItemData item, string stamp, Grade grade, Affix affix, float value, Affix affix2, float value2, string name)
		{
			if (grade == Grade.Masterpiece)
			{
				ItemKeys.WriteMasterpiece(item.m_customData, stamp, affix, value, affix2, value2, name);
			}
			else
			{
				ItemKeys.Write(item.m_customData, stamp, grade, affix, value);
			}
		}

		private static void OnProfile(ZRpc rpc, string text)
		{
			Profile = ProfileView.Parse(text);
		}

		private static void OnMessage(ZRpc rpc, string text)
		{
			MessageHud instance = MessageHud.instance;
			if (instance != null)
			{
				instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
			}
		}
	}
	internal sealed class CraftScope
	{
		public ItemData Upgrade;

		public int OldQuality;

		public Dictionary<string, string> Tags;

		public Recipe Recipe;

		public ItemKind StackKind;

		public int Batches = 1;

		public int OthersAmount;
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class DoCraftingPatch
	{
		internal static bool Active;

		internal static ItemData Created;

		internal static string ExpectedName;

		private static void Prefix(ItemData ___m_craftUpgradeItem, Recipe ___m_craftRecipe, bool ___m_multiCrafting, int ___m_multiCraftAmount, out CraftScope __state)
		{
			Active = true;
			Created = null;
			ExpectedName = ___m_craftRecipe?.m_item?.m_itemData?.m_shared?.m_name;
			__state = new CraftScope
			{
				Upgrade = ___m_craftUpgradeItem,
				OldQuality = (___m_craftUpgradeItem?.m_quality ?? 0),
				Tags = ItemKeys.Snapshot(___m_craftUpgradeItem?.m_customData)
			};
			if (___m_craftUpgradeItem == null && (Object)(object)___m_craftRecipe != (Object)null)
			{
				try
				{
					StackFlow.Prepare(__state, ___m_craftRecipe, (!___m_multiCrafting) ? 1 : ___m_multiCraftAmount);
				}
				catch (Exception ex)
				{
					__state.StackKind = ItemKind.None;
					Plugin.Log.LogError((object)("Путь: подготовка стопки: " + ex));
				}
			}
		}

		private static void Postfix(CraftScope __state)
		{
			ItemData created = Created;
			if (created == null || __state == null)
			{
				return;
			}
			try
			{
				CraftFlow.OnCreated(created, __state);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: создание: " + ex));
			}
		}

		private static void Finalizer()
		{
			Active = false;
			Created = null;
			ExpectedName = null;
		}
	}
	[HarmonyPatch(typeof(Inventory), "AddItem", new Type[]
	{
		typeof(string),
		typeof(int),
		typeof(int),
		typeof(int),
		typeof(long),
		typeof(string),
		typeof(Vector2i),
		typeof(bool),
		typeof(bool),
		typeof(bool)
	})]
	internal static class AddItemCapturePatch
	{
		private static void Postfix(ItemData __result)
		{
			if (DoCraftingPatch.Active && DoCraftingPatch.Created == null && __result != null && DoCraftingPatch.ExpectedName != null && __result.m_shared.m_name == DoCraftingPatch.ExpectedName)
			{
				DoCraftingPatch.Created = __result;
			}
		}
	}
	internal static class CraftFlow
	{
		public static void OnCreated(ItemData created, CraftScope scope)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)created.m_dropPrefab == (Object)null || ItemKeys.IsEventReward(created.m_customData) || (scope.Upgrade != null && ItemKeys.IsEventReward(scope.Upgrade.m_customData)))
			{
				return;
			}
			if (scope.Upgrade == null && scope.StackKind != ItemKind.None)
			{
				StackFlow.OnCrafted(localPlayer, scope, created);
			}
			else
			{
				if (ItemInfo.KindOf(created) == ItemKind.None)
				{
					return;
				}
				bool flag = PluginConfig.Enabled.Value && ClientSide.Connected;
				string name = ((Object)created.m_dropPrefab).name;
				if (scope.Upgrade == null)
				{
					if (flag)
					{
						string text = Guid.NewGuid().ToString("N");
						created.m_customData["sansara.Path.uid"] = text;
						int tier = ItemInfo.TierOf(created);
						ProfileView profile = ClientSide.Profile;
						bool flag2 = profile != null && profile.MasterpieceReady != "" && ClientSide.HasTrophy(Masterpieces.TrophyFor(tier));
						string sealUid = (flag2 ? null : ClientSide.SealFor(tier));
						ClientSide.SendCraft(text, name, sealUid, flag2 ? "1" : "");
					}
				}
				else if (!(created.m_shared.m_name != scope.Upgrade.m_shared.m_name))
				{
					if (scope.Tags.Count > 0)
					{
						ItemKeys.CopyTo(scope.Tags, created.m_customData);
						ItemStats.FillDurability(created);
						ClientSide.MarkChanged(((Humanoid)localPlayer).GetInventory());
					}
					int num = created.m_quality - scope.OldQuality;
					if (flag && num > 0)
					{
						ClientSide.SendUpgrade(name, num);
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(Humanoid), "EquipItem")]
	internal static class EquipPatch
	{
		private static void Postfix(Humanoid __instance, ItemData item, bool __result)
		{
			if (__result && item != null && (object)__instance == Player.m_localPlayer)
			{
				ClientSide.TryApplyPending(item);
				string uid = ItemKeys.GetUid(item.m_customData);
				if (uid != null && ItemKeys.HasStamp(item.m_customData))
				{
					ClientSide.SendWorn(uid);
				}
			}
		}
	}
	[HarmonyPatch(typeof(PlayerController), "TakeInput")]
	internal static class ControllerInputPatch
	{
		private static void Postfix(ref bool __result)
		{
			if (PathWindow.BlocksGame)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "TakeInput")]
	internal static class PlayerInputPatch
	{
		private static void Postfix(ref bool __result)
		{
			if (PathWindow.BlocksGame)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(Menu), "Update")]
	internal static class MenuPatch
	{
		private static bool Prefix()
		{
			return !PathWindow.BlocksGame;
		}
	}
	internal static class PathBindGuard
	{
		private static readonly FieldInfo Binds = AccessTools.Field(typeof(Terminal), "m_binds");

		private static object _saved;

		internal static void PatchServerDevcommands(Harmony harmony)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("ServerDevcommands.BindManager");
			MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "GetBestKeyCommands", (Type[])null, (Type[])null) : null);
			if (!(methodInfo == null))
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PathBindGuard), "SdcPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.Log.LogInfo((object)"Привязки клавиш Server Devcommands приглушаются, пока вы печатаете в окне «Путь».");
			}
		}

		private static bool SdcPrefix(ref IList __result, MethodBase __originalMethod)
		{
			if (!PathWindow.TextFocused)
			{
				return true;
			}
			__result = (IList)Activator.CreateInstance(((MethodInfo)__originalMethod).ReturnType);
			return false;
		}

		internal static void Suspend()
		{
			if (PathWindow.TextFocused && !(Binds == null) && _saved == null)
			{
				object value = Binds.GetValue(null);
				if (!(value is ICollection { Count: 0 }))
				{
					_saved = value;
					Binds.SetValue(null, Activator.CreateInstance(Binds.FieldType));
				}
			}
		}

		internal static void Resume()
		{
			if (_saved != null)
			{
				Binds.SetValue(null, _saved);
				_saved = null;
			}
		}
	}
	[HarmonyPatch(typeof(Chat), "Update")]
	[HarmonyAfter(new string[] { "sansara.Admin" })]
	internal static class ChatBindsPatch
	{
		private static void Prefix()
		{
			PathBindGuard.Suspend();
		}

		private static void Finalizer()
		{
			PathBindGuard.Resume();
		}
	}
	internal static class ItemInfo
	{
		public static ItemKind KindOf(ItemData item)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected I4, but got Unknown
			if (item != null)
			{
				return ItemClass.Classify((int)item.m_shared.m_itemType, ((object)Unsafe.As<SkillType, SkillType>(ref item.m_shared.m_skillType)/*cast due to .constrained prefix*/).ToString(), item.m_shared.m_maxStackSize, ItemKeys.IsEventReward(item.m_customData));
			}
			return ItemKind.None;
		}

		public static bool TryPrefab(string prefab, out ItemData data)
		{
			data = null;
			if (string.IsNullOrEmpty(prefab) || prefab.Length > 128 || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return false;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab);
			ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null);
			data = (((Object)(object)val != (Object)null) ? val.m_itemData : null);
			return data != null;
		}

		public static bool Orderable(string prefab, out ItemData data)
		{
			if (!TryPrefab(prefab, out data) || KindOf(data) == ItemKind.None)
			{
				return false;
			}
			Recipe recipe = ObjectDB.instance.GetRecipe(data);
			if ((Object)(object)recipe != (Object)null)
			{
				return recipe.m_enabled;
			}
			return false;
		}

		public static int TierOf(ItemData data)
		{
			return TierOfRecipe(((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetRecipe(data) : null);
		}

		public static int TierOfRecipe(Recipe recipe)
		{
			if ((Object)(object)recipe == (Object)null || recipe.m_resources == null)
			{
				return 1;
			}
			return Tiers.Of(from r in recipe.m_resources
				where r != null && (Object)(object)r.m_resItem != (Object)null
				select ((Object)((Component)r.m_resItem).gameObject).name);
		}

		public static string StationOf(Recipe recipe)
		{
			CraftingStation val = (((Object)(object)recipe != (Object)null) ? recipe.m_craftingStation : null);
			if ((Object)(object)val == (Object)null)
			{
				return "";
			}
			if (string.Equals(val.m_name, "$piece_cauldron", StringComparison.OrdinalIgnoreCase))
			{
				return "piece_cauldron";
			}
			if (string.Equals(val.m_name, "$piece_meadcauldron", StringComparison.OrdinalIgnoreCase))
			{
				return "piece_MeadCauldron";
			}
			return ((Object)((Component)val).gameObject).name;
		}

		public static ItemKind StackKindOf(Recipe recipe)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected I4, but got Unknown
			if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null || recipe.m_requireOnlyOneIngredient)
			{
				return ItemKind.None;
			}
			SharedData shared = recipe.m_item.m_itemData.m_shared;
			return ItemClass.StackKind((int)shared.m_itemType, ((object)Unsafe.As<SkillType, SkillType>(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString(), shared.m_maxStackSize, eventReward: false, StationOf(recipe));
		}

		public static bool TryStackRecipe(string prefab, out ItemKind kind, out int tier)
		{
			kind = ItemKind.None;
			tier = 1;
			if (string.IsNullOrEmpty(prefab) || prefab.Length > 128 || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return false;
			}
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || (Object)(object)recipe.m_item == (Object)null || ((Object)((Component)recipe.m_item).gameObject).name != prefab)
				{
					continue;
				}
				ItemKind itemKind = StackKindOf(recipe);
				if (itemKind != ItemKind.None)
				{
					int num = TierOfRecipe(recipe);
					if (kind == ItemKind.None || num > tier)
					{
						kind = itemKind;
						tier = num;
					}
				}
			}
			return kind != ItemKind.None;
		}
	}
	internal static class ItemStats
	{
		public static float Percent(ItemData item, Affix wanted)
		{
			Dictionary<string, string> dictionary = item?.m_customData;
			if (dictionary == null || dictionary.Count == 0 || !PluginConfig.Enabled.Value)
			{
				return 0f;
			}
			float value = PluginConfig.MaxAffixPercent.Value;
			float num = 0f;
			if (ItemKeys.TryAffix(dictionary, out var affix, out var value2) && affix == wanted)
			{
				num += Affixes.Clamp(value2, value);
			}
			if (ItemKeys.TryAffix2(dictionary, out var affix2, out var value3) && affix2 == wanted)
			{
				num += Affixes.Clamp(value3, value);
			}
			if (!(num > 0f) || !ItemKeys.IsEventReward(dictionary))
			{
				return num;
			}
			return 0f;
		}

		public static void FillDurability(ItemData item)
		{
			if (item != null && item.m_shared.m_useDurability)
			{
				item.m_durability = item.GetMaxDurability();
			}
		}
	}
	internal static class Net
	{
		public const string Craft = "SansaraPath_Craft";

		public const string Upgrade = "SansaraPath_Upgrade";

		public const string Worn = "SansaraPath_Worn";

		public const string Request = "SansaraPath_Request";

		public const string SealTaken = "SansaraPath_SealTaken";

		public const string PayoutTaken = "SansaraPath_PayoutTaken";

		public const string Stack = "SansaraPath_Stack";

		public const string Crafted = "SansaraPath_Crafted";

		public const string Profile = "SansaraPath_Profile";

		public const string Message = "SansaraPath_Message";

		public const string Seal = "SansaraPath_Seal";

		public const string Titles = "SansaraPath_Titles";

		public const string OrderBill = "SansaraPath_OrderBill";

		public const string OrderPaidSeen = "SansaraPath_OrderPaidSeen";

		public const string Payout = "SansaraPath_Payout";
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	internal static class NewConnectionPatch
	{
		private static void Postfix(ZNet __instance, ZNetPeer peer)
		{
			try
			{
				if (peer?.m_rpc == null)
				{
					return;
				}
				if (__instance.IsServer())
				{
					if (ServerSide.Active)
					{
						ServerSide.Register(peer);
					}
				}
				else
				{
					ClientSide.Register(peer);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Путь: OnNewConnection: " + ex));
			}
		}
	}
	internal static class OrderCatalog
	{
		internal sealed class Entry
		{
			public string Prefab = "";

			public string Name = "";

			public string Lower = "";

			public int Tier;

			public Craft Craft;

			public Sprite Icon;
		}

		private static ObjectDB _db;

		private static readonly List<Entry> Items = new List<Entry>();

		private static readonly Dictionary<string, Entry> ByPrefab = new Dictionary<string, Entry>(StringComparer.Ordinal);

		internal static List<Entry> All
		{
			get
			{
				if (_db != ObjectDB.instance || Items.Count == 0)
				{
					Build();
				}
				return Items;
			}
		}

		internal static Entry Find(string prefab)
		{
			_ = All;
			if (string.IsNullOrEmpty(prefab) || !ByPrefab.TryGetValue(prefab, out var value))
			{
				return null;
			}
			return value;
		}

		internal static string NameOf(string prefab)
		{
			return Find(prefab)?.Name ?? prefab;
		}

		private static void Build()
		{
			_db = ObjectDB.instance;
			Items.Clear();
			ByPrefab.Clear();
			if ((Object)(object)_db == (Object)null)
			{
				return;
			}
			foreach (GameObject item in _db.m_items)
			{
				if (!((Object)(object)item == (Object)null) && !ByPrefab.ContainsKey(((Object)item).name) && ItemInfo.Orderable(((Object)item).name, out var data))
				{
					string text = ((Localization.instance != null) ? Localization.instance.Localize(data.m_shared.m_name) : data.m_shared.m_name);
					Sprite[] icons = data.m_shared.m_icons;
					Entry entry = new Entry
					{
						Prefab = ((Object)item).name,
						Name = text,
						Lower = text.ToLowerInvariant(),
						Tier = ItemInfo.TierOf(data),
						Craft = Crafts.Of(ItemInfo.KindOf(data)),
						Icon = ((icons != null && icons.Length != 0) ? icons[0] : null)
					};
					Items.Add(entry);
					ByPrefab[entry.Prefab] = entry;
				}
			}
			Items.Sort((Entry a, Entry b) => (a.Tier == b.Tier) ? string.Compare(a.Name, b.Name, StringComparison.CurrentCulture) : a.Tier.CompareTo(b.Tier));
		}
	}
	internal static class PathAdmin
	{
		internal static readonly string[] Verbs = new string[18]
		{
			"status", "help", "info", "recipes", "xp", "reset", "audit", "saga", "deed", "seal",
			"seals", "sealcandidates", "masterpiece", "masterpieces", "runes", "orders", "order", "payouts"
		};

		private const string HelpText = "Персонаж — имя или ключ <platform id>/<имя>, как в info.\nstatus — сколько персонажей и изделий\ninfo <персонаж> — ремесло, опыт, главы, Слава, лимиты периода\nrecipes <персонаж> — рецепты Знахаря: сколько, каких уровней, список\nxp <персонаж> <smith|armorer|fletcher|herbalist> <±N> — добавить или снять опыт (главы проверяются сразу)\nreset <персонаж> — снять ремесло и срок смены; опыт, главы, Слава и титул остаются\naudit — надетые изделия с ключами «Пути», которых нет в реестре\nsaga <персонаж> — саги биомов: главы, «пройдена», «полная»\ndeed <персонаж> <hunt|siege|trial|treasure|order|raid> <уровень 1–7> — засчитать главу\nseal <персонаж> <уровень 1–7> — выдать «Печать саги»\nseals — печати: выданные, доставленные, использованные\nsealcandidates — нестакающиеся предметы без действия (выбор SealPrefab)\nmasterpiece <персонаж> reset — снять ожидание и срок Шедевра\nmasterpieces — созданные Шедевры: имя, префаб, создатель, дата\nrunes <персонаж> [reset] — руны судьбы: открытые, активные, срок смены; reset — снять срок смены\norders — незакрытые заказы мастерам: состояние, изделие, плата, заказчик, мастер, сроки\norder <номер> cancel — отменить заказ (и взятый) с возвратом монет заказчику\npayouts — невыданные выплаты: кому, сколько, за какой заказ";

		public static string Handle(string line, string admin, string adminId)
		{
			AdminCommand adminCommand = AdminCommand.Parse(line);
			if (!ServerSide.Active)
			{
				return "модуль работает только на выделенном сервере";
			}
			try
			{
				string brokenReason = ServerSide.BrokenReason;
				if (brokenReason != null)
				{
					return "состояние не прочитано (" + brokenReason + "); модуль остановлен, файл не тронут — исправьте или переименуйте его и перезапустите сервер";
				}
				PathState st = ServerSide.State;
				PathSettings s = ServerSide.Settings();
				switch (adminCommand.Verb)
				{
				case "help":
					return "Персонаж — имя или ключ <platform id>/<имя>, как в info.\nstatus — сколько персонажей и изделий\ninfo <персонаж> — ремесло, опыт, главы, Слава, лимиты периода\nrecipes <персонаж> — рецепты Знахаря: сколько, каких уровней, список\nxp <персонаж> <smith|armorer|fletcher|herbalist> <±N> — добавить или снять опыт (главы проверяются сразу)\nreset <персонаж> — снять ремесло и срок смены; опыт, главы, Слава и титул остаются\naudit — надетые изделия с ключами «Пути», которых нет в реестре\nsaga <персонаж> — саги биомов: главы, «пройдена», «полная»\ndeed <персонаж> <hunt|siege|trial|treasure|order|raid> <уровень 1–7> — засчитать главу\nseal <персонаж> <уровень 1–7> — выдать «Печать саги»\nseals — печати: выданные, доставленные, использованные\nsealcandidates — нестакающиеся предметы без действия (выбор SealPrefab)\nmasterpiece <персонаж> reset — снять ожидание и срок Шедевра\nmasterpieces — созданные Шедевры: имя, префаб, создатель, дата\nrunes <персонаж> [reset] — руны судьбы: открытые, активные, срок смены; reset — снять срок смены\norders — незакрытые заказы мастерам: состояние, изделие, плата, заказчик, мастер, сроки\norder <номер> cancel — отменить заказ (и взятый) с возвратом монет заказчику\npayouts — невыданные выплаты: кому, сколько, за какой заказ";
				case "status":
					return PathTexts.Status(st);
				case "audit":
					return PathTexts.Audit(st, s.ResetOffsetHours);
				case "info":
				{
					string error3;
					Profile profile3 = st.Find(adminCommand.Argument, out error3);
					return (profile3 == null) ? error3 : PathTexts.Info(st, s, profile3);
				}
				case "recipes":
				{
					string error9;
					Profile profile9 = st.Find(adminCommand.Argument, out error9);
					return (profile9 == null) ? error9 : PathTexts.RecipesInfo(profile9);
				}
				case "xp":
				{
					if (adminCommand.Args.Count < 3 || !int.TryParse(adminCommand.Args[adminCommand.Args.Count - 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3))
					{
						return "xp <персонаж> <smith|armorer|fletcher|herbalist> <±N>";
					}
					Craft craft = Crafts.Parse(adminCommand.Args[adminCommand.Args.Count - 2]);
					string error4;
					Profile profile4 = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 2)), out error4);
					if (profile4 == null)
					{
						return error4;
					}
					List<ChapterEvent> events;
					string result4 = st.AdminXp(s, profile4, craft, result3, out events);
					ServerSide.Announce(events);
					Plugin.Log.LogInfo((object)$"Путь: {admin}: xp {profile4.Key} {Crafts.Code(craft)} {result3}");
					return result4;
				}
				case "reset":
				{
					string error6;
					Profile profile6 = st.Find(adminCommand.Argument, out error6);
					if (profile6 == null)
					{
						return error6;
					}
					Plugin.Log.LogInfo((object)("Путь: " + admin + ": reset " + profile6.Key));
					return st.AdminReset(profile6);
				}
				case "saga":
				{
					string error8;
					Profile profile8 = st.Find(adminCommand.Argument, out error8);
					if (profile8 == null)
					{
						return error8;
					}
					StringBuilder stringBuilder = new StringBuilder(profile8.Name + ": Слава " + profile8.Renown);
					for (int num = 1; num <= 7; num++)
					{
						profile8.Sagas.TryGetValue(num, out var value);
						int mask = value?.Mask ?? 0;
						string text2 = string.Join(", ", (from i in Enumerable.Range(0, 5)
							where (mask & (1 << i)) != 0
							select i).Select(Sagas.ChapterName));
						stringBuilder.Append('\n').Append(Tiers.Biome(num)).Append(": ")
							.Append((text2 == "") ? "—" : text2)
							.Append((value != null && value.Awarded == 2) ? " (полная)" : ((value != null && value.Awarded == 1) ? " (пройдена)" : ""));
					}
					return stringBuilder.ToString();
				}
				case "deed":
				{
					if (adminCommand.Args.Count < 3 || !int.TryParse(adminCommand.Args[adminCommand.Args.Count - 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result5))
					{
						return "deed <персонаж> <вид> <уровень>";
					}
					string text = adminCommand.Args[adminCommand.Args.Count - 2].ToLowerInvariant();
					string error7;
					Profile profile7 = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 2)), out error7);
					if (profile7 == null)
					{
						return error7;
					}
					DeedOutcome deedOutcome = st.AdminDeed(s, profile7, text, result5, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
					ServerSide.DispatchAdmin(deedOutcome);
					Plugin.Log.LogInfo((object)$"Путь: {admin}: deed {profile7.Key} {text} {result5}");
					return deedOutcome.Applied ? $"{profile7.Name}: глава засчитана, Слава {profile7.Renown}" : "глава не засчитана: уже есть или вид/уровень не подходят";
				}
				case "seal":
				{
					if (adminCommand.Args.Count < 2 || !int.TryParse(adminCommand.Args[adminCommand.Args.Count - 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 < 1 || result2 > 7)
					{
						return "seal <персонаж> <уровень 1–7>";
					}
					string error;
					Profile profile = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 1)), out error);
					if (profile == null)
					{
						return error;
					}
					SealRecord sealRecord = st.IssueSeal(profile.Key, result2, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
					if (sealRecord == null)
					{
						return "уровень вне 1–7";
					}
					Plugin.Log.LogInfo((object)$"Путь: {admin}: seal {profile.Key} {result2} → {sealRecord.Uid}");
					return $"{profile.Name}: {Sagas.SealName(result2)} выдана ({sealRecord.Uid}), придёт при входе или в течение {PluginConfig.PlayerScanSeconds.Value:0} с";
				}
				case "seals":
				{
					List<string> list = (from x in st.Seals.Values
						orderby x.Issued
						select x.Uid + " " + Sagas.SealName(x.Tier) + " · владелец " + x.Owner + " · " + ((x.UsedBy != "") ? ("использована: " + x.UsedBy) : (x.Delivered ? "у игрока" : "ждёт выдачи"))).ToList();
					if (list.Count == 0)
					{
						list.Add("печатей нет");
					}
					if (st.SealSuspects.Count > 0)
					{
						list.Add($"подозрительных печатей/ID: {st.SealSuspects.Count}");
						list.AddRange(from x in st.SealSuspects.Values.Take(10)
							select "  " + x.Uid + " · " + x.Key + " · " + x.Name);
					}
					return string.Join("\n", list);
				}
				case "sealcandidates":
					return ServerSide.SealCandidates();
				case "masterpiece":
				{
					if (adminCommand.Args.Count < 2 || !string.Equals(adminCommand.Args[adminCommand.Args.Count - 1], "reset", StringComparison.OrdinalIgnoreCase))
					{
						return "masterpiece <персонаж> reset";
					}
					string error5;
					Profile profile5 = st.Find(string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 1)), out error5);
					if (profile5 == null)
					{
						return error5;
					}
					Plugin.Log.LogInfo((object)("Путь: " + admin + ": masterpiece reset " + profile5.Key));
					return st.AdminMasterpieceReset(profile5);
				}
				case "masterpieces":
				{
					List<string> list2 = (from x in st.Items.Values
						where x.Grade == Grade.Masterpiece
						orderby x.Created
						select x).Select(delegate(ItemRecord x)
					{
						st.Profiles.TryGetValue(x.Crafter, out var value2);
						string text3 = ((value2 != null && value2.Name != "") ? value2.Name : x.Crafter);
						return x.MasterpieceName + " · " + x.Prefab + " · " + text3 + " · " + PathTexts.Date(x.Created, s.ResetOffsetHours);
					}).ToList();
					return (list2.Count == 0) ? "Шедевров нет" : string.Join("\n", list2);
				}
				case "runes":
				{
					bool flag = adminCommand.Args.Count >= 2 && string.Equals(adminCommand.Args[adminCommand.Args.Count - 1], "reset", StringComparison.OrdinalIgnoreCase);
					string error2;
					Profile profile2 = st.Find(flag ? string.Join(" ", adminCommand.Args.Take(adminCommand.Args.Count - 1)) : adminCommand.Argument, out error2);
					if (profile2 == null)
					{
						return error2;
					}
					if (!flag)
					{
						return PathTexts.RunesInfo(s, profile2, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
					}
					Plugin.Log.LogInfo((object)("Путь: " + admin + ": runes reset " + profile2.Key));
					return st.AdminRuneReset(profile2);
				}
				case "orders":
					return PathTexts.OrdersInfo(st, s, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
				case "order":
				{
					if (adminCommand.Args.Count != 2 || !string.Equals(adminCommand.Args[1], "cancel", StringComparison.OrdinalIgnoreCase) || !int.TryParse(adminCommand.Args[0].TrimStart(new char[1] { '№' }), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
					{
						return "order <номер> cancel";
					}
					Plugin.Log.LogInfo((object)$"Путь: {admin}: order {result} cancel");
					return st.AdminOrderCancel(s, result, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
				}
				case "payouts":
					return PathTexts.PayoutsInfo(st, s);
				default:
					return AdminProtocol.Unknown(adminCommand.Verb, Verbs);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)$"Путь: {admin}: «{AdminProtocol.ForLog(line)}»: {ex}");
				return "ошибка: " + ex.Message;
			}
		}
	}
	internal static class PathHelp
	{
		internal sealed class Section
		{
			internal readonly string Title;

			internal readonly string[] Lines;

			internal Section(string title, params string[] lines)
			{
				Title = title;
				Lines = lines;
			}
		}

		internal static readonly Section[] Sections = new Section[10]
		{
			new Section("Что такое «Путь»", "«Путь» — это ваше ремесло и ваши подвиги на сервере. Всё собрано в этом окне (F8).", "Ничего не запрещено: любую вещь может сделать кто угодно. «Путь» только награждает тех, кто делает своё дело хорошо."),
			new Section("Ремесло", "Выберите одно ремесло во вкладке «Ремесло»:", "• Кузнец — оружие, инструменты, щиты;", "• Бронник — броня;", "• Оружейник — луки, арбалеты, стрелы и болты;", "• Знахарь — еда и основы медовух из котлов.", "Сменить ремесло можно, но не часто: часть опыта перейдёт в новое ремесло, завершённые главы останутся за вами.", "За вещи своего ремесла вы получаете опыт. Опыт и главы «Пути мастера» поднимают ранг: Подмастерье → Умелец → Мастер → Великий мастер."),
			new Section("Качество и клеймо", "Чем выше ранг, тем чаще вещь выходит Добротной или Отменной. У такой вещи есть свойство — острота, закалка, лёгкость или крепость — и ваше клеймо в подсказке.", "Когда вашу вещь надевает другой игрок, вам идут Слава и опыт. Делать вещи для других — главный путь к рангу.", "Стрелы, болты, еда и медовухи качества не имеют: мастер иногда получает лишнюю партию."),
			new Section("Саги", "В каждом биоме своя сага из пяти глав: Охота за головами, испытание через портал, клад, заказ и набег — в этом биоме.", "Главы засчитываются сами, когда вы участвуете в событии. Прогресс — во вкладке «Саги».", "3 главы — титул «Покоритель», все 5 — «Хранитель» и Печать саги.", "Печать отдайте мастеру: его следующая вещь этого биома выйдет Отменной."),
			new Section("Титул и Слава", "Титул выбирается во вкладке «Путь», его видно над головой и в чате.", "Слава копится за главы, ранги и надетые вещи. Лучшие — на доске в Чертоге славы."),
			new Section("Руны судьбы", "Небольшие постоянные бонусы: меньше выносливости на прыжок, перекат или плавание, скрытность, сопротивление стихиям и другие.", "Руна биома открывается за его сагу, руна ремесла — на ранге «Мастер». «Хранитель» и «Великий мастер» её усиливают.", "Одновременно действует до пяти рун. Поставить можно в любой момент, снять или заменить — раз в сутки."),
			new Section("Шедевр", "Великий мастер раз в неделю может создать Шедевр — вещь с вашим именем и двумя свойствами.", "Во вкладке «Ремесло» придумайте имя и нажмите «Подготовить». Держите в сумке трофей босса биома этой вещи и создайте её — трофей уйдёт, о Шедевре узнают все."),
			new Section("Кузница потенциала", "Мастер, улучшающий вещь своего ремесла, получает прибавку к шансу успеха. Чем выше ранг, тем больше прибавка."),
			new Section("Заказы мастерам", "Нужна хорошая вещь? Закажите её во вкладке «Заказы»: выберите вещь, качество и плату. Монеты уходят в залог.", "Мастер берёт заказ, делает вещь и передаёт вам любым способом. Как только вы её наденете, мастер получит монеты.", "Пока заказ никто не взял, его можно отменить — монеты вернутся. Не выполненный в срок заказ тоже возвращает монеты.", "Если в сумке нет места, монеты подождут: освободите место."),
			new Section("Если что-то не так", "Окно не обновилось — закройте и откройте его снова. Остались вопросы — спросите администратора.")
		};
	}
	internal static class PathWindow
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__Close;
		}

		private const float Width = 760f;

		private const float Height = 620f;

		private const float ViewWidth = 700f;

		private const float ViewHeight = 400f;

		private const float LineWidth = 652f;

		private static readonly string[] TabNames = new string[6] { "Путь", "Ремесло", "Саги", "Руны", "Заказы", "Помощь" };

		private static GameObject _panel;

		private static ScrollRect _scroll;

		private static RectTransform _content;

		private static readonly List<Button> TabButtons = new List<Button>();

		private static bool _open;

		private static bool _blocked;

		private static int _closedFrame = -1;

		private static int _tab;

		private static string _confirm = "";

		private static ProfileView _shown;

		private static bool _dirty;

		private static readonly List<InputField> Inputs = new List<InputField>();

		private static readonly List<string> InputIds = new List<string>();

		private static InputField _caretInput;

		private static string _masterpieceName = "";

		private static string _orderSearch = "";

		private static string _orderPrefab = "";

		private static Grade _orderGrade = Grade.Common;

		private static string _orderCoins = "";

		private static string _orderMaster = "";

		private const int CatalogShown = 40;

		private static bool _wasFocused;

		private static bool _bindsPatched;

		private const int HelpTab = 5;

		internal static bool IsOpen => _open;

		internal static bool TextFocused
		{
			get
			{
				if (_open)
				{
					if (!_wasFocused)
					{
						return AnyInputFocused();
					}
					return true;
				}
				return false;
			}
		}

		internal static bool BlocksGame
		{
			get
			{
				if (!_open)
				{
					return Time.frameCount == _closedFrame;
				}
				return true;
			}
		}

		private static bool AnyInputFocused()
		{
			for (int i = 0; i < Inputs.Count; i++)
			{
				if ((Object)(object)Inputs[i] != (Object)null && Inputs[i].isFocused)
				{
					return true;
				}
			}
			return false;
		}

		private static string FocusedInputId()
		{
			for (int i = 0; i < Inputs.Count; i++)
			{
				if ((Object)(object)Inputs[i] != (Object)null && Inputs[i].isFocused)
				{
					return InputIds[i];
				}
			}
			return null;
		}

		internal static void Update()
		{
			if (_open && ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || (Object)(object)_panel == (Object)null || GameUiOpen()))
			{
				Close();
				return;
			}
			if ((Object)(object)_caretInput != (Object)null && _caretInput.isFocused)
			{
				_caretInput.MoveTextEnd(false);
				_caretInput = null;
			}
			bool textFocused = TextFocused;
			_wasFocused = _open && AnyInputFocused();
			if (_open && !textFocused && (ZInput.GetKeyDown((KeyCode)27, false) || ZInput.GetButtonDown("JoyButtonB")))
			{
				Close();
				return;
			}
			if (!textFocused && HotkeyDown())
			{
				if (_open)
				{
					Close();
				}
				else
				{
					TryOpen();
				}
			}
			if (_open && (_dirty || _shown != ClientSide.Profile))
			{
				Rebuild();
			}
		}

		private static bool HotkeyDown()
		{
			//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_000d: 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)
			KeyboardShortcut value = PluginConfig.Hotkey.Value;
			if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, false))
			{
				return false;
			}
			return ((KeyboardShortcut)(ref value)).Modifiers.All((KeyCode m) => ZInput.GetKey(m, false));
		}

		private static bool GameUiOpen()
		{
			if (!Console.IsVisible() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Menu.IsVisible() && !InventoryGui.IsVisible() && !Minimap.IsOpen())
			{
				return StoreGui.IsVisible();
			}
			return true;
		}

		private static void TryOpen()
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null || GameUiOpen() || TextInput.IsVisible() || (Object)(object)GUIManager.CustomGUIFront == (Object)null)
			{
				return;
			}
			if (!ClientSide.Connected)
			{
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)1, "«Путь» работает на сервере Sansara.", 0, (Sprite)null, false, true);
				}
				return;
			}
			if ((Object)(object)_panel == (Object)null)
			{
				Build();
			}
			_panel.transform.localScale = Vector3.one * Mathf.Clamp(PluginConfig.Scale.Value, 0.75f, 1.75f);
			_panel.SetActive(true);
			_open = true;
			_confirm = "";
			_dirty = true;
			if (!_blocked)
			{
				GUIManager.BlockInput(true);
				_blocked = true;
			}
			ClientSide.Request("profile", "");
		}

		private static void Close()
		{
			_open = false;
			_closedFrame = Time.frameCount;
			_confirm = "";
			_wasFocused = false;
			_caretInput = null;
			for (int i = 0; i < Inputs.Count; i++)
			{
				if ((Object)(object)Inputs[i] != (Object)null && Inputs[i].isFocused)
				{
					Inputs[i].DeactivateInputField();
				}
			}
			if ((Object)(object)_panel != (Object)null)
			{
				_panel.SetActive(false);
			}
			if (_blocked)
			{
				GUIManager.BlockInput(false);
				_blocked = false;
			}
		}

		private static void Build()
		{
			//IL_004f: 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_0051: 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_008b: 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_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: 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_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Expected O, but got Unknown
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_027e: Expected O, but got Unknown
			GUIManager instance = GUIManager.Instance;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0.5f, 0.5f);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 1f);
			if (!_bindsPatched)
			{
				_bindsPatched = true;
				PathBindGuard.PatchServerDevcommands(new Harmony("sansara.Path.binds"));
			}
			_panel = instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, val, val, Vector2.zero, 760f, 620f, true);
			((Object)_panel).name = "SansaraPathWindow";
			instance.CreateText("Путь", _panel.transform, val2, val2, new Vector2(0f, -40f), instance.AveriaSerifBold, 34, instance.ValheimOrange, true, Color.black, 400f, 50f, false).GetComponent<Text>().alignment = (TextAnchor)4;
			TabButtons.Clear();
			for (int i = 0; i < TabNames.Length; i++)
			{
				int tab = i;
				Button component = instance.CreateButton(TabNames[i], _panel.transform, val2, val2, new Vector2(((float)i - 2.5f) * 120f, -95f), 112f, 40f).GetComponent<Button>();
				((UnityEvent)component.onClick).AddListener((UnityAction)delegate
				{
					SelectTab(tab);
				});
				TabButtons.Add(component);
			}
			GameObject obj = instance.CreateScrollView(_panel.transform, false, true, 8f, 10f, instance.ValheimScrollbarHandleColorBlock, new Color(0f, 0f, 0f, 0.35f), 700f, 400f);
			RectTransform val3 = (RectTransform)obj.transform;
			val3.anchorMin = val;
			val3.anchorMax = val;
			val3.anchoredPosition = new Vector2(0f, -25f);
			_scroll = obj.GetComponentInChildren<ScrollRect>();
			_content = _scroll.content;
			VerticalLayoutGroup component2 = ((Component)_content).GetComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)component2).spacing = 6f;
			((LayoutGroup)component2).padding = new RectOffset(10, 10, 10, 10);
			Vector2 val4 = default(Vector2);
			((Vector2)(ref val4))..ctor(0.5f, 0f);
			ButtonClickedEvent onClick = instance.CreateButton("Закрыть (Esc)", _panel.transform, val4, val4, new Vector2(0f, 40f), 220f, 40f).GetComponent<Button>().onClick;
			object obj2 = <>O.<0>__Close;
			if (obj2 == null)
			{
				UnityAction val5 = Close;
				<>O.<0>__Close = val5;
				obj2 = (object)val5;
			}
			((UnityEvent)onClick).AddListener((UnityAction)obj2);
			_panel.SetActive(false);
		}

		private static void SelectTab(int tab)
		{
			if (tab != _tab)
			{
				_tab = tab;
				_confirm = "";
				_dirty = true;
				if ((Object)(object)_scroll != (Object)null)
				{
					_scroll.verticalNormalizedPosition = 1f;
				}
			}
		}

		private static void Rebuild()
		{
			_dirty = false;
			_shown = ClientSide.Profile;
			string text = FocusedInputId();
			Inputs.Clear();
			InputIds.Clear();
			_caretInput = null;
			for (int i = 0; i < TabButtons.Count; i++)
			{
				((Selectable)TabButtons[i]).interactable = i != _tab;
			}
			for (int num = ((Transform)_content).childCount - 1; num >= 0; num--)
			{
				GameObject gameObject = ((Component)((Transform)_content).GetChild(num)).gameObject;
				gameObject.SetActive(false);
				Object.Destroy((Object)(object)gameObject);
			}
			ProfileView shown = _shown;
			if (_tab == 5)
			{
				DrawHelp();
			}
			else if (shown == null)
			{
				Line("Загрузка…");
			}
			else if (_tab == 0)
			{
				DrawPath(shown);
			}
			else if (_tab == 1)
			{
				DrawCraft(shown);
			}
			else if (_tab == 2)
			{
				DrawSagas(shown);
			}
			else if (_tab == 3)
			{
				DrawRunes(shown);
			}
			else
			{
				DrawOrders(shown);
			}
			int num2 = ((text != null) ? InputIds.IndexOf(text) : (-1));
			if (num2 >= 0)
			{
				((Selectable)Inputs[num2]).Select();
				Inputs[num2].ActivateInputField();
				_caretInput = Inputs[num2];
			}
		}

		private static void DrawHelp()
		{
			PathHelp.Section[] sections = PathHelp.Sections;
			foreach (PathHelp.Section obj in sections)
			{
				Header(obj.Title);
				string[] lines = obj.Lines;
				for (int j = 0; j < lines.Length; j++)
				{
					Line(lines[j]);
				}
			}
		}

		private static void DrawPath(ProfileView v)
		{
			Header(v.Name);
			Line("Титул: " + ((v.Title == "") ? "нет" : v.Title));
			Line("Слава: " + v.Renown);
			Line((v.Craft == Craft.None) ? "Ремесло не выбрано — вкладка «Ремесло»." : ("Ремесло: " + Crafts.Name(v.Craft) + ", " + Ranks.Name(v.Rank)));
			if (v.TitleOptions.Count == 0)
			{
				return;
			}
			Header("Выбрать титул");
			foreach (KeyValuePair<string, string> titleOption in v.TitleOptions)
			{
				string code = titleOption.Key;
				AddButton(titleOption.Value, delegate
				{
					ClientSide.Request("title", code);
				});
			}
			if (v.Title != "")
			{
				AddButton("Без титула", delegate
				{
					ClientSide.Request("title", "");
				});
			}
		}

		private static void DrawCraft(ProfileView v)
		{
			if (v.Craft == Craft.None)
			{
				Line("Выберите ремесло. Оно ничего не запрещает: любые вещи делаются как обычно, а изделия своего ремесла со временем выходят лучше и получают ваше клеймо, стопки — лишнюю партию.");
			}
			else
			{
				Header(Crafts.Name(v.Craft) + " — " + Ranks.Name(v.Rank));
				Line("Делает: " + Crafts.Makes(v.Craft) + ".");
				if (!PluginConfig.CraftEnabled(v.Craft))
				{
					Line("Ремесло на сервере выключено: опыта и бонусов нет.");
				}
				Line((v.NextXp < 0) ? $"Опыт: {v.Xp} (высший ранг)" : ($"Опыт: {v.Xp} / {v.NextXp}" + ((v.Xp >= v.NextXp) ? " — завершите главу саги (вкладка «Саги»)" : "")));
				if (Crafts.HasItems(v.Craft))
				{
					Line("Шанс изделия: Добротное " + PathTexts.Num(v.GoodChance) + " %, Отменное " + PathTexts.Num(v.FineChance) + " %");
					Line($"За период: полный опыт за первые {v.FullCrafts} одинаковых изделия; улучшений с опытом осталось " + $"{v.UpgradesLeft}; бонусов за надевание другими — {v.WornLeft}.");
					Line((v.Craft == Craft.Armorer) ? "Свойства: Закалка (прочность), Лёгкость (вес), Крепость (броня и блок)." : ((v.Craft == Craft.Fletcher) ? "Свойства: Острота (урон), Закалка (прочность), Лёгкость (вес)." : "Свойства: Острота (урон), Закалка (прочность), Лёгкость (вес), Крепость (броня и блок)."));
				}
				if (Crafts.HasStacks(v.Craft))
				{
					Line("Лишняя партия при создании " + ((v.Craft == Craft.Herbalist) ? "в котле" : "стрел и болтов") + ": " + PathTexts.Num(v.YieldChance) + " % на каждую партию; с бонусом наград «Выход изготовления» не складывается — действует больший.");
					Line($"Опыт за стопки — уровень рецепта × число партий; за период осталось {v.StackXpLeft} из {v.StackXpPerDay}.");
				}
				if (v.Craft == Craft.Herbalist)
				{
					Line($"Разных рецептов создано: {v.Recipes} (условия глав — вкладка «Саги»).");
				}
				if (v.GrandMaster)
				{
					DrawMasterpiece(v);
				}
			}
			if (v.NextSwitch != "")
			{
				Line("Сменить ремесло можно после " + v.NextSwitch + ".");
				return;
			}
			Craft[] all = Crafts.All;
			foreach (Craft craft in all)
			{
				if (craft == v.Craft || !PluginConfig.CraftEnabled(craft))
				{
					continue;
				}
				string code = Crafts.Code(craft);
				if (_confirm == code)
				{
					Line((v.Craft == Craft.None) ? ("Стать: " + Crafts.Name(craft) + "? Сменить ремесло можно будет через " + PathTexts.Num((float)v.SwitchDays) + " дн.") : $"Сменить на: {Crafts.Nam