Decompiled source of TameLab v0.5.0

plugins/TameLab.dll

Decompiled 2 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Sansara.Admin;
using Sansara.Bank;
using SansaraPath;
using TameLab.Core;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("TameLab")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.0.0")]
[assembly: AssemblyInformationalVersion("0.5.0+88fb6f13f4f159e7aebb95013d1ddbe1438c74da")]
[assembly: AssemblyProduct("TameLab")]
[assembly: AssemblyTitle("TameLab")]
[assembly: AssemblyVersion("0.5.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Sansara.Admin
{
	internal static class AdminChannel
	{
		private const float ScanSeconds = 1f;

		private const float DenialLogSeconds = 30f;

		private static ManualLogSource _log;

		private static bool _owner;

		private static bool _announced;

		private static float _nextScan;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		public readonly string Text;

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

		public const string RequestRpc = "Sansara_Admin";

		public const string ReplyRpc = "Sansara_AdminReply";

		public const int MaxLine = 256;

		public const int MaxReply = 4000;

		public const int Done = 0;

		public const int Denied = 1;

		public const int NoModule = 2;

		public const int Failed = 3;

		public const int Invalid = 4;

		public const int MaxLogText = 300;

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

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

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

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

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

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

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

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

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

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

		private readonly string _key;

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

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

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

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

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

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

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

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

		public const string Head = "ui=1";

		public const string NoteKey = "note";

		public const string MoreKey = "more";

		private const int Reserve = 16;

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

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

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

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

		public long Coins;
	}
	public sealed class CoinAccounts
	{
		private const string Header = "accounts\t1";

		public readonly Dictionary<string, CoinAccount> Players = new Dictionary<string, CoinAccount>(StringComparer.Ordinal);

		public long Balance(string id)
		{
			if (!Players.TryGetValue(id, out var value))
			{
				return 0L;
			}
			return value.Coins;
		}

		public void Deposit(string id, string name, long coins)
		{
			if (coins < 0)
			{
				throw new ArgumentOutOfRangeException("coins");
			}
			if (!Players.TryGetValue(id, out var value))
			{
				value = new CoinAccount();
				Players[id] = value;
			}
			if (!string.IsNullOrEmpty(name))
			{
				value.Name = Clean(name);
			}
			value.Coins += coins;
		}

		public bool TrySpend(string id, long coins)
		{
			if (coins < 0)
			{
				throw new ArgumentOutOfRangeException("coins");
			}
			if (!Players.TryGetValue(id, out var value) || value.Coins < coins)
			{
				return false;
			}
			value.Coins -= coins;
			return true;
		}

		public string Serialize()
		{
			StringBuilder stringBuilder = new StringBuilder("accounts\t1").Append('\n');
			foreach (KeyValuePair<string, CoinAccount> player in Players)
			{
				stringBuilder.Append(Clean(player.Key)).Append('\t').Append(player.Value.Coins.ToString(CultureInfo.InvariantCulture))
					.Append('\t')
					.Append(Clean(player.Value.Name))
					.Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static CoinAccounts Parse(string text)
		{
			CoinAccounts coinAccounts = new CoinAccounts();
			string[] array = (text ?? "").Replace("\r", "").Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				return coinAccounts;
			}
			if (array[0] != "accounts\t1")
			{
				throw new FormatException("нет заголовка");
			}
			for (int i = 1; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { '\t' });
				if (array2.Length != 3 || array2[0] == "" || !long.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 0)
				{
					throw new FormatException("строка " + (i + 1));
				}
				coinAccounts.Players[array2[0]] = new CoinAccount
				{
					Coins = result,
					Name = array2[2]
				};
			}
			return coinAccounts;
		}

		private static string Clean(string s)
		{
			return (s ?? "").Replace('\t', ' ').Replace('\n', ' ').Replace("\r", "");
		}
	}
}
namespace TameLab
{
	[BepInPlugin("sansara.TameLab", "Tame Lab", "0.5.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.TameLab";

		public const string Name = "Tame Lab";

		public const string Version = "0.5.0";

		private const string EffectsSource = "Wolf";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> StableEnabled;

		internal static readonly Dictionary<string, ConfigEntry<string>> Prices = new Dictionary<string, ConfigEntry<string>>();

		internal static ConfigEntry<int> StableLimit;

		internal static ConfigEntry<float> StableRadius;

		internal static ConfigEntry<float> PetHealthMultiplier;

		internal static ConfigEntry<float> PetRegenMinutes;

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

		internal static readonly HashSet<int> AddedHashes = new HashSet<int>();

		private float _nextTick;

		private float _nextScan;

		private bool _started;

		private void Awake()
		{
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Expected O, but got Unknown
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected O, but got Unknown
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			StableEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Stable", "Enabled", true, "Лавка: счёт монет в Чертоге славы и покупка ручного существа в окне F8 (настройки читает сервер).");
			Good[] goods = StableRules.Goods;
			foreach (Good good in goods)
			{
				Prices[good.Prefab] = ((BaseUnityPlugin)this).Config.Bind<string>("Prices", good.Prefab, good.DefaultPrices, good.Description + ": цены в монетах через запятую — без звёзд, 1 звезда, 2 звезды.");
			}
			StableLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Stable", "MaxTotalPerPlayer", 3, "Сколько живых купленных существ всех видов вместе может быть у игрока.");
			StableRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Stable", "BuyRadius", 30f, "Покупать можно не дальше стольких метров от сундука счёта (весь Чертог славы); 0 — откуда угодно.");
			PetHealthMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Pet", "HealthMultiplier", 3f, new ConfigDescription("Во сколько раз у ручного существа больше здоровья, чем у дикого того же уровня. Дикие не меняются.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			PetRegenMinutes = ((BaseUnityPlugin)this).Config.Bind<float>("Pet", "RegenMinutes", 15f, new ConfigDescription("За сколько минут ручное существо само восстанавливает здоровье от нуля до полного. В игре 60 минут.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			AdminChannel.Register("bank", BankAdmin.Verbs, Stable.Instance.HandleAdmin, Log);
			ShopTab.Register();
			new Harmony("sansara.TameLab").PatchAll();
			Log.LogInfo((object)"Tame Lab 0.5.0 загружен.");
		}

		private void Update()
		{
			ShopTab.Update();
			AdminChannel.Tick();
			if (!StableEnabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.LocationsGenerated)
			{
				return;
			}
			try
			{
				if (_started && Time.time >= _nextScan)
				{
					_nextScan = Time.time + 1f;
					Stable.Instance.Scan();
				}
				if (Time.time < _nextTick)
				{
					return;
				}
				_nextTick = Time.time + 5f;
				if (!_started)
				{
					if (!Stable.Instance.Prepare())
					{
						_nextTick = float.MaxValue;
						return;
					}
					_started = true;
				}
				AdminChannel.CheckFile("sansara.TameLab", "Лавка", Stable.Instance.HandleAdmin);
				Stable.Instance.Tick();
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Лавка: ошибка: " + ex));
			}
		}

		internal static void Apply()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			GameObject prefab = instance.GetPrefab("Wolf");
			Tameable val = ((prefab != null) ? prefab.GetComponent<Tameable>() : null);
			Good[] goods = StableRules.Goods;
			for (int i = 0; i < goods.Length; i++)
			{
				string prefab2 = goods[i].Prefab;
				GameObject prefab3 = instance.GetPrefab(prefab2);
				if ((Object)(object)prefab3 == (Object)null)
				{
					Log.LogWarning((object)("Префаб " + prefab2 + " не найден."));
					continue;
				}
				if ((Object)(object)prefab3.GetComponent<MonsterAI>() == (Object)null || (Object)(object)prefab3.GetComponent<Humanoid>() == (Object)null)
				{
					Log.LogWarning((object)("Префаб " + prefab2 + " без MonsterAI или Humanoid, приручаемость не добавлена."));
					continue;
				}
				if ((Object)(object)prefab3.GetComponent<Tameable>() != (Object)null)
				{
					Log.LogInfo((object)(Added.Contains(prefab2) ? (prefab2 + ": приручаемость уже добавлена.") : (prefab2 + ": приручаем в игре, не меняю.")));
					continue;
				}
				Tameable val2 = prefab3.AddComponent<Tameable>();
				val2.m_commandable = true;
				val2.m_fedDuration = float.MaxValue;
				if ((Object)(object)val != (Object)null)
				{
					val2.m_tamedEffect = val.m_tamedEffect;
					val2.m_sootheEffect = val.m_sootheEffect;
					val2.m_petEffect = val.m_petEffect;
				}
				Added.Add(prefab2);
				AddedHashes.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab3).name));
				Log.LogInfo((object)(prefab2 + ": приручаемость добавлена."));
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class ZNetSceneAwakePatch
	{
		private static void Postfix()
		{
			Plugin.Apply();
		}
	}
	[HarmonyPatch(typeof(Tameable), "TamingUpdate")]
	internal static class TamingUpdatePatch
	{
		private static bool Prefix(Tameable __instance)
		{
			return !Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject));
		}
	}
	[HarmonyPatch(typeof(Tameable), "Tame")]
	internal static class SpecialMobsPatch
	{
		private static readonly string[] Tags = new string[3] { "sansara.BountyHunt.tag", "sansara.Path.hunt", "sansara.Path.hunt.retinue" };

		private static bool Prefix(Tameable __instance, ZNetView ___m_nview, MonsterAI ___m_monsterAI)
		{
			ZDO val = (((Object)(object)___m_nview != (Object)null) ? ___m_nview.GetZDO() : null);
			if (val == null || !Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject)))
			{
				return true;
			}
			if ((Object)(object)___m_monsterAI != (Object)null && ___m_monsterAI.IsEventCreature())
			{
				return false;
			}
			string[] tags = Tags;
			foreach (string text in tags)
			{
				if (val.GetString(text, "") != "")
				{
					return false;
				}
			}
			return string.IsNullOrWhiteSpace(val.GetString(ZDOVars.s_overrideHoverName, ""));
		}
	}
	[HarmonyPatch(typeof(Tameable), "GetName")]
	internal static class LeaderNamePatch
	{
		private static void Postfix(Tameable __instance, ZNetView ___m_nview, Character ___m_character, ref string __result)
		{
			ZDO val = (((Object)(object)___m_nview != (Object)null && ___m_nview.IsValid()) ? ___m_nview.GetZDO() : null);
			if (val != null && !((Object)(object)___m_character == (Object)null) && !___m_character.IsTamed())
			{
				string text = val.GetString(ZDOVars.s_overrideHoverName, "");
				if (!string.IsNullOrWhiteSpace(text) && Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject)))
				{
					__result = text;
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "HandleDestroyedZDO")]
	internal static class NoEwpLootPatch
	{
		[HarmonyPriority(800)]
		private static void Prefix(ZDOMan __instance, ZDOID uid, Dictionary<ZDOID, long> ___m_deadZDOs)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
				{
					ZDO zDO = __instance.GetZDO(uid);
					if (zDO != null && Plugin.AddedHashes.Contains(zDO.GetPrefab()) && zDO.GetBool(ZDOVars.s_tamed, false))
					{
						___m_deadZDOs[uid] = ZNet.instance.GetTime().Ticks;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Удаление ручного существа: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(Character), "GetMaxHealthBase")]
	internal static class PetHealthPatch
	{
		private static void Postfix(Character __instance, ZNetView ___m_nview, ref float __result)
		{
			if (TameStats.IsPet(__instance, ___m_nview))
			{
				__result *= Plugin.PetHealthMultiplier.Value;
			}
		}
	}
	internal static class TameStats
	{
		internal static bool IsPet(Character character, ZNetView view)
		{
			if ((Object)(object)view == (Object)null || !view.IsValid() || character.IsPlayer() || !character.IsTamed())
			{
				return false;
			}
			return Plugin.AddedHashes.Contains(view.GetZDO().GetPrefab());
		}

		internal static void Refresh(Character character)
		{
			character.m_regenAllHPTime = Plugin.PetRegenMinutes.Value * 60f;
			if (!character.IsDead())
			{
				float maxHealth = character.GetMaxHealth();
				float num = character.GetMaxHealthBase() * (float)character.GetLevel();
				if (!(Mathf.Abs(num - maxHealth) < 1f))
				{
					float num2 = StableRules.ScaleHealth(character.GetHealth(), maxHealth, num);
					character.SetMaxHealth(num);
					character.SetHealth(num2);
					Plugin.Log.LogInfo((object)$"{Utils.GetPrefabName(((Component)character).gameObject)} уровня {character.GetLevel()}: максимум здоровья {maxHealth:0} → {num:0}, сейчас {num2:0}.");
				}
			}
		}
	}
	[HarmonyPatch(typeof(BaseAI), "UpdateRegeneration")]
	internal static class RegenerationPatch
	{
		internal static bool Active;

		private static void Prefix(Character ___m_character, ZNetView ___m_nview)
		{
			if (TameStats.IsPet(___m_character, ___m_nview))
			{
				TameStats.Refresh(___m_character);
			}
			Active = true;
		}

		private static void Finalizer()
		{
			Active = false;
		}
	}
	[HarmonyPatch(typeof(Character), "RPC_Heal")]
	internal static class HealPatch
	{
		private static bool Prefix(Character __instance)
		{
			if (!RegenerationPatch.Active && __instance.IsTamed())
			{
				return !Plugin.Added.Contains(Utils.GetPrefabName(((Component)__instance).gameObject));
			}
			return true;
		}
	}
	internal static class ShopTab
	{
		private const float AskEverySeconds = 2f;

		private static ZRpc _server;

		private static ShopState _state;

		private static string _stateText = "";

		private static float _askedAt = -100f;

		private static string _confirm = "";

		public static void Register()
		{
			PathTabs.Register("Лавка", (Action)Draw);
		}

		public static void Update()
		{
			ZRpc val = (((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) ? ZNet.instance.GetServerRPC() : null);
			if (val != _server)
			{
				_server = val;
				_state = null;
				_stateText = "";
				_confirm = "";
				if (val != null)
				{
					val.Register<string>("sansara.TameLab.State", (Action<ZRpc, string>)OnState);
				}
			}
		}

		private static void OnState(ZRpc rpc, string text)
		{
			if (text != _stateText)
			{
				_stateText = text;
				_state = ShopState.Parse(text);
				PathTabs.Refresh();
			}
		}

		private static void Send(string verb, string arg)
		{
			ZRpc server = _server;
			if (server != null)
			{
				server.Invoke("sansara.TameLab.Request", new object[2] { verb, arg });
			}
		}

		private static void Draw()
		{
			if (Time.time - _askedAt >= 2f)
			{
				_askedAt = Time.time;
				Send("state", "");
			}
			PathTabs.Header("Лавка");
			if (_state == null)
			{
				PathTabs.Line("Запрашиваю данные… Если это сообщение не исчезает, на этом сервере лавки нет.");
				return;
			}
			PathTabs.Line($"На счету: {_state.Balance} монет");
			PathTabs.Line("Пополнить счёт можно монетами через сундук в Чертоге славы, у западной стены.");
			PathTabs.Line("Ручное существо идёт за вами и сражается рядом. Кормить и лечить его нельзя, здоровье восстанавливается само. Погибшее не возвращается.");
			PathTabs.Line($"Куплено живых существ: {_state.TotalOwned} из {_state.Limit}");
			if (!_state.Near)
			{
				PathTabs.Line("Покупать можно только в Чертоге славы.");
			}
			foreach (ShopItem item in _state.Items)
			{
				Good good = StableRules.GoodOf(item.Prefab);
				if (good != null)
				{
					PathTabs.Header(good.Description);
					PathTabs.Line($"Сейчас у вас: {item.Owned}");
					for (int i = 0; i < item.Prices.Length; i++)
					{
						DrawTier(good, item, i);
					}
				}
			}
		}

		private static void DrawTier(Good good, ShopItem item, int tier)
		{
			long num = item.Prices[tier];
			string text = $"{good.Name} {StableRules.Stars(tier)}: {num} монет";
			Deal deal = StableRules.Decide(_state.Balance, num, _state.TotalOwned, _state.Limit, _state.Near);
			if (deal != Deal.Buy)
			{
				PathTabs.Line((deal == Deal.NeedCoins) ? $"{text} (не хватает {num - _state.Balance})" : text);
				return;
			}
			string arg = StableRules.BuyArg(good, tier);
			if (_confirm != arg)
			{
				PathTabs.Button("Купить: " + text, (Action)delegate
				{
					Choose(arg);
				});
				return;
			}
			PathTabs.Line($"{good.Name} {StableRules.Stars(tier)} за {num} монет? Появится рядом с вами.");
			PathTabs.Button("Да", (Action)delegate
			{
				Choose("");
				Send("buy", arg);
			});
			PathTabs.Button("Отмена", (Action)delegate
			{
				Choose("");
			});
		}

		private static void Choose(string arg)
		{
			_confirm = arg;
			PathTabs.Refresh();
		}
	}
	internal sealed class Stable : IBankHost
	{
		internal const string RequestRpc = "sansara.TameLab.Request";

		internal const string StateRpc = "sansara.TameLab.State";

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

		private const string OwnerKey = "sansara.TameLab.owner";

		private const float SpawnAhead = 3f;

		internal static readonly Stable Instance = new Stable();

		private readonly List<ZDO> _found = new List<ZDO>();

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

		private CoinAccounts _accounts = new CoinAccounts();

		private ZDO _chest;

		private long _handled = -1L;

		private long _lastDepositor;

		private string _path;

		private readonly Dictionary<string, long[]> _prices = new Dictionary<string, long[]>();

		private bool _ready;

		private bool _missingReported;

		private FieldInfo _ewpSkip;

		private bool _ewpLooked;

		private static Vector3 FameAnchor => new Vector3(10820f, 0f, 300f);

		public bool Prepare()
		{
			_prices.Clear();
			Good[] goods = StableRules.Goods;
			foreach (Good good in goods)
			{
				string value = Plugin.Prices[good.Prefab].Value;
				long[] array = StableRules.ParsePrices(value);
				if (array == null)
				{
					Plugin.Log.LogError((object)$"Лавка: цены {good.Prefab} неверны («{value}»), нужно {3} положительных числа. Лавка выключена.");
					return false;
				}
				GameObject prefab = ZNetScene.instance.GetPrefab(good.Prefab);
				if ((Object)(object)((prefab != null) ? prefab.GetComponent<Tameable>() : null) == (Object)null)
				{
					Plugin.Log.LogError((object)("Лавка: префаб " + good.Prefab + " не найден или не приручаем. Лавка выключена."));
					return false;
				}
				_prices[good.Prefab] = array;
			}
			string text = Path.Combine(Paths.ConfigPath, "sansara.Bank");
			Directory.CreateDirectory(text);
			string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			_path = Path.Combine(text, text2 + ".accounts");
			try
			{
				_accounts = (File.Exists(_path) ? CoinAccounts.Parse(File.ReadAllText(_path)) : new CoinAccounts());
			}
			catch (FormatException ex)
			{
				Plugin.Log.LogError((object)("Лавка: файл счетов повреждён (" + ex.Message + "): " + _path + ". Лавка выключена, файл не тронут."));
				return false;
			}
			_ready = true;
			Plugin.Log.LogInfo((object)string.Format("Лавка: цены {0}, предел {1}, радиус {2}, счетов {3}, здоровье ручных ×{4}, восстановление {5} мин.", string.Join("; ", _prices.Select((KeyValuePair<string, long[]> p) => p.Key + " " + string.Join(", ", p.Value))), Plugin.StableLimit.Value, Plugin.StableRadius.Value, _accounts.Players.Count, Plugin.PetHealthMultiplier.Value, Plugin.PetRegenMinutes.Value));
			return true;
		}

		public bool Save()
		{
			try
			{
				string text = _path + ".tmp";
				File.WriteAllText(text, _accounts.Serialize());
				if (File.Exists(_path))
				{
					File.Replace(text, _path, null);
				}
				else
				{
					File.Move(text, _path);
				}
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Лавка: счета не сохранены: " + ex.Message));
				return false;
			}
		}

		public bool FindOnline(string name, out string id, out string shownName)
		{
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (string.Equals(peer.m_playerName, name, StringComparison.OrdinalIgnoreCase))
				{
					ISocket socket = peer.m_socket;
					id = ((socket != null) ? socket.GetHostName() : null) ?? "";
					shownName = peer.m_playerName;
					return id != "";
				}
			}
			id = "";
			shownName = "";
			return false;
		}

		public string HandleAdmin(string line, string admin, string adminId)
		{
			if (!_ready)
			{
				return "лавка не запущена: мир загружается, выключена настройкой или файл счетов повреждён (см. журнал)";
			}
			string result = BankAdmin.Handle(line, _accounts, this);
			SendStateToAll();
			return result;
		}

		public void Scan()
		{
			HashSet<ZRpc> live = new HashSet<ZRpc>();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer?.m_rpc != null)
				{
					live.Add(peer.m_rpc);
					if (_registered.Add(peer.m_rpc))
					{
						peer.m_rpc.Register<string, string>("sansara.TameLab.Request", (Action<ZRpc, string, string>)OnRequest);
					}
				}
			}
			_registered.RemoveWhere((ZRpc r) => !live.Contains(r));
		}

		private void OnRequest(ZRpc rpc, string verb, string arg)
		{
			ZNetPeer val = ZNet.instance.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc);
			if (!_ready || val == null || !val.IsReady() || ((ZDOID)(ref val.m_characterID)).IsNone())
			{
				return;
			}
			try
			{
				if (verb == "buy" && StableRules.TryParseBuy(arg, out var good, out var tier))
				{
					Buy(val, good, tier);
				}
				SendState(val);
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogError((object)$"Лавка: ошибка запроса {val.m_playerName}: {arg2}");
			}
		}

		private void Buy(ZNetPeer peer, Good good, int tier)
		{
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: 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_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			string id = Id(peer);
			long num = _prices[good.Prefab][tier];
			int owned = StableRules.Goods.Sum((Good g) => Owned(id, g.Prefab));
			Deal deal = StableRules.Decide(_accounts.Balance(id), num, owned, Plugin.StableLimit.Value, Near(peer));
			if (deal != Deal.Buy)
			{
				Message(peer, StableRules.Refusal(deal, good, _accounts.Balance(id), num, Plugin.StableLimit.Value));
				return;
			}
			_accounts.TrySpend(id, num);
			if (!Save())
			{
				_accounts.Deposit(id, peer.m_playerName, num);
				Message(peer, "Покупка не удалась, монеты на месте.");
				return;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
			try
			{
				Spawn(good.Prefab, zDO.GetPosition() + zDO.GetRotation() * Vector3.forward * 3f, StableRules.Level(tier), id, peer.m_playerName);
			}
			catch
			{
				_accounts.Deposit(id, peer.m_playerName, num);
				Save();
				Message(peer, "Покупка не удалась, монеты на месте.");
				throw;
			}
			Plugin.Log.LogInfo((object)$"Лавка: {peer.m_playerName} ({id}) купил {good.Prefab} уровня {StableRules.Level(tier)} за {num}, на счету {_accounts.Balance(id)}.");
			Message(peer, StableRules.Bought(good, tier, _accounts.Balance(id)));
		}

		private bool Near(ZNetPeer peer)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			float value = Plugin.StableRadius.Value;
			if (value <= 0f)
			{
				return true;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
			if (zDO != null && _chest != null)
			{
				return Vector3.Distance(zDO.GetPosition(), _chest.GetPosition()) <= value;
			}
			return false;
		}

		private void SendState(ZNetPeer peer)
		{
			string id = Id(peer);
			ShopState shopState = new ShopState
			{
				Balance = _accounts.Balance(id),
				Limit = Plugin.StableLimit.Value,
				Near = Near(peer)
			};
			Good[] goods = StableRules.Goods;
			foreach (Good good in goods)
			{
				shopState.Items.Add(new ShopItem
				{
					Prefab = good.Prefab,
					Owned = Owned(id, good.Prefab),
					Prices = _prices[good.Prefab]
				});
			}
			peer.m_rpc.Invoke("sansara.TameLab.State", new object[1] { shopState.Serialize() });
		}

		private void SendStateToAll()
		{
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.IsReady() && !((ZDOID)(ref peer.m_characterID)).IsNone() && _registered.Contains(peer.m_rpc))
				{
					SendState(peer);
				}
			}
		}

		public void Tick()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			_found.Clear();
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(FameAnchor), new SimulationDistance(1, 0, true), _found, (List<ZDO>)null);
			_chest = ((IEnumerable<ZDO>)_found).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => z.GetString("sansara.PortalHall.role", "") == "bank.chest"));
			ZDO val = ((IEnumerable<ZDO>)_found).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => z.GetString("sansara.PortalHall.role", "") == "bank.sign"));
			if (_chest == null)
			{
				if (!_missingReported)
				{
					_missingReported = true;
					Plugin.Log.LogInfo((object)"Лавка: сундука счёта в Чертоге славы нет (нужен Portal Hall 0.5.0), жду.");
				}
				return;
			}
			_missingReported = false;
			if (val != null)
			{
				SetSign(val, "Счёт: кладите монеты. Покупки в окне F8");
			}
			try
			{
				Process(_chest);
			}
			catch (Exception ex)
			{
				_handled = _chest.DataRevision;
				Plugin.Log.LogError((object)("Лавка: ошибка в сундуке счёта: " + ex));
			}
		}

		private void Process(ZDO chest)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Expected O, but got Unknown
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			if (chest.GetInt(ZDOVars.s_inUse, 0) == 1)
			{
				return;
			}
			byte[] byteArray = chest.GetByteArray(ZDOVars.s_items, (byte[])null);
			if (byteArray == null || byteArray.Length == 0 || _handled == chest.DataRevision)
			{
				return;
			}
			ZNetPeer val = FindDepositor(chest);
			if (val == null)
			{
				return;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(chest.GetPrefab());
			Container val2 = ((prefab != null) ? prefab.GetComponent<Container>() : null);
			if ((Object)(object)val2 == (Object)null)
			{
				_handled = chest.DataRevision;
				return;
			}
			Inventory inventory = new Inventory("bank", (Sprite)null, val2.m_width, val2.m_height);
			inventory.Load(new ZPackage(byteArray));
			List<ItemData> list = inventory.GetAllItems().ToList();
			List<ItemData> list2 = list.Where((ItemData i) => (Object)(object)i.m_dropPrefab != (Object)null && ((Object)i.m_dropPrefab).name == "Coins").ToList();
			long num = ((IEnumerable<ItemData>)list2).Sum((Func<ItemData, long>)((ItemData i) => i.m_stack));
			if (num == 0L)
			{
				if (list.Count > 0)
				{
					Message(val, "Этот сундук принимает только монеты.");
				}
				_handled = chest.DataRevision;
				return;
			}
			string text = Id(val);
			_accounts.Deposit(text, val.m_playerName, num);
			if (!Save())
			{
				_accounts.TrySpend(text, num);
				return;
			}
			list2.ForEach(delegate(ItemData c)
			{
				inventory.RemoveItem(c);
			});
			ZPackage val3 = new ZPackage();
			inventory.Save(val3);
			chest.SetOwner(ZDOMan.GetSessionID());
			chest.Set(ZDOVars.s_items, val3.GetArray());
			ZDOMan.instance.ForceSendZDO(chest.m_uid);
			_handled = chest.DataRevision;
			Plugin.Log.LogInfo((object)$"Лавка: {val.m_playerName} ({text}) внёс {num}, на счету {_accounts.Balance(text)}.");
			Message(val, StableRules.Deposited(num, _accounts.Balance(text)));
			if (_registered.Contains(val.m_rpc))
			{
				SendState(val);
			}
		}

		private void Spawn(string prefabName, Vector3 pos, int level, string ownerId, string playerName)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			GameObject prefab = ZNetScene.instance.GetPrefab(prefabName);
			ZNetView component = prefab.GetComponent<ZNetView>();
			int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name);
			FieldInfo fieldInfo = EwpSkip();
			object obj = fieldInfo?.GetValue(null);
			ZDO val;
			try
			{
				fieldInfo?.SetValue(null, true);
				val = ZDOMan.instance.CreateNewZDO(pos, stableHashCode);
			}
			finally
			{
				if (obj != null)
				{
					fieldInfo.SetValue(null, obj);
				}
			}
			val.Persistent = component.m_persistent;
			val.Type = component.m_type;
			val.Distant = component.m_distant;
			val.SetPrefab(stableHashCode);
			val.SetRotation(Quaternion.identity);
			val.Set(ZDOVars.s_level, level, false);
			val.Set(ZDOVars.s_tamed, true);
			val.Set(ZDOVars.s_follow, playerName);
			val.Set("sansara.TameLab.owner", ownerId);
		}

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

		private int Owned(string id, string prefab)
		{
			List<ZDO> list = new List<ZDO>();
			int num = 0;
			while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(prefab, list, ref num))
			{
			}
			return list.Count((ZDO z) => z.GetString("sansara.TameLab.owner", "") == id);
		}

		private static string Id(ZNetPeer peer)
		{
			ISocket socket = peer.m_socket;
			string text = ((socket != null) ? socket.GetHostName() : null) ?? "";
			if (!(text != ""))
			{
				return "player:" + peer.m_uid;
			}
			return text;
		}

		private ZNetPeer FindDepositor(ZDO chest)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			long owner = chest.GetOwner();
			bool flag = owner == ZDOMan.GetSessionID();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				ZDO val = (((ZDOID)(ref peer.m_characterID)).IsNone() ? null : ZDOMan.instance.GetZDO(peer.m_characterID));
				if (val == null)
				{
					continue;
				}
				long num = val.GetLong(ZDOVars.s_playerID, 0L);
				bool num2;
				if (!flag)
				{
					num2 = peer.m_uid == owner;
				}
				else
				{
					if (num != _lastDepositor)
					{
						continue;
					}
					num2 = num != 0;
				}
				if (num2)
				{
					_lastDepositor = num;
					return peer;
				}
			}
			return null;
		}

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

		private static void Message(ZNetPeer peer, string text)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { 2, text });
		}
	}
}
namespace TameLab.Core
{
	public interface IBankHost
	{
		bool FindOnline(string name, out string id, out string shownName);

		bool Save();
	}
	public static class BankAdmin
	{
		public static readonly string[] Verbs = new string[4] { "help", "list", "add", "take" };

		private const string Help = "list — балансы всех игроков\nadd <игрок|@ID> <сумма> — пополнить счёт\ntake <игрок|@ID> <сумма> — списать со счёта";

		public static string Handle(string line, CoinAccounts accounts, IBankHost host)
		{
			AdminCommand adminCommand = AdminCommand.Parse(line);
			switch (adminCommand.Verb)
			{
			case "help":
				return "list — балансы всех игроков\nadd <игрок|@ID> <сумма> — пополнить счёт\ntake <игрок|@ID> <сумма> — списать со счёта";
			case "list":
				return List(accounts);
			case "add":
			case "take":
				return Change(adminCommand, accounts, host);
			default:
				return AdminProtocol.Unknown(adminCommand.Verb, Verbs);
			}
		}

		private static string List(CoinAccounts accounts)
		{
			if (accounts.Players.Count == 0)
			{
				return "счетов пока нет";
			}
			IEnumerable<string> values = from p in accounts.Players
				orderby p.Value.Coins descending
				select $"{p.Value.Name} (@{p.Key}): {p.Value.Coins}";
			return $"счетов {accounts.Players.Count}, всего монет {accounts.Players.Values.Sum((CoinAccount p) => p.Coins)}\n" + string.Join("\n", values);
		}

		private static string Change(AdminCommand c, CoinAccounts accounts, IBankHost host)
		{
			bool flag = c.Verb == "add";
			if (c.Args.Count < 2 || !long.TryParse(c.Args[c.Args.Count - 1], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0)
			{
				return "укажите игрока и положительную сумму: " + c.Verb + " <игрок|@ID> <сумма>";
			}
			string text = string.Join(" ", c.Args.Take(c.Args.Count - 1));
			if (!Resolve(text, accounts, host, out var id, out var name))
			{
				return "игрока «" + text + "» нет ни среди счетов, ни на сервере; для отключённого укажите @ID";
			}
			if (flag)
			{
				accounts.Deposit(id, name, result);
			}
			else if (!accounts.TrySpend(id, result))
			{
				return $"на счету {name} (@{id}) только {accounts.Balance(id)}, списание {result} не выполнено";
			}
			if (!host.Save())
			{
				if (flag)
				{
					accounts.TrySpend(id, result);
				}
				else
				{
					accounts.Deposit(id, name, result);
				}
				return "счета не сохранены, операция отменена; см. журнал";
			}
			return string.Format("{0} {1}: {2} (@{3}), теперь на счету {4}", flag ? "пополнено" : "списано", result, name, id, accounts.Balance(id));
		}

		private static bool Resolve(string who, CoinAccounts accounts, IBankHost host, out string id, out string name)
		{
			if (AdminCommand.TryId(who, out id))
			{
				name = (accounts.Players.TryGetValue(id, out var value) ? value.Name : "");
				return true;
			}
			string typed = who;
			string text = accounts.Players.Keys.FirstOrDefault((string k) => string.Equals(accounts.Players[k].Name, typed, StringComparison.OrdinalIgnoreCase));
			if (text != null)
			{
				id = text;
				name = accounts.Players[text].Name;
				return true;
			}
			return host.FindOnline(who, out id, out name);
		}
	}
	public enum Deal
	{
		NeedCoins,
		LimitReached,
		TooFar,
		Buy
	}
	public sealed class Good
	{
		public string Prefab { get; }

		public string Name { get; }

		public string Description { get; }

		public string DefaultPrices { get; }

		public Good(string prefab, string name, string description, string prices)
		{
			Prefab = prefab;
			Name = name;
			Description = description;
			DefaultPrices = prices;
		}
	}
	public static class StableRules
	{
		public const int Tiers = 3;

		public const string ChestRole = "bank.chest";

		public const string SignRole = "bank.sign";

		public const string OnlyCoins = "Этот сундук принимает только монеты.";

		public const string SignText = "Счёт: кладите монеты. Покупки в окне F8";

		public const float DefaultHealthMultiplier = 3f;

		public const float DefaultRegenMinutes = 15f;

		public static readonly Good[] Goods = new Good[2]
		{
			new Good("GoblinBrute", "Брут", "Брут фулингов", "10000,20000,30000"),
			new Good("SeekerBrute", "Искатель-солдат", "Искатель-солдат", "20000,40000,60000")
		};

		public static float ScaleHealth(float health, float oldMax, float newMax)
		{
			if (oldMax <= 0f || health <= 0f)
			{
				if (!(health <= 0f))
				{
					return newMax;
				}
				return 0f;
			}
			return ((health >= oldMax) ? 1f : (health / oldMax)) * newMax;
		}

		public static Good GoodOf(string prefab)
		{
			return Goods.FirstOrDefault((Good g) => g.Prefab == prefab);
		}

		public static int Level(int tier)
		{
			return tier + 1;
		}

		public static long[] ParsePrices(string text)
		{
			string[] array = (text ?? "").Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length != 3)
			{
				return null;
			}
			long[] array2 = new long[3];
			for (int i = 0; i < 3; i++)
			{
				if (!long.TryParse(array[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out array2[i]) || array2[i] <= 0)
				{
					return null;
				}
			}
			return array2;
		}

		public static Deal Decide(long balance, long price, int owned, int limit, bool near)
		{
			if (!near)
			{
				return Deal.TooFar;
			}
			if (balance < price)
			{
				return Deal.NeedCoins;
			}
			if (owned < limit)
			{
				return Deal.Buy;
			}
			return Deal.LimitReached;
		}

		public static bool TryParseBuy(string arg, out Good good, out int tier)
		{
			good = null;
			tier = 0;
			string[] array = (arg ?? "").Split(new char[1] { ':' });
			if (array.Length != 2 || !int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out tier) || tier < 0 || tier >= 3)
			{
				return false;
			}
			good = GoodOf(array[0]);
			return good != null;
		}

		public static string BuyArg(Good good, int tier)
		{
			return good.Prefab + ":" + tier.ToString(CultureInfo.InvariantCulture);
		}

		public static string Deposited(long coins, long balance)
		{
			return $"Зачислено монет: {coins}. На счету: {balance}.";
		}

		public static string Bought(Good good, int tier, long balance)
		{
			return $"{good.Name} {Stars(tier)} куплен. Остаток на счету: {balance}.";
		}

		public static string Refusal(Deal deal, Good good, long balance, long price, int limit)
		{
			return deal switch
			{
				Deal.TooFar => "Покупать можно только в Чертоге славы.", 
				Deal.NeedCoins => $"На счету {balance}, не хватает {price - balance}.", 
				_ => $"У вас уже достаточно купленных существ (предел {limit}).", 
			};
		}

		public static string Stars(int tier)
		{
			return tier switch
			{
				0 => "без звёзд", 
				1 => "с 1 звездой", 
				_ => $"с {tier} звёздами", 
			};
		}
	}
	public sealed class ShopItem
	{
		public string Prefab = "";

		public int Owned;

		public long[] Prices = new long[3];
	}
	public sealed class ShopState
	{
		public long Balance;

		public int Limit;

		public bool Near;

		public readonly List<ShopItem> Items = new List<ShopItem>();

		public int TotalOwned => Items.Sum((ShopItem x) => x.Owned);

		public string Serialize()
		{
			CultureInfo i = CultureInfo.InvariantCulture;
			List<string> list = new List<string>
			{
				Balance.ToString(i),
				Limit.ToString(i),
				Near ? "1" : "0"
			};
			list.AddRange(Items.Select((ShopItem x) => x.Prefab + "|" + x.Owned.ToString(i) + "|" + string.Join(",", x.Prices.Select((long p) => p.ToString(i)))));
			return string.Join(";", list);
		}

		public static ShopState Parse(string text)
		{
			string[] array = (text ?? "").Split(new char[1] { ';' });
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			ShopState shopState = new ShopState();
			if (array.Length < 3 || !long.TryParse(array[0], NumberStyles.Integer, invariantCulture, out shopState.Balance) || !int.TryParse(array[1], NumberStyles.Integer, invariantCulture, out shopState.Limit) || (array[2] != "0" && array[2] != "1"))
			{
				return null;
			}
			shopState.Near = array[2] == "1";
			for (int i = 3; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { '|' });
				ShopItem shopItem = new ShopItem();
				if (array2.Length != 3 || array2[0] == "" || !int.TryParse(array2[1], NumberStyles.Integer, invariantCulture, out shopItem.Owned))
				{
					return null;
				}
				shopItem.Prefab = array2[0];
				shopItem.Prices = StableRules.ParsePrices(array2[2]);
				if (shopItem.Prices == null)
				{
					return null;
				}
				shopState.Items.Add(shopItem);
			}
			return shopState;
		}
	}
}