Decompiled source of EventRewards v0.5.14

plugins/EventRewards.dll

Decompiled 16 hours 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.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using EventRewards.Core;
using EventRewards.Patches;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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("EventRewards")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.14.0")]
[assembly: AssemblyInformationalVersion("0.5.14+05004fdaa4ffbb029f3dec5e3bf805d8eabc20a4")]
[assembly: AssemblyProduct("EventRewards")]
[assembly: AssemblyTitle("EventRewards")]
[assembly: AssemblyVersion("0.5.14.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[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 EventRewards
{
	internal static class DeliveryServer
	{
		private sealed class Presence
		{
			public ZDOID Character;

			public float Since;

			public Vector3 LastPos;

			public bool Still;

			public long PeerUid;

			public bool Local;
		}

		private sealed class Retry
		{
			public float Next;

			public float Delay;
		}

		internal struct OnlineCharacter
		{
			public long PlayerId;

			public long PeerUid;

			public bool Local;

			public int Code;

			public string Name;
		}

		private sealed class Parsed<T>
		{
			public bool Ok;

			public T Value;

			public string Error;
		}

		private const float TeleportJumpMeters = 50f;

		private const float StillMeters = 2f;

		private const float RosterSaveSeconds = 300f;

		private const float SpawnHeight = 1f;

		private const float RetryFirstSeconds = 10f;

		private const float RetryMaxSeconds = 300f;

		private const int RecoveredQueueReserve = 1000;

		private const int ZAnimBoolOffset = 438569;

		private static string _loadError;

		private static PlayerRoster _roster;

		private static DeliveryQueue _queue;

		private static float _timer;

		private static float _rosterSaveTimer;

		private static bool _rosterDirty;

		private static bool _queueDirty;

		private static int _inWaterKey;

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

		private static readonly HashSet<long> Online = new HashSet<long>();

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

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

		private static readonly Dictionary<int, Retry> Retries = new Dictionary<int, Retry>();

		private static readonly RateLimiter IdentityWarnings = new RateLimiter(1, 300.0);

		public static string RosterPath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.players.json");

		public static string QueuePath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.queue.json");

		public static bool Running { get; private set; }

		internal static PlayerRoster Roster => _roster;

		public static void Init()
		{
			Shutdown();
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!LoadRoster(out var error) || !LoadQueue(out error))
				{
					_loadError = error;
					Plugin.Log.LogError((object)("Event Rewards: " + error + "; список игроков и очередь доставки отключены до исправления файла"));
				}
				else
				{
					Running = true;
					Plugin.Log.LogInfo((object)($"Event Rewards: игроков в списке {_roster.Count}, в очереди доставки ожидают {_queue.Pending(null).Count}" + (PluginConfig.DeliveryEnabled.Value ? "" : " (доставка выключена в cfg)")));
				}
			}
		}

		public static void Shutdown()
		{
			if (Running)
			{
				if (_rosterDirty)
				{
					SaveRoster();
				}
				if (_queueDirty)
				{
					SaveQueue();
				}
			}
			Running = false;
			_loadError = null;
			_roster = null;
			_queue = null;
			_timer = 0f;
			_rosterSaveTimer = 0f;
			_rosterDirty = false;
			_queueDirty = false;
			Presences.Clear();
			Online.Clear();
			WarnedEntries.Clear();
			HuntWarned.Clear();
			Retries.Clear();
		}

		public static void Tick(float deltaTime)
		{
			_timer += deltaTime;
			_rosterSaveTimer += deltaTime;
			if (_timer < PluginConfig.DeliveryCheckIntervalSeconds.Value)
			{
				return;
			}
			_timer = 0f;
			try
			{
				Check();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Event Rewards: ошибка проверки игроков и доставки: " + ex));
			}
		}

		private static void Check()
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null)
			{
				return;
			}
			DateTime utcNow = DateTime.UtcNow;
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			Online.Clear();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				ZDO val = CharacterOf(peer);
				if (val != null)
				{
					Observe(val, peer.m_characterID, peer.m_playerID, peer.m_playerName, HostOf(peer), peer.m_uid, local: false, utcNow, realtimeSinceStartup);
				}
			}
			if (!ZNet.instance.IsDedicated() && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(((Character)Player.m_localPlayer).GetZDOID());
				if (zDO != null)
				{
					ZDOID uid = zDO.m_uid;
					Game instance = Game.instance;
					long? obj;
					if (instance == null)
					{
						obj = null;
					}
					else
					{
						PlayerProfile playerProfile = instance.GetPlayerProfile();
						obj = ((playerProfile != null) ? new long?(playerProfile.GetPlayerID()) : ((long?)null));
					}
					long? num = obj;
					long valueOrDefault = num.GetValueOrDefault();
					Game instance2 = Game.instance;
					object fallbackName;
					if (instance2 == null)
					{
						fallbackName = null;
					}
					else
					{
						PlayerProfile playerProfile2 = instance2.GetPlayerProfile();
						fallbackName = ((playerProfile2 != null) ? playerProfile2.GetName() : null);
					}
					Observe(zDO, uid, valueOrDefault, (string)fallbackName, "local", ZDOMan.GetSessionID(), local: true, utcNow, realtimeSinceStartup);
				}
			}
			foreach (long item in Presences.Keys.Where((long id) => !Online.Contains(id)).ToList())
			{
				Presences.Remove(item);
			}
			RevocationServer.OnCheck(OnlineCharacters());
			if (_queueDirty)
			{
				SaveQueue();
			}
			if (_rosterDirty && _rosterSaveTimer >= 300f)
			{
				SaveRoster();
			}
			if (!PluginConfig.DeliveryEnabled.Value || _queueDirty)
			{
				return;
			}
			foreach (long item2 in Online.ToList())
			{
				if (_queue.HasPending(item2))
				{
					TryDeliver(item2, utcNow, realtimeSinceStartup);
				}
			}
		}

		private static ZDO CharacterOf(ZNetPeer peer)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (peer == null || !peer.IsReady() || ((ZDOID)(ref peer.m_characterID)).IsNone() || ZDOMan.instance == null)
			{
				return null;
			}
			if (((ZDOID)(ref peer.m_characterID)).UserID != peer.m_uid)
			{
				WarnIdentity(peer.m_uid, $"Event Rewards: {RewardLoss.Sanitize(peer.m_playerName)} ({peer.m_uid}) сообщил чужой ZDOID персонажа {peer.m_characterID}, пропущен");
				return null;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
			if (zDO == null)
			{
				return null;
			}
			if (zDO.GetOwner() != peer.m_uid)
			{
				return null;
			}
			return zDO;
		}

		private static void Observe(ZDO zdo, ZDOID character, long fallbackId, string fallbackName, string host, long uid, bool local, DateTime now, float t)
		{
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			long num = zdo.GetLong(ZDOVars.s_playerID, 0L);
			if (num == 0L)
			{
				num = fallbackId;
			}
			if (num == 0L)
			{
				return;
			}
			string text = zdo.GetString(ZDOVars.s_playerName, "");
			if (string.IsNullOrWhiteSpace(text))
			{
				text = fallbackName;
			}
			switch (_roster.Touch(num, text, host, now))
			{
			case RosterChange.HostMismatch:
			{
				_roster.TryGetById(num, out var entry2);
				WarnIdentity(num, $"Event Rewards: персонаж {RewardLoss.Sanitize(text)} ({uid}, платформа «{host}») выдаёт себя за " + PlayerCode.Format(entry2.Code) + " " + entry2.Name + ", привязанного к другой платформе; не учитывается (если это тот же игрок — eventreward players unbind " + PlayerCode.Format(entry2.Code) + ")");
				return;
			}
			case RosterChange.New:
			{
				_roster.TryGetById(num, out var entry);
				Plugin.Log.LogInfo((object)("Event Rewards: новый игрок " + PlayerCode.Format(entry.Code) + " " + entry.Name));
				SaveRoster();
				break;
			}
			case RosterChange.Updated:
				SaveRoster();
				break;
			case RosterChange.Seen:
				_rosterDirty = true;
				break;
			}
			Online.Add(num);
			Vector3 position = zdo.GetPosition();
			if (!Presences.TryGetValue(num, out var value) || value.Character != character)
			{
				value = new Presence
				{
					Character = character,
					Since = t,
					LastPos = position,
					Still = false
				};
				Presences[num] = value;
			}
			else
			{
				Vector3 val = position - value.LastPos;
				float magnitude = ((Vector3)(ref val)).magnitude;
				if (magnitude > 50f)
				{
					value.Since = t;
				}
				value.Still = magnitude < 2f;
			}
			value.LastPos = position;
			value.PeerUid = uid;
			value.Local = local;
		}

		internal static bool IsOnline(long playerId)
		{
			if (Running)
			{
				return Online.Contains(playerId);
			}
			return false;
		}

		internal static List<OnlineCharacter> OnlineCharacters()
		{
			List<OnlineCharacter> list = new List<OnlineCharacter>();
			if (!Running)
			{
				return list;
			}
			foreach (KeyValuePair<long, Presence> presence in Presences)
			{
				if (Online.Contains(presence.Key))
				{
					_roster.TryGetById(presence.Key, out var entry);
					list.Add(new OnlineCharacter
					{
						PlayerId = presence.Key,
						PeerUid = presence.Value.PeerUid,
						Local = presence.Value.Local,
						Code = (entry?.Code ?? 0),
						Name = (entry?.Name ?? "?")
					});
				}
			}
			return list;
		}

		internal static bool TryGetOnlineByPeer(long peerUid, out OnlineCharacter character)
		{
			foreach (OnlineCharacter item in OnlineCharacters())
			{
				if (item.PeerUid == peerUid)
				{
					character = item;
					return true;
				}
			}
			character = default(OnlineCharacter);
			return false;
		}

		internal static List<int> PendingWith(ICollection<string> rewardIds, long playerId)
		{
			List<int> list = new List<int>();
			if (!Running || rewardIds == null)
			{
				return list;
			}
			foreach (DeliveryEntry item in _queue.Pending(null))
			{
				if (rewardIds.Contains(item.RewardId) && (playerId == 0L || item.PlayerId == playerId))
				{
					list.Add(item.N);
				}
			}
			return list;
		}

		public static string HostOf(ZNetPeer peer)
		{
			try
			{
				object obj;
				if (peer == null)
				{
					obj = null;
				}
				else
				{
					ISocket socket = peer.m_socket;
					obj = ((socket != null) ? socket.GetHostName() : null);
				}
				if (obj == null)
				{
					obj = "";
				}
				return (string)obj;
			}
			catch (Exception)
			{
				return "";
			}
		}

		public static long PlayerIdOf(ZNetPeer peer)
		{
			ZDO val = CharacterOf(peer);
			long num = ((val != null) ? val.GetLong(ZDOVars.s_playerID, 0L) : 0);
			if (num == 0L)
			{
				if (val == null)
				{
					return 0L;
				}
				return peer.m_playerID;
			}
			return num;
		}

		private static void WarnIdentity(long key, string text)
		{
			if (IdentityWarnings.TryAcquire(key, Time.realtimeSinceStartup))
			{
				Plugin.Log.LogWarning((object)text);
			}
		}

		private static bool InWater(ZDO zdo)
		{
			try
			{
				if (_inWaterKey == 0)
				{
					_inWaterKey = 438569 + ZSyncAnimation.GetHash("inWater");
				}
				return zdo.GetInt(_inWaterKey, 0) != 0;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static void TryDeliver(long playerId, DateTime now, float t)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			if (!Presences.TryGetValue(playerId, out var value) || t - value.Since < PluginConfig.DeliverySettleSeconds.Value || !value.Still)
			{
				return;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(value.Character);
			if (zDO == null || zDO.GetBool(ZDOVars.s_dead, false) || InWater(zDO))
			{
				return;
			}
			foreach (DeliveryEntry item in _queue.PendingFor(playerId))
			{
				if (!ServerCatalog.Current.TryGet(item.RewardId, out var definition))
				{
					if (WarnedEntries.Add(item.N))
					{
						Plugin.Log.LogWarning((object)($"Event Rewards: #{item.N} {item.RewardId} — такой награды нет в наборе сервера, запись ждёт " + $"(исправьте набор и eventreward reload или eventreward cancel {item.N})"));
					}
				}
				else
				{
					if (Retries.TryGetValue(item.N, out var value2) && t < value2.Next)
					{
						continue;
					}
					string error;
					ZDO val = SpawnReward(definition, playerId, zDO.GetPosition() + Vector3.up * 1f, out error);
					if (val != null)
					{
						Retries.Remove(item.N);
						_queue.MarkDelivered(item.N, now);
						RosterEntry entry;
						string text = (_roster.TryGetById(playerId, out entry) ? entry.Name : "?");
						if (!SaveQueue())
						{
							ZDOMan.instance.DestroyZDO(val);
							_queue.Unmark(item.N);
							Plugin.Log.LogError((object)($"Event Rewards: #{item.N} {item.RewardId} → {PlayerCode.Format(item.Code)} {text}: файл очереди " + "не записан — предмет убран, запись снова ожидает"));
							break;
						}
						Plugin.Log.LogInfo((object)DeliveryText.DeliveredLog(item, text));
						string text2 = DeliveryText.FormatMessage(PluginConfig.DeliveryMessage.Value, definition.Name);
						if (text2.Length <= 0)
						{
							break;
						}
						if (value.Local)
						{
							MessageHud instance = MessageHud.instance;
							if (instance != null)
							{
								instance.ShowMessage((MessageType)2, text2, 0, (Sprite)null, false, true);
							}
							break;
						}
						ZRoutedRpc instance2 = ZRoutedRpc.instance;
						if (instance2 != null)
						{
							instance2.InvokeRoutedRPC(value.PeerUid, "ShowMessage", new object[2] { 2, text2 });
						}
						break;
					}
					float num = ((value2 == null) ? 10f : Math.Min(value2.Delay * 2f, 300f));
					Retries[item.N] = new Retry
					{
						Next = t + num,
						Delay = num
					};
					if (WarnedEntries.Add(item.N))
					{
						Plugin.Log.LogWarning((object)$"Event Rewards: #{item.N} {item.RewardId} не выложена: {error}; повтор с паузой до {300f:0} с");
					}
				}
			}
		}

		private static ZDO SpawnReward(RewardDefinition def, long recipient, Vector3 position, out string error)
		{
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			error = null;
			GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(def.BasePrefab) : null);
			ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
			ZNetView val3 = (((Object)(object)val != (Object)null) ? val.GetComponent<ZNetView>() : null);
			if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null)
			{
				error = "предмет " + def.BasePrefab + " не найден в ObjectDB";
				return null;
			}
			int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)val).name);
			if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZNetScene.instance.GetPrefab(stableHashCode) == (Object)null)
			{
				error = "префаб " + ((Object)val).name + " не зарегистрирован в ZNetScene";
				return null;
			}
			ItemData val4;
			try
			{
				val4 = val2.m_itemData.Clone();
				val4.m_dropPrefab = val;
				val4.m_stack = 1;
				val4.m_quality = def.Quality;
				val4.m_variant = def.Variant;
				val4.m_durability = BonusCalculator.ApplyMaxDurability(val4.GetMaxDurability(), BonusCalculator.EffectiveDurabilityPercent(def));
				val4.m_crafterID = 0L;
				val4.m_crafterName = "";
				val4.m_worldLevel = (byte)Game.m_worldLevel;
				val4.m_pickedUp = false;
				val4.m_cheated = false;
				val4.m_equipped = false;
				val4.m_customData = new Dictionary<string, string> { ["sansara.EventRewards.id"] = def.Id };
				RewardTag.SetOwner(val4.m_customData, recipient);
			}
			catch (Exception ex)
			{
				error = "данные предмета не собраны: " + ex.Message;
				return null;
			}
			ZDO val5 = null;
			try
			{
				val5 = ZDOMan.instance.CreateNewZDO(position, stableHashCode);
				val5.Persistent = val3.m_persistent;
				val5.Type = val3.m_type;
				val5.Distant = val3.m_distant;
				val5.SetPrefab(stableHashCode);
				val5.SetRotation(Quaternion.Euler(0f, Random.Range(0f, 360f), 0f));
				val5.Set(ZDOVars.s_spawnTime, ZNet.instance.GetTime().Ticks);
				ItemDrop.SaveToZDO(val4, val5, -1);
				return val5;
			}
			catch (Exception ex2)
			{
				if (val5 != null)
				{
					ZDOMan.instance.DestroyZDO(val5);
				}
				error = "ZDO предмета не создан: " + ex2.Message;
				return null;
			}
		}

		public static bool EnqueueHunt(HuntState state, HuntSettings settings)
		{
			if (state == null || settings == null || !state.Completed || !settings.AutoDeliver)
			{
				return false;
			}
			if (!Running)
			{
				if (HuntWarned.Add("off:" + state.Id))
				{
					Plugin.Log.LogError((object)("Event Hunt: автовыдача " + settings.RewardId + " невозможна — очередь доставки отключена (" + (_loadError ?? "не сервер") + ")"));
				}
				return false;
			}
			if (!ServerCatalog.Current.TryGet(settings.RewardId, out var _))
			{
				if (HuntWarned.Add("reward:" + state.Id + ":" + settings.RewardId))
				{
					Plugin.Log.LogError((object)("Event Hunt: награды " + settings.RewardId + " нет в наборе сервера — участники не поставлены в очередь (исправьте набор, eventreward reload; очередь пополнится при следующем изменении или перезапуске)"));
				}
				return false;
			}
			HuntEnqueueResult huntEnqueueResult = HuntDelivery.Enqueue(state, settings.RewardId, _queue, _roster, DateTime.UtcNow);
			if (huntEnqueueResult.RosterChanged)
			{
				SaveRoster();
			}
			if (huntEnqueueResult.Added.Count > 0)
			{
				if (!SaveQueue())
				{
					Plugin.Log.LogError((object)"Event Hunt: очередь не записана — новые записи охоты сохранятся при следующей успешной записи");
				}
				foreach (DeliveryEntry item in huntEnqueueResult.Added)
				{
					Plugin.Log.LogInfo((object)DeliveryText.EnqueuedLog(item, _roster.TryGetById(item.PlayerId, out var entry) ? entry.Name : "?"));
				}
			}
			foreach (KeyValuePair<long, HuntPlayer> conflict in huntEnqueueResult.Conflicts)
			{
				if (HuntWarned.Add("conflict:" + state.Id + ":" + conflict.Key))
				{
					Plugin.Log.LogWarning((object)("Event Hunt: участник " + RewardLoss.Sanitize(conflict.Value.Name) + " (платформа «" + conflict.Value.Host + "») сдавал с ID персонажа, который в списке игроков привязан к другой платформе, — не поставлен в очередь, проверьте и выдайте вручную"));
				}
			}
			List<KeyValuePair<string, int>> list = HuntDelivery.ManualOnly(state);
			if (list.Count > 0 && HuntWarned.Add("manual:" + state.Id + ":" + list.Count))
			{
				Plugin.Log.LogWarning((object)("Event Hunt: участники без ID персонажа (вклад записан до 0.5.11) — выдать " + settings.RewardId + " вручную: " + string.Join(", ", list.Select((KeyValuePair<string, int> m) => $"{m.Key} ({m.Value})"))));
			}
			return huntEnqueueResult.StateChanged;
		}

		public static string DescribeHuntRecipient(long playerId, string huntId)
		{
			if (!Running)
			{
				return "";
			}
			RosterEntry entry;
			string text = (_roster.TryGetById(playerId, out entry) ? (PlayerCode.Format(entry.Code) + " ") : "");
			string text2 = DeliveryReason.Hunt(huntId);
			foreach (DeliveryEntry entry2 in _queue.Entries)
			{
				if (entry2.PlayerId == playerId && !(entry2.Reason != text2))
				{
					string arg = ((entry2.State == DeliveryState.Delivered) ? "доставлено" : ((entry2.State == DeliveryState.Cancelled) ? "отменено" : "в очереди"));
					return $"{text}{arg} #{entry2.N}";
				}
			}
			return text + "не в очереди";
		}

		public static string HandleCommand(string sub, string arg1, string arg2)
		{
			if (!Running)
			{
				return "eventreward " + sub + ": " + (_loadError ?? "работает только на сервере");
			}
			switch (sub)
			{
			case "players":
			{
				string text = (arg1 ?? "").Trim();
				if (text == "unbind" || text.StartsWith("unbind ", StringComparison.Ordinal))
				{
					return Unbind(text.Substring("unbind".Length).Trim());
				}
				return DeliveryText.Players(_roster, Online, text, ServerOffset());
			}
			case "give":
				return Give(arg1, arg2);
			case "queue":
				return QueueList(arg1);
			case "cancel":
				return Cancel(arg1);
			case "revoke":
			case "unrevoke":
				return RevocationServer.HandleCommand(sub, arg1, arg2);
			default:
				return "eventreward: неизвестная команда '" + sub + "'";
			}
		}

		private static string Unbind(string codeArg)
		{
			if (!PlayerCode.TryParse(codeArg, out var code))
			{
				return "eventreward players unbind: укажите код игрока, например eventreward players unbind @3";
			}
			if (!_roster.TryGetByCode(code, out var entry))
			{
				return "eventreward players unbind: нет игрока с кодом " + PlayerCode.Format(code);
			}
			string host = entry.Host;
			_roster.Unbind(code);
			SaveRoster();
			Plugin.Log.LogInfo((object)("Event Rewards: привязка " + PlayerCode.Format(code) + " " + entry.Name + " к платформе «" + host + "» снята"));
			return "eventreward players unbind: привязка " + PlayerCode.Format(code) + " " + entry.Name + " к платформе «" + host + "» снята; персонаж привяжется к платформе, с которой войдёт первым";
		}

		private static string Give(string idList, string codeArg)
		{
			List<RewardDefinition> defs;
			RosterEntry player;
			string text = GiveCommand.ValidateDelivery(idList, codeArg, ServerCatalog.Current, _roster, out defs, out player);
			if (text != null)
			{
				return "eventreward give: " + text;
			}
			DateTime now = DateTime.UtcNow;
			List<DeliveryEntry> list = defs.Select((RewardDefinition d) => _queue.Enqueue(d.Id, player.PlayerId, player.Code, "admin", now)).ToList();
			bool flag = SaveQueue();
			foreach (DeliveryEntry item in list)
			{
				Plugin.Log.LogInfo((object)DeliveryText.EnqueuedLog(item, player.Name));
			}
			StringBuilder stringBuilder = new StringBuilder("eventreward give: в очереди ");
			stringBuilder.Append(string.Join(", ", list.Select((DeliveryEntry e) => $"#{e.N} {e.RewardId}")));
			stringBuilder.Append(" → " + PlayerCode.Format(player.Code) + " " + player.Name + "; ");
			stringBuilder.Append((!PluginConfig.DeliveryEnabled.Value) ? "доставка выключена в cfg ([Delivery (server)] Enabled)" : (Online.Contains(player.PlayerId) ? "игрок в игре — награда появится у его ног, когда он постоит на месте несколько секунд" : "игрок не в игре — доставка при следующем входе"));
			if (!flag)
			{
				stringBuilder.Append("; ВНИМАНИЕ: файл очереди не записан, доставка приостановлена — см. журнал сервера");
			}
			return stringBuilder.ToString();
		}

		private static string QueueList(string codeArg)
		{
			int? code = null;
			if (!string.IsNullOrWhiteSpace(codeArg))
			{
				if (!PlayerCode.TryParse(codeArg.Trim(), out var code2))
				{
					return "eventreward queue: код игрока '" + codeArg + "' должен быть вида @N (см. eventreward players)";
				}
				code = code2;
			}
			return DeliveryText.Queue(_queue, _roster, ServerCatalog.Current, code, ServerOffset());
		}

		private static string Cancel(string arg)
		{
			if (!DeliveryQueue.TryParseNumber((arg ?? "").Trim(), out var n))
			{
				return "eventreward cancel: укажите номер записи, например eventreward cancel 12 (см. eventreward queue)";
			}
			_queue.TryGet(n, out var entry);
			switch (_queue.Cancel(n, DateTime.UtcNow))
			{
			case CancelResult.NotFound:
				return $"eventreward cancel: записи #{n} нет";
			case CancelResult.AlreadyDelivered:
				return $"eventreward cancel: #{n} уже доставлена {DeliveryText.FormatTime(entry.ClosedAt, ServerOffset())}";
			case CancelResult.AlreadyCancelled:
				return $"eventreward cancel: #{n} уже отменена";
			default:
			{
				Retries.Remove(n);
				bool flag = SaveQueue();
				RosterEntry entry2;
				string text = (_roster.TryGetById(entry.PlayerId, out entry2) ? entry2.Name : "?");
				Plugin.Log.LogInfo((object)$"Event Rewards: отменено #{entry.N} {entry.RewardId} → {PlayerCode.Format(entry.Code)} {text}");
				return $"eventreward cancel: #{entry.N} {entry.RewardId} → {PlayerCode.Format(entry.Code)} {text} отменена" + (flag ? "" : "; ВНИМАНИЕ: файл очереди не записан — см. журнал сервера");
			}
			}
		}

		private static TimeSpan ServerOffset()
		{
			return TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow);
		}

		private static bool LoadFile<T>(string path, string what, Func<string, Parsed<T>> parse, Func<T> empty, out T value, out bool recovered, out string error)
		{
			value = default(T);
			recovered = false;
			error = null;
			try
			{
				if (File.Exists(path))
				{
					Parsed<T> parsed = parse(File.ReadAllText(path));
					if (!parsed.Ok)
					{
						error = what + " " + path + " не прочитан(а): " + parsed.Error;
						return false;
					}
					value = parsed.Value;
					return true;
				}
				List<string> list = ServerFiles.RecoveryCandidates(path);
				if (list.Count == 0)
				{
					value = empty();
					Plugin.Log.LogInfo((object)("Event Rewards: файла " + path + " нет, " + what + " пуст(а) (создастся при первой записи)"));
					return true;
				}
				foreach (string item in list)
				{
					Parsed<T> parsed2 = parse(File.ReadAllText(item));
					if (!parsed2.Ok)
					{
						Plugin.Log.LogWarning((object)("Event Rewards: " + item + " не прочитан: " + parsed2.Error));
						continue;
					}
					value = parsed2.Value;
					recovered = true;
					Plugin.Log.LogWarning((object)("Event Rewards: файла " + path + " нет — " + what + " восстановлен(а) из " + item));
					return true;
				}
				error = "файла " + path + " нет, а " + string.Join(" и ", list) + " не читаются — " + what + " не восстановлен(а); исправьте или удалите эти файлы";
				return false;
			}
			catch (Exception ex)
			{
				error = what + " " + path + " не прочитан(а): " + ex.Message;
				return false;
			}
		}

		private static bool LoadRoster(out string error)
		{
			if (!LoadFile(RosterPath, "список игроков", delegate(string json)
			{
				RosterResult rosterResult = PlayerRosterJson.Parse(json);
				return new Parsed<PlayerRoster>
				{
					Ok = rosterResult.Success,
					Value = rosterResult.Roster,
					Error = (rosterResult.Success ? null : rosterResult.Errors[0])
				};
			}, () => new PlayerRoster(), out _roster, out var recovered, out error))
			{
				return false;
			}
			if (recovered)
			{
				SaveRoster();
			}
			return true;
		}

		private static bool LoadQueue(out string error)
		{
			if (!LoadFile(QueuePath, "очередь доставки", delegate(string json)
			{
				DeliveryQueueResult deliveryQueueResult = DeliveryQueueJson.Parse(json);
				return new Parsed<DeliveryQueue>
				{
					Ok = deliveryQueueResult.Success,
					Value = deliveryQueueResult.Queue,
					Error = (deliveryQueueResult.Success ? null : deliveryQueueResult.Errors[0])
				};
			}, () => new DeliveryQueue(), out _queue, out var recovered, out error))
			{
				return false;
			}
			if (recovered)
			{
				_queue.ReserveNumbers(1000);
				Plugin.Log.LogWarning((object)($"Event Rewards: номера записей очереди продолжатся с #{_queue.NextN}; записи, сделанные после " + "резервной копии, потеряны — проверьте журнал сервера (строки «в очереди» и «доставлено»)"));
				if (!SaveQueue())
				{
					error = "восстановленная очередь доставки не записана";
					return false;
				}
			}
			return true;
		}

		private static void SaveRoster()
		{
			try
			{
				ServerFiles.WriteAtomic(RosterPath, PlayerRosterJson.Serialize(_roster));
				_rosterDirty = false;
				_rosterSaveTimer = 0f;
			}
			catch (Exception ex)
			{
				_rosterDirty = true;
				Plugin.Log.LogError((object)("Event Rewards: список игроков не сохранён в " + RosterPath + ": " + ex.Message));
			}
		}

		private static bool SaveQueue()
		{
			try
			{
				ServerFiles.WriteAtomic(QueuePath, DeliveryQueueJson.Serialize(_queue));
				if (_queueDirty)
				{
					Plugin.Log.LogInfo((object)"Event Rewards: очередь доставки снова сохраняется, доставка продолжена");
				}
				_queueDirty = false;
				return true;
			}
			catch (Exception ex)
			{
				if (!_queueDirty)
				{
					Plugin.Log.LogError((object)("Event Rewards: очередь доставки не сохранена в " + QueuePath + ": " + ex.Message + "; доставка приостановлена до успешной записи"));
				}
				_queueDirty = true;
				return false;
			}
		}
	}
	internal sealed class EventRewardCommand : ConsoleCommand
	{
		public override string Name => "eventreward";

		public override string Help => "list | give <id>[,<id>...] [count] | give <id>[,<id>...] @<код> | players [фильтр] | players unbind @<код> | queue [@код] | cancel <№> | revoke <id>[,<id>...] @<код>|all | revoke list | unrevoke #<№> | reload";

		public override List<string> CommandOptionList()
		{
			return new List<string> { "list", "give", "players", "queue", "cancel", "revoke", "unrevoke", "reload" };
		}

		public override void Run(string[] args)
		{
			switch ((args.Length != 0) ? args[0].ToLowerInvariant() : "")
			{
			case "list":
				List();
				break;
			case "give":
				Give(args);
				break;
			case "reload":
				Reload();
				break;
			case "players":
				Remote("players", string.Join(" ", args.Skip(1)).Trim(), "");
				break;
			case "queue":
				Remote("queue", (args.Length > 1) ? args[1] : "", "");
				break;
			case "cancel":
				Remote("cancel", (args.Length > 1) ? args[1] : "", "");
				break;
			case "revoke":
				Revoke(args);
				break;
			case "unrevoke":
				Unrevoke(args);
				break;
			default:
				Print("Использование: eventreward " + ((ConsoleCommand)this).Help);
				break;
			}
		}

		private static void List()
		{
			RewardCatalog catalog = RewardState.Catalog;
			if (catalog.Count == 0)
			{
				Print("Наград нет (сервер без мода или пустой набор)");
				return;
			}
			foreach (RewardDefinition item in catalog.All)
			{
				Print(string.Format("{0}: {1} ({2}, качество {3}{4})", item.Id, item.Name, item.BasePrefab, item.Quality, (item.Variant != 0) ? (", вариант " + item.Variant) : ""));
			}
		}

		private static void Give(string[] args)
		{
			if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Print("Отказано: нужны права администратора");
				return;
			}
			GiveRequest giveRequest = GiveCommand.Parse(args.Skip(1).ToList());
			if (!giveRequest.Ok)
			{
				Print(giveRequest.Error);
				return;
			}
			if (giveRequest.Code.HasValue)
			{
				Remote("give", giveRequest.IdList, PlayerCode.Format(giveRequest.Code.Value));
				return;
			}
			List<string> list = GiveCommand.UnknownIds(giveRequest.Ids, RewardState.Catalog);
			if (list.Count > 0)
			{
				Print(GiveCommand.FormatUnknown(list));
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Print("Нет персонажа");
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			List<string> list2 = new List<string>();
			bool flag = false;
			foreach (string id in giveRequest.Ids)
			{
				RewardState.Catalog.TryGet(id, out var definition);
				int i;
				for (i = 0; i < giveRequest.Count; i++)
				{
					if (!inventory.HaveEmptySlot())
					{
						Print("Нет места в инвентаре");
						flag = true;
						break;
					}
					ItemData val = inventory.AddItem(definition.BasePrefab, 1, definition.Quality, definition.Variant, 0L, "", false, false);
					if (val == null)
					{
						Print("Не удалось создать " + definition.BasePrefab);
						flag = true;
						break;
					}
					val.m_customData["sansara.EventRewards.id"] = definition.Id;
					DurabilityScale.FillToMax(val);
				}
				if (i > 0)
				{
					list2.Add($"{definition.Name} × {i}");
				}
				if (flag)
				{
					break;
				}
			}
			RewardState.MarkDirty();
			Print("Выдано: " + ((list2.Count > 0) ? string.Join(", ", list2) : "ничего"));
		}

		private static void Revoke(string[] args)
		{
			RevokeRequest revokeRequest = RevokeCommand.ParseRevoke(args.Skip(1).ToList());
			if (!revokeRequest.Ok)
			{
				Print(revokeRequest.Error);
			}
			else if (revokeRequest.Action == RevokeAction.List)
			{
				Remote("revoke", "list", "", "0.5.14");
			}
			else
			{
				Remote("revoke", revokeRequest.IdList, revokeRequest.Target, "0.5.14");
			}
		}

		private static void Unrevoke(string[] args)
		{
			RevokeRequest revokeRequest = RevokeCommand.ParseUnrevoke(args.Skip(1).ToList());
			if (!revokeRequest.Ok)
			{
				Print(revokeRequest.Error);
			}
			else
			{
				Remote("unrevoke", "#" + revokeRequest.N, "", "0.5.14");
			}
		}

		private static void Reload()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				Print("Нет подключения к миру");
			}
			else if (ZNet.instance.IsServer())
			{
				Print("eventreward reload: " + ServerCatalog.Reload());
				RewardNetwork.BroadcastCatalog();
			}
			else if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Print("Отказано: нужны права администратора");
			}
			else if (!ModCompatibility.IsModuleOnServer("sansara.EventRewards"))
			{
				Print("На сервере нет мода Event Rewards");
			}
			else if (!RewardNetwork.RequestReload())
			{
				Print("Нет подключения к серверу");
			}
			else
			{
				Print("Запрос отправлен на сервер");
			}
		}

		private static void Remote(string sub, string arg1, string arg2, string minServer = "0.5.11")
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				Print("Нет подключения к миру");
			}
			else if (ZNet.instance.IsServer())
			{
				Print(DeliveryServer.HandleCommand(sub, arg1, arg2));
			}
			else if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Print("Отказано: нужны права администратора");
			}
			else if (!ModCompatibility.IsModuleOnServer("sansara.EventRewards"))
			{
				Print("На сервере нет мода Event Rewards");
			}
			else if (!RewardNetwork.RequestDelivery(sub, arg1, arg2))
			{
				Print("Нет подключения к серверу");
			}
			else
			{
				Print("Запрос отправлен на сервер (нужен Event Rewards " + minServer + " или новее на сервере)");
			}
		}

		private static void Print(string text)
		{
			if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text);
			}
		}
	}
	internal sealed class HuntCommand : ConsoleCommand
	{
		private static readonly List<string> Subcommands = new List<string> { "setchest", "start", "stop", "status" };

		public override string Name => "eventhunt";

		public override string Help => "setchest | start | stop | status (ивент «Охота», только администратор)";

		public override List<string> CommandOptionList()
		{
			return Subcommands;
		}

		public override void Run(string[] args)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			string text = ((args.Length != 0) ? args[0].ToLowerInvariant() : "");
			if (!Subcommands.Contains(text))
			{
				Print("Использование: eventhunt setchest | start | stop | status");
				return;
			}
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				Print("Нет подключения к миру");
				return;
			}
			ZDOID id = ZDOID.None;
			if (text == "setchest" && !TryGetHoveredContainer(out id))
			{
				Print("eventhunt setchest: наведите курсор на сундук");
			}
			else if (ZNet.instance.IsServer())
			{
				Print(HuntServer.HandleCommand(text, id));
			}
			else if (!SynchronizationManager.Instance.PlayerIsAdmin)
			{
				Print("Отказано: нужны права администратора");
			}
			else if (!ModCompatibility.IsModuleOnServer("sansara.EventRewards"))
			{
				Print("На сервере нет мода Event Rewards");
			}
			else if (!RewardNetwork.RequestHunt(text, id))
			{
				Print("Нет подключения к серверу");
			}
			else
			{
				Print("Запрос отправлен на сервер (нужен Event Rewards 0.5.1 или новее на сервере)");
			}
		}

		private static bool TryGetHoveredContainer(out ZDOID id)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			id = ZDOID.None;
			GameObject val = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Humanoid)Player.m_localPlayer).GetHoverObject() : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Container componentInParent = val.GetComponentInParent<Container>();
			if ((Object)(object)componentInParent == (Object)null)
			{
				return false;
			}
			ZNetView componentInParent2 = ((Component)componentInParent).GetComponentInParent<ZNetView>();
			if ((Object)(object)componentInParent2 == (Object)null || !componentInParent2.IsValid())
			{
				return false;
			}
			id = componentInParent2.GetZDO().m_uid;
			return true;
		}

		private static void Print(string text)
		{
			if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text);
			}
		}
	}
	internal static class HuntServer
	{
		private const int StatusDecreases = 5;

		public static bool Watching;

		public static ZDOID ChestId = ZDOID.None;

		private static bool _running;

		private static string _loadError;

		private static HuntSettings _settings;

		private static HuntState _state;

		private static int _trophyHash;

		private static uint _lastRevision;

		private static bool _haveRevision;

		private static float _timer;

		private static bool _missingChestWarned;

		private static bool _oldFormatWarned;

		private static readonly ItemData Scratch = new ItemData();

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

		private const float ChestMatchDistance = 0.5f;

		public static string SettingsPath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.hunt.json");

		public static string StatePath => Path.Combine(Paths.ConfigPath, "sansara.EventRewards.hunt.state.json");

		public static void Init()
		{
			Shutdown();
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			_running = true;
			if (!LoadSettings(out var error))
			{
				_loadError = error;
				Plugin.Log.LogError((object)("Event Hunt: " + error));
				return;
			}
			if (!LoadState(out error))
			{
				_loadError = error;
				Plugin.Log.LogError((object)("Event Hunt: " + error));
				return;
			}
			UpdateWatch();
			Plugin.Log.LogInfo((object)("Event Hunt: id " + _state.Id + ", " + (_state.Active ? "идёт" : "не запущен") + ", " + string.Format("прогресс {0} / {1}, сундук {2}", _state.Best, _settings.Goal, (_state.Chest != null) ? "задан" : "не задан") + (_settings.AutoDeliver ? (", автовыдача " + _settings.RewardId) : "")));
			if (DeliveryServer.EnqueueHunt(_state, _settings))
			{
				Save();
			}
		}

		public static void Shutdown()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			_running = false;
			Watching = false;
			ChestId = ZDOID.None;
			_settings = null;
			_state = null;
			_loadError = null;
			_haveRevision = false;
			_timer = 0f;
			_missingChestWarned = false;
			_oldFormatWarned = false;
		}

		public static void Tick(float deltaTime)
		{
			if (!Watching)
			{
				return;
			}
			_timer += deltaTime;
			if (_timer < _settings.CheckIntervalSeconds)
			{
				return;
			}
			_timer = 0f;
			try
			{
				Check(null);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Event Hunt: ошибка проверки сундука: " + ex));
			}
		}

		public static void OnChestData(ZDO zdo)
		{
			try
			{
				Check(zdo);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Event Hunt: ошибка разбора данных сундука: " + ex));
			}
		}

		private static void Check(ZDO zdo)
		{
			if (zdo == null)
			{
				zdo = ResolveChest();
			}
			if (zdo == null)
			{
				if (!_missingChestWarned)
				{
					Plugin.Log.LogWarning((object)$"Event Hunt: сундук {_state.Chest.Prefab} ({_state.Chest.X:0.0}, {_state.Chest.Y:0.0}, {_state.Chest.Z:0.0}) не найден (разрушен?)");
				}
				_missingChestWarned = true;
				return;
			}
			_missingChestWarned = false;
			if (_haveRevision && zdo.DataRevision == _lastRevision)
			{
				return;
			}
			_lastRevision = zdo.DataRevision;
			_haveRevision = true;
			if (!TryCount(zdo, out var count))
			{
				return;
			}
			long playerId;
			string host;
			string text = ResolvePlayer(zdo.GetOwner(), out playerId, out host);
			int lastCount = _state.LastCount;
			HuntUpdateResult huntUpdateResult = HuntTracker.Update(_state, count, text, _settings.Goal, DateTime.UtcNow, playerId, host);
			if (huntUpdateResult.Changed)
			{
				Plugin.Log.LogInfo((object)$"Event Hunt: в сундуке {count} ({count - lastCount:+#;-#;0}), игрок {text}, прогресс {_state.Best} / {_settings.Goal}");
				Save();
				if (huntUpdateResult.GoalReached)
				{
					Announce(HuntText.Format(_settings.VictoryMessage, _state.Best, _settings.Goal));
					Plugin.Log.LogInfo((object)"Event Hunt: цель достигнута");
				}
				else if (huntUpdateResult.ProgressIncreased)
				{
					Announce(HuntText.Format(_settings.ProgressMessage, _state.Best, _settings.Goal));
				}
				if (_state.Completed && DeliveryServer.EnqueueHunt(_state, _settings))
				{
					Save();
				}
			}
		}

		private static bool TryCount(ZDO zdo, out int count)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			count = 0;
			byte[] byteArray = zdo.GetByteArray(ZDOVars.s_items, (byte[])null);
			if (byteArray == null)
			{
				return true;
			}
			try
			{
				ZPackage val = new ZPackage(byteArray);
				int num = val.ReadInt();
				if (num < 108)
				{
					if (!_oldFormatWarned)
					{
						Plugin.Log.LogWarning((object)$"Event Hunt: сундук в старом формате {num}, считаю 0");
					}
					_oldFormatWarned = true;
					return true;
				}
				int num2 = val.ReadUShort();
				for (int i = 0; i < num2; i++)
				{
					if (HuntCounter.Matches(ItemData.Load(val, Scratch, (Item)num), Scratch.m_quality, Scratch.m_worldLevel, Scratch.m_customData, _trophyHash, _settings.TrophyQuality, _settings.TrophyWorldLevel, _state.Id))
					{
						count += Scratch.m_stack;
					}
				}
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Event Hunt: содержимое сундука не прочитано: " + ex.Message));
				return false;
			}
		}

		private static string ResolvePlayer(long owner, out long playerId, out string host)
		{
			playerId = 0L;
			host = "";
			if (owner == 0L || owner == ZDOMan.GetSessionID())
			{
				return "неизвестно";
			}
			ZNet instance = ZNet.instance;
			ZNetPeer val = ((instance != null) ? instance.GetPeer(owner) : null);
			if (val == null)
			{
				return "неизвестно";
			}
			playerId = DeliveryServer.PlayerIdOf(val);
			host = DeliveryServer.HostOf(val);
			if (string.IsNullOrWhiteSpace(val.m_playerName))
			{
				return "неизвестно";
			}
			return val.m_playerName;
		}

		private static void Announce(string text)
		{
			if (!string.IsNullOrEmpty(text))
			{
				Plugin.Log.LogInfo((object)("Event Hunt: объявление: " + text));
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text });
				}
			}
		}

		public static string HandleCommand(string sub, ZDOID target)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (!_running)
			{
				return "eventhunt: модуль работает только на сервере";
			}
			return sub switch
			{
				"setchest" => SetChest(target), 
				"start" => Start(), 
				"stop" => Stop(), 
				"status" => Status(), 
				_ => "eventhunt: неизвестная команда '" + sub + "'", 
			};
		}

		private static string SetChest(ZDOID target)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			if (_state == null)
			{
				return "eventhunt setchest: " + (_loadError ?? "состояние не загружено");
			}
			if (_state.Active)
			{
				return "eventhunt setchest: ивент идёт, сначала eventhunt stop";
			}
			if (((ZDOID)(ref target)).IsNone())
			{
				return "eventhunt setchest: наведите курсор на сундук";
			}
			ZDO zDO = ZDOMan.instance.GetZDO(target);
			if (zDO == null)
			{
				return "eventhunt setchest: объект не найден на сервере";
			}
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(zDO.GetPrefab()) : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponentInChildren<Container>(true) == (Object)null)
			{
				return "eventhunt setchest: это не контейнер";
			}
			if ((Object)(object)val.GetComponentInChildren<Vagon>(true) != (Object)null || (Object)(object)val.GetComponentInChildren<Ship>(true) != (Object)null || (Object)(object)val.GetComponent<ZSyncTransform>() != (Object)null)
			{
				return "eventhunt setchest: " + ((Object)val).name + " передвигается (телега, корабль), нужен неподвижный сундук";
			}
			Vector3 position = zDO.GetPosition();
			_state.Chest = new HuntChest
			{
				UserId = ((ZDOID)(ref target)).UserID,
				Id = ((ZDOID)(ref target)).ID,
				Prefab = ((Object)val).name,
				X = position.x,
				Y = position.y,
				Z = position.z
			};
			UpdateWatch();
			Save();
			TryCount(zDO, out var count);
			return $"eventhunt setchest: сундук {((Object)val).name} ({position.x:0}, {position.y:0}, {position.z:0}) задан, засчитываемых трофеев в нём: {count}";
		}

		private static string Start()
		{
			if (_state == null)
			{
				return "eventhunt start: " + (_loadError ?? "состояние не загружено");
			}
			if (_state.Active)
			{
				return "eventhunt start: ивент уже идёт";
			}
			if (!LoadSettings(out var error))
			{
				return "eventhunt start: " + error + " (прежние настройки оставлены)";
			}
			if (_state.Id != _settings.Id)
			{
				SwitchEvent();
			}
			if (_state.Chest == null)
			{
				return "eventhunt start: сначала eventhunt setchest";
			}
			ZDO val = ResolveChest();
			if (val == null)
			{
				return "eventhunt start: сундук не найден на сервере, задайте его заново";
			}
			if (!TryCount(val, out var count))
			{
				return "eventhunt start: содержимое сундука не прочитано";
			}
			HuntTracker.Start(_state, count, DateTime.UtcNow);
			bool flag = HuntTracker.CheckGoal(_state, _settings.Goal);
			_lastRevision = val.DataRevision;
			_haveRevision = true;
			UpdateWatch();
			Save();
			Plugin.Log.LogInfo((object)$"Event Hunt: запущен {_state.Id}, в сундуке {count}, прогресс {_state.Best} / {_settings.Goal}");
			Announce(HuntText.Format(_settings.StartMessage, _state.Best, _settings.Goal));
			if (flag)
			{
				Announce(HuntText.Format(_settings.VictoryMessage, _state.Best, _settings.Goal));
				Plugin.Log.LogInfo((object)"Event Hunt: цель достигнута");
			}
			if (DeliveryServer.EnqueueHunt(_state, _settings))
			{
				Save();
			}
			return $"eventhunt start: ивент {_state.Id} идёт; в сундуке {count}, прогресс {_state.Best} / {_settings.Goal}" + ((count > 0) ? " (уже лежавшие трофеи записаны на «неизвестно»)" : "") + (flag ? "; цель уже достигнута, объявлена победа" : "");
		}

		private static string Stop()
		{
			if (_state == null)
			{
				return "eventhunt stop: " + (_loadError ?? "состояние не загружено");
			}
			if (!_state.Active)
			{
				return "eventhunt stop: ивент не запущен";
			}
			HuntTracker.Stop(_state);
			UpdateWatch();
			Save();
			if (!_state.Completed)
			{
				Announce(HuntText.Format(_settings.StopMessage, _state.Best, _settings.Goal));
			}
			Plugin.Log.LogInfo((object)$"Event Hunt: остановлен {_state.Id}, прогресс {_state.Best} / {_settings.Goal}");
			return $"eventhunt stop: ивент {_state.Id} остановлен, прогресс {_state.Best} / {_settings.Goal}";
		}

		private static string Status()
		{
			if (_state == null || _settings == null)
			{
				return "eventhunt status: " + (_loadError ?? "состояние не загружено");
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("Ивент " + _state.Id + ": " + (_state.Active ? "идёт" : "остановлен") + (_state.Completed ? ", цель достигнута" : ""));
			stringBuilder.Append($"\nПрогресс (максимум): {_state.Best} / {_settings.Goal}, сейчас в сундуке: {_state.LastCount}");
			stringBuilder.Append(string.Format("\nТрофей: {0}, уровень {1}, уровень мира {2}, метка {3}={4}", _settings.TrophyPrefab, _settings.TrophyQuality, _settings.TrophyWorldLevel, "sansara.EventHunt.id", _state.Id));
			if (_settings.Id != _state.Id)
			{
				stringBuilder.Append("\nВ настройках уже id " + _settings.Id + ": он начнётся после eventhunt stop и eventhunt start");
			}
			if (_state.Chest == null)
			{
				stringBuilder.Append("\nСундук: не задан");
			}
			else
			{
				bool flag = ResolveChest() != null;
				stringBuilder.Append(string.Format("\nСундук: {0} ({1:0}, {2:0}, {3:0}){4}", _state.Chest.Prefab, _state.Chest.X, _state.Chest.Y, _state.Chest.Z, flag ? "" : " — НЕ НАЙДЕН"));
			}
			List<KeyValuePair<string, int>> list = HuntTracker.Participants(_state);
			stringBuilder.Append($"\nУчастники ({list.Count}):");
			foreach (KeyValuePair<string, int> item in list)
			{
				stringBuilder.Append($"\n  {item.Key}: {item.Value}");
			}
			stringBuilder.Append("\nАвтовыдача: " + (_settings.AutoDeliver ? "включена" : "выключена") + ", награда " + _settings.RewardId + ((_settings.AutoDeliver && !_state.Completed) ? " (после победы)" : ""));
			List<KeyValuePair<long, HuntPlayer>> list2 = HuntDelivery.Recipients(_state);
			if (list2.Count > 0)
			{
				stringBuilder.Append($"\nПо персонажам ({list2.Count}):");
				foreach (KeyValuePair<long, HuntPlayer> item2 in list2)
				{
					string text = DeliveryServer.DescribeHuntRecipient(item2.Key, _state.Id);
					stringBuilder.Append(string.Format("\n  {0}: {1}{2}", item2.Value.Name, item2.Value.Count, (text.Length > 0) ? (" — " + text) : ""));
				}
			}
			List<KeyValuePair<string, int>> list3 = HuntDelivery.ManualOnly(_state);
			if (list3.Count > 0)
			{
				stringBuilder.Append("\nБез ID персонажа (вклад до 0.5.11, выдать вручную " + _settings.RewardId + "):");
				foreach (KeyValuePair<string, int> item3 in list3)
				{
					stringBuilder.Append($"\n  {item3.Key}: {item3.Value}");
				}
			}
			List<HuntLogEntry> list4 = HuntTracker.LastDecreases(_state, 5);
			if (list4.Count > 0)
			{
				stringBuilder.Append("\nПоследние уменьшения (UTC):");
				foreach (HuntLogEntry item4 in list4)
				{
					stringBuilder.Append($"\n  {item4.Time:yyyy-MM-dd HH:mm:ss} {item4.Delta} (стало {item4.Count}), {item4.Player}");
				}
			}
			return stringBuilder.ToString();
		}

		private static bool LoadSettings(out string error)
		{
			error = null;
			try
			{
				if (!File.Exists(SettingsPath))
				{
					WriteFile(SettingsPath, HuntSettingsJson.Serialize(HuntSettings.Default()));
					Plugin.Log.LogInfo((object)("Event Hunt: создан файл настроек по умолчанию " + SettingsPath));
				}
				HuntSettingsResult huntSettingsResult = HuntSettingsJson.Parse(File.ReadAllText(SettingsPath));
				foreach (string warning in huntSettingsResult.Warnings)
				{
					Plugin.Log.LogWarning((object)("Event Hunt: " + warning));
				}
				if (!huntSettingsResult.Success)
				{
					error = "настройки " + SettingsPath + " не приняты: " + huntSettingsResult.Errors[0];
					return false;
				}
				_settings = huntSettingsResult.Settings;
				_trophyHash = StringExtensionMethods.GetStableHashCode(_settings.TrophyPrefab);
				if ((Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ObjectDB.instance.GetItemPrefab(_settings.TrophyPrefab) == (Object)null)
				{
					Plugin.Log.LogWarning((object)("Event Hunt: предмет " + _settings.TrophyPrefab + " не найден в ObjectDB"));
				}
				return true;
			}
			catch (Exception ex)
			{
				error = "настройки " + SettingsPath + " не прочитаны: " + ex.Message;
				return false;
			}
		}

		private static bool LoadState(out string error)
		{
			error = null;
			try
			{
				if (!File.Exists(StatePath))
				{
					_state = HuntState.New(_settings.Id);
					return true;
				}
				HuntStateResult huntStateResult = HuntStateJson.Parse(File.ReadAllText(StatePath));
				if (!huntStateResult.Success)
				{
					error = "состояние " + StatePath + " не прочитано: " + huntStateResult.Errors[0] + "; модуль отключён до исправления файла";
					return false;
				}
				_state = huntStateResult.State;
				if (_state.Id != _settings.Id)
				{
					if (_state.Active)
					{
						Plugin.Log.LogWarning((object)("Event Hunt: в настройках id " + _settings.Id + ", а идёт " + _state.Id + "; ивент продолжается до eventhunt stop"));
					}
					else
					{
						SwitchEvent();
					}
				}
				return true;
			}
			catch (Exception ex)
			{
				error = "состояние " + StatePath + " не прочитано: " + ex.Message;
				return false;
			}
		}

		private static void SwitchEvent()
		{
			if (File.Exists(StatePath))
			{
				string text = Path.Combine(Paths.ConfigPath, "sansara.EventRewards.hunt.state." + _state.Id + ".json");
				int num = 2;
				while (File.Exists(text))
				{
					text = Path.Combine(Paths.ConfigPath, $"sansara.EventRewards.hunt.state.{_state.Id}.{num}.json");
					num++;
				}
				File.Copy(StatePath, text, overwrite: false);
				Plugin.Log.LogInfo((object)("Event Hunt: новый ивент " + _settings.Id + "; состояние " + _state.Id + " сохранено в " + text));
			}
			else
			{
				Plugin.Log.LogInfo((object)("Event Hunt: новый ивент " + _settings.Id + " (файла состояния " + _state.Id + " не было)"));
			}
			HuntChest chest = _state.Chest;
			_state = HuntState.New(_settings.Id);
			_state.Chest = chest;
			Save();
		}

		private static void Save()
		{
			try
			{
				WriteFile(StatePath, HuntStateJson.Serialize(_state));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Event Hunt: состояние не сохранено в " + StatePath + ": " + ex.Message));
			}
		}

		private static void WriteFile(string path, string text)
		{
			ServerFiles.WriteAtomic(path, text);
		}

		private static ZDO ResolveChest()
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			HuntChest huntChest = _state?.Chest;
			if (huntChest == null || ZDOMan.instance == null)
			{
				return null;
			}
			int stableHashCode = StringExtensionMethods.GetStableHashCode(huntChest.Prefab);
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(huntChest.X, huntChest.Y, huntChest.Z);
			ZDO val2 = (((ZDOID)(ref ChestId)).IsNone() ? null : ZDOMan.instance.GetZDO(ChestId));
			Vector3 val3;
			if (val2 != null && val2.GetPrefab() == stableHashCode)
			{
				val3 = val2.GetPosition() - val;
				if (((Vector3)(ref val3)).sqrMagnitude <= 0.25f)
				{
					return val2;
				}
			}
			ZDO val4 = null;
			float num = 0.25f;
			SearchBuffer.Clear();
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(val), new SimulationDistance(1, 0, true), SearchBuffer, (List<ZDO>)null);
			foreach (ZDO item in SearchBuffer)
			{
				if (item.GetPrefab() == stableHashCode)
				{
					val3 = item.GetPosition() - val;
					float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
					if (sqrMagnitude <= num)
					{
						val4 = item;
						num = sqrMagnitude;
					}
				}
			}
			SearchBuffer.Clear();
			if (val4 == null)
			{
				return null;
			}
			ChestId = val4.m_uid;
			_haveRevision = false;
			if (huntChest.UserId != ((ZDOID)(ref ChestId)).UserID || huntChest.Id != ((ZDOID)(ref ChestId)).ID)
			{
				huntChest.UserId = ((ZDOID)(ref ChestId)).UserID;
				huntChest.Id = ((ZDOID)(ref ChestId)).ID;
				Save();
			}
			Plugin.Log.LogInfo((object)$"Event Hunt: сундук {huntChest.Prefab} найден, ZDOID {ChestId}");
			return val4;
		}

		private static ZDOID ChestFromState()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (_state?.Chest != null)
			{
				return new ZDOID(_state.Chest.UserId, _state.Chest.Id);
			}
			return ZDOID.None;
		}

		private static void UpdateWatch()
		{
			//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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			ChestId = ChestFromState();
			if (_running && _state?.Chest != null && ResolveChest() == null)
			{
				ChestId = ZDOID.None;
			}
			Watching = _running && _settings != null && _state != null && _state.Active && _state.Chest != null;
			_timer = 0f;
			if (!Watching)
			{
				_haveRevision = false;
			}
		}
	}
	[BepInPlugin("sansara.EventRewards", "Event Rewards", "0.5.14")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "sansara.EventRewards";

		public const string Version = "0.5.14";

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			new Harmony("sansara.EventRewards").PatchAll(typeof(Plugin).Assembly);
			RewardNetwork.Init();
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new EventRewardCommand());
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new HuntCommand());
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			string text = "Russian";
			string text2 = "item_newgameplusitem";
			localization.AddTranslation(ref text, ref text2, "Трофей ивента");
			Log.LogInfo((object)"Event Rewards 0.5.14 loaded");
		}
	}
	internal static class PluginConfig
	{
		public static ConfigEntry<bool> Enabled;

		private static ConfigEntry<float> _maxCarryWeight;

		private static ConfigEntry<float> _maxMoveSpeed;

		private static ConfigEntry<float> _maxStaminaRegen;

		private static ConfigEntry<float> _maxRunDrainReduction;

		private static ConfigEntry<float> _maxHealth;

		private static ConfigEntry<float> _maxStamina;

		private static ConfigEntry<float> _maxDamage;

		private static ConfigEntry<float> _maxBlockPower;

		private static ConfigEntry<float> _maxDurability;

		private static ConfigEntry<float> _maxToolTier;

		public static ConfigEntry<int> MaxQualityAboveBase;

		private static ConfigEntry<string> _maxResistance;

		private static ConfigEntry<string> _allowedEffects;

		private static ConfigEntry<int> _maxAirJumps;

		private static ConfigEntry<float> _maxEitr;

		private static ConfigEntry<float> _maxEitrRegen;

		private static ConfigEntry<float> _maxEitrCostReduction;

		private static ConfigEntry<float> _maxHealthCostReduction;

		private static ConfigEntry<float> _maxSkillLevel;

		private static ConfigEntry<float> _maxSkillGain;

		private static ConfigEntry<float> _maxDamageSkill;

		private static ConfigEntry<float> _maxAdrenaline;

		private static ConfigEntry<float> _maxAdrenalineThresholdReduction;

		private static ConfigEntry<float> _maxAdrenalineDuration;

		private static ConfigEntry<float> _maxAdrenalineOnHit;

		private static ConfigEntry<float> _maxShipSpeed;

		private static ConfigEntry<float> _maxAdrenalineEffectDuration;

		private static ConfigEntry<string> _allowedAdrenalineEffects;

		private static ConfigEntry<float> _maxHealthRegen;

		private static ConfigEntry<float> _maxFoodStats;

		private static ConfigEntry<float> _maxFoodDrainReduction;

		private static ConfigEntry<float> _maxPotionDuration;

		private static ConfigEntry<float> _maxPotionCooldownReduction;

		private static ConfigEntry<float> _maxPotionPower;

		private static ConfigEntry<float> _maxBuildStaminaReduction;

		private static ConfigEntry<float> _maxBuildRange;

		private static ConfigEntry<float> _maxArmor;

		private static ConfigEntry<float> _maxFallDamageReduction;

		private static ConfigEntry<float> _maxCraftCostReduction;

		private static ConfigEntry<float> _maxUpgradeCostReduction;

		private static ConfigEntry<float> _maxAmmoSave;

		private static ConfigEntry<float> _maxCraftYield;

		public static ConfigEntry<bool> AnnounceRewardLoss;

		public static ConfigEntry<string> RewardLossMessage;

		public static ConfigEntry<bool> DeliveryEnabled;

		public static ConfigEntry<float> DeliveryCheckIntervalSeconds;

		public static ConfigEntry<float> DeliverySettleSeconds;

		public static ConfigEntry<string> DeliveryMessage;

		public static ConfigEntry<bool> AnnounceRevocation;

		public static ConfigEntry<string> RevocationMessage;

		public static ConfigEntry<float> RevocationRescanMinutes;

		private static string _warnedMaxResistance;

		public static void Bind(ConfigFile cfg)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Expected O, but got Unknown
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Expected O, but got Unknown
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Expected O, but got Unknown
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Expected O, but got Unknown
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Expected O, but got Unknown
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Expected O, but got Unknown
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Expected O, but got Unknown
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Expected O, but got Unknown
			//IL_0268: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Expected O, but got Unknown
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Expected O, but got Unknown
			//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Expected O, but got Unknown
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Expected O, but got Unknown
			//IL_0320: Unknown result type (might be due to invalid IL or missing references)
			//IL_032a: Expected O, but got Unknown
			//IL_0358: Unknown result type (might be due to invalid IL or missing references)
			//IL_0362: Expected O, but got Unknown
			//IL_0382: Unknown result type (might be due to invalid IL or missing references)
			//IL_038c: Expected O, but got Unknown
			//IL_03ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b6: Expected O, but got Unknown
			//IL_03d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e0: Expected O, but got Unknown
			//IL_040e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0418: Expected O, but got Unknown
			//IL_0438: Unknown result type (might be due to invalid IL or missing references)
			//IL_0442: Expected O, but got Unknown
			//IL_0470: Unknown result type (might be due to invalid IL or missing references)
			//IL_047a: Expected O, but got Unknown
			//IL_04a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b2: Expected O, but got Unknown
			//IL_04e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ea: Expected O, but got Unknown
			//IL_0537: Unknown result type (might be due to invalid IL or missing references)
			//IL_0541: Expected O, but got Unknown
			//IL_056f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0579: Expected O, but got Unknown
			//IL_05a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b1: Expected O, but got Unknown
			//IL_05df: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e9: Expected O, but got Unknown
			//IL_0617: Unknown result type (might be due to invalid IL or missing references)
			//IL_0621: Expected O, but got Unknown
			//IL_064f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0659: Expected O, but got Unknown
			//IL_0687: Unknown result type (might be due to invalid IL or missing references)
			//IL_0691: Expected O, but got Unknown
			//IL_06bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c9: Expected O, but got Unknown
			//IL_06e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f3: Expected O, but got Unknown
			//IL_0713: Unknown result type (might be due to invalid IL or missing references)
			//IL_071d: Expected O, but got Unknown
			//IL_074b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0755: Expected O, but got Unknown
			//IL_0783: Unknown result type (might be due to invalid IL or missing references)
			//IL_078d: Expected O, but got Unknown
			//IL_07bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_07c5: Expected O, but got Unknown
			//IL_07f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_07fd: Expected O, but got Unknown
			//IL_082b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0835: Expected O, but got Unknown
			//IL_08d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_08e1: Expected O, but got Unknown
			//IL_092a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0934: Expected O, but got Unknown
			//IL_0962: Unknown result type (might be due to invalid IL or missing references)
			//IL_096c: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Включает бонусы и подсказки наград на этом клиенте. Предметы с меткой остаются ванильными при false.");
			Enabled.SettingChanged += delegate
			{
				RewardState.MarkDirty();
			};
			AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 100000f);
			_maxCarryWeight = cfg.Bind<float>("Limits (server)", "MaxCarryWeight", 500f, new ConfigDescription("Предел бонуса carryWeight, единицы веса.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxMoveSpeed = cfg.Bind<float>("Limits (server)", "MaxMoveSpeedPercent", 20f, new ConfigDescription("Предел бонуса moveSpeed, %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxStaminaRegen = cfg.Bind<float>("Limits (server)", "MaxStaminaRegenPercent", 50f, new ConfigDescription("Предел бонуса staminaRegen, %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxRunDrainReduction = cfg.Bind<float>("Limits (server)", "MaxRunStaminaDrainReductionPercent", 50f, new ConfigDescription("Предел экономии runStaminaDrain, %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxHealth = cfg.Bind<float>("Limits (server)", "MaxHealth", 50f, new ConfigDescription("Предел бонуса maxHealth, единицы здоровья.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxStamina = cfg.Bind<float>("Limits (server)", "MaxStamina", 50f, new ConfigDescription("Предел бонуса maxStamina, единицы выносливости.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxDamage = cfg.Bind<float>("Limits (server)", "MaxDamagePercent", 20f, new ConfigDescription("Предел бонуса damage, %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxBlockPower = cfg.Bind<float>("Limits (server)", "MaxBlockPowerPercent", 100f, new ConfigDescription("Предел бонуса blockPower (прочность блока и отбрасывание при парировании самого предмета), %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxToolTier = cfg.Bind<float>("Limits (server)", "MaxToolTier", 10f, new ConfigDescription("Предел бонуса toolTier (минимальный уровень инструмента).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			MaxQualityAboveBase = cfg.Bind<int>("Limits (server)", "MaxQualityAboveBase", 0, new ConfigDescription("На сколько уровней выше обычного максимума предмета можно выдавать награду (как после Кузницы возможностей). 0 — не выше обычного максимума; -1 — без ограничения (технический предел игры 65535). Не действует на предметы с одним уровнем качества (пояса).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 65535), Array.Empty<object>()));
			_maxResistance = cfg.Bind<string>("Limits (server)", "MaxResistance", "veryResistant", "Предел бонуса resist: уровень сильнее предела понижается до него с предупреждением. Значения (регистр не важен): none (сопротивления наград отключены), slightlyResistant, resistant, veryResistant, immune. Неверное значение — используется veryResistant (предупреждение в журнале). frost: immune всегда понижается до veryResistant.");
			_allowedEffects = cfg.Bind<string>("Limits (server)", "AllowedEffects", "Wishbone,Demister,WindRun,SlowFall,GP_Eikthyr,GP_TheElder,GP_Bonemass,GP_Moder,GP_Yagluth,GP_Queen,GP_Fader,Potion_frostresist,Potion_poisonresist,Potion_barleywine,Potion_swimmer,Potion_LightFoot,Potion_tamer,Potion_BugRepellent", "Имена ванильных StatusEffect, разрешённые в поле effects наград (через запятую, с учётом регистра). Силы боссов (GP_*) из effects действуют без таймера, пока награда надета. Медовухи без группы (Potion_*) из effects действуют без таймера, пока награда надета; медовухи лечения, выносливости и эйтра сюда не добавлять — постоянный эффект группы навсегда запретит пить медовухи этой группы. Пусто — эффекты наград отключены.");
			_maxAirJumps = cfg.Bind<int>("Limits (server)", "MaxAirJumps", 1, new ConfigDescription("Предел бонуса doubleJump (число прыжков в воздухе).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10), Array.Empty<object>()));
			AcceptableValueRange<float> val2 = new AcceptableValueRange<float>(0f, 100f);
			_maxEitr = cfg.Bind<float>("Limits (server)", "MaxEitr", 100f, new ConfigDescription("Предел бонуса maxEitr, единицы эйтра (базового эйтра у игрока нет, бонус даёт эйтр и без еды).", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxEitrRegen = cfg.Bind<float>("Limits (server)", "MaxEitrRegenPercent", 100f, new ConfigDescription("Предел бонуса eitrRegen, %. Полный комплект мага даёт +100 %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxEitrCostReduction = cfg.Bind<float>("Limits (server)", "MaxEitrCostReductionPercent", 50f, new ConfigDescription("Предел экономии eitrCost (расход эйтра на заклинания), %.", (AcceptableValueBase)(object)val2, Array.Empty<object>()));
			_maxHealthCostReduction = cfg.Bind<float>("Limits (server)", "MaxHealthCostReductionPercent", 50f, new ConfigDescription("Предел экономии healthCost (цена здоровьем у магии крови), %.", (AcceptableValueBase)(object)val2, Array.Empty<object>()));
			_maxSkillLevel = cfg.Bind<float>("Limits (server)", "MaxSkillLevel", 25f, new ConfigDescription("Предел бонуса skillLevel для каждого навыка, уровни.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f), Array.Empty<object>()));
			_maxSkillGain = cfg.Bind<float>("Limits (server)", "MaxSkillGainPercent", 100f, new ConfigDescription("Предел бонуса skillGain (ускорение роста навыка) для каждого навыка, %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxDamageSkill = cfg.Bind<float>("Limits (server)", "MaxDamageSkillPercent", 100f, new ConfigDescription("Предел бонуса damageSkill (урон атак навыком) для каждого навыка, %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxAdrenaline = cfg.Bind<float>("Limits (server)", "MaxAdrenalinePercent", 100f, new ConfigDescription("Предел бонуса adrenaline (ускорение набора адреналина), %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxAdrenalineThresholdReduction = cfg.Bind<float>("Limits (server)", "MaxAdrenalineThresholdReductionPercent", 50f, new ConfigDescription("Предел бонуса adrenalineThreshold (снижение порога шкалы наградного украшения), %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>()));
			_maxAdrenalineDuration = cfg.Bind<float>("Limits (server)", "MaxAdrenalineDurationPercent", 100f, new ConfigDescription("Предел бонуса adrenalineDuration (удлинение эффекта полной шкалы наградного украшения), %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxAdrenalineOnHit = cfg.Bind<float>("Limits (server)", "MaxAdrenalineOnHit", 2f, new ConfigDescription("Предел бонуса adrenalineOnHit, единиц адреналина за попадание по врагу (для сравнения: идеальный блок даёт 5, обычный — 2).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			_maxShipSpeed = cfg.Bind<float>("Limits (server)", "MaxShipSpeedPercent", 50f, new ConfigDescription("Предел бонуса shipSpeed одной награды (прибавка к максимальной скорости корабля под парусом и на вёслах), %. Сумму нескольких наград игрока клиенты ограничивают 50 %; на корабле действует лучшее значение среди игроков на борту, не сумма.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>()));
			_maxAdrenalineEffectDuration = cfg.Bind<float>("Limits (server)", "MaxAdrenalineEffectDuration", 180f, new ConfigDescription("Предел adrenalineEffectDuration (длительность дополнительных эффектов полной шкалы), секунды.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3600f), Array.Empty<object>()));
			_allowedAdrenalineEffects = cfg.Bind<string>("Limits (server)", "AllowedAdrenalineEffects", "TrinketBlackDamageHealth,TrinketBlackStamina,TrinketBronzeHealth,TrinketBronzeStamina,TrinketCarapaceEitr,TrinketChitinSwim,TrinketFlametalEitr,TrinketFlametalStaminaHealth,TrinketIronHealth,TrinketIronStamina,TrinketScaleStaminaDamage,TrinketSilverDamage,TrinketSilverResist,Demister,WindRun,SlowFall", "Имена ванильных StatusEffect, разрешённые в поле adrenalineEffects наград-украшений (через запятую, с учётом регистра). Пусто — дополнительные эффекты полной шкалы отключены.");
			_maxHealthRegen = cfg.Bind<float>("Limits (server)", "MaxHealthRegenPercent", 100f, new ConfigDescription("Предел бонуса healthRegen (восстановление здоровья от еды), %. Сумму наград игрока клиенты ограничивают 200 %. Для сравнения: «Отдых» у огня +200 %, мощная медовуха лечения +25 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>()));
			_maxFoodStats = cfg.Bind<float>("Limits (server)", "MaxFoodStatsPercent", 50f, new ConfigDescription("Предел бонуса foodStats (здоровье, выносливость и эйтр, которые даёт еда; база 25/75 и бонусы наград не растут), %. Сумму наград клиенты ограничивают 100 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			_maxFoodDrainReduction = cfg.Bind<float>("Limits (server)", "MaxFoodDrainReductionPercent", 50f, new ConfigDescription("Предел бонуса foodDrain (замедление выдыхания еды), %. Сумму наград клиенты ограничивают 90 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>()));
			_maxPotionDuration = cfg.Bind<float>("Limits (server)", "MaxPotionDurationPercent", 100f, new ConfigDescription("Предел бонуса potionDuration (длительность медовух без перезарядки: сопротивления, мощные и др.), %. Сумму наград клиенты ограничивают 200 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>()));
			_maxPotionCooldownReduction = cfg.Bind<float>("Limits (server)", "MaxPotionCooldownReductionPercent", 50f, new ConfigDescription("Предел бонуса potionCooldown (сокращение запрета на группу у мгновенных медовух лечения, выносливости и эйтра, в игре 120 с), %. Сумму наград клиенты ограничивают 90 %; время лечения медовухи (+2 с) не сокращается.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>()));
			_maxPotionPower = cfg.Bind<float>("Limits (server)", "MaxPotionPowerPercent", 50f, new ConfigDescription("Предел бонуса potionPower (сила медовух: лечение, выносливость и эйтр, прибавка мощных медовух к восстановлению), %. Сумму наград клиенты ограничивают 100 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			_maxBuildStaminaReduction = cfg.Bind<float>("Limits (server)", "MaxBuildStaminaReductionPercent", 50f, new ConfigDescription("Предел бонуса buildStamina (экономия выносливости на строительство молотом, мотыгой и культиватором: установка, разбор, ремонт), %. Сумму наград клиенты ограничивают 90 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>()));
			_maxBuildRange = cfg.Bind<float>("Limits (server)", "MaxBuildRange", 10f, new ConfigDescription("Предел бонуса buildRange (прибавка к дальности строительства, в игре 5 м: установка, разбор, ремонт, мотыга и культиватор), метры. Сумму наград клиенты ограничивают 20 м.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>()));
			_maxArmor = cfg.Bind<float>("Limits (server)", "MaxArmorPercent", 100f, new ConfigDescription("Предел бонуса armorPercent (броня самого предмета-награды: вся ванильная броня предмета с учётом качества и уровня мира умножается на 1 + N/100), %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxDurability = cfg.Bind<float>("Limits (server)", "MaxDurabilityPercent", 400f, new ConfigDescription("Предел бонуса durability (прочность самого предмета-награды: максимальная ванильная прочность с учётом качества умножается на 1 + N/100; 400 — впятеро), %.", (AcceptableValueBase)(object)val, Array.Empty<object>()));
			_maxFallDamageReduction = cfg.Bind<float>("Limits (server)", "MaxFallDamageReductionPercent", 50f, new ConfigDescription("Предел бонуса fallDamage (снижение урона от падения у игрока с наградой), %. Сумму наград клиенты ограничивают 90 %. «Словно пёрышко» и плащ из перьев по-прежнему убирают урон полностью.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>()));
			_maxCraftCostReduction = cfg.Bind<float>("Limits (server)", "MaxCraftCostReductionPercent", 30f, new ConfigDescription("Предел бонуса craftCost (экономия материалов на изготовление новой вещи у станции или в инвентаре; каждый материал округляется вверх, не меньше 1), %. Постройки не дешевеют. Сумму наград клиенты ограничивают 50 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>()));
			_maxUpgradeCostReduction = cfg.Bind<float>("Limits (server)", "MaxUpgradeCostReductionPercent", 30f, new ConfigDescription("Предел бонуса upgradeCost (экономия материалов на улучшение вещи; каждый материал округляется вверх, не меньше 1), %. Сумму наград клиенты ограничивают 50 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>()));
			_maxAmmoSave = cfg.Bind<float>("Limits (server)", "MaxAmmoSavePercent", 30f, new ConfigDescription("Предел бонуса ammoSave (шанс не потратить стрелу, болт или другой снаряд при выстреле), %. Сумму наград клиенты ограничивают 50 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>()));
			_maxCraftYield = cfg.Bind<float>("Limits (server)", "MaxCraftYieldPercent", 50f, new ConfigDescription("Предел бонуса craftYield (больше штук за изготовление по рецепту, дающему несколько предметов: стрелы, болты; дробная часть отбрасывается), %. Сумму наград клиенты ограничивают 100 %.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			AnnounceRewardLoss = cfg.Bind<bool>("Announcements (server)", "AnnounceRewardLoss", true, "Объявлять всем игрокам (сообщение по центру экрана, видно и без мода), что награда с onDeath = destroy пропала при смерти игрока. Строка в журнале сервера пишется всегда.");
			RewardLossMessage = cfg.Bind<string>("Announcements (server)", "RewardLossMessage", "Боги смеются: «{reward}» забрала Хель, и {player} вновь начинает путь. Вставай, викинг, сага не окончена!", "Текст объявления; {player} — имя игрока, {reward} — название награды (теги оформления из имён удаляются). Пустая строка — без объявления.");
			AnnounceRevocation = cfg.Bind<bool>("Announcements (server)", "AnnounceRevocation", true, "Объявлять всем игрокам (сообщение по центру экрана, видно и без мода), что у игрока изъяты награды (eventreward revoke). При false сообщение видит только сам владелец. Строка в журнале сервера пишется всегда; изъятие из сундуков и с земли только записывается в журнал.");
			RevocationMessage = cfg.Bind<string>("Announcements (server)", "RevocationMessage", "Суд Форсети: дар «{reward}» возвращается к асам. Владелец — {player}.", "Текст сообщения об изъятии; {player} — имя игрока, {reward} — название награды (несколько — через «, »; теги оформления из имён удаляются). Пустая строка — без сообщения.");
			RevocationRescanMinutes = cfg.Bind<float>("Revocation (server)", "RescanMinutes", 10f, new ConfigDescription("Пока есть действующие изъятия, сервер заново проверяет все сундуки, повозки, корабли, надгробия и предметы на земле раз в столько минут (и сразу после eventreward revoke).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), Array.Empty<object>()));
			DeliveryEnabled = cfg.Bind<bool>("Delivery (server)", "Enabled", true, "Доставка наград из очереди (eventreward give <id> @<код>, автовыдача охоты): сервер кладёт предмет к ногам персонажа, когда тот в игре. При false очередь копится, команды работают, ничего не выкладывается.");
			DeliveryCheckIntervalSeconds = cfg.Bind<float>("Delivery (server)", "CheckIntervalSeconds", 3f, new ConfigDescription("Период проверки игроков в сети (список игроков и доставка), секунды.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>()));
			DeliverySettleSeconds = cfg.Bind<float>("Delivery (server)", "SettleSeconds", 10f, new ConfigDescription("Сколько секунд персонаж должен пробыть в мире после входа, возрождения или телепорта (скачок больше 50 м), прежде чем ему выложат награду.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), Array.Empty<object>()));
			DeliveryMessage = cfg.Bind<string>("Delivery (server)", "Message", "Дар асов: «{reward}» лежит у ваших ног", "Сообщение получателю в центре экрана (ванильное, видно и без мода); {reward} — название награды. Пустая строка — без сообщения.");
		}

		private static ResistLevel MaxResistanceLevel()
		{
			string value = _maxResistance.Value;
			if (Resist.TryParseLimit(value, out var level))
			{
				return level;
			}
			if (_warnedMaxResistance != value)
			{
				_warnedMaxResistance = value;
				Plugin.Log.LogWarning((object)("MaxResistance = '" + value + "' не распознано, используется veryResistant (допустимо: none, slightlyResistant, resistant, veryResistant, immune)"));
			}
			return level;
		}

		public static BonusLimits Limits()
		{
			return new BonusLimits
			{
				MaxCarryWeight = _maxCarryWeight.Value,
				MaxMoveSpeedPercent = _maxMoveSpeed.Value,
				MaxStaminaRegenPercent = _maxStaminaRegen.Value,
				MaxRunStaminaDrainReductionPercent = _maxRunDrainReduction.Value,
				MaxHealth = _maxHealth.Value,
				MaxStamina = _maxStamina.Value,
				MaxDamagePercent = _maxDamage.Value,
				MaxBlockPowerPercent = _maxBlockPower.Value,
				MaxToolTier = _maxToolTier.Value,
				MaxResistance = MaxResistanceLevel(),
				MaxAirJumps = _maxAirJumps.Value,
				MaxEitr = _maxEitr.Value,
				MaxEitrRegenPercent = _maxEitrRegen.Value,
				MaxEitrCostReductionPercent = _maxEitrCostReduction.Value,
				MaxHealthCostReductionPercent = _maxHealthCostReduction.Value,
				MaxSkillLevel = _maxSkillLevel.Value,
				MaxSkillGainPercent = _maxSkillGain.Value,
				MaxDamageSkillPercent = _maxDamageSkill.Value,
				AllowedEffects = BonusLimits.ParseEffectList(_allowedEffects.Value),
				MaxAdrenalinePercent = _maxAdrenaline.Value,
				MaxAdrenalineThresholdReductionPercent = _maxAdrenalineThresholdReduction.Value,
				MaxAdrenalineDurationPercent = _maxAdrenalineDuration.Value,
				MaxAdrenalineOnHit = _maxAdrenalineOnHit.Value,
				MaxShipSpeedPercent = _maxShipSpeed.Value,
				MaxAdrenalineEffectDuration = _maxAdrenalineEffectDuration.Value,
				AllowedAdrenalineEffects = BonusLimits.ParseEffectList(_allowedAdrenalineEffects.Value),
				MaxHealthRegenPercent = _maxHealthRegen.Value,
				MaxFoodStatsPercent = _maxFoodStats.Value,
				MaxFoodDrainReductionPercent = _maxFoodDrainReduction.Value,
				MaxPotionDurationPercent = _maxPotionDuration.Value,
				MaxPotionCooldownReductionPercent = _maxPotionCooldownReduction.Value,
				MaxPotionPowerPercent = _maxPotionPower.Value,
				MaxBuildStaminaReductionPercent = _maxBuildStaminaReduction.Value,
				MaxBuildRange = _maxBuildRange.Value,
				MaxArmorPercent = _maxArmor.Value,
				MaxFallDamageReductionPercent = _maxFallDamageReduction.Value,
				MaxDurabilityPercent = _maxDurability.Value,
				MaxCraftCostReductionPercent = _maxCraftCostReduction.Value,
				MaxUpgradeCostReductionPercent = _maxUpgradeCostReduction.Value,
				MaxAmmoSavePercent = _maxAmmoSave.Value,
				MaxCraftYieldPercent = _maxCraftYield.Value
			};
		}
	}
	internal static class RevocationClient
	{
		private const float PassSeconds = 10f;

		private const float RetrySeconds = 1f;

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

		private static bool _pending;

		private static float _timer;

		public static bool IsRevoked(string rewardId)
		{
			if (rewardId != null && _ids.Count > 0)
			{
				return _ids.Contains(rewardId);
			}
			return false;
		}

		public static void SetIds(IEnumerable<string> ids)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			if (ids != null)
			{
				foreach (string id in ids)
				{
					if (!string.IsNullOrEmpty(id))
					{
						hashSet.Add(id);
					}
				}
			}
			bool num = !hashSet.SetEquals(_ids);
			_ids = hashSet;
			_pending = hashSet.Count > 0;
			_timer = 0f;
			if (num)
			{
				RewardState.MarkDirty();
				Plugin.Log.LogInfo((object)$"Изъятые награды с сервера: {hashSet.Count}");
			}
		}

		public static void Reset()
		{
			_ids = new HashSet<string>(StringComparer.Ordinal);
			_pending = false;
			_timer = 0f;
		}

		public static void Tick(Player player, float dt)
		{
			if (_ids.Count == 0)
			{
				return;
			}
			_timer -= dt;
			if (_timer > 0f)
			{
				return;
			}
			_timer = (_pending ? 1f : 10f);
			if (!CanApply(player))
			{
				return;
			}
			_pending = false;
			try
			{
				Apply(player);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Изъятие наград: " + ex));
			}
		}

		private static bool CanApply(Player player)
		{
			if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && !DeathFatePatch.Holding && DeathFatePatch.InOnDeath == 0)
			{
				return !InventoryGui.IsVisible();
			}
			return false;
		}

		private static void Apply(Player player)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return;
			}
			List<ItemData> list = RevocationMatch.Select(inventory.GetAllItems().ToList(), (ItemData i) => RewardTag.GetId(i.m_customData), _ids);
			if (list.Count == 0)
			{
				return;