Decompiled source of ServerRates v1.7.0

plugins/ServerRates/ServerRates.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ServerRates")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.7.0.0")]
[assembly: AssemblyInformationalVersion("1.7.0+c69a267d1e72e69b997e6c0141bc3b68a2ce29b3")]
[assembly: AssemblyProduct("ServerRates")]
[assembly: AssemblyTitle("ServerRates")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.7.0.0")]
[module: UnverifiableCode]
namespace ServerRates;

internal static class CategoryGroundAmp
{
	private sealed class ItemInfo
	{
		public int Hash;

		public GameObject Prefab;

		public ItemDrop Template;

		public DropClassifier.Kind Kind;

		public int MaxStack;
	}

	private sealed class Pending
	{
		public ItemInfo Info;

		public Vector3 Pos;

		public float Since;

		public int Still;
	}

	private sealed class OwnSpawn
	{
		public int Hash;

		public Vector3 Pos;

		public float Until;

		public int Left;

		public long SentTicks;
	}

	private const float ScanInterval = 0.25f;

	private const float PruneInterval = 30f;

	private const float MaxWait = 8f;

	private const int StillScansNeeded = 2;

	private const float MoveEpsilonSqr = 0.0004f;

	private static readonly int CatFlag = StringExtensionMethods.GetStableHashCode("SR.Cat");

	private static readonly Dictionary<int, ItemInfo> Items = new Dictionary<int, ItemInfo>();

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

	private static readonly Dictionary<ZDOID, Pending> Waiting = new Dictionary<ZDOID, Pending>();

	private static readonly HashSet<ZDOID> Done = new HashSet<ZDOID>();

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

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

	private static readonly List<ZDOID> Scratch = new List<ZDOID>();

	private static float _nextScan;

	private static float _nextPrune;

	private static int _amplified;

	private static readonly List<OwnSpawn> OwnSpawns = new List<OwnSpawn>();

	private const long SpawnTimeSlackTicks = 1000000L;

	public static void LogStatus()
	{
		if (Plugin.Log != null && Rates.GroundAmp != null)
		{
			Plugin.Log.LogInfo((object)("ServerRates loot: GroundAmp=" + Rates.GroundAmp.Display() + " MaxPiles=" + Rates.MaxPiles.Display() + " (rates changed: " + CountNonDefaultLoot() + " of " + Rates.InGroup("loot").Count + " categories)"));
		}
	}

	private static int CountNonDefaultLoot()
	{
		int num = 0;
		foreach (Rate item in Rates.InGroup("loot"))
		{
			if (!item.IsDefault())
			{
				num++;
			}
		}
		return num;
	}

	public static void Tick()
	{
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: 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)
		if (!Rates.Ready || ZDOMan.instance == null || (Object)(object)ZNet.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
		{
			return;
		}
		float time = Time.time;
		if (time < _nextScan)
		{
			return;
		}
		_nextScan = time + 0.25f;
		List<ZNetPeer> peers = ZNet.instance.GetPeers();
		if (peers != null && peers.Count > 0)
		{
			Near.Clear();
			Distant.Clear();
			SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance();
			for (int i = 0; i < peers.Count; i++)
			{
				ZNetPeer val = peers[i];
				if (val != null && val.IsReady())
				{
					ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(val.GetRefPos()), syncedSimulationDistance, Near, Distant);
				}
			}
			bool flag = Rates.GroundAmp.Flag;
			for (int j = 0; j < Near.Count; j++)
			{
				StationBoost.Observe(Near[j], time);
				StationBoost.Info(Near[j], time);
				if (flag)
				{
					Observe(Near[j], time);
				}
			}
			for (int k = 0; k < Distant.Count; k++)
			{
				StationBoost.Observe(Distant[k], time);
				StationBoost.Info(Distant[k], time);
				if (flag)
				{
					Observe(Distant[k], time);
				}
			}
		}
		ProcessWaiting(time);
		if (time >= _nextPrune)
		{
			_nextPrune = time + 30f;
			Prune();
			StationBoost.Prune();
		}
	}

	private static ItemInfo GetInfo(int hash)
	{
		if (Items.TryGetValue(hash, out var value))
		{
			return value;
		}
		if (Ignored.Contains(hash))
		{
			return null;
		}
		GameObject prefab = ZNetScene.instance.GetPrefab(hash);
		ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
		if ((Object)(object)val == (Object)null || val.m_itemData == null || (Object)(object)prefab.GetComponent<Fish>() != (Object)null)
		{
			Ignored.Add(hash);
			return null;
		}
		value = new ItemInfo
		{
			Hash = hash,
			Prefab = prefab,
			Template = val,
			Kind = DropClassifier.Classify(prefab),
			MaxStack = ((val.m_itemData.m_shared == null) ? 1 : Mathf.Max(1, val.m_itemData.m_shared.m_maxStackSize))
		};
		Items[hash] = value;
		return value;
	}

	private static void Observe(ZDO zdo, float now)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: 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_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0115: 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)
		if (zdo == null)
		{
			return;
		}
		int prefab = zdo.GetPrefab();
		if (Ignored.Contains(prefab))
		{
			return;
		}
		ZDOID uid = zdo.m_uid;
		if (Done.Contains(uid) || Waiting.ContainsKey(uid))
		{
			return;
		}
		ItemInfo info = GetInfo(prefab);
		if (info == null)
		{
			return;
		}
		if (IsOwnSpawn(prefab, zdo.GetPosition(), zdo, now))
		{
			Done.Add(uid);
			return;
		}
		if (zdo.GetInt(CatFlag, 0) != 0)
		{
			Done.Add(uid);
			return;
		}
		ItemData val = info.Template.m_itemData.Clone();
		ItemDrop.LoadFromZDO(val, zdo, 0);
		if (val.m_pickedUp || val.m_crafterID != 0L)
		{
			Done.Add(uid);
			return;
		}
		if (Rates.DropMultiplier(info.Kind) <= 1.001f)
		{
			Done.Add(uid);
			return;
		}
		Pending pending = new Pending
		{
			Info = info,
			Pos = zdo.GetPosition(),
			Since = now
		};
		if (ViaClient())
		{
			Amplify(zdo, pending);
			Done.Add(uid);
		}
		else
		{
			Waiting[uid] = pending;
		}
	}

	private static bool ViaClient()
	{
		return string.Equals(Rates.LootSpawn.Text, "Client", StringComparison.OrdinalIgnoreCase);
	}

	private static void ProcessWaiting(float now)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: 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_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: 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_00c9: Unknown result type (might be due to invalid IL or missing references)
		if (Waiting.Count == 0)
		{
			return;
		}
		Scratch.Clear();
		foreach (KeyValuePair<ZDOID, Pending> item in Waiting)
		{
			ZDO zDO = ZDOMan.instance.GetZDO(item.Key);
			if (zDO == null)
			{
				Scratch.Add(item.Key);
				continue;
			}
			Pending value = item.Value;
			Vector3 position = zDO.GetPosition();
			Vector3 val = position - value.Pos;
			bool flag = ((Vector3)(ref val)).sqrMagnitude > 0.0004f;
			value.Pos = position;
			value.Still = ((!flag) ? (value.Still + 1) : 0);
			if (value.Still >= 2 || now - value.Since > 8f)
			{
				Amplify(zDO, value);
				Scratch.Add(item.Key);
			}
		}
		for (int i = 0; i < Scratch.Count; i++)
		{
			Waiting.Remove(Scratch[i]);
			Done.Add(Scratch[i]);
		}
	}

	private static void Amplify(ZDO zdo, Pending p)
	{
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		ItemInfo info = p.Info;
		float num = Mathf.Max(0f, Rates.DropMultiplier(info.Kind));
		ItemData val = info.Template.m_itemData.Clone();
		ItemDrop.LoadFromZDO(val, zdo, 0);
		int num2 = Mathf.Max(1, val.m_stack);
		int num3 = Mathf.RoundToInt((float)num2 * num) - num2;
		try
		{
			zdo.Set(CatFlag, 1, false);
		}
		catch
		{
		}
		if (num3 <= 0)
		{
			return;
		}
		int num4 = Mathf.Max(1, Mathf.RoundToInt(Rates.MaxPiles.Value));
		Quaternion rotation = zdo.GetRotation();
		bool flag = ViaClient();
		int num5 = 0;
		int num6 = 0;
		if (flag)
		{
			int count = Mathf.Min(num3, num4);
			num5 = SpawnViaClient(info, p.Pos, rotation, count);
			num6 = num5;
		}
		else
		{
			while (num3 > 0 && num5 < num4)
			{
				int num7 = Mathf.Min(num3, info.MaxStack);
				num3 -= num7;
				num6 += num7;
				num5++;
				SpawnPile(info, p.Pos, rotation, val, num7);
			}
		}
		if (_amplified++ < 10)
		{
			Plugin.Log.LogInfo((object)("ServerRates loot: " + ((Object)info.Prefab).name + " (" + info.Kind.ToString() + ") x" + Rate.Fmt(num) + ", " + num2 + " -> " + (num2 + num6) + " via " + (flag ? "player client" : "server object") + ", " + num5 + " spawn(s)"));
		}
	}

	private static bool IsOwnSpawn(int hash, Vector3 pos, ZDO zdo, float now)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: 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)
		for (int num = OwnSpawns.Count - 1; num >= 0; num--)
		{
			OwnSpawn ownSpawn = OwnSpawns[num];
			if (now > ownSpawn.Until || ownSpawn.Left <= 0)
			{
				OwnSpawns.RemoveAt(num);
			}
			else if (ownSpawn.Hash == hash)
			{
				Vector3 val = pos - ownSpawn.Pos;
				if (!(val.x * val.x + val.z * val.z > 1f) && !(Mathf.Abs(val.y) > 3f))
				{
					long num2 = zdo.GetLong(ZDOVars.s_spawnTime, 0L);
					if (num2 == 0L || num2 >= ownSpawn.SentTicks - 1000000)
					{
						ownSpawn.Left--;
						return true;
					}
				}
			}
		}
		return false;
	}

	private static ZNetPeer NearestPeer(Vector3 at)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		ZNetPeer result = null;
		float num = float.MaxValue;
		List<ZNetPeer> peers = ZNet.instance.GetPeers();
		int num2 = 0;
		while (peers != null && num2 < peers.Count)
		{
			ZNetPeer val = peers[num2];
			if (val != null && val.IsReady())
			{
				Vector3 val2 = val.GetRefPos() - at;
				float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
				if (sqrMagnitude < num)
				{
					num = sqrMagnitude;
					result = val;
				}
			}
			num2++;
		}
		return result;
	}

	private static int SpawnViaClient(ItemInfo info, Vector3 at, Quaternion rot, int count)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: 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_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: 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)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		ZNetPeer val = NearestPeer(at);
		if (val == null || ZRoutedRpc.instance == null || count <= 0)
		{
			return 0;
		}
		for (int i = 0; i < count; i++)
		{
			Vector2 val2 = Random.insideUnitCircle * 0.5f;
			Vector3 val3 = at + new Vector3(val2.x, 0.5f, val2.y);
			ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "SpawnObject", new object[3] { val3, rot, info.Hash });
		}
		OwnSpawns.Add(new OwnSpawn
		{
			Hash = info.Hash,
			Pos = at,
			Until = Time.time + 6f,
			Left = count,
			SentTicks = ZNet.instance.GetTime().Ticks
		});
		return count;
	}

	private static void SpawnPile(ItemInfo info, Vector3 at, Quaternion rot, ItemData template, int stack)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: 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)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: 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)
		Vector2 val = Random.insideUnitCircle * 0.4f;
		Vector3 val2 = at + new Vector3(val.x, 0.2f, val.y);
		ZDO val3 = ZDOMan.instance.CreateNewZDO(val2, info.Hash);
		val3.SetRotation(rot);
		ZNetView component = info.Prefab.GetComponent<ZNetView>();
		if ((Object)(object)component != (Object)null)
		{
			val3.Persistent = component.m_persistent;
			val3.Distant = component.m_distant;
			val3.SetType(component.m_type);
		}
		val3.Set(CatFlag, 1, false);
		ItemData obj = template.Clone();
		obj.m_stack = stack;
		ItemDrop.SaveToZDO(obj, val3, 0);
		Done.Add(val3.m_uid);
	}

	private static void Prune()
	{
		//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)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		if (Done.Count < 500)
		{
			return;
		}
		Scratch.Clear();
		foreach (ZDOID item in Done)
		{
			if (ZDOMan.instance.GetZDO(item) == null)
			{
				Scratch.Add(item);
			}
		}
		for (int i = 0; i < Scratch.Count; i++)
		{
			Done.Remove(Scratch[i]);
		}
	}
}
internal static class ChatBridge
{
	[HarmonyPatch(typeof(ZNet), "WritePlayerInfo")]
	private static class WritePlayerInfoPatch
	{
		private static void Postfix(ZNet __instance, ref ZPackage __result)
		{
			if (__result == null || !Plugin.Active || !Rates.Ready || !Rates.ChatBridge.Flag || !__instance.IsServer())
			{
				return;
			}
			try
			{
				string text = ServerUserId(__instance);
				if (text != null)
				{
					int realPlayers = Traverse.Create((object)__instance).Field("m_players").GetValue<ICollection>()?.Count ?? 0;
					AppendServerPlayer(__result, realPlayers, ZDOMan.GetSessionID(), text, "ServerRates");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("ServerRates: chat bridge failed: " + ex.Message));
				}
			}
		}
	}

	internal const string DisplayName = "ServerRates";

	private static string _userId;

	private static PlatformUserID? _serverUser;

	private static bool _failed;

	internal static PlatformUserID? ServerUser()
	{
		if ((Object)(object)ZNet.instance != (Object)null)
		{
			ServerUserId(ZNet.instance);
		}
		return _serverUser;
	}

	private unsafe static string ServerUserId(ZNet net)
	{
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		if (_userId != null)
		{
			return _userId;
		}
		if (_failed)
		{
			return null;
		}
		try
		{
			Type type = Type.GetType("Steamworks.SteamGameServer, com.rlabrecque.steamworks.net", throwOnError: false);
			MethodInfo methodInfo = ((type != null) ? type.GetMethod("GetSteamID") : null);
			string text = ((methodInfo != null) ? methodInfo.Invoke(null, null) : null)?.ToString();
			if (string.IsNullOrEmpty(text) || text == "0")
			{
				throw new InvalidOperationException("server Steam id not available");
			}
			PlatformUserID value = default(PlatformUserID);
			((PlatformUserID)(ref value))..ctor(new Platform("Steam"), text);
			string text2 = ((object)(*(PlatformUserID*)(&value))/*cast due to .constrained prefix*/).ToString();
			if (!((PlatformUserID)(ref value)).IsValid || string.IsNullOrEmpty(text2))
			{
				throw new InvalidOperationException("server user id '" + text + "' is not valid");
			}
			_userId = text2;
			_serverUser = value;
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("ServerRates: chat bridge lists the server as 'ServerRates' (" + _userId + ")."));
			}
			return _userId;
		}
		catch (Exception ex)
		{
			_failed = true;
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("ServerRates: chat bridge disabled, cannot build the server user id: " + ex.Message));
			}
			return null;
		}
	}

	internal static bool AppendServerPlayer(ZPackage pkg, int realPlayers, long sessionId, string userId, string name)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		int pos = pkg.GetPos();
		int num = new ZPackage(pkg.GetArray()).ReadInt();
		if (num > realPlayers)
		{
			return false;
		}
		pkg.SetPos(0);
		pkg.Write(num + 1);
		pkg.SetPos(pos);
		pkg.Write(name);
		pkg.Write(new ZDOID(sessionId, uint.MaxValue));
		pkg.Write(userId);
		pkg.Write(name);
		pkg.Write(name);
		pkg.Write("");
		pkg.Write(false);
		return true;
	}
}
internal static class ConfigWatch
{
	private const float PollSeconds = 1f;

	private const float SettleSeconds = 0.8f;

	private const int MaxRetries = 5;

	private static string _path;

	private static DateTime _lastWrite;

	private static float _nextPoll;

	private static float _reloadAt = -1f;

	private static int _retries;

	public static void Start()
	{
		_path = (((Object)(object)Plugin.Instance != (Object)null) ? ((BaseUnityPlugin)Plugin.Instance).Config.ConfigFilePath : null);
		_lastWrite = Stamp();
		if (_path != null)
		{
			Plugin.Log.LogInfo((object)("ServerRates watching config: " + _path));
		}
	}

	public static void NotifySaved()
	{
		_lastWrite = Stamp();
		_reloadAt = -1f;
	}

	public static void Tick()
	{
		if (_path == null)
		{
			return;
		}
		float unscaledTime = Time.unscaledTime;
		if (_reloadAt < 0f)
		{
			if (!(unscaledTime < _nextPoll))
			{
				_nextPoll = unscaledTime + 1f;
				DateTime dateTime = Stamp();
				if (!(dateTime == _lastWrite))
				{
					_lastWrite = dateTime;
					_reloadAt = unscaledTime + 0.8f;
					_retries = 0;
				}
			}
		}
		else if (!(unscaledTime < _reloadAt))
		{
			DateTime dateTime2 = Stamp();
			if (dateTime2 != _lastWrite)
			{
				_lastWrite = dateTime2;
				_reloadAt = unscaledTime + 0.8f;
			}
			else
			{
				_reloadAt = -1f;
				Reload();
			}
		}
	}

	private static DateTime Stamp()
	{
		try
		{
			return (_path != null && File.Exists(_path)) ? File.GetLastWriteTimeUtc(_path) : DateTime.MinValue;
		}
		catch
		{
			return DateTime.MinValue;
		}
	}

	private static void Reload()
	{
		try
		{
			((BaseUnityPlugin)Plugin.Instance).Config.Reload();
		}
		catch (Exception ex)
		{
			if (++_retries <= 5)
			{
				_reloadAt = Time.unscaledTime + 1f;
			}
			else
			{
				Plugin.Log.LogWarning((object)("ServerRates: config reload failed: " + ex.Message));
			}
			return;
		}
		List<Rate> list = Rates.TakeChanges();
		RateApplier.ApplyGlobalKeys();
		CategoryGroundAmp.LogStatus();
		if (list.Count == 0)
		{
			Plugin.Log.LogInfo((object)"ServerRates: config reloaded, nothing changed.");
			return;
		}
		Plugin.Log.LogInfo((object)("ServerRates: config reloaded, " + list.Count + " rate(s) changed."));
		if (Plugin.Active)
		{
			RatesFeedback.AnnounceChanges(list, "config");
		}
	}
}
internal static class DropClassifier
{
	public enum Kind
	{
		Other,
		Wood,
		FineWood,
		CoreWood,
		SpecialWood,
		Ore,
		Scrap,
		Stone,
		Flint,
		Crystal,
		Fuel,
		Gems,
		BossMats,
		Crops,
		Seeds,
		Mushrooms,
		BerriesHoney,
		Fish,
		Potions,
		Hide,
		Trophy,
		Meat,
		Parts,
		Feathers,
		SpecialParts
	}

	private static readonly Dictionary<string, Kind> Exact;

	static DropClassifier()
	{
		Exact = new Dictionary<string, Kind>();
		Add(Kind.FineWood, "finewood");
		Add(Kind.CoreWood, "roundlog");
		Add(Kind.SpecialWood, "elderbark", "yggdrasilwood", "ashwood", "blackwood");
		Add(Kind.Wood, "wood");
		Add(Kind.Ore, "copperore", "tinore", "ironore", "silverore", "flametalore", "flametalorenew");
		Add(Kind.Scrap, "ironscrap", "blackmetalscrap", "copperscrap");
		Add(Kind.Flint, "flint");
		Add(Kind.Crystal, "crystal", "obsidian");
		Add(Kind.Stone, "stone", "grausten", "sharpeningstone", "thunderstone");
		Add(Kind.Fuel, "coal", "resin", "tar");
		Add(Kind.Gems, "coins", "amber", "amberpearl", "ruby", "silvernecklace");
		Add(Kind.BossMats, "surtlingcore", "dragontear", "ancientseed", "ymirremains", "queenbee", "yagluthdrop", "vegvisirshardbonemass", "blackcore", "refinedeitr", "mechanicalspring", "dvergrkeyfragment", "trophybonemass", "trophyeikthyr", "trophytheelder", "trophydragonqueen", "trophygoblinking", "trophyseekerqueen", "trophyfader");
		Add(Kind.Seeds, "carrotseeds", "onionseeds", "turnipseeds", "beechseeds", "birchseeds", "fircone", "pinecone", "acorn", "oakseeds");
		Add(Kind.Mushrooms, "mushroom", "mushroomyellow", "mushroomblue", "magecap");
		Add(Kind.BerriesHoney, "raspberry", "blueberry", "cloudberry", "honey", "bukeperries");
		Add(Kind.Meat, "rawmeat", "necktail", "boarmeat", "deer", "deermeat", "wolfmeat", "loxmeat", "serpentmeat", "chickenmeat", "haremeat", "asksvinmeat", "bugmeat", "meatrotten", "necktailgrilled", "cookedmeat", "cookedbackmeat", "serpentmeatcooked", "cookeddeer", "cookedwolf", "loxpie", "fishcooked");
		Add(Kind.Fish, "fishraw", "fish", "perch", "pike", "tuna", "trollfish", "pufferfish", "anglerfish", "coralcod", "northernsalmon", "magmapander", "voltureegg");
		Add(Kind.Potions, "meadbasehealth", "meadbasehealthminor", "meadbasestamina", "meadbasetasty", "meadbasepoisonresist", "meadbasefrostresist", "meadbaseeitr", "meadbaselight", "potionhealthminor", "potionhealthmedium", "potionstaminaminor", "potionstaminamedium", "potioneitrminor", "poisonbomb");
		Add(Kind.Crops, "barley", "barleyflour", "flax", "carrot", "onion", "turnip", "thistle", "dandelion", "sap", "royaljelly", "jotunpuffs", "smokepuffs");
		Add(Kind.Hide, "deerhide", "leatherscraps", "trollhide", "wolfpelt", "loxpelt", "serpentscale", "chitin", "scalehide", "asksvinhide", "harepelt", "wolfhairbundle");
		Add(Kind.Feathers, "feathers");
		Add(Kind.SpecialParts, "guck", "ooze", "softtissue", "bilebag", "seekerchitin", "carapace", "moltencore", "charredskull", "flametal", "proustitepowder");
		Add(Kind.Parts, "bonefragments", "witheredbone", "entrails", "bloodbag", "needle", "greydwarfeye", "freezegland", "hardantler", "wolffang", "root", "mandible", "chain");
	}

	private static void Add(Kind kind, params string[] names)
	{
		foreach (string key in names)
		{
			if (!Exact.ContainsKey(key))
			{
				Exact[key] = kind;
			}
		}
	}

	public static Kind Classify(GameObject prefab)
	{
		if ((Object)(object)prefab == (Object)null)
		{
			return Kind.Other;
		}
		ItemDrop component = prefab.GetComponent<ItemDrop>();
		if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null)
		{
			Kind kind = ClassifyKey(Normalize(component.m_itemData.m_shared.m_name));
			if (kind != Kind.Other)
			{
				return kind;
			}
		}
		return ClassifyKey(Normalize(((Object)prefab).name));
	}

	private static Kind ClassifyKey(string key)
	{
		if (string.IsNullOrEmpty(key))
		{
			return Kind.Other;
		}
		if (Exact.TryGetValue(key, out var value))
		{
			return value;
		}
		if (key.EndsWith("wood") && !key.Contains("yggdrasil"))
		{
			return Kind.Wood;
		}
		if (key.EndsWith("ore") && !key.EndsWith("core"))
		{
			return Kind.Ore;
		}
		if (key.EndsWith("scrap"))
		{
			return Kind.Scrap;
		}
		if (key.EndsWith("seeds") || key.EndsWith("cone"))
		{
			return Kind.Seeds;
		}
		if (key.Contains("mushroom"))
		{
			return Kind.Mushrooms;
		}
		if (key.Contains("berry"))
		{
			return Kind.BerriesHoney;
		}
		if (key.StartsWith("fish") || key.Contains("salmon") || key.Contains("puffer"))
		{
			return Kind.Fish;
		}
		if (key.Contains("mead") || key.Contains("potion"))
		{
			return Kind.Potions;
		}
		if (key.Contains("trophy"))
		{
			return Kind.Trophy;
		}
		if (key.Contains("hide") || key.Contains("pelt") || key.Contains("leather") || key.Contains("scale"))
		{
			return Kind.Hide;
		}
		if (key.Contains("meat") || key.Contains("necktail"))
		{
			return Kind.Meat;
		}
		if (key.Contains("feather"))
		{
			return Kind.Feathers;
		}
		if (key.Contains("bone") || key.Contains("entrails") || key.Contains("gland") || key.Contains("fang") || key.Contains("eye"))
		{
			return Kind.Parts;
		}
		return Kind.Other;
	}

	private static string Normalize(string raw)
	{
		if (string.IsNullOrEmpty(raw))
		{
			return "";
		}
		string text = raw.Trim().ToLowerInvariant();
		int num = text.IndexOf("(clone)");
		if (num >= 0)
		{
			text = text.Substring(0, num).Trim();
		}
		if (text.StartsWith("$item_"))
		{
			text = text.Substring(6);
		}
		return text.Replace(" ", "").Replace("-", "").Replace("_", "");
	}
}
internal enum RateKind
{
	Percent,
	Multiplier,
	Number,
	Toggle,
	Switch,
	Choice
}
internal sealed class Rate
{
	public string Short;

	public string Key;

	public string Section;

	public string Group;

	public string Label;

	public string Description;

	public RateKind Kind;

	public GlobalKeys? GlobalKey;

	public DropClassifier.Kind? Drop;

	public float Min;

	public float Max = 10000f;

	public bool Integer;

	public float DefaultValue;

	public bool DefaultFlag;

	public string DefaultText = "Unchanged";

	public string[] Options;

	private ConfigEntry<float> _num;

	private ConfigEntry<string> _text;

	private ConfigEntry<bool> _flag;

	public bool IsNumeric
	{
		get
		{
			if (Kind != RateKind.Percent && Kind != RateKind.Multiplier)
			{
				return Kind == RateKind.Number;
			}
			return true;
		}
	}

	public float Value
	{
		get
		{
			if (_num == null)
			{
				return 0f;
			}
			return _num.Value;
		}
	}

	public bool Flag
	{
		get
		{
			if (_flag != null)
			{
				return _flag.Value;
			}
			return false;
		}
	}

	public string Text
	{
		get
		{
			if (_text == null)
			{
				return "";
			}
			return _text.Value;
		}
	}

	internal void Bind(ConfigFile file)
	{
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Expected O, but got Unknown
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: Expected O, but got Unknown
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Expected O, but got Unknown
		switch (Kind)
		{
		case RateKind.Percent:
		case RateKind.Multiplier:
		case RateKind.Number:
		{
			string text = ((Kind == RateKind.Percent) ? " 100 = vanilla." : ((Kind == RateKind.Multiplier) ? " 1 = vanilla." : ""));
			_num = file.Bind<float>(Section, Key, DefaultValue, new ConfigDescription(Description + text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(Min, Max), Array.Empty<object>()));
			break;
		}
		case RateKind.Switch:
			_flag = file.Bind<bool>(Section, Key, DefaultFlag, Description);
			break;
		case RateKind.Toggle:
			Options = new string[3] { "Unchanged", "On", "Off" };
			_text = file.Bind<string>(Section, Key, "Unchanged", new ConfigDescription(Description + " Values: Unchanged | On | Off", (AcceptableValueBase)(object)new AcceptableValueList<string>(Options), Array.Empty<object>()));
			break;
		default:
			_text = file.Bind<string>(Section, Key, DefaultText, new ConfigDescription(Description + " Values: " + string.Join(" | ", Options), (AcceptableValueBase)(object)new AcceptableValueList<string>(Options), Array.Empty<object>()));
			break;
		}
	}

	public static string Fmt(float v)
	{
		return v.ToString("0.###", CultureInfo.InvariantCulture);
	}

	public string Display()
	{
		switch (Kind)
		{
		case RateKind.Percent:
			return Fmt(Value) + "%";
		case RateKind.Multiplier:
			return "x" + Fmt(Value);
		case RateKind.Number:
			return Fmt(Value);
		case RateKind.Switch:
			if (!Flag)
			{
				return "Off";
			}
			return "On";
		default:
			return Text;
		}
	}

	public bool IsDefault()
	{
		switch (Kind)
		{
		case RateKind.Switch:
			return Flag == DefaultFlag;
		case RateKind.Toggle:
		case RateKind.Choice:
			return string.Equals(Text, DefaultText, StringComparison.OrdinalIgnoreCase);
		default:
			return Math.Abs(Value - DefaultValue) < 0.0001f;
		}
	}

	public void Reset()
	{
		switch (Kind)
		{
		case RateKind.Switch:
			_flag.Value = DefaultFlag;
			break;
		case RateKind.Toggle:
		case RateKind.Choice:
			_text.Value = DefaultText;
			break;
		default:
			_num.Value = DefaultValue;
			break;
		}
	}

	public string RangeHint()
	{
		return Kind switch
		{
			RateKind.Switch => "on/off", 
			RateKind.Toggle => "on/off/unchanged", 
			RateKind.Choice => string.Join("/", Options).ToLowerInvariant(), 
			_ => Fmt(Min) + " - " + Fmt(Max), 
		};
	}

	public bool TrySet(string raw, out string error)
	{
		error = null;
		string text = (raw ?? "").Trim().ToLowerInvariant();
		if (IsNumeric)
		{
			text = text.TrimStart('x', '*').TrimEnd('x', '%').Replace(',', '.');
			if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				error = Short + ": number expected (" + RangeHint() + ")";
				return false;
			}
			if (Integer)
			{
				result = (float)Math.Round(result);
			}
			if (result < Min || result > Max)
			{
				error = Short + ": allowed " + RangeHint();
				return false;
			}
			_num.Value = result;
			return true;
		}
		int num;
		switch (text)
		{
		default:
			num = ((text == "1") ? 1 : 0);
			break;
		case "on":
		case "true":
		case "yes":
			num = 1;
			break;
		}
		bool flag = (byte)num != 0;
		int num2;
		switch (text)
		{
		default:
			num2 = ((text == "0") ? 1 : 0);
			break;
		case "off":
		case "false":
		case "no":
			num2 = 1;
			break;
		}
		bool flag2 = (byte)num2 != 0;
		if (Kind == RateKind.Switch)
		{
			if (!flag && !flag2)
			{
				error = Short + ": use on or off";
				return false;
			}
			_flag.Value = flag;
			return true;
		}
		if (Kind == RateKind.Toggle)
		{
			if (flag)
			{
				_text.Value = "On";
				return true;
			}
			if (flag2)
			{
				_text.Value = "Off";
				return true;
			}
			switch (text)
			{
			case "unchanged":
			case "default":
			case "-":
				_text.Value = "Unchanged";
				return true;
			default:
				error = Short + ": use on, off or unchanged";
				return false;
			}
		}
		string[] options = Options;
		foreach (string text2 in options)
		{
			if (string.Equals(text2, text, StringComparison.OrdinalIgnoreCase))
			{
				_text.Value = text2;
				return true;
			}
		}
		error = Short + ": use " + RangeHint();
		return false;
	}
}
internal static class Rates
{
	public static readonly List<Rate> All = new List<Rate>();

	private static readonly Dictionary<string, Rate> ByShort = new Dictionary<string, Rate>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, Rate> ByKey = new Dictionary<string, Rate>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<DropClassifier.Kind, Rate> ByDrop = new Dictionary<DropClassifier.Kind, Rate>();

	public static readonly Dictionary<string, string[]> Groups = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
	{
		{
			"general",
			new string[1] { "general" }
		},
		{
			"skills",
			new string[1] { "skills" }
		},
		{
			"combat",
			new string[1] { "combat" }
		},
		{
			"survival",
			new string[1] { "survival" }
		},
		{
			"resources",
			new string[1] { "resources" }
		},
		{
			"loot",
			new string[3] { "materials", "consumables", "mobs" }
		},
		{
			"materials",
			new string[1] { "materials" }
		},
		{
			"consumables",
			new string[1] { "consumables" }
		},
		{
			"mobs",
			new string[1] { "mobs" }
		},
		{
			"death",
			new string[1] { "death" }
		},
		{
			"build",
			new string[1] { "build" }
		},
		{
			"map",
			new string[1] { "map" }
		},
		{
			"world",
			new string[1] { "world" }
		},
		{
			"stations",
			new string[1] { "stations" }
		}
	};

	public static Rate Enabled;

	public static Rate ChatEnabled;

	public static Rate ChatAccess;

	public static Rate ChatBridge;

	public static Rate StationInfo;

	public static Rate GroundAmp;

	public static Rate MaxPiles;

	public static Rate LootSpawn;

	public static Rate Smelter;

	public static Rate Fermenter;

	public static Rate Cooking;

	public static Rate Plants;

	private static ConfigFile _file;

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

	public static bool Ready
	{
		get
		{
			if (Plugin.Active && Enabled != null)
			{
				return Enabled.Flag;
			}
			return false;
		}
	}

	public static Rate Find(string name)
	{
		if (string.IsNullOrEmpty(name))
		{
			return null;
		}
		if (ByShort.TryGetValue(name, out var value))
		{
			return value;
		}
		if (!ByKey.TryGetValue(name, out value))
		{
			return null;
		}
		return value;
	}

	public static float DropMultiplier(DropClassifier.Kind kind)
	{
		if (!ByDrop.TryGetValue(kind, out var value))
		{
			return 1f;
		}
		return value.Value;
	}

	public static List<Rate> InGroup(string group)
	{
		List<Rate> list = new List<Rate>();
		if (!Groups.TryGetValue(group, out var value))
		{
			return list;
		}
		foreach (Rate item in All)
		{
			if (Array.IndexOf(value, item.Group) >= 0)
			{
				list.Add(item);
			}
		}
		return list;
	}

	public static void Save()
	{
		if (_file != null)
		{
			_file.Save();
		}
	}

	public static Dictionary<string, string> Snapshot()
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		foreach (Rate item in All)
		{
			dictionary[item.Short] = item.Display();
		}
		return dictionary;
	}

	public static List<Rate> TakeChanges()
	{
		Dictionary<string, string> dictionary = Snapshot();
		List<Rate> list = new List<Rate>();
		foreach (Rate item in All)
		{
			if (!_applied.TryGetValue(item.Short, out var value) || value != dictionary[item.Short])
			{
				list.Add(item);
			}
		}
		_applied = dictionary;
		return list;
	}

	public static void Init(ConfigFile file)
	{
		_file = file;
		file.SaveOnConfigSet = false;
		Enabled = Sw("enabled", "Enabled", "1 - General", "general", "ServerRates", def: true, "Master switch. Dedicated server only; vanilla clients need no install.");
		ChatEnabled = Sw("chatcmds", "ChatCommandsEnabled", "1 - General", "general", "Chat commands", def: true, "Allow rate commands in chat (sr_wood 9 / rates status). Console players type WITHOUT a slash.");
		ChatAccess = Ch("chataccess", "ChatAccess", "1 - General", "general", "Chat access", "Everyone", new string[2] { "Everyone", "AdminOnly" }, "Who may CHANGE rates in chat. Everyone = no adminlist needed (console players). AdminOnly = sender must be in the server adminlist. Reading values is always allowed.");
		ChatBridge = Sw("chatbridge", "ChatBridge", "1 - General", "general", "Chat bridge", def: true, "Lists the server as a pseudo player so that clients send their chat to it. Needed for chat commands: without it the server never sees chat while only one player is online.");
		StationInfo = Sw("stationinfo", "StationInfo", "1 - General", "general", "Station info", def: true, "Shows a short message (top left) with the time per item when you stand next to a smelter, kiln, cooking station or fermenter. Turn off with sr_stationinfo off.");
		Pct("skillgain", "SkillGainPercent", "2 - Skills", "skills", "Skill Gain", (GlobalKeys)12, "XP for ALL skills. 200 = x2, 1000 = x10.");
		Pct("skillloss", "SkillReductionPercent", "2 - Skills", "skills", "Skill Loss", (GlobalKeys)13, "Skill loss on death. 0 = none.");
		Pct("playerdmg", "PlayerDamagePercent", "3 - Combat", "combat", "Player Damage", (GlobalKeys)0, "Damage players deal.");
		Pct("enemydmg", "EnemyDamagePercent", "3 - Combat", "combat", "Enemy Damage", (GlobalKeys)1, "Damage enemies deal.");
		Pct("enemyspeed", "EnemySpeedSizePercent", "3 - Combat", "combat", "Enemy Speed", (GlobalKeys)14, "Enemy move speed / size.");
		Pct("enemystars", "EnemyLevelUpPercent", "3 - Combat", "combat", "Enemy Stars", (GlobalKeys)15, "How fast enemies star-up.");
		Pct("events", "EventRatePercent", "3 - Combat", "combat", "Raid Rate", (GlobalKeys)3, "Raid / event frequency. 0 = none.");
		Add(new Rate
		{
			Short = "worldlevel",
			Key = "WorldLevel",
			Section = "3 - Combat",
			Group = "combat",
			Label = "World Level",
			Kind = RateKind.Number,
			Min = 0f,
			Max = 10f,
			Integer = true,
			DefaultValue = 0f,
			GlobalKey = (GlobalKeys)2,
			Description = "World level 0-10. 0 = vanilla start."
		});
		Tg("passivemobs", "PassiveMobs", "3 - Combat", "combat", "Passive Mobs", (GlobalKeys)30, "On = mobs do not attack.");
		Tg("playerevents", "PlayerEvents", "3 - Combat", "combat", "Player Events", (GlobalKeys)17, "Player-based raid events.");
		Pct("stamina", "StaminaPercent", "4 - Survival", "survival", "Stamina", (GlobalKeys)5, "Stamina drain. Lower = less drain.");
		Pct("movestamina", "MoveStaminaPercent", "4 - Survival", "survival", "Move Stamina", (GlobalKeys)10, "Movement stamina drain.");
		Pct("staminaregen", "StaminaRegenPercent", "4 - Survival", "survival", "Stamina Regen", (GlobalKeys)11, "Stamina regen. Higher = faster.");
		Pct("eitr", "EitrPercent", "4 - Survival", "survival", "Eitr", (GlobalKeys)7, "Eitr rate.");
		Pct("adrenaline", "AdrenalinePercent", "4 - Survival", "survival", "Adrenaline", (GlobalKeys)6, "Adrenaline rate.");
		Pct("food", "FoodPercent", "4 - Survival", "survival", "Food Duration", (GlobalKeys)9, "Food duration scaling.");
		Pct("durability", "DurabilityPercent", "4 - Survival", "survival", "Durability", (GlobalKeys)8, "Tool/weapon durability loss. Lower = lasts longer.");
		Pct("carry", "CarryWeightPercent", "4 - Survival", "survival", "Carry Weight", (GlobalKeys)16, "Carry weight. 200 = x2 capacity.");
		Pct("resourcerate", "ResourceRatePercent", "5 - Resources", "resources", "Resource Rate", (GlobalKeys)4, "GLOBAL drop rate (vanilla world modifier ResourceRate). Synced to all clients. Category multipliers stack on top.");
		GroundAmp = Sw("groundamp", "CategoryGroundAmp", "6 - Loot", "resources", "Ground Amp", def: true, "Server multiplies categorized ground loot after it dropped (works for vanilla/PS5/Xbox clients). Player-dropped items are skipped.");
		MaxPiles = new Rate
		{
			Short = "maxpiles",
			Key = "MaxExtraPiles",
			Section = "6 - Loot",
			Group = "resources",
			Label = "Max Piles",
			Kind = RateKind.Number,
			Min = 1f,
			Max = 200f,
			Integer = true,
			DefaultValue = 20f,
			Description = "Safety cap: max extra items (LootSpawn=Client) or piles (LootSpawn=Server) created per single drop."
		};
		Add(MaxPiles);
		LootSpawn = Ch("lootspawn", "LootSpawn", "6 - Loot", "resources", "Loot spawn", "Client", new string[2] { "Client", "Server" }, "How extra loot is created. Client = the server asks the nearest player's game to spawn it (real ground and physics, recommended). Server = the server creates the item objects itself.");
		Drp("other", "OtherDropMultiplier", "6 - Loot", "resources", "Other Loot", DropClassifier.Kind.Other, "Anything not matched below. Keep at 1 unless you want everything amplified.");
		Drp("wood", "WoodDropMultiplier", "6a - Materials", "materials", "Wood", DropClassifier.Kind.Wood, "Basic wood.");
		Drp("finewood", "FineWoodDropMultiplier", "6a - Materials", "materials", "Fine Wood", DropClassifier.Kind.FineWood, "Fine wood.");
		Drp("corewood", "CoreWoodDropMultiplier", "6a - Materials", "materials", "Core Wood", DropClassifier.Kind.CoreWood, "Core wood (RoundLog).");
		Drp("specialwood", "SpecialWoodDropMultiplier", "6a - Materials", "materials", "Special Wood", DropClassifier.Kind.SpecialWood, "Elder bark, Yggdrasil, Ash, Black wood.");
		Drp("ore", "OreDropMultiplier", "6a - Materials", "materials", "Ore", DropClassifier.Kind.Ore, "Raw ores.");
		Drp("scrap", "ScrapDropMultiplier", "6a - Materials", "materials", "Scrap", DropClassifier.Kind.Scrap, "Iron/black metal/copper scrap.");
		Drp("stone", "StoneDropMultiplier", "6a - Materials", "materials", "Stone", DropClassifier.Kind.Stone, "Stone and similar.");
		Drp("flint", "FlintDropMultiplier", "6a - Materials", "materials", "Flint", DropClassifier.Kind.Flint, "Flint.");
		Drp("crystal", "CrystalDropMultiplier", "6a - Materials", "materials", "Crystal", DropClassifier.Kind.Crystal, "Crystal, obsidian.");
		Drp("fuel", "FuelDropMultiplier", "6a - Materials", "materials", "Fuel", DropClassifier.Kind.Fuel, "Coal, resin, tar.");
		Drp("gems", "GemDropMultiplier", "6a - Materials", "materials", "Gems", DropClassifier.Kind.Gems, "Coins, amber, rubies, necklace.");
		Drp("boss", "BossDropMultiplier", "6a - Materials", "materials", "Boss Mats", DropClassifier.Kind.BossMats, "Boss materials and boss trophies.");
		Drp("crops", "CropDropMultiplier", "6b - Consumables", "consumables", "Crops", DropClassifier.Kind.Crops, "Crops / plants that spawn as ground items.");
		Drp("seeds", "SeedDropMultiplier", "6b - Consumables", "consumables", "Seeds", DropClassifier.Kind.Seeds, "Seeds and cones.");
		Drp("mushrooms", "MushroomDropMultiplier", "6b - Consumables", "consumables", "Mushrooms", DropClassifier.Kind.Mushrooms, "Mushrooms / magecap.");
		Drp("berries", "BerryHoneyDropMultiplier", "6b - Consumables", "consumables", "Berries", DropClassifier.Kind.BerriesHoney, "Berries, honey.");
		Drp("fish", "FishDropMultiplier", "6b - Consumables", "consumables", "Fish", DropClassifier.Kind.Fish, "Fish and similar.");
		Drp("potions", "PotionDropMultiplier", "6b - Consumables", "consumables", "Potions", DropClassifier.Kind.Potions, "Meads / potions on the ground.");
		Drp("hide", "HideDropMultiplier", "6c - Mob Drops", "mobs", "Hide", DropClassifier.Kind.Hide, "Hides, leather, pelts, scales.");
		Drp("trophy", "TrophyDropMultiplier", "6c - Mob Drops", "mobs", "Trophy", DropClassifier.Kind.Trophy, "Normal mob trophies.");
		Drp("meat", "MeatDropMultiplier", "6c - Mob Drops", "mobs", "Meat", DropClassifier.Kind.Meat, "Raw / cooked meat.");
		Drp("parts", "PartsDropMultiplier", "6c - Mob Drops", "mobs", "Parts", DropClassifier.Kind.Parts, "Bones, entrails, eyes, glands, fangs.");
		Drp("feathers", "FeatherDropMultiplier", "6c - Mob Drops", "mobs", "Feathers", DropClassifier.Kind.Feathers, "Feathers.");
		Drp("specialparts", "SpecialPartsDropMultiplier", "6c - Mob Drops", "mobs", "Special Parts", DropClassifier.Kind.SpecialParts, "Guck, ooze, soft tissue, carapace, bile, ...");
		Tg("keepequip", "DeathKeepEquip", "7 - Death", "death", "Keep Equipment", (GlobalKeys)19, "Keep equipped gear on death.");
		Tg("keepinv", "DeathKeepInventory", "7 - Death", "death", "Keep Inventory", (GlobalKeys)23, "Keep full inventory on death.");
		Tg("deleteitems", "DeathDeleteItems", "7 - Death", "death", "Delete Items", (GlobalKeys)20, "Delete items on death.");
		Tg("deleteunequipped", "DeathDeleteUnequipped", "7 - Death", "death", "Delete Unequipped", (GlobalKeys)21, "Delete unequipped items on death.");
		Tg("skillreset", "DeathSkillsReset", "7 - Death", "death", "Skill Reset", (GlobalKeys)22, "Reset skills on death.");
		Tg("nobuildcost", "NoBuildCost", "8 - Build Craft", "build", "Free Building", (GlobalKeys)24, "Building costs nothing.");
		Tg("nocraftcost", "NoCraftCost", "8 - Build Craft", "build", "Free Crafting", (GlobalKeys)25, "Crafting costs nothing.");
		Tg("allpieces", "AllPiecesUnlocked", "8 - Build Craft", "build", "All Pieces", (GlobalKeys)26, "All build pieces unlocked.");
		Tg("allrecipes", "AllRecipesUnlocked", "8 - Build Craft", "build", "All Recipes", (GlobalKeys)28, "All recipes unlocked.");
		Tg("noworkbench", "NoWorkbench", "8 - Build Craft", "build", "No Workbench", (GlobalKeys)27, "No workbench range required.");
		Tg("toollocks", "WorldLevelLockedTools", "8 - Build Craft", "build", "Tool Locks", (GlobalKeys)29, "World-level tool locks.");
		Tg("nomap", "NoMap", "9 - Map Portals", "map", "No Map", (GlobalKeys)31, "Disable map.");
		Tg("noportals", "NoPortals", "9 - Map Portals", "map", "No Portals", (GlobalKeys)32, "Disable portals.");
		Tg("nobossportals", "NoBossPortals", "9 - Map Portals", "map", "No Boss Portals", (GlobalKeys)33, "Disable boss portals.");
		Tg("teleportall", "TeleportAll", "9 - Map Portals", "map", "Teleport All", (GlobalKeys)35, "Teleport with ores/metals.");
		Tg("dungeonbuild", "DungeonBuild", "9 - Map Portals", "map", "Dungeon Build", (GlobalKeys)34, "Allow building in dungeons.");
		Tg("nopseudo", "NoPseudoDrops", "10 - World Flags", "world", "No Pseudo Drops", (GlobalKeys)36, "Disable pseudo drops.");
		Tg("nobuildfall", "NoBuildingFall", "10 - World Flags", "world", "No Building Fall", (GlobalKeys)37, "Buildings do not fall.");
		Tg("noheavysnow", "NoHeavySnow", "10 - World Flags", "world", "No Heavy Snow", (GlobalKeys)38, "No heavy snow.");
		Tg("allheavysnow", "AllHeavySnow", "10 - World Flags", "world", "All Heavy Snow", (GlobalKeys)39, "Force heavy snow rules.");
		Tg("fire", "Fire", "10 - World Flags", "world", "Fire", (GlobalKeys)18, "Fire-related world flag.");
		Smelter = Mul("smelter", "SmelterSpeedMultiplier", "11 - Stations", "stations", "Smelter", "Smelter / kiln / furnace speed. 2 = twice as fast. Values below 1 are ignored.");
		Cooking = Mul("cooking", "CookingSpeedMultiplier", "11 - Stations", "stations", "Cooking", "Cooking station speed. 2 = twice as fast. Values below 1 are ignored.");
		Fermenter = Mul("fermenter", "FermenterSpeedMultiplier", "11 - Stations", "stations", "Fermenter", "Fermenter speed. 2 = twice as fast, 0.5 = half as fast. Applies to batches started after the change.");
		Plants = Mul("plants", "PlantGrowSpeedMultiplier", "11 - Stations", "stations", "Plant Grow", "Plant grow speed. 2 = twice as fast, 0.5 = half as fast. Applies to plants planted after the change.");
		foreach (Rate item in All)
		{
			item.Bind(file);
		}
		file.Save();
		_applied = Snapshot();
	}

	private static Rate Add(Rate r)
	{
		All.Add(r);
		ByShort[r.Short] = r;
		ByKey[r.Key] = r;
		if (r.Drop.HasValue)
		{
			ByDrop[r.Drop.Value] = r;
		}
		return r;
	}

	private static Rate Pct(string sh, string key, string sec, string grp, string label, GlobalKeys gk, string desc)
	{
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		return Add(new Rate
		{
			Short = sh,
			Key = key,
			Section = sec,
			Group = grp,
			Label = label,
			Description = desc,
			Kind = RateKind.Percent,
			Min = 0f,
			Max = 10000f,
			DefaultValue = 100f,
			GlobalKey = gk
		});
	}

	private static Rate Mul(string sh, string key, string sec, string grp, string label, string desc)
	{
		return Add(new Rate
		{
			Short = sh,
			Key = key,
			Section = sec,
			Group = grp,
			Label = label,
			Description = desc,
			Kind = RateKind.Multiplier,
			Min = 0f,
			Max = 100f,
			DefaultValue = 1f
		});
	}

	private static Rate Drp(string sh, string key, string sec, string grp, string label, DropClassifier.Kind kind, string desc)
	{
		return Add(new Rate
		{
			Short = sh,
			Key = key,
			Section = sec,
			Group = grp,
			Label = label,
			Description = desc,
			Kind = RateKind.Multiplier,
			Min = 0f,
			Max = 100f,
			DefaultValue = 1f,
			Drop = kind
		});
	}

	private static Rate Tg(string sh, string key, string sec, string grp, string label, GlobalKeys gk, string desc)
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		return Add(new Rate
		{
			Short = sh,
			Key = key,
			Section = sec,
			Group = grp,
			Label = label,
			Description = desc,
			Kind = RateKind.Toggle,
			GlobalKey = gk
		});
	}

	private static Rate Sw(string sh, string key, string sec, string grp, string label, bool def, string desc)
	{
		return Add(new Rate
		{
			Short = sh,
			Key = key,
			Section = sec,
			Group = grp,
			Label = label,
			Description = desc,
			Kind = RateKind.Switch,
			DefaultFlag = def
		});
	}

	private static Rate Ch(string sh, string key, string sec, string grp, string label, string def, string[] options, string desc)
	{
		return Add(new Rate
		{
			Short = sh,
			Key = key,
			Section = sec,
			Group = grp,
			Label = label,
			Description = desc,
			Kind = RateKind.Choice,
			DefaultText = def,
			Options = options
		});
	}
}
[HarmonyPatch(typeof(ZNet), "Awake")]
internal static class ZNetAwakePatch
{
	private static void Postfix(ZNet __instance)
	{
		Plugin.TryActivate(__instance);
	}
}
[HarmonyPatch(typeof(ZoneSystem), "Start")]
internal static class ZoneSystemStartPatch
{
	private static void Postfix()
	{
		if ((Object)(object)ZNet.instance != (Object)null)
		{
			Plugin.TryActivate(ZNet.instance);
		}
		RateApplier.ApplyGlobalKeys();
	}
}
[HarmonyPatch(typeof(Game), "UpdateWorldRates")]
internal static class UpdateWorldRatesPatch
{
	private static void Postfix()
	{
		RateApplier.ApplyGlobalKeys();
	}
}
[BepInPlugin("com.morda.serverrates", "ServerRates", "1.7.0")]
public class Plugin : BaseUnityPlugin
{
	public const string ModGuid = "com.morda.serverrates";

	public const string ModName = "ServerRates";

	public const string ModVersion = "1.7.0";

	public const string ModAuthor = "Morda";

	private Harmony _harmony;

	private float _nextActivationCheck;

	private static bool _warned;

	internal static Plugin Instance { get; private set; }

	internal static ManualLogSource Log { get; private set; }

	internal static bool Active { get; private set; }

	private void Awake()
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		Rates.Init(((BaseUnityPlugin)this).Config);
		_harmony = new Harmony("com.morda.serverrates");
		_harmony.PatchAll(Assembly.GetExecutingAssembly());
		LogPatchOwners(typeof(Chat), "RPC_ChatMessage");
		LogPatchOwners(typeof(ZRoutedRpc), "RPC_RoutedRPC");
		LogPatchOwners(typeof(ZRoutedRpc), "RouteRPC");
		LogPatchOwners(typeof(ZRoutedRpc), "HandleRoutedRPC");
		ConfigWatch.Start();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"ServerRates v1.7.0 by Morda loaded (dedicated server only, vanilla clients OK).");
	}

	private static void LogPatchOwners(Type type, string method)
	{
		try
		{
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)AccessTools.Method(type, method, (Type[])null, (Type[])null));
			if (patchInfo == null)
			{
				Log.LogWarning((object)("ServerRates: " + type.Name + "." + method + " is not patched!"));
				return;
			}
			List<string> list = new List<string>();
			foreach (Patch prefix in patchInfo.Prefixes)
			{
				string owner = prefix.owner;
				int priority = prefix.priority;
				list.Add(owner + "(prio " + priority + ")");
			}
			Log.LogInfo((object)("ServerRates: " + type.Name + "." + method + " prefixes: " + string.Join(", ", list.ToArray())));
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("ServerRates: patch check failed for " + method + ": " + ex.Message));
		}
	}

	private void Update()
	{
		if (!Active && (Object)(object)ZNet.instance != (Object)null && Time.unscaledTime >= _nextActivationCheck)
		{
			_nextActivationCheck = Time.unscaledTime + 2f;
			TryActivate(ZNet.instance);
		}
		ConfigWatch.Tick();
		if (Active)
		{
			CategoryGroundAmp.Tick();
		}
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		if ((Object)(object)Instance == (Object)(object)this)
		{
			Instance = null;
		}
		Active = false;
	}

	internal static void TryActivate(ZNet net)
	{
		RatesCommands.Register();
		if ((Object)(object)net == (Object)null || !net.IsDedicated())
		{
			Active = false;
			if (!_warned && (Object)(object)net != (Object)null)
			{
				_warned = true;
				Log.LogWarning((object)("ServerRates: not a dedicated server (IsServer=" + net.IsServer() + ", IsDedicated=" + net.IsDedicated() + "). Rates are inactive."));
			}
		}
		else
		{
			if (!Active)
			{
				Log.LogInfo((object)"ServerRates: dedicated server detected, rates enabled.");
			}
			Active = true;
			RateApplier.ApplyGlobalKeys();
			CategoryGroundAmp.LogStatus();
		}
	}
}
internal static class RateApplier
{
	private static bool _busy;

	private static float _lastLog = -100f;

	public static void ApplyGlobalKeys()
	{
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		if (_busy || !Rates.Ready || (Object)(object)ZoneSystem.instance == (Object)null)
		{
			return;
		}
		_busy = true;
		try
		{
			int num = 0;
			foreach (Rate item in Rates.All)
			{
				if (item.GlobalKey.HasValue && ((item.Kind == RateKind.Toggle) ? SetToggle(item.GlobalKey.Value, item.Text) : SetScalar(item.GlobalKey.Value, item.Value)))
				{
					num++;
				}
			}
			if (num > 0)
			{
				string text = "ServerRates: " + num + " world modifier(s) applied.";
				if (Time.unscaledTime - _lastLog > 10f)
				{
					Plugin.Log.LogInfo((object)text);
				}
				else
				{
					Plugin.Log.LogDebug((object)text);
				}
				_lastLog = Time.unscaledTime;
			}
		}
		finally
		{
			_busy = false;
		}
	}

	private static bool SetScalar(GlobalKeys key, float value)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		value = Mathf.Max(0f, value);
		float num = default(float);
		if (ZoneSystem.instance.GetGlobalKey(key, ref num) && Mathf.Abs(num - value) < 0.001f)
		{
			return false;
		}
		ZoneSystem.instance.SetGlobalKey(key, value);
		return true;
	}

	private static bool SetToggle(GlobalKeys key, string mode)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		bool globalKey = ZoneSystem.instance.GetGlobalKey(key);
		if (string.Equals(mode, "On", StringComparison.OrdinalIgnoreCase))
		{
			if (globalKey)
			{
				return false;
			}
			ZoneSystem.instance.SetGlobalKey(key);
			return true;
		}
		if (string.Equals(mode, "Off", StringComparison.OrdinalIgnoreCase))
		{
			if (!globalKey)
			{
				return false;
			}
			ZoneSystem.instance.RemoveGlobalKey(key);
			return true;
		}
		return false;
	}
}
internal static class RatesChat
{
	[HarmonyPatch(typeof(Chat), "InputText")]
	private static class ChatInputPatch
	{
		private static bool Prefix(Chat __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			object value = Traverse.Create((object)__instance).Field("m_input").GetValue();
			if (value == null)
			{
				return true;
			}
			string value2 = Traverse.Create(value).Property("text", (object[])null).GetValue<string>();
			if (string.IsNullOrWhiteSpace(value2))
			{
				return true;
			}
			string text = value2.Trim();
			if (text[0] != '/' || !TryGetRatesBody(text, out var _))
			{
				return true;
			}
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				__instance.SendText((Type)1, text);
			}
			Traverse.Create(value).Property("text", (object[])null).SetValue((object)"");
			GameObject value3 = Traverse.Create(value).Property("gameObject", (object[])null).GetValue<GameObject>();
			if ((Object)(object)value3 != (Object)null)
			{
				value3.SetActive(false);
			}
			return false;
		}
	}

	[HarmonyPriority(800)]
	[HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")]
	private static class RoutedRpcEntryPatch
	{
		private static bool Prefix(ZRpc rpc, ZPackage pkg)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			if (pkg == null || !Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return true;
			}
			try
			{
				RoutedRPCData val = new RoutedRPCData();
				val.Deserialize(new ZPackage(pkg.GetArray()));
				if (!TryParse(val, out var position, out var user, out var text))
				{
					return true;
				}
				if (val.m_methodHash == SayHash)
				{
					position = PositionOf(val.m_senderPeerID);
				}
				return !Intercept("RPC_RoutedRPC", val.m_senderPeerID, position, user, text);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("ServerRates: could not inspect routed RPC: " + ex.Message));
				}
				return true;
			}
		}
	}

	[HarmonyPriority(800)]
	[HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")]
	private static class RouteRpcPatch
	{
		private static bool Prefix(RoutedRPCData rpcData)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return true;
			}
			if (!TryParse(rpcData, out var position, out var user, out var text))
			{
				return true;
			}
			if (rpcData.m_methodHash == SayHash)
			{
				position = PositionOf(rpcData.m_senderPeerID);
			}
			return !Intercept("RouteRPC", rpcData.m_senderPeerID, position, user, text);
		}
	}

	[HarmonyPriority(800)]
	[HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")]
	private static class HandleRoutedRpcPatch
	{
		private static bool Prefix(RoutedRPCData data)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if (data == null || data.m_methodHash != ChatMessageHash)
			{
				return true;
			}
			if (!Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return true;
			}
			if (!TryParse(data, out var position, out var user, out var text))
			{
				return true;
			}
			return !Intercept("HandleRoutedRPC", data.m_senderPeerID, position, user, text);
		}
	}

	[HarmonyPatch(typeof(Chat), "RPC_ChatMessage")]
	private static class ChatRpcPatch
	{
		private static bool Prefix(long sender, Vector3 position, int type, UserInfo userInfo, string text)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return !Intercept("RPC_ChatMessage", sender, position, userInfo, text);
		}
	}

	private static readonly int SayHash = StringExtensionMethods.GetStableHashCode("Say");

	private static readonly int ChatMessageHash = StringExtensionMethods.GetStableHashCode("ChatMessage");

	private static long _lastSender;

	private static string _lastText;

	private static float _lastTime = -10f;

	private static bool _lastHandled;

	private static int _seen;

	internal static bool TryGetRatesBody(string text, out string body)
	{
		body = null;
		if (string.IsNullOrWhiteSpace(text))
		{
			return false;
		}
		string text2 = text.Trim();
		if (text2[0] == '/' || text2[0] == '!')
		{
			text2 = text2.Substring(1).Trim();
		}
		if (text2.Length == 0)
		{
			return false;
		}
		string text3 = text2.ToLowerInvariant();
		if (!text3.StartsWith("sr_") && !text3.StartsWith("sr ") && !(text3 == "rates") && !text3.StartsWith("rates "))
		{
			return false;
		}
		body = text2;
		return true;
	}

	private static bool CanChange(UserInfo user)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		if (!string.Equals(Rates.ChatAccess.Text, "AdminOnly", StringComparison.OrdinalIgnoreCase))
		{
			return true;
		}
		try
		{
			return user != null && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.PlayerIsAdmin(user.UserId);
		}
		catch (Exception ex)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("ServerRates admin check failed: " + ex.Message));
			}
			return false;
		}
	}

	internal static bool TryHandle(long sender, Vector3 position, UserInfo userInfo, string text)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: 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_010d: Unknown result type (might be due to invalid IL or missing references)
		if (!TryGetRatesBody(text, out var body))
		{
			return false;
		}
		if (!Rates.Ready)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("ServerRates: chat command ignored, mod inactive (Active=" + Plugin.Active + ", Enabled=" + Rates.Enabled.Display() + ")."));
			}
			return false;
		}
		if (!Rates.ChatEnabled.Flag)
		{
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)"ServerRates: chat command ignored, ChatCommandsEnabled=false in the config.");
			}
			RatesFeedback.ReplyChat(sender, "ServerRates: chat commands are switched off (ChatCommandsEnabled=false).", position);
			return false;
		}
		string text2 = ((userInfo != null && !string.IsNullOrEmpty(userInfo.Name)) ? userInfo.Name : "player");
		ManualLogSource log3 = Plugin.Log;
		if (log3 != null)
		{
			log3.LogInfo((object)("ServerRates chat command from " + text2 + ": " + body));
		}
		try
		{
			RatesCommands.Process(body, new CommandContext
			{
				Who = text2,
				CanWrite = CanChange(userInfo),
				Position = position,
				Reply = delegate(string line)
				{
					//IL_0008: Unknown result type (might be due to invalid IL or missing references)
					RatesFeedback.ReplyChat(sender, line, position);
				}
			});
		}
		catch (Exception ex)
		{
			ManualLogSource log4 = Plugin.Log;
			if (log4 != null)
			{
				log4.LogError((object)("ServerRates command failed: " + ex));
			}
			RatesFeedback.ReplyChat(sender, "ServerRates: command failed, see server log.", position);
		}
		return true;
	}

	private static bool IsFromServer(long sender)
	{
		if ((Object)(object)ZNet.instance != (Object)null)
		{
			if (sender != ZNet.GetUID())
			{
				if (ZDOMan.instance != null)
				{
					return sender == ZDOMan.GetSessionID();
				}
				return false;
			}
			return true;
		}
		return false;
	}

	private static bool TryParse(RoutedRPCData data, out Vector3 position, out UserInfo user, out string text)
	{
		//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_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Expected O, but got Unknown
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Expected O, but got Unknown
		//IL_006a: 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)
		position = Vector3.zero;
		user = null;
		text = null;
		if (data == null || data.m_parameters == null)
		{
			return false;
		}
		if (data.m_methodHash != SayHash && data.m_methodHash != ChatMessageHash)
		{
			return false;
		}
		if (IsFromServer(data.m_senderPeerID))
		{
			return false;
		}
		try
		{
			ZPackage val = new ZPackage(data.m_parameters.GetArray());
			if (data.m_methodHash == ChatMessageHash)
			{
				position = val.ReadVector3();
			}
			val.ReadInt();
			user = new UserInfo();
			user.Deserialize(ref val);
			text = val.ReadString();
			return true;
		}
		catch (Exception ex)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("ServerRates: could not read chat RPC: " + ex.Message));
			}
			return false;
		}
	}

	internal static bool Intercept(string via, long sender, Vector3 position, UserInfo user, string text)
	{
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ZNet.instance != (Object)null && (sender == ZNet.GetUID() || (ZDOMan.instance != null && sender == ZDOMan.GetSessionID())))
		{
			return false;
		}
		if (_seen < 8)
		{
			_seen++;
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("ServerRates saw chat via " + via + " from " + sender + ": " + text));
			}
		}
		if (!TryGetRatesBody(text, out var _))
		{
			return false;
		}
		float unscaledTime = Time.unscaledTime;
		if (sender == _lastSender && text == _lastText && unscaledTime - _lastTime < 0.5f)
		{
			_lastTime = unscaledTime;
			return _lastHandled;
		}
		_lastSender = sender;
		_lastText = text;
		_lastTime = unscaledTime;
		_lastHandled = TryHandle(sender, position, user, text);
		return _lastHandled;
	}

	private static Vector3 PositionOf(long peerUid)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeer(peerUid) : null);
			return (val != null) ? val.GetRefPos() : Vector3.zero;
		}
		catch
		{
			return Vector3.zero;
		}
	}
}
internal sealed class CommandContext
{
	public string Who = "console";

	public bool CanWrite = true;

	public Action<string> Reply;

	public Vector3? Position;
}
internal static class RatesCommands
{
	private static readonly Dictionary<string, string> GroupAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
	{
		{ "mob", "mobs" },
		{ "mobdrops", "mobs" },
		{ "drops", "loot" },
		{ "mats", "materials" },
		{ "station", "stations" },
		{ "resource", "resources" },
		{ "res", "resources" }
	};

	private static bool _registered;

	public static void Process(string line, CommandContext ctx)
	{
		if (!Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated())
		{
			ctx.Reply("ServerRates: commands only work on the dedicated server.");
			return;
		}
		string[] array = Tokens(line);
		if (array.Length == 0)
		{
			Help(ctx);
			return;
		}
		string text = array[0].ToLowerInvariant();
		if (text.StartsWith("sr_"))
		{
			Target(text.Substring(3), array, 1, ctx);
			return;
		}
		if (text != "rates" && text != "sr")
		{
			Help(ctx);
			return;
		}
		if (array.Length == 1)
		{
			Help(ctx);
			return;
		}
		string text2 = array[1].ToLowerInvariant();
		switch (text2)
		{
		case "help":
		case "?":
			Help(ctx);
			break;
		case "status":
			Status(ctx);
			break;
		case "list":
		case "keys":
			ListRates(ctx, (array.Length > 2) ? array[2] : null);
			break;
		case "cmds":
		case "commands":
			Commands(ctx);
			break;
		case "get":
			if (array.Length < 3)
			{
				ctx.Reply("Usage: rates get <name>");
			}
			else
			{
				Target(array[2].ToLowerInvariant(), array, 3, ctx, readOnly: true);
			}
			break;
		case "set":
		case "apply":
			if (array.Length < 4)
			{
				ctx.Reply("Usage: rates set <name> <value>");
			}
			else
			{
				Target(array[2].ToLowerInvariant(), array, 3, ctx);
			}
			break;
		case "reset":
			Reset(ctx, (array.Length > 2) ? array[2].ToLowerInvariant() : null);
			break;
		case "inspect":
			StationBoost.Inspect(ctx, repair: false);
			break;
		case "repair":
			if (!ctx.CanWrite)
			{
				ctx.Reply("Only server admins can do this (ChatAccess=AdminOnly).");
			}
			else
			{
				StationBoost.Inspect(ctx, repair: true);
			}
			break;
		case "preset":
			Preset(ctx, (array.Length > 2) ? array[2].ToLowerInvariant() : null);
			break;
		default:
			Target(text2, array, 2, ctx);
			break;
		}
	}

	private static string[] Tokens(string line)
	{
		if (string.IsNullOrWhiteSpace(line))
		{
			return new string[0];
		}
		return line.Replace('=', ' ').Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
	}

	private static string Join(string[] t, int from)
	{
		if (from < t.Length)
		{
			return string.Join(" ", t, from, t.Length - from);
		}
		return "";
	}

	private static bool Resolve(string name, CommandContext ctx, out List<Rate> rates, out string group)
	{
		rates = null;
		group = null;
		name = name.ToLowerInvariant();
		if (name.StartsWith("sr_"))
		{
			name = name.Substring(3);
		}
		Rate rate = Rates.Find(name);
		if (rate != null)
		{
			rates = new List<Rate> { rate };
			return true;
		}
		string text = name;
		if (GroupAliases.TryGetValue(name, out var value))
		{
			text = value;
		}
		if (Rates.Groups.ContainsKey(text))
		{
			group = text.ToLowerInvariant();
			rates = Rates.InGroup(group);
			return true;
		}
		List<Rate> list = new List<Rate>();
		List<Rate> list2 = new List<Rate>();
		foreach (Rate item in Rates.All)
		{
			if (item.Short.StartsWith(name, StringComparison.OrdinalIgnoreCase))
			{
				list.Add(item);
			}
			else if (item.Short.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				list2.Add(item);
			}
		}
		List<Rate> list3 = ((list.Count > 0) ? list : list2);
		if (list3.Count == 1)
		{
			rates = list3;
			return true;
		}
		if (list3.Count > 1)
		{
			List<string> list4 = new List<string>();
			for (int i = 0; i < list3.Count && i < 8; i++)
			{
				list4.Add(list3[i].Short);
			}
			ctx.Reply("'" + name + "' could be: " + string.Join(", ", list4.ToArray()));
			return false;
		}
		ctx.Reply("Unknown '" + name + "'. Try: rates cmds");
		return false;
	}

	private static void Target(string name, string[] t, int valueFrom, CommandContext ctx, bool readOnly = false)
	{
		if (!Resolve(name, ctx, out var rates, out var group))
		{
			return;
		}
		string text = Join(t, valueFrom);
		if (readOnly || text.Length == 0)
		{
			Show(ctx, rates, group);
			return;
		}
		if (!ctx.CanWrite)
		{
			ctx.Reply("Only server admins can change rates here (ChatAccess=AdminOnly).");
			return;
		}
		string text2 = null;
		int num = 0;
		foreach (Rate item in rates)
		{
			if (item.TrySet(text, out var error))
			{
				num++;
			}
			else if (text2 == null)
			{
				text2 = error;
			}
		}
		if (num == 0)
		{
			ctx.Reply(text2 ?? "Nothing changed.");
			return;
		}
		Commit(ctx, (group != null) ? Cap(group) : null);
		if (text2 != null && rates.Count > 1)
		{
			ctx.Reply("Some rates were skipped: " + text2);
		}
	}

	private static void Show(CommandContext ctx, List<Rate> rates, string group)
	{
		if (rates.Count == 1)
		{
			Rate rate = rates[0];
			ctx.Reply(rate.Label + ": " + rate.Display() + "  (sr_" + rate.Short + " <" + rate.RangeHint() + ">)");
		}
		else
		{
			Reply(ctx, Cap(group ?? "rates"), rates);
		}
	}

	private static void Commit(CommandContext ctx, string label)
	{
		Rates.Save();
		ConfigWatch.NotifySaved();
		List<Rate> list = Rates.TakeChanges();
		RateApplier.ApplyGlobalKeys();
		CategoryGroundAmp.LogStatus();
		if (list.Count == 0)
		{
			ctx.Reply("Already set, nothing changed.");
		}
		else
		{
			RatesFeedback.AnnounceChanges(list, ctx.Who, label);
		}
	}

	private static void Reset(CommandContext ctx, string name)
	{
		if (string.IsNullOrEmpty(name))
		{
			ctx.Reply("Usage: rates reset <name|group|all>");
		}
		else if (!ctx.CanWrite)
		{
			ctx.Reply("Only server admins can change rates here (ChatAccess=AdminOnly).");
		}
		else if (name == "all")
		{
			ResetAll();
			Commit(ctx, "All rates");
		}
		else
		{
			if (!Resolve(name, ctx, out var rates, out var group))
			{
				return;
			}
			foreach (Rate item in rates)
			{
				item.Reset();
			}
			Commit(ctx, (group != null) ? Cap(group) : null);
		}
	}

	private static void ResetAll()
	{
		foreach (Rate item in Rates.All)
		{
			if (item.Group != "general")
			{
				item.Reset();
			}
		}
	}

	private static void Preset(CommandContext ctx, string name)
	{
		if (string.IsNullOrEmpty(name))
		{
			ctx.Reply("Usage: rates preset vanilla | x2 | x3 | x5 | x10 (any number 1-100)");
			return;
		}
		if (!ctx.CanWrite)
		{
			ctx.Reply("Only server admins can change rates here (ChatAccess=AdminOnly).");
			return;
		}
		switch (name)
		{
		case "vanilla":
		case "default":
		case "off":
			ResetAll();
			Commit(ctx, "Vanilla");
			return;
		}
		string text = name.TrimStart(new char[1] { 'x' });
		if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 1f || result > 100f)
		{
			ctx.Reply("Unknown preset '" + name + "'. Try vanilla, x2, x5, x10");
			return;
		}
		string error;
		foreach (Rate item in Rates.InGroup("loot"))
		{
			item.TrySet(text, out error);
		}
		Rates.Find("skillgain").TrySet((result * 100f).ToString(CultureInfo.InvariantCulture), out error);
		foreach (Rate item2 in Rates.InGroup("stations"))
		{
			item2.TrySet(text, out error);
		}
		Commit(ctx, "Preset x" + Rate.Fmt(result));
	}

	private static void Help(CommandContext ctx)
	{
		ctx.Reply("ServerRates (type without a slash):");
		ctx.Reply("sr_wood 5 = set | sr_wood = show | sr_loot 5 = all loot");
		ctx.Reply("rates status | rates list loot | rates cmds");
		ctx.Reply("rates preset x5 | rates reset all | rates reset wood");
		ctx.Reply("rates inspect | rates repair  (station next to you)");
	}

	private static void Status(CommandContext ctx)
	{
		ctx.Reply("ServerRates v1.7.0 " + Rates.Enabled.Display() + " | chat: " + (Rates.ChatEnabled.Flag ? Rates.ChatAccess.Display() : "off"));
		List<Rate> list = new List<Rate>();
		foreach (Rate item in Rates.All)
		{
			if (item.Group != "general" && !item.IsDefault())
			{
				list.Add(item);
			}
		}
		if (list.Count == 0)
		{
			ctx.Reply("All rates are vanilla.");
		}
		else
		{
			Reply(ctx, "Changed", list);
		}
	}

	private static void ListRates(CommandContext ctx, string name)
	{
		List<Rate> rates;
		string group;
		if (string.IsNullOrEmpty(name))
		{
			ctx.Reply("Groups: " + string.Join(" ", GroupNames()));
			ctx.Reply("Example: rates list loot");
		}
		else if (Resolve(name, ctx, out rates, out group))
		{
			Show(ctx, rates, group);
		}
	}

	private static void Commands(CommandContext ctx)
	{
		ctx.Reply("Groups (set all at once): " + string.Join(" ", GroupNames()));
		foreach (string item in GroupNames())
		{
			if (item == "loot")
			{
				continue;
			}
			List<string> list = new List<string>();
			foreach (Rate item2 in Rates.InGroup(item))
			{
				list.Add(item2.Short);
			}
			Pack(ctx, item, list);
		}
	}

	private static List<string> GroupNames()
	{
		List<string> list = new List<string>();
		foreach (string key in Rates.Groups.Keys)
		{
			list.Add(key);
		}
		return list;
	}

	private static void Reply(CommandContext ctx, string title, List<Rate> rates)
	{
		List<string> list = new List<string>();
		foreach (Rate rate in rates)
		{
			list.Add(rate.Short + " " + rate.Display());
		}
		Pack(ctx, title, list);
	}

	private static void Pack(CommandContext ctx, string title, List<string> items)
	{
		StringBuilder stringBuilder = new StringBuilder(title + ":");
		foreach (string item in items)
		{
			if (stringBuilder.Length + item.Length + 3 > 90)
			{
				ctx.Reply(stringBuilder.ToString());
				stringBuilder.Clear();
				stringBuilder.Append("  ");
			}
			stringBuilder.Append((stringBuilder.Length > 2 && stringBuilder[stringBuilder.Length - 1] != ':') ? " | " : " ").Append(item);
		}
		if (stringBuilder.Length > 2)
		{
			ctx.Reply(stringBuilder.ToString());
		}
	}

	private static string Cap(string s)
	{
		if (!string.IsNullOrEmpty(s))
		{
			return char.ToUpperInvariant(s[0]) + s.Substring(1);
		}
		return s;
	}

	public static void Register()
	{
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Expected O, but got Unknown
		//IL_006b: Expected O, but got Unknown
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			IDictionary value = Traverse.Create(typeof(Terminal)).Field("commands").GetValue<IDictionary>();
			if (_registered && value != null && value.Contains("rates"))
			{
				return;
			}
			new ConsoleCommand("rates", "ServerRates: status | list | cmds | preset | reset | help", new ConsoleEvent(OnConsole), false, true, true, false, true, false, new ConsoleOptionsFetcher(FetchOptions), true, true, false);
			foreach (Rate item in Rates.All)
			{
				RegisterOne("sr_" + item.Short, item.Key + " [value]");
			}
			foreach (string key in Rates.Groups.Keys)
			{
				RegisterOne("sr_" + key, "all " + key + " rates [value]");
			}
			_registered = true;
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)"ServerRates: registered F5/console commands (rates, sr_*).");
			}
		}
		catch (Exception ex)
		{
			_registered = false;
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogError((object)("ServerRates: failed to register console commands: " + ex));
			}
		}
	}

	private static void RegisterOne(string cmd, string help)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Expected O, but got Unknown
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		new ConsoleCommand(cmd, "ServerRates " + help, new ConsoleEvent(OnConsole), false, true, true, false, true, false, (ConsoleOptionsFetcher)null, false, true, false);
	}

	private static List<string> FetchOptions()
	{
		List<string> list = new List<string> { "status", "list", "cmds", "preset", "reset", "help" };
		foreach (Rate item in Rates.All)
		{
			list.Add(item.Short);
		}
		return list;
	}

	private static void OnConsole(ConsoleEventArgs args)
	{
		if (args != null && !((Object)(object)args.Context == (Object)null) && args.Args != null && args.Args.Length != 0)
		{
			Process(string.Join(" ", args.Args), new CommandContext
			{
				Who = "admin",
				CanWrite = true,
				Reply = RatesFeedback.Say
			});
		}
	}
}
[HarmonyPatch(typeof(Terminal), "InitTerminal")]
internal static class RatesTerminalPatch
{
	private static void Postfix()
	{
		RatesCommands.Register();
	}
}
internal static class RatesFeedback
{
	[HarmonyPatch(typeof(ZNet), "RPC_RemoteCommand")]
	private static class RemoteCommandPatch
	{
		private static void Prefix(ZRpc rpc)
		{
			CurrentRemoteRpc = rpc;
		}
	}

	[HarmonyPatch(typeof(ZNet), "InternalCommand")]
	private static class InternalCommandPatch
	{
		private static void Prefix(ZRpc rpc)
		{
			CurrentRemoteRpc = rpc;
		}

		private static void Postfix()
		{
			CurrentRemoteRpc = null;
		}

		private static Exception Finalizer(Exception __exception)
		{
			CurrentRemoteRpc = null;
			return __exception;
		}
	}

	internal static ZRpc CurrentRemoteRpc { get; private set; }

	private static string[] SplitLines(string msg)
	{
		return msg.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
	}

	public static void Say(string msg)
	{
		if (string.IsNullOrEmpty(msg))
		{
			return;
		}
		string[] array = SplitLines(msg);
		foreach (string text in array)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)text);
			}
			ZRpc currentRemoteRpc = CurrentRemoteRpc;
			if (currentRemoteRpc != null && currentRemoteRpc.IsConnected() && (Object)(object)ZNet.instance != (Object)null)
			{
				try
				{
					ZNet.instance.RemotePrint(currentRemoteRpc, text);
				}
				catch (Exception ex)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("ServerRates RemotePrint failed: " + ex.Message));
					}
				}
			}
			else if ((Object)(object)Console.instance != (Object)null)
			{
				Console.instance.Print(text);
			}
		}
	}

	public static void ReplyChat(long peer, string text, Vector3 near = default(Vector3))
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		string[] array = SplitLines(text ?? "");
		foreach (string text2 in array)
		{
			SendChat(peer, text2, near);
		}
	}

	public static void Announce(string hud, string chatLine)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		ManualLogSource log = Plugin.Log;
		if (log != null)
		{
			log.LogInfo((object)chatLine);
		}
		SendHud(hud);
		SendChat(0L, chatLine);
	}

	public static void AnnounceChanges(List<Rate> changed, string who, string label = null)
	{
		if (changed == null || changed.Count == 0)
		{
			return;
		}
		string text;
		if (changed.Count > 3)
		{
			text = (string.IsNullOrEmpty(label) ? (changed.Count + " rates changed") : (label + " " + changed[0].Display()));
		}
		else
		{
			List<string> list = new List<string>();
			foreach (Rate item in changed)
			{
				list.Add(item.Label + " " + item.Display());
			}
			text = string.Join(" | ", list.ToArray());
		}
		string text2 = (string.IsNullOrEmpty(who) ? "" : (" (" + who + ")"));
		Announce(text, "[ServerRates] " + text + text2);
	}

	private static void SendHud(string text)
	{
		if (string.IsNullOrEmpty(text) || ZRoutedRpc.instance == null)
		{
			return;
		}
		try
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text });
		}
		catch (Exception ex)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("ServerRates HUD failed: " + ex.Message));
			}
		}
	}

	private static void SendChat(long target, string text, Vector3 at = default(Vector3))
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrEmpty(text) || ZRoutedRpc.instance == null)
		{
			return;
		}
		try
		{
			UserInfo val = new UserInfo
			{
				Name = "ServerRates"
			};
			PlatformUserID? val2 = ChatBridge.ServerUser();
			if (val2.HasValue)
			{
				val.UserId = val2.Value;
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(target, "ChatMessage", new object[4] { at, 1, val, text });
		}
		catch (Exception ex)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("ServerRates chat failed: " + ex.Message));
			}
		}
	}
}
internal static class StationBoost
{
	private enum Kind
	{
		Plant,
		Fermenter,
		Smelter,
		Cooking
	}

	private sealed class StationInfo
	{
		public Kind Kind;

		public string Name;

		public float Min;

		public float Max;

		public float Duration;

		public int MaxFuel;

		public int MaxOre;

		public float SecPerProduct;

		public int Slots;

		public Dictionary<string, float> CookTime;
	}

	private sealed class Track
	{
		public float NextCheck;

		public float LastTime;

		public float LastBake;

		public float LastFuel;

		public float[] LastCooked;
	}

	private const double FreshSeconds = 120.0;

	private const uint RevisionJump = 64u;

	private const float SmelterPeriod = 5f;

	private const float CookingPeriod = 2f;

	private const float MaxElapsed = 15f;

	private static readonly Dictionary<int, StationInfo> Known = new Dictionary<int, StationInfo>();

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

	private static readonly HashSet<ZDOID> DonePlants = new HashSet<ZDOID>();

	private static readonly Dictionary<ZDOID, long> LastStart = new Dictionary<ZDOID, long>();

	private static readonly Dictionary<ZDOID, Track> Tracks = new Dictionary<ZDOID, Track>();

	private static readonly List<ZDOID> Scratch = new List<ZDOID>();

	private static int _logged;

	private const float InfoRange = 3f;

	private const float InfoCooldown = 30f;

	private static readonly Dictionary<string, float> InfoShown = new Dictionary<string, float>();

	private static StationInfo GetInfo(int hash)
	{
		if (Known.TryGetValue(hash, out var value))
		{
			return value;
		}
		if (NotAStation.Contains(hash))
		{
			return null;
		}
		GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(hash) : null);
		if ((Object)(object)val != (Object)null)
		{
			Plant component = val.GetComponent<Plant>();
			Fermenter val2 = (((Object)(object)component == (Object)null) ? val.GetComponent<Fermenter>() : null);
			Smelter component2 = val.GetComponent<Smelter>();
			CookingStation component3 = val.GetComponent<CookingStation>();
			if ((Object)(object)component != (Object)null)
			{
				value = new StationInfo
				{
					Kind = Kind.Plant,
					Min = component.m_growTime,
					Max = component.m_growTimeMax
				};
			}
			else if ((Object)(object)val2 != (Object)null)
			{
				value = new StationInfo
				{
					Kind = Kind.Fermenter,
					Duration = val2.m_fermentationDuration
				};
			}
			else if ((Object)(object)component2 != (Object)null)
			{
				value = new StationInfo
				{
					Kind = Kind.Smelter,
					MaxFuel = component2.m_maxFuel,
					MaxOre = component2.m_maxOre,
					SecPerProduct = component2.m_secPerProduct
				};
			}
			else if ((Object)(object)component3 != (Object)null && component3.m_slots != null)
			{
				value = new StationInfo
				{
					Kind = Kind.Cooking,
					Slots = component3.m_slots.Length,
					CookTime = new Dictionary<string, float>()
				};
				foreach (ItemConversion item in component3.m_conversion)
				{
					if (item != null && (Object)(object)item.m_from != (Object)null)
					{
						value.CookTime[((Object)item.m_from).name] = item.m_cookTime;
					}
				}
			}
			if (value != null)
			{
				value.Name = ((Object)val).name;
			}
		}
		if (value == null)
		{
			NotAStation.Add(hash);
			return null;
		}
		Known[hash] = value;
		return value;
	}

	public static void Observe(ZDO zdo, float now)
	{
		int prefab = zdo.GetPrefab();
		if (NotAStation.Contains(prefab) || (Mathf.Approximately(Rates.Plants.Value, 1f) && Mathf.Approximately(Rates.Fermenter.Value, 1f) && Mathf.Approximately(Rates.Smelter.Value, 1f) && Mathf.Approximately(Rates.Cooking.Value, 1f)))
		{
			return;
		}
		StationInfo info = GetInfo(prefab);
		if (info != null)
		{
			switch (info.Kind)
			{
			case Kind.Plant:
				BoostPlant(zdo, info, Rates.Plants.Value);
				break;
			case Kind.Fermenter:
				BoostFermenter(zdo, info, Rates.Fermenter.Value);
				break;
			case Kind.Smelter:
				BoostSmelter(zdo, info, Rates.Smelter.Value, now);
				break;
			case Kind.Cooking:
				BoostCooking(zdo, info, Rates.Cooking.Value, now);
				break;
			}
		}
	}

	private static void BoostPlant(ZDO zdo, StationInfo info, float mult)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		mult = Mathf.Max(0.01f, mult);
		if (Mathf.Approximately(mult, 1f) || DonePlants.Contains(zdo.m_uid))
		{
			return;
		}
		long num = zdo.GetLong(ZDOVars.s_plantTime, 0L);
		if (num != 0L)
		{
			if (!IsFresh(num))
			{
				DonePlants.Add(zdo.m_uid);
				return;
			}
			State state = Random.state;
			Random.InitState(zdo.GetInt(ZDOVars.s_seed, 0));
			float value = Random.value;
			Random.state = state;
			double duration = Mathf.Lerp(info.Min, info.Max, value);
			ShiftStart(zdo, ZDOVars.s_plantTime, num, duration, mult, info.Name);
			DonePlants.Add(zdo.m_uid);
		}
	}

	private static void BoostFermenter(ZDO zdo, StationInfo info, float mult)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		mult = Mathf.Max(0.01f, mult);
		if (Mathf.Approximately(mult, 1f))
		{
			return;
		}
		long num = zdo.GetLong(ZDOVars.s_startTime, 0L);
		if (num != 0L && (!LastStart.TryGetValue(zdo.m_uid, out var value) || value != num))
		{
			if (!IsFresh(num))
			{
				LastStart[zdo.m_uid] = num;
			}
			else
			{
				LastStart[zdo.m_uid] = ShiftStart(zdo, ZDOVars.s_startTime, num, info.Duration, mult, info.Name);
			}
		}
	}

	private static bool IsFresh(long ticks)
	{
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			return false;
		}
		double totalSeconds = new TimeSpan(ZNet.instance.GetTime().Ticks - ticks).TotalSeconds;
		if (totalSeconds >= 0.0)
		{
			return totalSeconds < 120.0;
		}
		return false;
	}

	private static long ShiftStart(ZDO zdo, int key, long startTicks, double duration, float mult, string name)
	{
		double num = duration * (1.0 - 1.0 / (double)mult);
		long num2 = startTicks - (long)(num * 10000000.0);
		zdo.Set(key, num2);
		Publish(zdo);
		Log(name + " x" + Rate.Fmt(mult) + ": needs " + Rate.Fmt((float)(duration / (double)mult)) + "s instead of " + Rate.Fmt((float)duration) + "s");
		return num2;
	}

	private static void BoostSmelter(ZDO zdo, StationInfo info, float mult, float now)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		if (mult <= 1.001f)
		{
			return;
		}
		if (!Tracks.TryGetValue(zdo.m_uid, out var value))
		{
			Dictionary<ZDOID, Track> tracks = Tracks;
			ZDOID uid = zdo.m_uid;
			Track obj = new Track
			{
				NextCheck = now + 5f,
				LastTime = now,
				LastBake = zdo.GetFloat(ZDOVars.s_bakeTimer, 0f),
				LastFuel = zdo.GetFloat(ZDOVars.s_fuel, 0f)
			};
			value = obj;
			tracks[uid] = obj;
		}
		else if (!(now < value.NextCheck))
		{
			float num = Mathf.Min(now - value.LastTime, 15f);
			value.NextCheck = now + 5f;
			value.LastTime = now;
			float num2 = zdo.GetFloat(ZDOVars.s_bakeTimer, 0f);
			float num3 = zdo.GetFloat(ZDOVars.s_fuel, 0f);
			bool num4 = zdo.GetInt(ZDOVars.s_queued, 0) > 0 && (info.MaxFuel == 0 || num3 > 0f) && (Mathf.Abs(num2 - value.LastBake) > 0.001f || num3 < value.LastFuel - 0.0001f);
			value.LastBake = num2;
			value.LastFuel = num3;
			if (num4)
			{
				float num5 = zdo.GetFloat(ZDOVars.s_accTime, 0f) + (mult - 1f) * num;
				zdo.Set(ZDOVars.s_accTime, Mathf.Min(num5, 3000f));
				Publish(zdo);
				Log(info.Name + " x" + Rate.Fmt(mult) + ": +" + Rate.Fmt((mult - 1f) * num) + "s of work");
			}
		}
	}

	private static void BoostCooking(ZDO zdo, StationInfo info, float mult, float now)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		if (mult <= 1.001f)
		{
			return;
		}
		if (!Tracks.TryGetValue(zdo.m_uid, out var value))
		{
			value = new Track
			{
				NextCheck = now + 2f,
				LastTime = now,
				LastCooked = new float[info.Slots]
			};
			for (int i = 0; i < info.Slots; i++)
			{
				value.LastCooked[i] = zdo.GetFloat("slot" + i, 0f);
			}
			Tracks[zdo.m_uid] = value;
		}
		else
		{
			if (now < value.NextCheck || value.LastCooked == null)
			{
				return;
			}
			float num = Mathf.Min(now - value.LastTime, 15f);
			value.NextCheck = now + 2f;
			value.LastTime = now;
			bool flag = false;
			for (int j = 0; j < info.Slots; j++)
			{
				string text = zdo.GetString("slot" + j, "");
				int num2 = zdo.GetInt("slotstatus" + j, 0);
				float num3 = zdo.GetFloat("slot" + j, 0f);
				float num4 = value.LastCooked[j];
				value.LastCooked[j] = num3;
				if (!string.IsNullOrEmpty(text) && num2 == 0 && !(num3 <= num4 + 0.001f) && info.CookTime.TryGetValue(text, out var value2) && !(value2 <= 0f) && !(num3 >= value2))
				{
					float num5 = Mathf.Min(num3 + (mult - 1f) * num, value2);
					zdo.Set("slot" + j, num5);
					value.LastCooked[j] = num5;
					flag = true;
				}
			}
			if (flag)
			{
				Publish(zdo);
				Log(info.Name + " x" + Rate.Fmt(mult) + ": cooking advanced");
			}
		}
	}

	public unsafe static void Info(ZDO zdo, float now)
	{
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: 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_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		if (!Rates.StationInfo.Flag)
		{
			return;
		}
		int prefab = zdo.GetPrefab();
		if (NotAStation.Contains(prefab))
		{
			return;
		}
		StationInfo info = GetInfo(prefab);
		if (info == null || info.Kind == Kind.Plant)
		{
			return;
		}
		List<ZNetPeer> peers = ZNet.instance.GetPeers();
		Vector3 position = zdo.GetPosition();
		int num = 0;
		while (peers != null && num < peers.Count)
		{
			ZNetPeer val = peers[num];
			if (val != null && val.IsReady())
			{
				Vector3 val2 = val.GetRefPos() - position;
				if (!(((Vector3)(ref val2)).sqrMagnitude > 9f))
				{
					string text = val.m_uid.ToString();
					ZDOID uid = zdo.m_uid;
					string key = text + ":" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
					if (!InfoShown.TryGetValue(key, out var value) || !(now < value))
					{
						InfoShown[key] = now + 30f;
						string text2 = InfoText(info);
						if (text2 != null && ZRoutedRpc.instance != null)
						{
							ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "ShowMessage", new object[2] { 1, text2 });
						}
					}
				}
			}
			num++;
		}
	}

	private static string InfoText(StationInfo info)
	{
		string text = Pretty(info.Name);
		switch (info.Kind)
		{
		case Kind.Smelter:
			return text + ": " + PerItem(info.SecPerProduct, Rates.Smelter.Value) + " per item";
		case Kind.Fermenter:
			return text + ": " + PerItem(info.Duration, Rates.Fermenter.Value, canSlowDown: true) + " per batch";
		case Kind.Cooking:
		{
			float num = float.MaxValue;
			float num2 = 0f;
			foreach (float value in info.CookTime.Values)
			{
				num = Mathf.Min(num, value);
				num2 = Mathf.Max(num2, value);
			}
			if (num2 <= 0f)
			{
				return null;
			}
			float num3 = Mathf.Max(1f, Rates.Cooking.Value);
			string text2 = (Mathf.Approximately(num, num2) ? (Rate.Fmt(num / num3) + "s") : (Rate.Fmt(num / num3) + "s - " + Rate.Fmt(num2 / num3) + "s"));
			return text + ": " + text2 + " per item" + ((num3 > 1.001f) ? (" (x" + Rate.Fmt(num3) + ")") : "");
		}
		default:
			return null;
		}
	}

	private static string Pretty(string prefabName)
	{
		string[] array = (prefabName ?? "").Replace("piece_", "").Replace('_', ' ').Trim()
			.Replace("blastfurnace", "blast furnace")
			.Replace("cookingstation", "cooking station")
			.Replace("eitrrefinery", "eitr refinery")
			.Replace("spinningwheel", "spinning wheel")
			.Replace("charcoalkiln", "charcoal kiln")
			.Split(new char[1] { ' ' });
		for (int i = 0; i < array.Length; i++)
		{
			if (array[i].Length > 0)
			{
				array[i] = char.ToUpperInvariant(array[i][0]) + array[i].Substring(1);
			}
		}
		return string.Join(" ", array);
	}

	public static void Inspect(CommandContext ctx, bool repair)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		if (!ctx.Position.HasValue || ZDOMan.instance == null || (Object)(object)ZNet.instance == (Object)null)
		{
			ctx.Reply("Stand next to the station and use this in the game chat.");
			return;
		}
		Vector3 value = ctx.Position.Value;
		List<ZDO> list = new List<ZDO>();
		List<ZDO> list2 = new List<ZDO>();
		ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(value), ZNet.instance.GetSyncedSimulationDistance(), list, list2);
		List<KeyValuePair<float, ZDO>> list3 = new List<KeyValuePair<float, ZDO>>();
		foreach (ZDO item in list)
		{
			float num = Vector3.Distance(item.GetPosition(), value);
			if (num <= 8f && GetInfo(item.GetPrefab()) != null)
			{
				list3.Add(new KeyValuePair<float, ZDO>(num, item));
			}
		}
		list3.Sort((KeyValuePair<float, ZDO> a, KeyValuePair<float, ZDO> b) => a.Key.CompareTo(b.Key));
		if (list3.Count == 0)
		{
			ctx.Reply("No station within 8 m of you.");
			return;
		}
		long sessionID = ZDOMan.GetSessionID();
		for (int num2 = 0; num2 < list3.Count && num2 < 3; num2++)
		{
			ZDO value2 = list3[num2].Value;
			StationInfo info = GetInfo(value2.GetPrefab());
			long owner = value2.GetOwner();
			string text = info.Name + " " + Rate.Fmt(list3[num2].Key) + "m owner=" + ((own