Decompiled source of JunkBuyer v0.2.2

plugins/JunkBuyer.dll

Decompiled 6 days 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 JunkBuyer.Core;
using Microsoft.CodeAnalysis;
using Sansara.Admin;
using Sansara.Clock;
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("JunkBuyer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.2.0")]
[assembly: AssemblyInformationalVersion("0.2.2+ee6a46e381b50add1bc5a1ee15a3276bde7e4e27")]
[assembly: AssemblyProduct("JunkBuyer")]
[assembly: AssemblyTitle("JunkBuyer")]
[assembly: AssemblyVersion("0.2.2.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.Clock
{
	public static class RealClock
	{
		private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

		public const double WorldClockLimit = 1000000000.0;

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

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

		public static double FromWorld(double t, double worldNow, double realNow)
		{
			if (!IsWorldTime(t))
			{
				return t;
			}
			return realNow - (worldNow - t);
		}
	}
}
namespace JunkBuyer
{
	internal static class Buyer
	{
		private sealed class Outcome
		{
			public int Items;

			public int Coins;

			public readonly Dictionary<string, int> Metal = new Dictionary<string, int>();

			public Refusal Refusal;

			public string RefusedItem = "";

			public string RefusedDetail = "";

			public void Refuse(Refusal r, ItemData item, string detail = "")
			{
				if (Refusal == Refusal.None)
				{
					Refusal = r;
					RefusedItem = Name(item);
					RefusedDetail = detail;
				}
			}
		}

		private sealed class Depositor
		{
			public string Id = "";

			public long PlayerId;

			public string Name = "";
		}

		private static DailyLedger _ledger = new DailyLedger();

		private static string _path;

		private static Dictionary<string, int> _prices = new Dictionary<string, int>();

		private static HashSet<string> _excluded = new HashSet<string>();

		private static HashSet<string> _metals = new HashSet<string>();

		private static readonly Dictionary<ZDOID, string> Handled = new Dictionary<ZDOID, string>();

		private static bool _stallMissingReported;

		private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

		private static readonly Dictionary<ZDOID, Depositor> Last = new Dictionary<ZDOID, Depositor>();

		internal static readonly string[] Verbs = new string[4] { "status", "help", "reset", "ui" };

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

		public static void Load(Dictionary<string, int> prices, HashSet<string> excluded, HashSet<string> metals)
		{
			_prices = prices;
			_excluded = excluded;
			_metals = metals;
			string text = Path.Combine(Paths.ConfigPath, "sansara.JunkBuyer");
			Directory.CreateDirectory(text);
			string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			_path = Path.Combine(text, text2 + ".daily");
			try
			{
				_ledger = (File.Exists(_path) ? DailyLedger.Parse(File.ReadAllText(_path)) : new DailyLedger());
			}
			catch (FormatException ex)
			{
				File.Move(_path, _path + "." + DateTime.UtcNow.ToString("yyyyMMddHHmmss") + ".bad");
				_ledger = new DailyLedger();
				Plugin.Log.LogWarning((object)("Скупщик: файл счётчиков повреждён (" + ex.Message + ") — отложен как .bad, счёт заново."));
			}
			Plugin.Log.LogInfo((object)$"Скупщик: период {_ledger.Day}, игроков в счётчиках {_ledger.Players.Count}.");
		}

		private static bool Save()
		{
			try
			{
				string text = _path + ".tmp";
				File.WriteAllText(text, _ledger.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;
			}
		}

		private static bool FindDepositor(ZDO chest, out Depositor d)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			d = new Depositor();
			if (chest.GetOwner() == ZDOMan.GetSessionID())
			{
				if (!Last.TryGetValue(chest.m_uid, out var value) || !Stall.Online(value.PlayerId, out var id, out var name))
				{
					return false;
				}
				value.Id = id;
				value.Name = name;
				d = value;
				return true;
			}
			if (!Stall.Depositor(chest, out var id2, out var playerId, out var name2))
			{
				return false;
			}
			d = new Depositor
			{
				Id = id2,
				PlayerId = playerId,
				Name = name2
			};
			Last[chest.m_uid] = d;
			return true;
		}

		private static long Today()
		{
			return DailyLedger.DayOf(RealClock.Now, Plugin.ResetOffsetHours.Value, (double)Plugin.ResetHours.Value * 60.0);
		}

		public static void Tick()
		{
			long num = Today();
			if (num != _ledger.Day)
			{
				_ledger.Roll(num);
				Save();
				Plugin.Log.LogInfo((object)$"Скупщик: новый период {num}, лимиты сброшены.");
			}
			Dictionary<string, ZDO> dictionary = Stall.Find();
			ZDO value;
			bool flag = dictionary.TryGetValue("buyer.chest.sell", out value);
			ZDO value2;
			bool flag2 = dictionary.TryGetValue("buyer.chest.salvage", out value2);
			if (!flag || !flag2)
			{
				if (!_stallMissingReported)
				{
					_stallMissingReported = true;
					string text = string.Join(", ", new string[2]
					{
						flag ? null : "buyer.chest.sell",
						flag2 ? null : "buyer.chest.salvage"
					}.Where((string r) => r != null));
					Plugin.Log.LogInfo((object)("Скупщик: в Чертоге славы (Portal Hall 0.2.3) не найдено: " + text + " — жду."));
				}
			}
			else
			{
				_stallMissingReported = false;
			}
			dictionary.TryGetValue("buyer.sign.sell", out var value3);
			dictionary.TryGetValue("buyer.sign.salvage", out var value4);
			Stall.SetSign(value3, BuyerTexts.SellSign(Plugin.CoinLimit.Value, Plugin.ResetHours.Value));
			Stall.SetSign(value4, BuyerTexts.SalvageSign(Plugin.SalvageFraction.Value, Plugin.SalvageLimit.Value, Plugin.ResetHours.Value));
			if (flag)
			{
				Guard(value, selling: true, num);
			}
			if (flag2)
			{
				Guard(value2, selling: false, num);
			}
		}

		private static void Guard(ZDO chest, bool selling, long day)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Process(chest, selling, day);
			}
			catch (Exception arg)
			{
				Handled[chest.m_uid] = chest.DataRevision + ":" + day;
				Plugin.Log.LogError((object)string.Format("Скупщик: ошибка в сундуке {0}: {1}", selling ? "скупки" : "разборки", arg));
			}
		}

		private static void Process(ZDO chest, bool selling, long day)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: 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)
			{
				return;
			}
			string text = chest.DataRevision + ":" + day;
			if ((Handled.TryGetValue(chest.m_uid, out var value) && value == text) || !FindDepositor(chest, out var d))
			{
				return;
			}
			string id = d.Id;
			string name = d.Name;
			long playerId = d.PlayerId;
			Inventory val = Stall.Load(chest, byteArray);
			if (val == null)
			{
				Handled[chest.m_uid] = text;
				return;
			}
			LedgerEntry ledgerEntry = _ledger.For(id, name);
			int coins = ledgerEntry.Coins;
			int salvaged = ledgerEntry.Salvaged;
			Outcome outcome;
			try
			{
				outcome = (selling ? Sell(val, ledgerEntry) : Salvage(val, ledgerEntry));
			}
			catch
			{
				ledgerEntry.Coins = coins;
				ledgerEntry.Salvaged = salvaged;
				throw;
			}
			if (outcome.Items > 0)
			{
				if (!Save())
				{
					ledgerEntry.Coins = coins;
					ledgerEntry.Salvaged = salvaged;
					return;
				}
				Stall.Save(chest, val);
				Plugin.Log.LogInfo((object)(selling ? $"Скупщик: {name} ({id}) продал {outcome.Items} за {outcome.Coins} монет; за период {ledgerEntry.Coins}/{Plugin.CoinLimit.Value}." : string.Format("Скупщик: {0} ({1}) разобрал {2} → {3}; за период {4}/{5}.", name, id, outcome.Items, string.Join(", ", outcome.Metal.Select((KeyValuePair<string, int> m) => m.Value + " " + m.Key)), ledgerEntry.Salvaged, Plugin.SalvageLimit.Value)));
			}
			List<string> list = new List<string>();
			if (outcome.Items > 0)
			{
				list.Add(selling ? BuyerTexts.Sold(outcome.Items, outcome.Coins, Plugin.CoinLimit.Value - ledgerEntry.Coins) : BuyerTexts.Salvaged(outcome.Items, string.Join(", ", outcome.Metal.Select((KeyValuePair<string, int> m) => m.Value + " " + ItemNames.Of(m.Key))), Plugin.SalvageLimit.Value - ledgerEntry.Salvaged));
			}
			if (outcome.Refusal != Refusal.None)
			{
				list.Add(BuyerTexts.Refused(outcome.Refusal, outcome.RefusedItem, outcome.RefusedDetail));
			}
			if (list.Count > 0)
			{
				Stall.Message(playerId, string.Join("\n", list));
			}
			Handled[chest.m_uid] = chest.DataRevision + ":" + day;
		}

		private static Outcome Sell(Inventory inventory, LedgerEntry entry)
		{
			Outcome outcome = new Outcome();
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Coins");
			foreach (ItemData item in inventory.GetAllItems().ToList())
			{
				string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : "");
				byte[] snapshot = Stall.Snapshot(inventory);
				int coins = entry.Coins;
				int items = outcome.Items;
				int coins2 = outcome.Coins;
				try
				{
					if (text == "Coins")
					{
						continue;
					}
					bool flag = item.m_shared.m_maxStackSize > 1;
					bool special = item.m_customData != null && item.m_customData.Count > 0;
					int coins3;
					Refusal refusal = PriceList.Quote(text, flag, special, !flag && HasMetal(item), item.m_shared.m_value, item.m_quality, _prices, _excluded, Plugin.DefaultPrice.Value, Plugin.DefaultItemPrice.Value, out coins3);
					if (refusal != Refusal.None)
					{
						outcome.Refuse(refusal, item);
						continue;
					}
					int num = SellRules.Sellable(item.m_stack, coins3, Plugin.CoinLimit.Value - entry.Coins);
					if (num == 0)
					{
						outcome.Refuse(Refusal.Limit, item);
						continue;
					}
					if (num == item.m_stack)
					{
						inventory.RemoveItem(item);
					}
					else
					{
						item.m_stack -= num;
					}
					if (!Stall.AddAll(inventory, itemPrefab, num * coins3))
					{
						Stall.Restore(inventory, snapshot);
						outcome.Refuse(Refusal.Full, item);
						break;
					}
					entry.Coins += num * coins3;
					outcome.Items += num;
					outcome.Coins += num * coins3;
				}
				catch (Exception arg)
				{
					Stall.Restore(inventory, snapshot);
					entry.Coins = coins;
					outcome.Items = items;
					outcome.Coins = coins2;
					Plugin.Log.LogError((object)$"Скупщик: ошибка на предмете {text}: {arg}");
					break;
				}
			}
			return outcome;
		}

		private static Outcome Salvage(Inventory inventory, LedgerEntry entry)
		{
			Outcome outcome = new Outcome();
			HashSet<string> keys = new HashSet<string>(ZoneSystem.instance.GetGlobalKeys());
			foreach (ItemData item in inventory.GetAllItems().ToList())
			{
				string arg = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : "");
				byte[] snapshot = Stall.Snapshot(inventory);
				int salvaged = entry.Salvaged;
				int items = outcome.Items;
				Dictionary<string, int> dictionary = new Dictionary<string, int>(outcome.Metal);
				try
				{
					if ((Object)(object)item.m_dropPrefab != (Object)null && _metals.Contains(((Object)item.m_dropPrefab).name))
					{
						continue;
					}
					bool flag = item.m_shared.m_maxStackSize > 1;
					bool flag2 = item.m_customData != null && item.m_customData.Count > 0;
					Dictionary<string, int> dictionary2 = new Dictionary<string, int>();
					Requirement[] array = (Requirement[])(((object)((flag || flag2) ? null : ObjectDB.instance.GetRecipe(item))?.m_resources) ?? ((object)new Requirement[0]));
					foreach (Requirement val in array)
					{
						string text = (((Object)(object)val?.m_resItem != (Object)null) ? ((Object)val.m_resItem).name : "");
						if (_metals.Contains(text))
						{
							dictionary2[text] = (dictionary2.TryGetValue(text, out var value) ? value : 0) + SalvageRules.Spent((Func<int, int>)val.GetAmount, item.m_quality);
						}
					}
					Dictionary<string, int> dictionary3 = dictionary2.ToDictionary((KeyValuePair<string, int> p) => p.Key, (KeyValuePair<string, int> p) => SalvageRules.Yield(p.Value, Plugin.SalvageFraction.Value));
					Func<string, bool> portalOpen = PortalOpen(keys);
					Refusal refusal = SalvageRules.Check(flag, flag2, dictionary3, portalOpen);
					if (refusal != Refusal.None)
					{
						string detail = ((refusal == Refusal.PortalClosed) ? ItemNames.Of(SalvageRules.FirstClosed(dictionary3, portalOpen)) : "");
						outcome.Refuse(refusal, item, detail);
						continue;
					}
					if (entry.Salvaged >= Plugin.SalvageLimit.Value)
					{
						outcome.Refuse(Refusal.Limit, item);
						continue;
					}
					inventory.RemoveItem(item);
					if (!dictionary3.Where((KeyValuePair<string, int> y) => y.Value > 0).All((KeyValuePair<string, int> y) => Stall.AddAll(inventory, ObjectDB.instance.GetItemPrefab(y.Key), y.Value)))
					{
						Stall.Restore(inventory, snapshot);
						outcome.Refuse(Refusal.Full, item);
						break;
					}
					entry.Salvaged++;
					outcome.Items++;
					foreach (KeyValuePair<string, int> item2 in dictionary3.Where((KeyValuePair<string, int> y) => y.Value > 0))
					{
						outcome.Metal[item2.Key] = (outcome.Metal.TryGetValue(item2.Key, out var value2) ? value2 : 0) + item2.Value;
					}
				}
				catch (Exception arg2)
				{
					Stall.Restore(inventory, snapshot);
					entry.Salvaged = salvaged;
					outcome.Items = items;
					outcome.Metal.Clear();
					foreach (KeyValuePair<string, int> item3 in dictionary)
					{
						outcome.Metal[item3.Key] = item3.Value;
					}
					Plugin.Log.LogError((object)$"Скупщик: ошибка на предмете {arg}: {arg2}");
					break;
				}
			}
			return outcome;
		}

		private static bool HasMetal(ItemData item)
		{
			Requirement[] array = (Requirement[])(((object)ObjectDB.instance.GetRecipe(item)?.m_resources) ?? ((object)new Requirement[0]));
			foreach (Requirement val in array)
			{
				if ((Object)(object)val?.m_resItem != (Object)null && _metals.Contains(((Object)val.m_resItem).name))
				{
					return true;
				}
			}
			return false;
		}

		private static Func<string, bool> PortalOpen(HashSet<string> keys)
		{
			return delegate(string metal)
			{
				GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(metal);
				bool valueOrDefault = ((itemPrefab == null) ? ((bool?)null) : itemPrefab.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_teleportable) == true;
				string text = Metals.PortalKey(metal);
				return valueOrDefault || (text != null && keys.Contains(text));
			};
		}

		private static string Name(ItemData item)
		{
			if (!((Object)(object)item.m_dropPrefab != (Object)null))
			{
				return item.m_shared.m_name;
			}
			return ItemNames.Of(((Object)item.m_dropPrefab).name);
		}

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

		public static string Handle(string line, string admin, string adminId)
		{
			AdminCommand adminCommand = AdminCommand.Parse(line);
			switch (adminCommand.Verb)
			{
			case "help":
				return "status — начало текущего периода, игроки со счётчиками, выдано монет и разобрано вещей\nreset <игрок|@ID> — сбросить счётчики игрока за текущий период\nui — данные для окна Sansara Admin (машинный ответ): игроки со счётчиками периода";
			case "status":
				return Status();
			case "ui":
				if (_path != null)
				{
					return BuyerUi.Text(_ledger);
				}
				return AdminUi.Note(Plugin.Enabled.Value ? "Скупщик ещё не запущен: мир загружается" : "модуль выключен в настройках (Enabled = false)");
			case "reset":
				if (!Plugin.Enabled.Value)
				{
					return "модуль выключен в настройках (Enabled = false)";
				}
				if (_path != null)
				{
					return Reset(adminCommand.Argument);
				}
				return "Скупщик ещё не запущен: мир загружается";
			default:
				return AdminProtocol.Unknown(adminCommand.Verb, Verbs);
			}
		}

		private static string Status()
		{
			string text = (Plugin.Enabled.Value ? "" : "выключен в настройках; ");
			if (_path == null || _ledger.Day < 0)
			{
				if (Plugin.Enabled.Value)
				{
					return "Скупщик ещё не запущен: мир загружается";
				}
				return "модуль выключен в настройках (Enabled = false)";
			}
			double num = Plugin.ResetOffsetHours.Value;
			DateTime epoch = Epoch;
			DateTime dateTime = epoch.AddSeconds(DailyLedger.PeriodStart(_ledger.Day, num, (double)Plugin.ResetHours.Value * 60.0) + num * 3600.0);
			string text2 = text + "период с " + dateTime.ToString("dd.MM HH:mm", CultureInfo.InvariantCulture) + " (время сервера), длина " + BuyerTexts.Period(Plugin.ResetHours.Value) + "; " + $"игроков {_ledger.Players.Count}, выдано монет {_ledger.Players.Values.Sum((LedgerEntry p) => p.Coins)}, разобрано вещей {_ledger.Players.Values.Sum((LedgerEntry p) => p.Salvaged)}";
			if (_ledger.Players.Count != 0)
			{
				return text2 + "\n" + string.Join("; ", _ledger.Players.Select((KeyValuePair<string, LedgerEntry> p) => $"{p.Value.Name} ({p.Key}) монет {p.Value.Coins}, вещей {p.Value.Salvaged}"));
			}
			return text2;
		}

		private static string Reset(string who)
		{
			if (who == "")
			{
				return "укажите игрока: reset <имя|@ID>";
			}
			if (AdminCommand.TryId(who, out var id))
			{
				who = id;
			}
			string text = _ledger.Players.Keys.FirstOrDefault((string k) => k == who || string.Equals(_ledger.Players[k].Name, who, StringComparison.OrdinalIgnoreCase));
			if (text == null)
			{
				return "игрока «" + who + "» в счётчиках нет";
			}
			string name = _ledger.Players[text].Name;
			_ledger.Players.Remove(text);
			bool flag = Save();
			Handled.Clear();
			return "счётчики " + name + " (" + text + ") сброшены" + (flag ? "" : "; файл счётчиков не записан — см. журнал");
		}
	}
	internal static class ItemNames
	{
		private static readonly Dictionary<string, string> Russian = new Dictionary<string, string>();

		public static void Load()
		{
			try
			{
				foreach (TextAsset item in Assets())
				{
					ReadColumn(item, "Russian", Russian);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Скупщик: русская локализация не прочитана: " + ex.Message));
			}
			Plugin.Log.LogInfo((object)$"Скупщик: строк русской локализации {Russian.Count}.");
		}

		public static string Of(string prefab)
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab);
			string text = ((itemPrefab == null) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_name) ?? prefab;
			string key = text.TrimStart(new char[1] { '$' });
			if (Russian.TryGetValue(key, out var value) && value != "")
			{
				return value;
			}
			if (Localization.instance == null)
			{
				return prefab;
			}
			return Localization.instance.Localize(text);
		}

		public static string Token(string prefab)
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefab);
			return ((itemPrefab == null) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_name) ?? "";
		}

		private static IEnumerable<TextAsset> Assets()
		{
			_ = Localization.instance;
			object obj = AccessTools.Field(typeof(Localization), "m_localizationSettings")?.GetValue(null);
			if (obj == null)
			{
				yield break;
			}
			Traverse val = Traverse.Create(obj);
			if (!((val.Field("Localizations").GetValue() ?? val.Property("Localizations", (object[])null).GetValue()) is IEnumerable enumerable))
			{
				yield break;
			}
			foreach (object item in enumerable)
			{
				TextAsset val2 = (TextAsset)((item is TextAsset) ? item : null);
				if (val2 != null && (Object)(object)val2 != (Object)null)
				{
					yield return val2;
				}
			}
		}

		private static void ReadColumn(TextAsset asset, string language, Dictionary<string, string> into)
		{
			List<List<string>> list = SplitCsv(asset.text);
			if (list.Count == 0)
			{
				return;
			}
			int num = list[0].FindIndex((string h) => StripQuotes(h).Trim() == language);
			if (num < 0)
			{
				return;
			}
			foreach (List<string> item in list.Skip(1))
			{
				if (item.Count != 0 && item[0].Length != 0 && !item[0].StartsWith("//"))
				{
					string text = ((item.Count > num) ? item[num].Trim() : "");
					if (text.Length == 0 && item.Count > 1)
					{
						text = item[1].Trim();
					}
					into[item[0]] = text;
				}
			}
		}

		private static string StripQuotes(string s)
		{
			if (s.Length < 2 || s[0] != '"' || s[s.Length - 1] != '"')
			{
				return s;
			}
			return s.Substring(1, s.Length - 2);
		}

		private static List<List<string>> SplitCsv(string text)
		{
			List<List<string>> list = new List<List<string>>();
			List<string> list2 = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			for (int i = 0; i < text.Length; i++)
			{
				char c = text[i];
				switch (c)
				{
				case '"':
					if (flag && i + 1 < text.Length && text[i + 1] == '"')
					{
						stringBuilder.Append('"');
						i++;
					}
					else
					{
						flag = !flag;
					}
					continue;
				case ',':
					if (!flag)
					{
						list2.Add(stringBuilder.ToString());
						stringBuilder.Length = 0;
						continue;
					}
					break;
				}
				if (c == '\n' && !flag)
				{
					list2.Add(stringBuilder.ToString().TrimEnd(new char[1] { '\r' }));
					stringBuilder.Length = 0;
					list.Add(list2);
					list2 = new List<string>();
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			if (stringBuilder.Length > 0 || list2.Count > 0)
			{
				list2.Add(stringBuilder.ToString());
				list.Add(list2);
			}
			return list;
		}
	}
	[BepInPlugin("sansara.JunkBuyer", "Junk Buyer", "0.2.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.JunkBuyer";

		public const string Name = "Junk Buyer";

		public const string Version = "0.2.2";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> Catalog;

		internal static ConfigEntry<string> Excluded;

		internal static ConfigEntry<int> DefaultPrice;

		internal static ConfigEntry<int> DefaultItemPrice;

		internal static ConfigEntry<int> CoinLimit;

		internal static ConfigEntry<string> MetalList;

		internal static ConfigEntry<float> SalvageFraction;

		internal static ConfigEntry<int> SalvageLimit;

		internal static ConfigEntry<float> ResetHours;

		internal static ConfigEntry<float> ResetOffsetHours;

		private float _nextTick;

		private bool _started;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Скупщик в Чертоге славы.");
			Catalog = ((BaseUnityPlugin)this).Config.Bind<string>("Sell", "Catalog", "Resin:1, LeatherScraps:1, Feathers:1, TrophyBoar:5, TrophyDeer:5, TrophyNeck:5, BoneFragments:2, GreydwarfEye:2, AncientSeed:2, TrophyGreydwarf:10, TrophyGreydwarfBrute:10, TrophyGreydwarfShaman:10, TrophySkeleton:10, TrophyForestTroll:10, Entrails:3, WitheredBone:3, Bloodbag:3, Ooze:3, Guck:3, TrophyDraugr:15, TrophyDraugrElite:15, TrophyBlob:15, TrophyLeech:15, TrophySurtling:15, TrophyWraith:15, TrophyAbomination:15, FreezeGland:4, TrophyWolf:20, TrophyFenring:20, TrophyHatchling:20, TrophyCultist:20, TrophySGolem:20, TrophyUlv:20, Needle:5, TrophyDeathsquito:25, TrophyGoblin:25, TrophyGoblinBrute:25, TrophyGoblinShaman:25, TrophyLox:25, TrophyGrowth:25, Mandible:6, RoyalJelly:6, TrophySeeker:30, TrophySeekerBrute:30, TrophyTick:30, TrophyGjall:30, TrophyHare:30, TrophyDvergr:30, CharredBone:8, TrophyCharredMelee:40, TrophyCharredArcher:40, TrophyCharredMage:40, TrophyAsksvin:40, TrophyVolture:40, TrophyFallenValkyrie:40, TrophyMorgen:40, TrophyBonemawSerpent:40", "Предмет:цена в монетах через запятую — явные цены, они важнее остальных правил.");
			Excluded = ((BaseUnityPlugin)this).Config.Bind<string>("Sell", "Excluded", "Coins, Copper, Tin, Bronze, Iron, Silver, BlackMetal, FlametalNew, CopperOre, TinOre, CopperScrap, BronzeScrap, IronScrap, IronOre, SilverOre, BlackMetalScrap, FlametalOre, FlametalOreNew, TrophyEikthyr, TrophyTheElder, TrophyBonemass, TrophyDragonQueen, TrophyGoblinKing, TrophySeekerQueen, TrophyFader, TrophyWrithan", "Имена предметов через запятую, которые скупка не берёт (монеты, металлы, трофеи боссов).");
			DefaultPrice = ((BaseUnityPlugin)this).Config.Bind<int>("Sell", "DefaultPrice", 1, "Цена за штуку для стекуемых предметов не из прайса; 0 — не брать.");
			DefaultItemPrice = ((BaseUnityPlugin)this).Config.Bind<int>("Sell", "DefaultItemPrice", 5, "Цена штучной вещи не из прайса за уровень качества; 0 — не брать.");
			CoinLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "CoinLimit", 500, "Монет со скупки на игрока за период.");
			MetalList = ((BaseUnityPlugin)this).Config.Bind<string>("Salvage", "Metals", "Copper, Tin, Bronze, Iron, Silver, BlackMetal, FlametalNew", "Металлы, которые возвращает разборка (имена предметов).");
			SalvageFraction = ((BaseUnityPlugin)this).Config.Bind<float>("Salvage", "SalvageFraction", 0.5f, "Доля металла рецепта (с улучшениями), которая возвращается; округление вниз.");
			SalvageLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "SalvageLimit", 10, "Разобранных вещей на игрока за период.");
			ResetHours = ((BaseUnityPlugin)this).Config.Bind<float>("Limits", "ResetHours", 4f, "Длина периода в часах (реальное время); лимиты сбрасываются на границе периода.");
			ResetOffsetHours = ((BaseUnityPlugin)this).Config.Bind<float>("Limits", "ResetOffsetHours", 3f, "Границы периодов отсчитываются от полуночи UTC + столько часов (пояс сервера).");
			AdminChannel.Register("buyer", Buyer.Verbs, Buyer.Handle, Log);
			Log.LogInfo((object)"Junk Buyer 0.2.2 загружен.");
		}

		private void Update()
		{
			AdminChannel.Tick();
			if (!Enabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.LocationsGenerated || Time.time < _nextTick)
			{
				return;
			}
			_nextTick = Time.time + 5f;
			try
			{
				if (!_started)
				{
					try
					{
						Prepare();
						_started = true;
					}
					catch (Exception ex)
					{
						Log.LogError((object)("Скупщик: запуск не удался: " + ex));
						_nextTick = Time.time + 60f;
						return;
					}
				}
				Buyer.CheckCommand();
				Buyer.Tick();
			}
			catch (Exception ex2)
			{
				Log.LogError((object)("Скупщик: ошибка: " + ex2));
			}
		}

		private static void Prepare()
		{
			List<string> list = new List<string>();
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (Price item in PriceList.Parse(Catalog.Value, list))
			{
				if ((Object)(object)ObjectDB.instance.GetItemPrefab(item.Item) == (Object)null)
				{
					Log.LogWarning((object)("Скупщик: предмета " + item.Item + " нет в игре — строка прайса пропущена."));
				}
				else
				{
					dictionary[item.Item] = item.Coins;
				}
			}
			list.ForEach(delegate(string e)
			{
				Log.LogWarning((object)("Скупщик: Catalog — " + e));
			});
			HashSet<string> hashSet = new HashSet<string>(Metals.Parse(MetalList.Value).Where(delegate(string m)
			{
				bool num = (Object)(object)ObjectDB.instance.GetItemPrefab(m) != (Object)null;
				if (!num)
				{
					Log.LogWarning((object)("Скупщик: металла " + m + " нет в игре — пропущен."));
				}
				return num;
			}), StringComparer.Ordinal);
			HashSet<string> hashSet2 = new HashSet<string>(PriceList.ParseNames(Excluded.Value).Where(delegate(string n)
			{
				bool num = (Object)(object)ObjectDB.instance.GetItemPrefab(n) != (Object)null;
				if (!num)
				{
					Log.LogWarning((object)("Скупщик: Excluded — предмета " + n + " нет в игре, пропущен."));
				}
				return num;
			}), StringComparer.Ordinal);
			ItemNames.Load();
			Buyer.Load(dictionary, hashSet2, hashSet);
			Log.LogInfo((object)$"Скупщик: в прайсе {dictionary.Count} предметов, исключено {hashSet2.Count}, металлов {hashSet.Count}, лимиты {CoinLimit.Value} монет и {SalvageLimit.Value} вещей за {BuyerTexts.Period(ResetHours.Value)}.");
		}
	}
	internal static class Stall
	{
		private const string RoleKey = "sansara.PortalHall.role";

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

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

		public static Dictionary<string, ZDO> Find()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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);
			Dictionary<string, ZDO> dictionary = new Dictionary<string, ZDO>();
			foreach (ZDO item in Found)
			{
				string text = item.GetString("sansara.PortalHall.role", "");
				if (text.StartsWith("buyer.", StringComparison.Ordinal) && !dictionary.ContainsKey(text))
				{
					dictionary[text] = item;
				}
			}
			return dictionary;
		}

		public static bool Depositor(ZDO chest, out string id, out long playerId, out string name)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			id = "";
			playerId = 0L;
			name = "";
			float num = 10f;
			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;
				}
				bool flag = peer.m_uid == chest.GetOwner();
				float num2 = Vector3.Distance(val.GetPosition(), chest.GetPosition());
				if (flag || num2 <= num)
				{
					num = num2;
					playerId = val.GetLong(ZDOVars.s_playerID, 0L);
					name = peer.m_playerName;
					ISocket socket = peer.m_socket;
					string text = ((socket != null) ? socket.GetHostName() : null) ?? "";
					id = ((text != "") ? text : ("player:" + playerId));
					if (flag)
					{
						break;
					}
				}
			}
			return playerId != 0;
		}

		public static bool Online(long playerId, out string id, out string name)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			id = "";
			name = "";
			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 && val.GetLong(ZDOVars.s_playerID, 0L) == playerId)
				{
					name = peer.m_playerName;
					ISocket socket = peer.m_socket;
					string text = ((socket != null) ? socket.GetHostName() : null) ?? "";
					id = ((text != "") ? text : ("player:" + playerId));
					return true;
				}
			}
			return false;
		}

		public static Inventory Load(ZDO chest, byte[] bytes)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_004c: Expected O, but got Unknown
			GameObject prefab = ZNetScene.instance.GetPrefab(chest.GetPrefab());
			Container val = ((prefab != null) ? prefab.GetComponent<Container>() : null);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Inventory val2 = new Inventory("buyer", (Sprite)null, val.m_width, val.m_height);
			val2.Load(new ZPackage(bytes));
			return val2;
		}

		public static void Save(ZDO chest, Inventory inventory)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			inventory.Save(val);
			chest.SetOwner(ZDOMan.GetSessionID());
			chest.Set(ZDOVars.s_items, val.GetArray());
			ZDOMan.instance.ForceSendZDO(chest.m_uid);
		}

		public static byte[] Snapshot(Inventory inventory)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			inventory.Save(val);
			return val.GetArray();
		}

		public static void Restore(Inventory inventory, byte[] snapshot)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			inventory.Load(new ZPackage(snapshot));
		}

		public static bool AddAll(Inventory inventory, GameObject prefab, int amount)
		{
			ItemDrop component = prefab.GetComponent<ItemDrop>();
			while (amount > 0)
			{
				ItemData val = component.m_itemData.Clone();
				val.m_dropPrefab = prefab;
				val.m_stack = Math.Min(amount, Math.Max(1, val.m_shared.m_maxStackSize));
				val.m_worldLevel = (byte)Game.m_worldLevel;
				amount -= val.m_stack;
				if (!inventory.AddItem(val))
				{
					return false;
				}
			}
			return true;
		}

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

		public static void Message(long playerId, string text)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			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 && val.GetLong(ZDOVars.s_playerID, 0L) == playerId)
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { 2, text });
				}
			}
		}
	}
}
namespace JunkBuyer.Core
{
	public static class BuyerRoles
	{
		public const string Prefix = "buyer.";

		public const string SellChest = "buyer.chest.sell";

		public const string SellSign = "buyer.sign.sell";

		public const string SalvageChest = "buyer.chest.salvage";

		public const string SalvageSign = "buyer.sign.salvage";
	}
	public static class BuyerTexts
	{
		public const int SignLimit = 50;

		public static string SellSign(int coinLimit, double resetHours)
		{
			return Fit("Скупка: всё, кроме металла · " + N(coinLimit) + " за " + Period(resetHours));
		}

		public static string SalvageSign(double fraction, int limit, double resetHours)
		{
			return Fit("Разборка: " + Share(fraction) + " металла · " + N(limit) + " вещей за " + Period(resetHours));
		}

		public static string Period(double hours)
		{
			if (double.IsNaN(hours) || hours <= 0.0)
			{
				return "период";
			}
			if (hours < 1.0)
			{
				return Math.Max(1, (int)Math.Round(hours * 60.0)).ToString(CultureInfo.InvariantCulture) + " мин";
			}
			return Math.Round(hours, 2).ToString("0.##", CultureInfo.InvariantCulture).Replace('.', ',') + " ч";
		}

		public static string Sold(int items, int coins, int left)
		{
			return "Скупщик: продано " + N(items) + ", получено монет " + N(coins) + ". Осталось до сброса: " + N(Math.Max(0, left)) + ".";
		}

		public static string Salvaged(int items, string metals, int left)
		{
			return "Скупщик: разобрано " + N(items) + " → " + metals + ". Осталось до сброса: " + N(Math.Max(0, left)) + ".";
		}

		public static string Refused(Refusal r, string item, string detail)
		{
			string text;
			switch (r)
			{
			case Refusal.NotListed:
				text = "Скупщик такое не берёт";
				break;
			case Refusal.Excluded:
				text = "металл и трофеи боссов не покупаются";
				break;
			case Refusal.MetalItem:
				text = "в ней металл — положи в сундук разборки";
				break;
			case Refusal.Limit:
				text = "лимит до сброса исчерпан";
				break;
			case Refusal.Full:
				text = "в сундуке нет места для оплаты";
				break;
			case Refusal.Stackable:
				text = "разбираются только оружие, броня и инструменты";
				break;
			case Refusal.Special:
				text = "особая вещь, не принимается";
				break;
			case Refusal.NoMetal:
				text = "в ней нет металла";
				break;
			case Refusal.PortalClosed:
				text = "портал ещё не пропустит " + detail;
				break;
			case Refusal.NothingBack:
				text = "металла слишком мало";
				break;
			default:
				return "";
			}
			return "Не принято: " + item + " — " + text + ".";
		}

		private static string Share(double f)
		{
			if (Math.Abs(f - 0.5) < 1E-06)
			{
				return "½";
			}
			if (Math.Abs(f - 0.25) < 1E-06)
			{
				return "¼";
			}
			if (Math.Abs(f - 0.75) < 1E-06)
			{
				return "¾";
			}
			return Math.Round(f * 100.0).ToString(CultureInfo.InvariantCulture) + " %";
		}

		private static string N(int n)
		{
			return n.ToString(CultureInfo.InvariantCulture);
		}

		private static string Fit(string s)
		{
			if (s.Length > 50)
			{
				return s.Substring(0, 49) + "…";
			}
			return s;
		}
	}
	public static class BuyerUi
	{
		public static string Text(DailyLedger ledger)
		{
			return AdminUi.Text(from p in ledger.Players.OrderBy<KeyValuePair<string, LedgerEntry>, string>((KeyValuePair<string, LedgerEntry> p) => p.Value.Name, StringComparer.OrdinalIgnoreCase).ThenBy<KeyValuePair<string, LedgerEntry>, string>((KeyValuePair<string, LedgerEntry> p) => p.Key, StringComparer.Ordinal)
				select AdminUi.Row("player", p.Key, p.Value.Name, p.Value.Coins, p.Value.Salvaged));
		}
	}
	public sealed class LedgerEntry
	{
		public string Name = "";

		public int Coins;

		public int Salvaged;
	}
	public sealed class DailyLedger
	{
		public long Day = -1L;

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

		public static long DayOf(double realSeconds, double offsetHours, double dayMinutes)
		{
			double num = Math.Max(1.0, dayMinutes) * 60.0;
			return (long)Math.Floor((realSeconds + offsetHours * 3600.0) / num);
		}

		public static double PeriodStart(long day, double offsetHours, double dayMinutes)
		{
			return (double)day * (Math.Max(1.0, dayMinutes) * 60.0) - offsetHours * 3600.0;
		}

		public void Roll(long day)
		{
			if (day != Day)
			{
				Day = day;
				Players.Clear();
			}
		}

		public LedgerEntry For(string id, string name)
		{
			if (!Players.TryGetValue(id, out var value))
			{
				value = new LedgerEntry();
				Players[id] = value;
			}
			if (!string.IsNullOrEmpty(name))
			{
				value.Name = Clean(name);
			}
			return value;
		}

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

		public static DailyLedger Parse(string text)
		{
			DailyLedger dailyLedger = new DailyLedger();
			string[] array = (text ?? "").Replace("\r", "").Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				return dailyLedger;
			}
			string[] array2 = array[0].Split(new char[1] { '\t' });
			if (array2.Length != 2 || array2[0] != "day" || !long.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out dailyLedger.Day))
			{
				throw new FormatException("нет строки day");
			}
			for (int i = 1; i < array.Length; i++)
			{
				string[] array3 = array[i].Split(new char[1] { '\t' });
				if (array3.Length != 4 || array3[0] == "" || !int.TryParse(array3[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array3[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
				{
					throw new FormatException("строка " + (i + 1));
				}
				dailyLedger.Players[array3[0]] = new LedgerEntry
				{
					Coins = result,
					Salvaged = result2,
					Name = array3[3]
				};
			}
			return dailyLedger;
		}

		private static string Clean(string s)
		{
			return (s ?? "").Replace('\t', ' ').Replace('\n', ' ').Replace("\r", "");
		}
	}
	public sealed class Price
	{
		public string Item { get; }

		public int Coins { get; }

		public Price(string item, int coins)
		{
			Item = item;
			Coins = coins;
		}
	}
	public static class PriceList
	{
		public const string Default = "Resin:1, LeatherScraps:1, Feathers:1, TrophyBoar:5, TrophyDeer:5, TrophyNeck:5, BoneFragments:2, GreydwarfEye:2, AncientSeed:2, TrophyGreydwarf:10, TrophyGreydwarfBrute:10, TrophyGreydwarfShaman:10, TrophySkeleton:10, TrophyForestTroll:10, Entrails:3, WitheredBone:3, Bloodbag:3, Ooze:3, Guck:3, TrophyDraugr:15, TrophyDraugrElite:15, TrophyBlob:15, TrophyLeech:15, TrophySurtling:15, TrophyWraith:15, TrophyAbomination:15, FreezeGland:4, TrophyWolf:20, TrophyFenring:20, TrophyHatchling:20, TrophyCultist:20, TrophySGolem:20, TrophyUlv:20, Needle:5, TrophyDeathsquito:25, TrophyGoblin:25, TrophyGoblinBrute:25, TrophyGoblinShaman:25, TrophyLox:25, TrophyGrowth:25, Mandible:6, RoyalJelly:6, TrophySeeker:30, TrophySeekerBrute:30, TrophyTick:30, TrophyGjall:30, TrophyHare:30, TrophyDvergr:30, CharredBone:8, TrophyCharredMelee:40, TrophyCharredArcher:40, TrophyCharredMage:40, TrophyAsksvin:40, TrophyVolture:40, TrophyFallenValkyrie:40, TrophyMorgen:40, TrophyBonemawSerpent:40";

		public const string DefaultExcluded = "Coins, Copper, Tin, Bronze, Iron, Silver, BlackMetal, FlametalNew, CopperOre, TinOre, CopperScrap, BronzeScrap, IronScrap, IronOre, SilverOre, BlackMetalScrap, FlametalOre, FlametalOreNew, TrophyEikthyr, TrophyTheElder, TrophyBonemass, TrophyDragonQueen, TrophyGoblinKing, TrophySeekerQueen, TrophyFader, TrophyWrithan";

		public static HashSet<string> ParseNames(string text)
		{
			return new HashSet<string>((text ?? "").Split(new char[5] { ',', ';', ' ', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries), StringComparer.Ordinal);
		}

		public static Refusal Quote(string prefab, bool stackable, bool special, bool hasMetal, int vanillaValue, int quality, IDictionary<string, int> catalog, ISet<string> excluded, int defaultPrice, int defaultItemPrice, out int coins)
		{
			coins = 0;
			if (excluded != null && excluded.Contains(prefab))
			{
				return Refusal.Excluded;
			}
			if (special)
			{
				return Refusal.Special;
			}
			if (catalog != null && catalog.TryGetValue(prefab, out var value))
			{
				coins = value;
			}
			else
			{
				if (!stackable && hasMetal)
				{
					return Refusal.MetalItem;
				}
				if (vanillaValue > 0)
				{
					coins = vanillaValue;
				}
				else if (stackable)
				{
					coins = defaultPrice;
				}
				else
				{
					coins = defaultItemPrice * Math.Max(1, quality);
				}
			}
			if (coins <= 0)
			{
				coins = 0;
				return Refusal.NotListed;
			}
			return Refusal.None;
		}

		public static List<Price> Parse(string text, List<string> errors)
		{
			List<Price> list = new List<Price>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array = (text ?? "").Split(new char[3] { ',', ';', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0)
				{
					continue;
				}
				string[] array2 = text2.Split(new char[1] { ':' });
				if (array2.Length != 2 || array2[0].Trim() == "" || !int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 1)
				{
					errors.Add("строка «" + text2 + "» пропущена: нужно Предмет:цена (цена ≥ 1)");
					continue;
				}
				string text3 = array2[0].Trim();
				if (!hashSet.Add(text3))
				{
					errors.Add("предмет " + text3 + " указан повторно, оставлена первая строка");
				}
				else
				{
					list.Add(new Price(text3, result));
				}
			}
			return list;
		}
	}
	public static class SellRules
	{
		public static int Sellable(int stack, int price, int coinsLeft)
		{
			if (stack > 0 && price > 0 && coinsLeft > 0)
			{
				return Math.Min(stack, coinsLeft / price);
			}
			return 0;
		}
	}
	public enum Refusal
	{
		None,
		NotListed,
		Excluded,
		MetalItem,
		Limit,
		Full,
		Stackable,
		Special,
		NoMetal,
		PortalClosed,
		NothingBack
	}
	public static class Metals
	{
		public const string Default = "Copper, Tin, Bronze, Iron, Silver, BlackMetal, FlametalNew";

		public static HashSet<string> Parse(string text)
		{
			return new HashSet<string>((text ?? "").Split(new char[4] { ',', ';', ' ', '\n' }, StringSplitOptions.RemoveEmptyEntries), StringComparer.Ordinal);
		}

		public static string PortalKey(string metal)
		{
			switch (metal)
			{
			case "Bronze":
			case "Copper":
			case "Tin":
				return "defeated_gdking";
			case "Iron":
				return "defeated_bonemass";
			case "Silver":
				return "defeated_dragon";
			case "BlackMetal":
				return "defeated_goblinking";
			case "FlametalNew":
				return "defeated_fader";
			default:
				return null;
			}
		}
	}
	public static class SalvageRules
	{
		public static int Spent(Func<int, int> amountAtLevel, int quality)
		{
			int num = 0;
			for (int i = 1; i <= Math.Max(1, quality); i++)
			{
				num += amountAtLevel(i);
			}
			return num;
		}

		public static int Yield(int spent, double fraction)
		{
			if (spent > 0 && !double.IsNaN(fraction) && !(fraction <= 0.0))
			{
				return (int)Math.Floor((double)spent * Math.Min(fraction, 1.0) + 1E-06);
			}
			return 0;
		}

		public static string FirstClosed(IDictionary<string, int> yields, Func<string, bool> portalOpen)
		{
			return (from p in yields
				where p.Value > 0 && !portalOpen(p.Key)
				select p.Key).FirstOrDefault();
		}

		public static Refusal Check(bool stackable, bool special, IDictionary<string, int> yields, Func<string, bool> portalOpen)
		{
			if (stackable)
			{
				return Refusal.Stackable;
			}
			if (special)
			{
				return Refusal.Special;
			}
			if (yields.Count == 0)
			{
				return Refusal.NoMetal;
			}
			if (yields.Values.All((int v) => v <= 0))
			{
				return Refusal.NothingBack;
			}
			if (FirstClosed(yields, portalOpen) != null)
			{
				return Refusal.PortalClosed;
			}
			return Refusal.None;
		}
	}
}