Decompiled source of OrderBoard v0.1.1

plugins/OrderBoard.dll

Decompiled a week 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 Microsoft.CodeAnalysis;
using OrderBoard.Core;
using Sansara.Chronicle;
using UnityEngine;

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Sansara.Chronicle
{
	public sealed class ChroniclePlayer
	{
		public long Id { get; }

		public string Name { get; }

		public ChroniclePlayer(long id, string name)
		{
			Id = id;
			Name = name ?? "";
		}
	}
	public sealed class ChronicleEntry
	{
		public double Time;

		public string Date = "";

		public string Kind = "";

		public string Id = "";

		public string Result = "";

		public List<ChroniclePlayer> Players = new List<ChroniclePlayer>();

		public const string Victory = "victory";

		public bool IsVictory
		{
			get
			{
				if (!(Result == "victory"))
				{
					return Result == "found";
				}
				return true;
			}
		}

		public static string Clean(string s)
		{
			return new string((s ?? "").Where((char c) => c != '\t' && c != '\n' && c != '\r' && c != '|' && c != '=').ToArray()).Trim();
		}

		public string ToLine()
		{
			string text = string.Join("|", Players.Select((ChroniclePlayer p) => p.Id.ToString(CultureInfo.InvariantCulture) + "=" + Clean(p.Name)));
			return string.Join("\t", Time.ToString("R", CultureInfo.InvariantCulture), Clean(Date), Clean(Kind), Clean(Id), Clean(Result), text);
		}

		public static ChronicleEntry Parse(string line)
		{
			if (string.IsNullOrWhiteSpace(line) || line.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal))
			{
				return null;
			}
			string[] array = line.TrimEnd(new char[1] { '\r' }).Split(new char[1] { '\t' });
			if (array.Length < 5 || !double.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || array[2].Trim() == "" || array[3].Trim() == "")
			{
				return null;
			}
			ChronicleEntry chronicleEntry = new ChronicleEntry
			{
				Time = result,
				Date = array[1].Trim(),
				Kind = array[2].Trim(),
				Id = array[3].Trim(),
				Result = array[4].Trim()
			};
			if (array.Length > 5)
			{
				string[] array2 = array[5].Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text in array2)
				{
					int num = text.IndexOf('=');
					long.TryParse((num > 0) ? text.Substring(0, num) : "0", NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2);
					chronicleEntry.Players.Add(new ChroniclePlayer(result2, (num >= 0) ? text.Substring(num + 1) : text));
				}
			}
			return chronicleEntry;
		}

		public static List<ChronicleEntry> ParseAll(string text, out int bad)
		{
			List<ChronicleEntry> list = new List<ChronicleEntry>();
			bad = 0;
			string[] array = (text ?? "").Split(new char[1] { '\n' });
			foreach (string text2 in array)
			{
				ChronicleEntry chronicleEntry = Parse(text2);
				if (chronicleEntry != null)
				{
					list.Add(chronicleEntry);
				}
				else if (!string.IsNullOrWhiteSpace(text2) && !text2.TrimStart(Array.Empty<char>()).StartsWith("#", StringComparison.Ordinal))
				{
					bad++;
				}
			}
			return list;
		}
	}
	public static class ChronicleFile
	{
		public static string PathFor(string configDir, string world)
		{
			string text = string.Concat((world ?? "world").Split(Path.GetInvalidFileNameChars()));
			return Path.Combine(configDir, "sansara.Chronicle", text + ".tsv");
		}

		public static void Append(string path, ChronicleEntry entry)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			File.AppendAllText(path, entry.ToLine() + "\n", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		}

		public static List<ChronicleEntry> Read(string path, out int bad)
		{
			bad = 0;
			if (!File.Exists(path))
			{
				return new List<ChronicleEntry>();
			}
			return ChronicleEntry.ParseAll(File.ReadAllText(path), out bad);
		}
	}
	public sealed class FirstVictory
	{
		public string Id;

		public string Date;

		public List<string> Names = new List<string>();

		public int Count;
	}
	public sealed class Leader
	{
		public string Name;

		public int Count;
	}
	public static class ChronicleStats
	{
		public static Dictionary<string, FirstVictory> Firsts(IEnumerable<ChronicleEntry> all, string kind)
		{
			Dictionary<string, FirstVictory> dictionary = new Dictionary<string, FirstVictory>();
			foreach (ChronicleEntry item in from e in all
				where e.Kind == kind && e.IsVictory
				orderby e.Time
				select e)
			{
				if (!dictionary.TryGetValue(item.Id, out var value))
				{
					value = new FirstVictory
					{
						Id = item.Id,
						Date = item.Date,
						Names = (from p in item.Players
							select p.Name into n
							where n != ""
							select n).Distinct().ToList()
					};
					dictionary[item.Id] = value;
				}
				value.Count++;
			}
			return dictionary;
		}

		public static List<Leader> Leaders(IEnumerable<ChronicleEntry> all, string kind)
		{
			Dictionary<long, int> dictionary = new Dictionary<long, int>();
			Dictionary<long, string> names = new Dictionary<long, string>();
			foreach (ChronicleEntry item in from e in all
				where e.Kind == kind && e.IsVictory
				orderby e.Time
				select e)
			{
				foreach (ChroniclePlayer item2 in from p in item.Players
					group p by (p.Id == 0L) ? p.Name : p.Id.ToString() into g
					select g.First())
				{
					long key = ((item2.Id != 0L) ? item2.Id : item2.Name.GetHashCode());
					dictionary[key] = ((!dictionary.TryGetValue(key, out var value)) ? 1 : (value + 1));
					if (item2.Name != "")
					{
						names[key] = item2.Name;
					}
				}
			}
			string value2;
			return (from kv in dictionary
				select new Leader
				{
					Name = (names.TryGetValue(kv.Key, out value2) ? value2 : "?"),
					Count = kv.Value
				} into l
				orderby l.Count descending
				select l).ThenBy<Leader, string>((Leader l) => l.Name, StringComparer.Ordinal).ToList();
		}

		public static string Names(IList<string> names, int max)
		{
			if (names.Count == 0)
			{
				return "";
			}
			for (int num = names.Count; num >= 1; num--)
			{
				string text = string.Join(", ", names.Take(num)) + ((num < names.Count) ? $" +{names.Count - num}" : "");
				if (text.Length <= max)
				{
					return text;
				}
			}
			string text2 = names[0];
			if (text2.Length <= max)
			{
				return text2;
			}
			return text2.Substring(0, Math.Max(1, max - 1)) + "…";
		}
	}
}
namespace OrderBoard
{
	internal static class Board
	{
		private const string RoleKey = "sansara.PortalHall.role";

		private static readonly Random Rng = new Random();

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

		private static OrderState _state = new OrderState();

		private static string _path;

		private static MethodInfo _enqueue;

		private static bool _apiChecked;

		private static bool _stallMissingReported;

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

		public static void Load()
		{
			string text = Path.Combine(Paths.ConfigPath, "sansara.OrderBoard");
			Directory.CreateDirectory(text);
			string text2 = string.Concat(ZNet.instance.GetWorldName().Split(Path.GetInvalidFileNameChars()));
			_path = Path.Combine(text, text2 + ".orders");
			_state = (File.Exists(_path) ? OrderState.Parse(File.ReadAllText(_path)) : new OrderState());
			int num = OrderPlanner.Refresh(_state.Orders, Plugin.Templates);
			if (num > 0)
			{
				Save();
				Plugin.Log.LogInfo((object)$"Доска заказов: количество в открытых заказах обновлено по каталогу: {num}.");
			}
			Plugin.Log.LogInfo((object)$"Доска заказов: цикл {_state.Cycle}, заказов {_state.Orders.Count}, выполнено {_state.Orders.Count((Order o) => o.Done)}.");
		}

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

		public static void Tick()
		{
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			double timeSeconds = ZNet.instance.GetTimeSeconds();
			double num = Math.Max(0.01, Plugin.CycleDays.Value) * 86400.0;
			if (_state.Cycle < 0 || OrderPlanner.CycleAt(timeSeconds, _state.CycleStart, num) >= 1 || _state.CycleStart > timeSeconds)
			{
				NewCycle(_state.Cycle + 1, timeSeconds);
			}
			Dictionary<string, ZDO> dictionary = Stall();
			if (dictionary.Count == 0)
			{
				if (!_stallMissingReported)
				{
					_stallMissingReported = true;
					Plugin.Log.LogInfo((object)"Доска заказов: торгового ряда Чертога славы (Portal Hall 0.2.0) не найдено — жду.");
				}
				return;
			}
			_stallMissingReported = false;
			DateTime dateTime = DateTime.UtcNow.AddSeconds(_state.CycleStart + num - timeSeconds);
			for (int i = 0; i < 3; i++)
			{
				dictionary.TryGetValue("order.chest." + i, out var value);
				dictionary.TryGetValue("order.sign." + i, out var value2);
				Order order = ((i < _state.Orders.Count) ? _state.Orders[i] : null);
				if (order != null && !order.Done && value != null)
				{
					TryDeliver(i, order, value);
				}
				if (value2 != null)
				{
					string itemName = ((order != null) ? ItemNames.Of(order.Item) : "");
					string text = ((order == null) ? "Заказов нет" : (order.Done ? OrderTexts.Done(order, itemName, order.DoneBy) : OrderTexts.Open(order, itemName, dateTime.ToString("dd.MM", CultureInfo.InvariantCulture))));
					text = "<color=white>" + text + "</color>";
					if (value2.GetString(ZDOVars.s_text, "") != text)
					{
						value2.SetOwner(ZDOMan.GetSessionID());
						value2.Set(ZDOVars.s_text, text);
						ZDOMan.instance.ForceSendZDO(value2.m_uid);
					}
				}
			}
		}

		private static void NewCycle(int cycle, double now)
		{
			HashSet<string> keys = new HashSet<string>(ZoneSystem.instance.GetGlobalKeys());
			int num = OrderPlanner.OpenTier(keys);
			List<Order> list = OrderPlanner.Plan(Plugin.Templates, num, (OrderTemplate t) => Deliverable(t, keys), Rng);
			int num2 = _state.Orders.Count((Order o) => !o.Done);
			_state = new OrderState
			{
				Cycle = cycle,
				CycleStart = now,
				Orders = list
			};
			Save();
			Plugin.Log.LogInfo((object)($"Доска заказов: цикл {cycle}, открытый уровень {num}, заказы: " + string.Join(", ", list.Select((Order o) => $"{o.Amount} {o.Item} (уровень {o.Tier})")) + ((num2 > 0) ? $"; сгорело невыполненных {num2}." : ".")));
		}

		private static bool Deliverable(OrderTemplate t, ICollection<string> keys)
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(t.Item);
			if (((itemPrefab == null) ? ((bool?)null) : itemPrefab.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_teleportable) != true)
			{
				return keys.Contains(OrderPlanner.BiomeBossKeys[t.Tier - 1]);
			}
			return true;
		}

		private static Dictionary<string, ZDO> Stall()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			Vector3 fameAnchor = FameAnchor;
			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("order.", StringComparison.Ordinal) && !dictionary.ContainsKey(text))
				{
					dictionary[text] = item;
				}
			}
			return dictionary;
		}

		private static void TryDeliver(int slot, Order order, ZDO chest)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			//IL_00ac: 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)
			{
				string text = ItemNames.Token(order.Item);
				Inventory val = new Inventory("order", (Sprite)null, 8, 20);
				val.Load(new ZPackage(byteArray));
				if (val.CountItems(text, -1, true) >= order.Amount && Deliverer(chest, out var playerId, out var name))
				{
					val.RemoveItem(text, order.Amount, -1, true);
					ZPackage val2 = new ZPackage();
					val.Save(val2);
					chest.SetOwner(ZDOMan.GetSessionID());
					chest.Set(ZDOVars.s_items, val2.GetArray());
					ZDOMan.instance.ForceSendZDO(chest.m_uid);
					order.Done = true;
					order.DoneBy = name;
					Save();
					bool flag = Reward(slot, order, playerId);
					Message(playerId, $"Заказ выполнен: {order.Amount} {ItemNames.Of(order.Item)}." + (flag ? " Награда ждёт." : ""));
					Chronicle(order, playerId, name);
					Plugin.Log.LogInfo((object)string.Format("Доска заказов: заказ {0} ({1} {2}) выполнил {3} ({4}){5}.", slot, order.Amount, order.Item, name, playerId, flag ? ", награда поставлена" : ""));
				}
			}
		}

		private static bool Deliverer(ZDO chest, out long playerId, out string name)
		{
			//IL_0041: 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)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			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)
				{
					if (peer.m_uid == chest.GetOwner())
					{
						playerId = val.GetLong(ZDOVars.s_playerID, 0L);
						name = peer.m_playerName;
						return playerId != 0;
					}
					float num2 = Vector3.Distance(val.GetPosition(), chest.GetPosition());
					if (num2 <= num)
					{
						num = num2;
						playerId = val.GetLong(ZDOVars.s_playerID, 0L);
						name = peer.m_playerName;
					}
				}
			}
			return playerId != 0;
		}

		private static bool Reward(int slot, Order order, long playerId)
		{
			string[] array = Plugin.RewardPool[order.Tier].Value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				Plugin.Log.LogWarning((object)$"Доска заказов: награда не выдана — RewardPool уровня {order.Tier} пуст.");
				return false;
			}
			if (!_apiChecked)
			{
				_apiChecked = true;
				Type type = AccessTools.TypeByName("EventRewards.ExternalRewards");
				_enqueue = ((type == null) ? null : AccessTools.Method(type, "Enqueue", new Type[3]
				{
					typeof(long),
					typeof(string),
					typeof(string)
				}, (Type[])null));
			}
			if (_enqueue == null)
			{
				Plugin.Log.LogError((object)"Доска заказов: награда не выдана — нет Event Rewards (ExternalRewards.Enqueue).");
				return false;
			}
			bool result = false;
			for (int i = 1; i <= Math.Max(1, Plugin.RewardCount[order.Tier].Value); i++)
			{
				string text = array[Rng.Next(array.Length)];
				string text2 = $"order:{_state.Cycle}:{slot}" + ((i > 1) ? $"/{i}" : "");
				try
				{
					string text3 = (string)_enqueue.Invoke(null, new object[3] { playerId, text, text2 });
					if (text3 != null)
					{
						Plugin.Log.LogError((object)("Доска заказов: награда " + text + " не выдана: " + text3 + "."));
					}
					else
					{
						result = true;
					}
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Доска заказов: ошибка выдачи награды: {arg}");
				}
			}
			return result;
		}

		private 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 });
				}
			}
		}

		private static void Chronicle(Order order, long playerId, string name)
		{
			try
			{
				ChronicleFile.Append(ChronicleFile.PathFor(Paths.ConfigPath, ZNet.instance.GetWorldName()), new ChronicleEntry
				{
					Time = ZNet.instance.GetTimeSeconds(),
					Date = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
					Kind = "order",
					Id = order.Item,
					Result = "victory",
					Players = new List<ChroniclePlayer>
					{
						new ChroniclePlayer(playerId, name)
					}
				});
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Доска заказов: ошибка записи в летопись: " + ex));
			}
		}
	}
	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.OrderBoard", "Order Board", "0.1.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.OrderBoard";

		public const string Name = "Order Board";

		public const string Version = "0.1.1";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> CycleDays;

		internal static ConfigEntry<string> Catalog;

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

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

		internal static List<OrderTemplate> Templates = new List<OrderTemplate>();

		private float _nextTick;

		private bool _started;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Доска заказов в Чертоге славы.");
			CycleDays = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CycleDays", 3f, "Через сколько дней (реальных) доска обновляется; невыполненные заказы сгорают.");
			Catalog = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Catalog", "Wood:250:1, Flint:100:1, LeatherScraps:100:1, DeerHide:60:1, Copper:100:2, Tin:60:2, Bronze:60:2, RoundLog:200:2, TrollHide:30:2, Iron:100:3, Guck:50:3, ElderBark:200:3, Bloodbag:50:3, Silver:100:4, Obsidian:150:4, WolfPelt:60:4, FreezeGland:50:4, BlackMetal:100:5, LinenThread:80:5, Barley:200:5, Needle:80:5, Sap:80:6, Eitr:40:6, Carapace:80:6, RoyalJelly:40:6, FlametalNew:60:7, CharredBone:120:7, AskHide:50:7, Grausten:200:7", "Предмет:количество:уровень (1 Луга … 8 Дальний Север) через запятую — из чего выбираются заказы.");
			for (int i = 1; i <= 8; i++)
			{
				RewardPool[i] = ((BaseUnityPlugin)this).Config.Bind<string>($"Tier{i}", "RewardPool", "", "Награды Event Rewards за заказ этого уровня через запятую; пусто — без награды.");
				RewardCount[i] = ((BaseUnityPlugin)this).Config.Bind<int>($"Tier{i}", "RewardCount", 1, "Наград за один заказ.");
			}
			Log.LogInfo((object)"Order Board 0.1.1 загружен.");
		}

		private void Update()
		{
			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)
				{
					_started = true;
					List<string> list = new List<string>();
					Templates = OrderCatalog.Parse(Catalog.Value, list);
					list.ForEach(delegate(string e)
					{
						Log.LogWarning((object)("Доска заказов: Catalog — " + e));
					});
					Templates.RemoveAll(delegate(OrderTemplate t)
					{
						bool num = (Object)(object)ObjectDB.instance.GetItemPrefab(t.Item) == (Object)null;
						if (num)
						{
							Log.LogWarning((object)("Доска заказов: предмета " + t.Item + " нет в игре — строка пропущена."));
						}
						return num;
					});
					ItemNames.Load();
					Board.Load();
					Log.LogInfo((object)$"Доска заказов: в каталоге {Templates.Count} предметов.");
				}
				Board.Tick();
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Доска заказов: ошибка: " + ex));
			}
		}
	}
}
namespace OrderBoard.Core
{
	public sealed class OrderTemplate
	{
		public string Item { get; }

		public int Amount { get; }

		public int Tier { get; }

		public OrderTemplate(string item, int amount, int tier)
		{
			Item = item;
			Amount = amount;
			Tier = tier;
		}
	}
	public static class OrderCatalog
	{
		public const int MaxTier = 8;

		public const string Default = "Wood:250:1, Flint:100:1, LeatherScraps:100:1, DeerHide:60:1, Copper:100:2, Tin:60:2, Bronze:60:2, RoundLog:200:2, TrollHide:30:2, Iron:100:3, Guck:50:3, ElderBark:200:3, Bloodbag:50:3, Silver:100:4, Obsidian:150:4, WolfPelt:60:4, FreezeGland:50:4, BlackMetal:100:5, LinenThread:80:5, Barley:200:5, Needle:80:5, Sap:80:6, Eitr:40:6, Carapace:80:6, RoyalJelly:40:6, FlametalNew:60:7, CharredBone:120:7, AskHide:50:7, Grausten:200:7";

		public static List<OrderTemplate> Parse(string text, List<string> errors)
		{
			List<OrderTemplate> list = new List<OrderTemplate>();
			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)
				{
					string[] array2 = text2.Split(new char[1] { ':' });
					if (array2.Length != 3 || array2[0].Trim() == "" || !int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result < 1 || !int.TryParse(array2[2].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 < 1 || result2 > 8)
					{
						errors.Add($"строка «{text2}» пропущена: нужно Предмет:количество:уровень 1–{8}");
					}
					else if (!hashSet.Add(array2[0].Trim()))
					{
						errors.Add("предмет " + array2[0].Trim() + " указан повторно, оставлена первая строка");
					}
					else
					{
						list.Add(new OrderTemplate(array2[0].Trim(), result, result2));
					}
				}
			}
			return list;
		}
	}
	public sealed class Order
	{
		public string Item = "";

		public int Amount;

		public int Tier;

		public bool Done;

		public string DoneBy = "";
	}
	public sealed class OrderState
	{
		public int Cycle = -1;

		public double CycleStart;

		public List<Order> Orders = new List<Order>();

		public string Serialize()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("cycle\t").Append(Cycle.ToString(CultureInfo.InvariantCulture)).Append('\t')
				.Append(CycleStart.ToString("R", CultureInfo.InvariantCulture))
				.Append('\n');
			foreach (Order order in Orders)
			{
				stringBuilder.Append(string.Join("\t", order.Item, order.Amount.ToString(CultureInfo.InvariantCulture), order.Tier.ToString(CultureInfo.InvariantCulture), order.Done ? "1" : "0", (order.DoneBy ?? "").Replace("\t", " ").Replace("\n", " "))).Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static OrderState Parse(string text)
		{
			OrderState orderState = new OrderState();
			string[] array = (text ?? "").Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].TrimEnd(new char[1] { '\r' }).Split(new char[1] { '\t' });
				int result;
				int result2;
				if (array2.Length >= 3 && array2[0] == "cycle")
				{
					int.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out orderState.Cycle);
					double.TryParse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture, out orderState.CycleStart);
				}
				else if (array2.Length >= 4 && array2[0] != "" && int.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out result) && int.TryParse(array2[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out result2))
				{
					orderState.Orders.Add(new Order
					{
						Item = array2[0],
						Amount = result,
						Tier = result2,
						Done = (array2[3] == "1"),
						DoneBy = ((array2.Length > 4) ? array2[4] : "")
					});
				}
			}
			return orderState;
		}
	}
	public static class OrderPlanner
	{
		public static readonly string[] OpeningKeys = new string[8] { null, "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader" };

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

		public static int Refresh(List<Order> orders, List<OrderTemplate> catalog)
		{
			int num = 0;
			foreach (Order o in orders)
			{
				OrderTemplate orderTemplate = catalog.FirstOrDefault((OrderTemplate c) => string.Equals(c.Item, o.Item, StringComparison.OrdinalIgnoreCase));
				if (!o.Done && orderTemplate != null && orderTemplate.Amount != o.Amount)
				{
					o.Amount = orderTemplate.Amount;
					num++;
				}
			}
			return num;
		}

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

		public static int CycleAt(double now, double firstStart, double period)
		{
			if (!(period <= 0.0))
			{
				return (int)Math.Floor((now - firstStart) / period);
			}
			return 0;
		}

		public static List<Order> Plan(IList<OrderTemplate> catalog, int openTier, Func<OrderTemplate, bool> deliverable, Random rng)
		{
			List<Order> list = new List<Order>();
			HashSet<string> used = new HashSet<string>();
			int num = Math.Max(1, (int)Math.Round((double)openTier / 3.0));
			int val = Math.Max(1, (int)Math.Round((double)(openTier * 2) / 3.0));
			int num2 = Math.Min(num + 1, openTier);
			int[][] array = new int[3][]
			{
				new int[2] { 1, num },
				new int[2]
				{
					num2,
					Math.Max(val, num2)
				},
				new int[2] { openTier, openTier }
			};
			foreach (int[] array2 in array)
			{
				OrderTemplate orderTemplate = null;
				for (int top = array2[1]; top >= 1; top--)
				{
					if (orderTemplate != null)
					{
						break;
					}
					int bottom = Math.Min(array2[0], top);
					List<OrderTemplate> list2 = catalog.Where((OrderTemplate t) => t.Tier >= bottom && t.Tier <= top && !used.Contains(t.Item) && deliverable(t)).ToList();
					if (list2.Count > 0)
					{
						orderTemplate = list2[rng.Next(list2.Count)];
					}
				}
				if (orderTemplate != null)
				{
					used.Add(orderTemplate.Item);
					list.Add(new Order
					{
						Item = orderTemplate.Item,
						Amount = orderTemplate.Amount,
						Tier = orderTemplate.Tier
					});
				}
			}
			return list;
		}
	}
	public static class OrderTexts
	{
		public const int SignLimit = 50;

		public const string None = "Заказов нет";

		public static string Open(Order o, string itemName, string until)
		{
			return Fit("Заказ: " + o.Amount.ToString(CultureInfo.InvariantCulture) + " " + itemName + " · до " + until);
		}

		public static string Done(Order o, string itemName, string by)
		{
			return Fit("Выполнено: " + by + " · " + itemName);
		}

		public static string Fit(string s)
		{
			if (s.Length > 50)
			{
				return s.Substring(0, 49) + "…";
			}
			return s;
		}
	}
}