Blot.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.GUI;
using Jotunn.Managers;
using Jotunn.Utils;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("Blot")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.1.0")]
[assembly: AssemblyInformationalVersion("0.3.1")]
[assembly: AssemblyProduct("Blot")]
[assembly: AssemblyTitle("Blot")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.1.0")]
[module: UnverifiableCode]
namespace Blot
{
	[BepInPlugin("com.taeguk.valheim.blot", "Blot", "0.3.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class BlotPlugin : BaseUnityPlugin
	{
		public const string Guid = "com.taeguk.valheim.blot";

		public const string ModName = "Blot";

		public const string Version = "0.3.1";

		private Harmony _harmony;

		public static BlotPlugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static bool IsHeadless { get; private set; }

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				IsHeadless = (int)SystemInfo.graphicsDeviceType == 4;
			}
			catch
			{
				IsHeadless = false;
			}
			BlotConfig.Bind(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("com.taeguk.valheim.blot");
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			OfferingCapture.Apply(_harmony);
			CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BlotCommand());
			Log.LogInfo((object)("Blot 0.3.1 loaded (" + (IsHeadless ? "headless server" : "game client") + ")."));
		}

		private void Update()
		{
			try
			{
				BlotServer.Tick();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Server tick: {arg}");
			}
			try
			{
				BlotClient.Tick();
			}
			catch (Exception arg2)
			{
				Log.LogError((object)$"Client tick: {arg2}");
			}
			try
			{
				BlotGui.Tick();
			}
			catch (Exception arg3)
			{
				Log.LogError((object)$"GUI tick: {arg3}");
			}
		}

		private void OnDestroy()
		{
			try
			{
				BlotServer.SaveAll();
			}
			catch
			{
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class BlotClient
	{
		public class RiteInfo
		{
			public string Name;

			public int Cost;

			public bool GateOpen;

			public string Blocker;
		}

		public class BlessingInfo
		{
			public string Name;

			public string Effect;

			public float SecondsLeft;
		}

		public class ClientState
		{
			public bool Received;

			public int Pool;

			public int Offered;

			public int MilestoneEvery;

			public List<RiteInfo> Rites = new List<RiteInfo>();

			public List<BlessingInfo> Blessings = new List<BlessingInfo>();

			public bool VoteOpen;

			public int VoteId;

			public string VoteRite = "";

			public string VoteProposer = "";

			public float VoteSeconds;

			public int VoteYes;

			public int VoteNo;

			public int VoteEligible;

			public bool CanVote;

			public bool HaveVoted;

			public int MyOffered;

			public int CapPoints;

			public int CapUsed;

			public float ReceivedAt;
		}

		[HarmonyPatch(typeof(Incinerator), "GetLeverHoverText")]
		private static class Patch_LeverHover
		{
			private static void Postfix(ref string __result)
			{
				try
				{
					__result += LeverText();
				}
				catch
				{
				}
			}
		}

		[HarmonyPatch(typeof(Switch), "Interact")]
		private static class Patch_SwitchInteract
		{
			private static bool Prefix(Switch __instance, bool hold, bool alt, ref bool __result)
			{
				try
				{
					if ((!alt && !ZInput.GetKey((KeyCode)308, true) && !ZInput.GetKey((KeyCode)307, true)) || hold || (Object)(object)((Component)__instance).GetComponentInParent<Incinerator>() == (Object)null)
					{
						return true;
					}
					BlotGui.OpenRites();
					__result = true;
					return false;
				}
				catch
				{
					return true;
				}
			}
		}

		public static ClientState S = new ClientState();

		public static void Tick()
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || (Object)(object)Game.instance == (Object)null)
			{
				ResetSession();
			}
		}

		private static void ResetSession()
		{
			if (S.Received)
			{
				S = new ClientState();
				BlotGui.CloseAll();
			}
		}

		public static void OnState(ZPackage pkg)
		{
			ClientState clientState = new ClientState
			{
				Received = true,
				ReceivedAt = Time.time
			};
			if (pkg.ReadInt() >= 4)
			{
				clientState.Pool = pkg.ReadInt();
				clientState.Offered = pkg.ReadInt();
				clientState.MilestoneEvery = pkg.ReadInt();
				int num = pkg.ReadInt();
				for (int i = 0; i < num; i++)
				{
					clientState.Rites.Add(new RiteInfo
					{
						Name = pkg.ReadString(),
						Cost = pkg.ReadInt(),
						GateOpen = pkg.ReadBool(),
						Blocker = pkg.ReadString()
					});
				}
				int num2 = pkg.ReadInt();
				for (int j = 0; j < num2; j++)
				{
					clientState.Blessings.Add(new BlessingInfo
					{
						Name = pkg.ReadString(),
						Effect = pkg.ReadString(),
						SecondsLeft = pkg.ReadSingle()
					});
				}
				clientState.VoteOpen = pkg.ReadBool();
				if (clientState.VoteOpen)
				{
					clientState.VoteId = pkg.ReadInt();
					clientState.VoteRite = pkg.ReadString();
					clientState.VoteProposer = pkg.ReadString();
					clientState.VoteSeconds = pkg.ReadSingle();
					clientState.VoteYes = pkg.ReadInt();
					clientState.VoteNo = pkg.ReadInt();
					clientState.VoteEligible = pkg.ReadInt();
					clientState.CanVote = pkg.ReadBool();
					clientState.HaveVoted = pkg.ReadBool();
				}
				clientState.MyOffered = pkg.ReadInt();
				clientState.CapPoints = pkg.ReadInt();
				clientState.CapUsed = pkg.ReadInt();
				S = clientState;
			}
		}

		public static void OnReply(ZPackage pkg)
		{
			Print(pkg.ReadString());
		}

		public static void OnNotice(ZPackage pkg)
		{
			bool num = pkg.ReadBool();
			string text = pkg.ReadString();
			if (num && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
			Print("[Blot] " + text, chatOnly: true);
		}

		public static void Print(string text, bool chatOnly = false)
		{
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string[] array = text.Split(new char[1] { '\n' });
			foreach (string text2 in array)
			{
				try
				{
					if ((Object)(object)Chat.instance != (Object)null)
					{
						((Terminal)Chat.instance).AddString(text2);
					}
				}
				catch
				{
				}
				if (chatOnly)
				{
					continue;
				}
				try
				{
					if ((Object)(object)Console.instance != (Object)null)
					{
						((Terminal)Console.instance).AddString(text2);
					}
				}
				catch
				{
				}
			}
		}

		public static string SendCommand(string text)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			if (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null)
			{
				return "Not connected to a world.";
			}
			ZPackage val = new ZPackage();
			val.Write(text);
			Net.ToServer("Blot_Command", val);
			return null;
		}

		public static string LeverText()
		{
			if (!S.Received)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"\n<color=#E0C078>Blot</color> - pool {S.Pool}");
			foreach (RiteInfo rite in S.Rites)
			{
				bool flag = string.IsNullOrEmpty(rite.Blocker);
				stringBuilder.Append("  |  " + rite.Name + " " + (flag ? "<color=#7CFC7C>ready</color>" : (rite.GateOpen ? $"{Math.Min(S.Pool, rite.Cost)}/{rite.Cost}" : "sealed")));
			}
			if (S.CapPoints > 0)
			{
				stringBuilder.Append($"\nYour offerings today: {S.CapUsed}/{S.CapPoints}");
			}
			stringBuilder.Append((ZInput.IsNonClassicFunctionality() && ZInput.IsGamepadActive()) ? "\n[<color=yellow><b>$KEY_AltKeys + $KEY_Use</b></color>] Rites" : "\n[<color=yellow><b>$KEY_AltPlace + $KEY_Use</b></color>] Rites");
			if (Localization.instance == null)
			{
				return stringBuilder.ToString();
			}
			return Localization.instance.Localize(stringBuilder.ToString());
		}
	}
	internal class BlotCommand : ConsoleCommand
	{
		public override string Name => "blot";

		public override string Help => "Blot - offerings, blessings and rites. `blot help` lists the commands.";

		public override void Run(string[] args)
		{
			string text = BlotClient.SendCommand("blot " + string.Join(" ", args));
			if (text != null)
			{
				BlotClient.Print(text);
			}
		}
	}
	internal static class BlotGui
	{
		[HarmonyPatch(typeof(Menu), "Update")]
		private static class Patch_MenuUpdate
		{
			private static bool Prefix()
			{
				try
				{
					if (!Blocking || !ZInput.GetKeyDown((KeyCode)27, true))
					{
						return true;
					}
					CloseRites();
					return false;
				}
				catch
				{
					return true;
				}
			}
		}

		[HarmonyPatch(typeof(Player), "TakeInput")]
		private static class Patch_PlayerTakeInput
		{
			private static void Postfix(ref bool __result)
			{
				if (Blocking)
				{
					__result = false;
				}
			}
		}

		[HarmonyPatch(typeof(PlayerController), "TakeInput")]
		private static class Patch_ControllerTakeInput
		{
			private static void Postfix(ref bool __result)
			{
				if (Blocking)
				{
					__result = false;
				}
			}
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static UnityAction <>9__24_0;

			public static UnityAction <>9__24_1;

			internal void <BuildVote>b__24_0()
			{
				Cast(yes: true);
			}

			internal void <BuildVote>b__24_1()
			{
				Cast(yes: false);
			}
		}

		private static bool _ritesOpen;

		private static int _confirm = -1;

		private static int _dismissedVote = -1;

		private static GameObject _rites;

		private static GameObject _vote;

		private static object _ritesBuiltFor;

		private static object _voteBuiltFor;

		private static int _ritesBuiltConfirm = -2;

		private static float _voteBuiltAt;

		private static bool _blocked;

		public static bool Blocking => _ritesOpen;

		private static bool VoteVisible
		{
			get
			{
				BlotClient.ClientState s = BlotClient.S;
				if (s.VoteOpen && s.CanVote && !s.HaveVoted)
				{
					return _dismissedVote != s.VoteId;
				}
				return false;
			}
		}

		public static void OpenRites()
		{
			_ritesOpen = true;
			_confirm = -1;
			_ritesBuiltFor = null;
		}

		public static void CloseAll()
		{
			_ritesOpen = false;
			Destroy(ref _rites);
			Destroy(ref _vote);
			SetBlock(block: false);
		}

		private static void CloseRites()
		{
			_ritesOpen = false;
			Destroy(ref _rites);
		}

		public static void Tick()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: 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)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			if (BlotPlugin.IsHeadless)
			{
				return;
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null || !UiKit.Ready)
			{
				if ((Object)(object)_rites != (Object)null || (Object)(object)_vote != (Object)null || _blocked)
				{
					CloseAll();
				}
				return;
			}
			if (_ritesOpen)
			{
				if ((Object)(object)_rites == (Object)null)
				{
					_rites = UiKit.Panel("BlotRites", 560f, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero);
					_ritesBuiltFor = null;
				}
				if (_ritesBuiltFor != BlotClient.S || _ritesBuiltConfirm != _confirm)
				{
					BuildRites();
				}
			}
			else
			{
				Destroy(ref _rites);
			}
			if (VoteVisible)
			{
				if ((Object)(object)_vote == (Object)null)
				{
					_vote = UiKit.Panel("BlotVote", 400f, new Vector2(1f, 0.75f), new Vector2(1f, 1f), new Vector2(-40f, 0f));
					_voteBuiltFor = null;
				}
				if (_voteBuiltFor != BlotClient.S || Time.time - _voteBuiltAt > 1f)
				{
					BuildVote();
				}
			}
			else
			{
				Destroy(ref _vote);
			}
			SetBlock(Blocking);
		}

		private static void SetBlock(bool block)
		{
			if (block == _blocked)
			{
				return;
			}
			_blocked = block;
			try
			{
				GUIManager.BlockInput(block);
			}
			catch
			{
			}
		}

		private static void Destroy(ref GameObject go)
		{
			if ((Object)(object)go != (Object)null)
			{
				Object.Destroy((Object)(object)go);
			}
			go = null;
		}

		private static void BuildRites()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Expected O, but got Unknown
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Expected O, but got Unknown
			//IL_036b: Unknown result type (might be due to invalid IL or missing references)
			//IL_037b: Expected O, but got Unknown
			BlotClient.ClientState clientState = (BlotClient.ClientState)(_ritesBuiltFor = BlotClient.S);
			_ritesBuiltConfirm = _confirm;
			UiKit.Clear(_rites);
			Transform transform = _rites.transform;
			UiKit.Header(transform, "Rites of the Blot");
			if (!clientState.Received)
			{
				UiKit.Body(transform, "No word from the server yet.");
				UiKit.Button(transform, "Close", new UnityAction(CloseRites));
				return;
			}
			UiKit.Body(transform, $"Pool {UiKit.Orange(clientState.Pool.ToString())}    Offered {clientState.Offered}");
			UiKit.Small(transform, string.Format("Your offerings: {0}{1}", clientState.MyOffered, (clientState.CapPoints > 0) ? $", {clientState.CapUsed}/{clientState.CapPoints} today" : ""));
			UiKit.Space(transform, 8f);
			for (int i = 0; i < clientState.Rites.Count; i++)
			{
				BlotClient.RiteInfo r = clientState.Rites[i];
				int index = i;
				bool flag = string.IsNullOrEmpty(r.Blocker);
				Transform parent = UiKit.Row(transform);
				UiKit.Grow((Component)(object)UiKit.Body(parent, string.Format("{0}  -  cost {1}\n{2}", UiKit.Orange(r.Name), r.Cost, flag ? "Ready" : UiKit.Grey(r.Blocker))));
				bool interactable = flag && !clientState.VoteOpen;
				if (_confirm == index)
				{
					UiKit.Button(parent, "Confirm", (UnityAction)delegate
					{
						BlotClient.SendCommand("blot rite " + r.Name.ToLowerInvariant());
						_confirm = -1;
						CloseRites();
					}, interactable, 130f);
				}
				else
				{
					UiKit.Button(parent, "Perform", (UnityAction)delegate
					{
						_confirm = index;
					}, interactable, 130f);
				}
				if (_confirm == index)
				{
					UiKit.Small(transform, $"If the vote passes this spends {r.Cost} of the pool ({clientState.Pool}).");
				}
			}
			UiKit.Space(transform, 8f);
			if (clientState.Blessings.Count == 0)
			{
				UiKit.Small(transform, UiKit.Grey("No blessing is upon the realm."));
			}
			foreach (BlotClient.BlessingInfo blessing in clientState.Blessings)
			{
				UiKit.Small(transform, "Blessing: " + blessing.Name + " - " + blessing.Effect + ", " + BlotServer.Format(blessing.SecondsLeft) + " left");
			}
			if (clientState.VoteOpen)
			{
				UiKit.Small(transform, $"Vote on {clientState.VoteRite}: {clientState.VoteYes} yes / {clientState.VoteNo} no of {clientState.VoteEligible}");
			}
			UiKit.Space(transform, 10f);
			Transform parent2 = UiKit.Row(transform);
			UiKit.Grow((Component)(object)UiKit.Small(parent2, UiKit.Grey("Esc to close")));
			UiKit.Button(parent2, "Close", new UnityAction(CloseRites), interactable: true, 130f);
		}

		private static void BuildVote()
		{
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Expected O, but got Unknown
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Expected O, but got Unknown
			BlotClient.ClientState s = BlotClient.S;
			_voteBuiltFor = s;
			_voteBuiltAt = Time.time;
			UiKit.Clear(_vote);
			Transform transform = _vote.transform;
			UiKit.Header(transform, "Rite vote");
			UiKit.Body(transform, $"{s.VoteProposer} proposes {UiKit.Orange(s.VoteRite)}.\nIt spends {VoteCost(s)} of the pool ({s.Pool}).");
			UiKit.Small(transform, $"{s.VoteYes} yes / {s.VoteNo} no of {s.VoteEligible} - {Math.Max(0f, s.VoteSeconds - (Time.time - s.ReceivedAt)):0} s");
			Transform parent = UiKit.Row(transform);
			object obj = <>c.<>9__24_0;
			if (obj == null)
			{
				UnityAction val = delegate
				{
					Cast(yes: true);
				};
				<>c.<>9__24_0 = val;
				obj = (object)val;
			}
			UiKit.Button(parent, "Yes", (UnityAction)obj, interactable: true, 104f);
			object obj2 = <>c.<>9__24_1;
			if (obj2 == null)
			{
				UnityAction val2 = delegate
				{
					Cast(yes: false);
				};
				<>c.<>9__24_1 = val2;
				obj2 = (object)val2;
			}
			UiKit.Button(parent, "No", (UnityAction)obj2, interactable: true, 104f);
			UiKit.Button(parent, "Later", (UnityAction)delegate
			{
				_dismissedVote = s.VoteId;
			}, interactable: true, 104f);
		}

		private static int VoteCost(BlotClient.ClientState s)
		{
			foreach (BlotClient.RiteInfo rite in s.Rites)
			{
				if (rite.Name == s.VoteRite)
				{
					return rite.Cost;
				}
			}
			return 0;
		}

		private static void Cast(bool yes)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(BlotClient.S.VoteId);
			val.Write(yes);
			Net.ToServer("Blot_Vote", val);
			_dismissedVote = BlotClient.S.VoteId;
		}
	}
	internal static class OfferingCapture
	{
		private static readonly Dictionary<Inventory, KeyValuePair<Incinerator, long>> Burning = new Dictionary<Inventory, KeyValuePair<Incinerator, long>>();

		public static void Apply(Harmony h)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Incinerator), "Incinerate", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = ((methodInfo != null) ? AccessTools.EnumeratorMoveNext((MethodBase)methodInfo) : null);
				if (methodInfo2 == null)
				{
					BlotPlugin.Log.LogError((object)"Incinerator.Incinerate coroutine not found - offerings will not score.");
					return;
				}
				h.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(OfferingCapture), "IncineratePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				h.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(OfferingCapture), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception arg)
			{
				BlotPlugin.Log.LogError((object)$"Offering capture patch failed - offerings will not score: {arg}");
			}
		}

		private static void IncineratePrefix(Incinerator __instance, long uid)
		{
			try
			{
				Inventory val = (((Object)(object)__instance.m_container != (Object)null) ? __instance.m_container.GetInventory() : null);
				if (val != null)
				{
					Burning[val] = new KeyValuePair<Incinerator, long>(__instance, uid);
				}
			}
			catch
			{
			}
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo getInventory = AccessTools.Method(typeof(Container), "GetInventory", (Type[])null, (Type[])null);
			MethodInfo snapshot = AccessTools.Method(typeof(OfferingCapture), "Snapshot", (Type[])null, (Type[])null);
			bool done = false;
			foreach (CodeInstruction ci in instructions)
			{
				yield return ci;
				if (!done && (ci.opcode == OpCodes.Callvirt || ci.opcode == OpCodes.Call) && ci.operand as MethodInfo == getInventory)
				{
					yield return new CodeInstruction(OpCodes.Dup, (object)null);
					yield return new CodeInstruction(OpCodes.Call, (object)snapshot);
					done = true;
				}
			}
			if (!done)
			{
				BlotPlugin.Log.LogError((object)"Offering capture: GetInventory call not found in Incinerate - offerings will not score.");
			}
		}

		public static void Snapshot(Inventory inv)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (inv == null || !Burning.TryGetValue(inv, out var value))
				{
					return;
				}
				Burning.Remove(inv);
				Incinerator key = value.Key;
				if ((Object)(object)key == (Object)null || (Object)(object)key.m_nview == (Object)null || !key.m_nview.IsValid() || !key.m_nview.IsOwner() || ((Object)(object)key.m_container != (Object)null && key.m_container.IsInUse()))
				{
					return;
				}
				List<ItemData> allItems = inv.GetAllItems();
				if (allItems.Count == 0)
				{
					return;
				}
				ZPackage val = new ZPackage();
				val.Write(key.m_nview.GetZDO().m_uid);
				val.Write(value.Value);
				val.Write(allItems.Count);
				foreach (ItemData item in allItems)
				{
					string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : (item.m_shared?.m_name ?? ""));
					val.Write(text);
					val.Write(Math.Max(1, item.m_stack));
					int num = 0;
					if (item.m_cheated)
					{
						num |= 1;
					}
					if (item.m_customData != null && item.m_customData.ContainsKey("valcoin"))
					{
						num |= 4;
					}
					val.Write(num);
				}
				Net.ToServer("Blot_Offering", val);
			}
			catch (Exception ex)
			{
				BlotPlugin.Log.LogWarning((object)("Offering snapshot failed: " + ex.Message));
			}
		}
	}
	internal static class UiKit
	{
		private static bool _loggedPanel;

		private static InventoryGui Inv => InventoryGui.instance;

		public static bool Ready
		{
			get
			{
				if ((Object)(object)Inv != (Object)null && GUIManager.Instance != null)
				{
					return (Object)(object)GUIManager.CustomGUIFront != (Object)null;
				}
				return false;
			}
		}

		public static GameObject Panel(string name, float width, Vector2 anchor, Vector2 pivot, Vector2 position)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: 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_004c: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: 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_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Expected O, but got Unknown
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(GUIManager.CustomGUIFront.transform, false);
			RectTransform val2 = (RectTransform)val.transform;
			Vector2 anchorMin = (val2.anchorMax = anchor);
			val2.anchorMin = anchorMin;
			val2.pivot = pivot;
			val2.anchoredPosition = position;
			val2.sizeDelta = new Vector2(width, 200f);
			Image val4 = val.AddComponent<Image>();
			Image val5 = PanelImage();
			if ((Object)(object)val5 != (Object)null)
			{
				val4.sprite = val5.sprite;
				val4.type = val5.type;
				((Graphic)val4).material = ((Graphic)val5).material;
				((Graphic)val4).color = ((Graphic)val5).color;
				val4.pixelsPerUnitMultiplier = val5.pixelsPerUnitMultiplier;
				val4.fillCenter = val5.fillCenter;
			}
			else
			{
				GUIManager.Instance.ApplyWoodpanelStyle(val.transform);
			}
			VerticalLayoutGroup obj = val.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj).padding = new RectOffset(26, 26, 22, 22);
			((HorizontalOrVerticalLayoutGroup)obj).spacing = 4f;
			((LayoutGroup)obj).childAlignment = (TextAnchor)0;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			ContentSizeFitter obj2 = val.AddComponent<ContentSizeFitter>();
			obj2.verticalFit = (FitMode)2;
			obj2.horizontalFit = (FitMode)0;
			try
			{
				val.AddComponent<DragWindowCntrl>();
			}
			catch
			{
			}
			return val;
		}

		private static Image PanelImage()
		{
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Inv == (Object)null)
			{
				return null;
			}
			Image val = null;
			List<string> list = new List<string>();
			Transform[] array = (Transform[])(object)new Transform[3]
			{
				(Transform)Inv.m_container,
				(Transform)Inv.m_player,
				((Component)Inv).transform
			};
			foreach (Transform val2 in array)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				float num = 0f;
				Image[] componentsInChildren = ((Component)val2).GetComponentsInChildren<Image>(true);
				foreach (Image val3 in componentsInChildren)
				{
					if ((Object)(object)val3.sprite == (Object)null)
					{
						continue;
					}
					string text = ((Object)val3.sprite).name.ToLowerInvariant();
					if (list.Count < 40 && !list.Contains(text))
					{
						list.Add(text);
					}
					if (text.Contains("woodpanel") && !text.Contains("mask") && !(((Graphic)val3).color.a < 0.5f))
					{
						Rect rect = ((Graphic)val3).rectTransform.rect;
						float num2 = Mathf.Abs(((Rect)(ref rect)).width * ((Rect)(ref rect)).height) + ((text == "woodpanel_container") ? 1E+09f : 0f);
						if ((Object)(object)val == (Object)null || num2 > num)
						{
							val = val3;
							num = num2;
						}
					}
				}
				if ((Object)(object)val != (Object)null)
				{
					break;
				}
			}
			if (!_loggedPanel)
			{
				_loggedPanel = true;
				ManualLogSource log = BlotPlugin.Log;
				object obj;
				if (!((Object)(object)val != (Object)null))
				{
					obj = "No wood panel in the inventory - Blot panels use Jotunn's copy of the game's wood panel.";
				}
				else
				{
					object[] obj2 = new object[5]
					{
						((Object)val.sprite).name,
						val.type,
						((Graphic)val).color.a,
						null,
						null
					};
					TMP_Text containerName = Inv.m_containerName;
					object obj3;
					if (containerName == null)
					{
						obj3 = null;
					}
					else
					{
						TMP_FontAsset font = containerName.font;
						obj3 = ((font != null) ? ((Object)font).name : null);
					}
					obj2[3] = obj3;
					TMP_Text containerName2 = Inv.m_containerName;
					obj2[4] = ((containerName2 != null) ? new float?(containerName2.fontSize) : ((float?)null));
					obj = string.Format("Blot panels copy the inventory background '{0}' ({1}, alpha {2:0.##}); text styles from InventoryGui (title font '{3}', size {4}).", obj2);
				}
				log.LogInfo(obj);
				BlotPlugin.Log.LogInfo((object)("Inventory sprites seen: " + string.Join(", ", list)));
			}
			return val;
		}

		public static TMP_Text Header(Transform parent, string text)
		{
			return Text(parent, text, ((Object)(object)Inv != (Object)null) ? Inv.m_containerName : null, 1f);
		}

		public static TMP_Text Body(Transform parent, string text)
		{
			return Text(parent, text, ((Object)(object)Inv != (Object)null) ? (Inv.m_recipeDecription ?? Inv.m_weight) : null, 1f);
		}

		public static TMP_Text Small(Transform parent, string text)
		{
			return Text(parent, text, ((Object)(object)Inv != (Object)null) ? (Inv.m_weight ?? Inv.m_recipeDecription) : null, 1f);
		}

		private static TMP_Text Text(Transform parent, string text, TMP_Text style, float scale)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("text", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			if ((Object)(object)style != (Object)null)
			{
				((TMP_Text)val2).font = style.font;
				((TMP_Text)val2).fontSharedMaterial = style.fontSharedMaterial;
				((TMP_Text)val2).fontSize = style.fontSize * scale;
				((Graphic)val2).color = ((Graphic)style).color;
				((TMP_Text)val2).fontStyle = style.fontStyle;
				((TMP_Text)val2).characterSpacing = style.characterSpacing;
			}
			else
			{
				((TMP_Text)val2).fontSize = 18f;
				((Graphic)val2).color = GUIManager.Instance.ValheimBeige;
			}
			((TMP_Text)val2).enableAutoSizing = false;
			((TMP_Text)val2).richText = true;
			((TMP_Text)val2).enableWordWrapping = true;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)257;
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)val2).text = text ?? "";
			return (TMP_Text)(object)val2;
		}

		public static Button Button(Transform parent, string label, UnityAction onClick, bool interactable = true, float width = 0f)
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			Button val = (((Object)(object)Inv != (Object)null) ? Inv.m_takeAllButton : null);
			GameObject val2;
			if ((Object)(object)val != (Object)null)
			{
				val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent, false);
				((Object)val2).name = "button";
				UIGamePad component = val2.GetComponent<UIGamePad>();
				if ((Object)(object)component != (Object)null)
				{
					Object.Destroy((Object)(object)component);
				}
				UIGamePad[] componentsInChildren = val2.GetComponentsInChildren<UIGamePad>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					Object.Destroy((Object)(object)componentsInChildren[i]);
				}
				val2.SetActive(true);
			}
			else
			{
				val2 = GUIManager.Instance.CreateButton(label, parent, Vector2.zero, Vector2.zero, Vector2.zero, 160f, 32f);
			}
			Button component2 = val2.GetComponent<Button>();
			component2.onClick = new ButtonClickedEvent();
			if (onClick != null)
			{
				((UnityEvent)component2.onClick).AddListener(onClick);
			}
			((Selectable)component2).interactable = interactable;
			TMP_Text componentInChildren = val2.GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				componentInChildren.text = label;
			}
			else
			{
				Text componentInChildren2 = val2.GetComponentInChildren<Text>(true);
				if ((Object)(object)componentInChildren2 != (Object)null)
				{
					componentInChildren2.text = label;
				}
			}
			LayoutElement val3 = val2.GetComponent<LayoutElement>() ?? val2.AddComponent<LayoutElement>();
			float num;
			if (!((Object)(object)val != (Object)null))
			{
				num = 32f;
			}
			else
			{
				Rect rect = ((RectTransform)((Component)val).transform).rect;
				num = Mathf.Max(28f, ((Rect)(ref rect)).height);
			}
			float num2 = num;
			float minHeight = (val3.preferredHeight = num2);
			val3.minHeight = minHeight;
			if (width > 0f)
			{
				minHeight = (val3.preferredWidth = width);
				val3.minWidth = minHeight;
				val3.flexibleWidth = 0f;
			}
			return component2;
		}

		public static Transform Row(Transform parent, float spacing = 8f)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("row", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)obj).spacing = spacing;
			((LayoutGroup)obj).childAlignment = (TextAnchor)3;
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
			return val.transform;
		}

		public static void Grow(Component c)
		{
			(c.GetComponent<LayoutElement>() ?? c.gameObject.AddComponent<LayoutElement>()).flexibleWidth = 1f;
		}

		public static void Space(Transform parent, float h)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("space", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			LayoutElement obj = val.AddComponent<LayoutElement>();
			float minHeight = (obj.preferredHeight = h);
			obj.minHeight = minHeight;
		}

		public static void Clear(GameObject panel)
		{
			if (!((Object)(object)panel == (Object)null))
			{
				for (int num = panel.transform.childCount - 1; num >= 0; num--)
				{
					GameObject gameObject = ((Component)panel.transform.GetChild(num)).gameObject;
					gameObject.SetActive(false);
					Object.Destroy((Object)(object)gameObject);
				}
			}
		}

		public static string Orange(string s)
		{
			return "<color=#FFA500>" + s + "</color>";
		}

		public static string Grey(string s)
		{
			return "<color=#A0A0A0>" + s + "</color>";
		}
	}
	public class Who
	{
		public string Account = "";

		public string Name = "";

		public long Uid;

		public bool IsHost;

		public ZNetPeer Peer;

		public long PlayerId
		{
			get
			{
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				if (IsHost)
				{
					if (!((Object)(object)Player.m_localPlayer != (Object)null))
					{
						return 0L;
					}
					return Player.m_localPlayer.GetPlayerID();
				}
				if (Peer == null)
				{
					return 0L;
				}
				try
				{
					if (!((ZDOID)(ref Peer.m_characterID)).IsNone())
					{
						ZDO zDO = ZDOMan.instance.GetZDO(Peer.m_characterID);
						if (zDO != null)
						{
							long num = zDO.GetLong(ZDOVars.s_playerID, 0L);
							if (num != 0L)
							{
								return num;
							}
						}
					}
				}
				catch
				{
				}
				return Peer.m_playerID;
			}
		}

		public Vector3 Position
		{
			get
			{
				//IL_0079: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: 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_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_0057: 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_006b: Unknown result type (might be due to invalid IL or missing references)
				//IL_007f: Unknown result type (might be due to invalid IL or missing references)
				if (IsHost)
				{
					if (!((Object)(object)Player.m_localPlayer != (Object)null))
					{
						return Vector3.zero;
					}
					return ((Component)Player.m_localPlayer).transform.position;
				}
				if (Peer == null)
				{
					return Vector3.zero;
				}
				try
				{
					if (!((ZDOID)(ref Peer.m_characterID)).IsNone())
					{
						ZDO zDO = ZDOMan.instance.GetZDO(Peer.m_characterID);
						if (zDO != null)
						{
							return zDO.GetPosition();
						}
					}
				}
				catch
				{
				}
				return Peer.GetRefPos();
			}
		}
	}
	internal static class Accounts
	{
		public static bool IsLocalHost
		{
			get
			{
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					return !BlotPlugin.IsHeadless;
				}
				return false;
			}
		}

		public static string Normalize(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				return "";
			}
			id = id.Trim();
			if (!id.StartsWith("Steam_", StringComparison.OrdinalIgnoreCase))
			{
				return id;
			}
			return id.Substring(6);
		}

		public static string OfPeer(ZNetPeer peer)
		{
			try
			{
				return (peer?.m_socket != null) ? Normalize(peer.m_socket.GetHostName()) : "";
			}
			catch
			{
				return "";
			}
		}

		public static string Local()
		{
			//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)
			try
			{
				return Normalize(((object)((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID/*cast due to .constrained prefix*/).ToString());
			}
			catch
			{
				return "";
			}
		}

		public static Who FromSender(long sender)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return null;
			}
			if (ZRoutedRpc.instance != null && sender == ZRoutedRpc.instance.m_id)
			{
				if (!IsLocalHost)
				{
					return null;
				}
				return new Who
				{
					Account = Local(),
					Name = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile().GetName() : "Host"),
					Uid = sender,
					IsHost = true
				};
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			if (peer != null)
			{
				return FromPeer(peer);
			}
			return null;
		}

		public static Who FromPeer(ZNetPeer peer)
		{
			return new Who
			{
				Account = OfPeer(peer),
				Name = (peer.m_playerName ?? ""),
				Uid = peer.m_uid,
				Peer = peer
			};
		}

		public static List<Who> Online()
		{
			List<Who> list = new List<Who>();
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return list;
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer != null && peer.IsReady() && !((ZDOID)(ref peer.m_characterID)).IsNone())
				{
					list.Add(FromPeer(peer));
				}
			}
			if (IsLocalHost && (Object)(object)Player.m_localPlayer != (Object)null && ZRoutedRpc.instance != null)
			{
				list.Add(FromSender(ZRoutedRpc.instance.m_id));
			}
			return list;
		}

		public static Who FindOnline(string query)
		{
			if (string.IsNullOrWhiteSpace(query))
			{
				return null;
			}
			query = query.Trim();
			foreach (Who item in Online())
			{
				if (item.Account == Normalize(query) || string.Equals(item.Name, query, StringComparison.OrdinalIgnoreCase))
				{
					return item;
				}
			}
			return null;
		}

		public unsafe static string OfPlayer(Player player)
		{
			//IL_0031: 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_004b: 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_0051: 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_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				return "";
			}
			if ((Object)(object)player == (Object)(object)Player.m_localPlayer)
			{
				return Local();
			}
			try
			{
				ZDOID zDOID = ((Character)player).GetZDOID();
				foreach (PlayerInfo player2 in ZNet.instance.GetPlayerList())
				{
					if (player2.m_characterID == zDOID)
					{
						PlatformUserID id = player2.m_userInfo.m_id;
						return Normalize(((object)(*(PlatformUserID*)(&id))/*cast due to .constrained prefix*/).ToString());
					}
				}
			}
			catch
			{
			}
			return "";
		}
	}
	internal static class BlotConfig
	{
		public static ConfigEntry<int> RestockCost;

		public static ConfigEntry<int> RegrowthCost;

		public static ConfigEntry<string> RestockGate;

		public static ConfigEntry<string> RegrowthGate;

		public static ConfigEntry<int> RiteMinContribution;

		public static ConfigEntry<bool> RiteVote;

		public static ConfigEntry<int> RiteVoteSeconds;

		public static ConfigEntry<int> RiteVoteCooldownMinutes;

		public static ConfigEntry<string> RestockSkipLocations;

		public static ConfigEntry<string> RegrowthProtectPrefabs;

		public static ConfigEntry<float> OccupiedRadius;

		public static ConfigEntry<int> MaxZdoOpsPerFrame;

		public static ConfigEntry<bool> DailyCapEnabled;

		public static ConfigEntry<bool> DailyCapIncludesBossOfferings;

		public static ConfigEntry<int> DailyCapPoints;

		public static ConfigEntry<int> DailyCapResetHour;

		public static ConfigEntry<int> MilestoneEvery;

		public static ConfigEntry<bool> OnlineOnlyTimers;

		public static ConfigEntry<string> DiscordWebhookUrl;

		public static ConfigEntry<string> DiscordWebhookUrlAdmin;

		public static ConfigEntry<bool> PreferServerGuard;

		public static void Bind(ConfigFile c)
		{
			RestockCost = c.Bind<int>("Rites", "restockCost", 250, "Pool points Restock costs. Performing a rite spends its cost; the rest of the pool stays.");
			RestockGate = c.Bind<string>("Rites", "restockGate", "defeated_gdking", "World key that must be set for Restock (The Elder).");
			RegrowthCost = c.Bind<int>("Rites", "regrowthCost", 750, "Pool points Regrowth costs.");
			RegrowthGate = c.Bind<string>("Rites", "regrowthGate", "defeated_goblinking", "World key that must be set for Regrowth (Yagluth).");
			RiteMinContribution = c.Bind<int>("Rites", "riteMinContribution", 10, "Points an account must have offered (in total) to propose or vote on a rite.");
			RiteVote = c.Bind<bool>("Rites", "riteVote", true, "A rite proposal needs yes from more than half of the online contributors.");
			RiteVoteSeconds = c.Bind<int>("Rites", "riteVoteSeconds", 120, "How long a rite vote stays open.");
			RiteVoteCooldownMinutes = c.Bind<int>("Rites", "riteVoteCooldownMinutes", 10, "A proposer whose vote failed waits this long to propose again.");
			RestockSkipLocations = c.Bind<string>("Rites", "restockSkipLocations", "StartTemple,Vendor_BlackForest,Hildir_camp,BogWitch_Camp,Hildir_crypt,Hildir_cave,Hildir_plainsfortress", "Location prefabs Restock never touches (comma separated).");
			RegrowthProtectPrefabs = c.Bind<string>("Rites", "regrowthProtectPrefabs", "Player_tombstone", "Extra prefabs that protect their zone from Regrowth, besides anything a player built and tamed creatures.");
			OccupiedRadius = c.Bind<float>("Rites", "occupiedRadius", 300f, "A location or zone within this many metres of a player is left for later.");
			MaxZdoOpsPerFrame = c.Bind<int>("Rites", "maxZdoOpsPerFrame", 500, "World objects Restock/Regrowth process per frame.");
			DailyCapEnabled = c.Bind<bool>("Offerings", "dailyCapEnabled", true, "Anti-farm cap per account and day.");
			DailyCapPoints = c.Bind<int>("Offerings", "dailyCapPoints", 200, "Points an account can score per day.");
			DailyCapResetHour = c.Bind<int>("Offerings", "dailyCapResetHour", 0, "Server-local hour the day rolls over.");
			DailyCapIncludesBossOfferings = c.Bind<bool>("Offerings", "dailyCapIncludesBossOfferings", false, "Count boss/Lord full-value offerings against the cap.");
			MilestoneEvery = c.Bind<int>("Blessings", "milestoneEvery", 150, "A blessing is granted each time the realm's total offered points cross a multiple of this.");
			OnlineOnlyTimers = c.Bind<bool>("Blessings", "onlineOnlyTimers", true, "Blessing timers count down only while someone is online.");
			DiscordWebhookUrl = c.Bind<string>("Discord", "discordWebhookUrl", "", "Public webhook (used when ServerGuard 2.2 is not present).");
			DiscordWebhookUrlAdmin = c.Bind<string>("Discord", "discordWebhookUrlAdmin", "", "Admin webhook (falls back to the public one).");
			PreferServerGuard = c.Bind<bool>("Discord", "preferServerGuard", true, "Post through ServerGuard's webhooks when ServerGuard 2.2 is loaded.");
		}
	}
	internal static class Net
	{
		[HarmonyPatch(typeof(ZNet), "Awake")]
		private static class Patch_ZNet_Awake
		{
			private static void Postfix()
			{
				try
				{
					Register();
				}
				catch (Exception arg)
				{
					BlotPlugin.Log.LogError((object)$"RPC registration failed: {arg}");
				}
			}
		}

		public const string Offering = "Blot_Offering";

		public const string Command = "Blot_Command";

		public const string Vote = "Blot_Vote";

		public const string State = "Blot_State";

		public const string Reply = "Blot_Reply";

		public const string Notice = "Blot_Notice";

		public static long ServerId
		{
			get
			{
				if (ZRoutedRpc.instance == null)
				{
					return 0L;
				}
				return ZRoutedRpc.instance.GetServerPeerID();
			}
		}

		public static void ToServer(string rpc, ZPackage pkg)
		{
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(ServerId, rpc, new object[1] { pkg });
			}
		}

		public static void ToClient(long uid, string rpc, ZPackage pkg)
		{
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(uid, rpc, new object[1] { pkg });
			}
		}

		public static void NoticeTo(long uid, string text, bool center = true)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(center);
			val.Write(text ?? "");
			if (uid == 0L)
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(0L, "Blot_Notice", new object[1] { val });
				}
			}
			else
			{
				ToClient(uid, "Blot_Notice", val);
			}
		}

		private static void Register()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			instance.Register<ZPackage>("Blot_Offering", (Action<long, ZPackage>)delegate(long s, ZPackage p)
			{
				Guard(delegate
				{
					BlotServer.OnOffering(s, p);
				});
			});
			instance.Register<ZPackage>("Blot_Command", (Action<long, ZPackage>)delegate(long s, ZPackage p)
			{
				Guard(delegate
				{
					BlotServer.OnCommand(s, p);
				});
			});
			instance.Register<ZPackage>("Blot_Vote", (Action<long, ZPackage>)delegate(long s, ZPackage p)
			{
				Guard(delegate
				{
					BlotServer.OnVote(s, p);
				});
			});
			instance.Register<ZPackage>("Blot_State", (Action<long, ZPackage>)delegate(long s, ZPackage p)
			{
				Guard(delegate
				{
					BlotClient.OnState(p);
				});
			});
			instance.Register<ZPackage>("Blot_Reply", (Action<long, ZPackage>)delegate(long s, ZPackage p)
			{
				Guard(delegate
				{
					BlotClient.OnReply(p);
				});
			});
			instance.Register<ZPackage>("Blot_Notice", (Action<long, ZPackage>)delegate(long s, ZPackage p)
			{
				Guard(delegate
				{
					BlotClient.OnNotice(p);
				});
			});
		}

		private static void Guard(Action a)
		{
			try
			{
				a();
			}
			catch (Exception arg)
			{
				BlotPlugin.Log.LogError((object)$"RPC handler: {arg}");
			}
		}
	}
	internal static class YamlIO
	{
		private static readonly ISerializer Ser = ((BuilderSkeleton<SerializerBuilder>)new SerializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling((DefaultValuesHandling)0).Build();

		private static readonly IDeserializer De = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build();

		public static T Load<T>(string path) where T : class, new()
		{
			try
			{
				if (!File.Exists(path))
				{
					return new T();
				}
				string text = File.ReadAllText(path);
				if (string.IsNullOrWhiteSpace(text))
				{
					return new T();
				}
				return De.Deserialize<T>(text) ?? new T();
			}
			catch (Exception ex)
			{
				BlotPlugin.Log.LogError((object)("Could not read " + path + ": " + ex.Message + ". Keeping a copy as .broken and starting empty."));
				try
				{
					File.Copy(path, path + ".broken", overwrite: true);
				}
				catch
				{
				}
				return new T();
			}
		}

		public static bool TryLoad<T>(string path, out T value, out string error) where T : class, new()
		{
			value = null;
			error = null;
			try
			{
				string text = File.ReadAllText(path);
				value = (string.IsNullOrWhiteSpace(text) ? new T() : (De.Deserialize<T>(text) ?? new T()));
				return true;
			}
			catch (Exception ex)
			{
				error = ex.Message.Replace('\r', ' ').Replace('\n', ' ');
				return false;
			}
		}

		public static T LoadOrNull<T>(string path) where T : class, new()
		{
			if (!File.Exists(path))
			{
				return null;
			}
			return Load<T>(path);
		}

		public static void Save<T>(string path, T value, string header = null)
		{
			SaveText(path, header + Ser.Serialize((object)value));
		}

		public static void SaveText(string path, string text)
		{
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				string text2 = path + ".tmp";
				File.WriteAllText(text2, text);
				if (File.Exists(path))
				{
					File.Replace(text2, path, null);
				}
				else
				{
					File.Move(text2, path);
				}
			}
			catch (Exception ex)
			{
				BlotPlugin.Log.LogError((object)("Could not write " + path + ": " + ex.Message));
			}
		}

		public static void SaveBytes(string path, byte[] data)
		{
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				string text = path + ".tmp";
				File.WriteAllBytes(text, data);
				if (File.Exists(path))
				{
					File.Replace(text, path, null);
				}
				else
				{
					File.Move(text, path);
				}
			}
			catch (Exception ex)
			{
				BlotPlugin.Log.LogError((object)("Could not write " + path + ": " + ex.Message));
			}
		}
	}
	public class BlotState
	{
		public int Pool { get; set; }

		public int Offered { get; set; }

		public int MilestonesGranted { get; set; }

		public List<ActiveBlessing> Blessings { get; set; } = new List<ActiveBlessing>();

		public List<string> RitesAnnouncedReady { get; set; } = new List<string>();

		public List<string> RitesPerformed { get; set; } = new List<string>();

		public List<string> Log { get; set; } = new List<string>();

		public ActiveBlessing Permanent { get; set; }

		public string Difficulty { get; set; } = "";

		public List<string> DifficultyOriginal { get; set; }
	}
	public class ActiveBlessing
	{
		public string Id { get; set; } = "";

		public double SecondsLeft { get; set; }

		public string Key { get; set; } = "";

		public bool HadOriginal { get; set; }

		public string Original { get; set; } = "";
	}
	public class ContributionsDoc
	{
		public Dictionary<string, AccountContribution> Accounts { get; set; } = new Dictionary<string, AccountContribution>();
	}
	public class AccountContribution
	{
		public string Name { get; set; } = "";

		[YamlMember(Alias = "allTime")]
		public int Total { get; set; }

		public string CapDay { get; set; } = "";

		public int CapUsed { get; set; }

		public List<string> FullOfferings { get; set; } = new List<string>();
	}
	public class OfferingDef
	{
		public int Points { get; set; }

		public string Kind { get; set; } = "item";
	}
	public class OfferingsTable
	{
		public int Version { get; set; }

		public int RepeatBossPoints { get; set; } = 1;

		public int DefaultTrophyPoints { get; set; }

		public Dictionary<string, OfferingDef> Items { get; set; } = new Dictionary<string, OfferingDef>();
	}
	public class BlessingDef
	{
		public string Id { get; set; } = "";

		public string Name { get; set; } = "";

		public string Key { get; set; } = "";

		public float Multiplier { get; set; }

		public float Minutes { get; set; } = 60f;

		public string Effect { get; set; } = "";
	}
	public class BlessingsTable
	{
		public List<string> MilestoneOrder { get; set; } = new List<string>();

		public List<string> RandomPool { get; set; } = new List<string>();

		public List<BlessingDef> Blessings { get; set; } = new List<BlessingDef>();
	}
	internal static class Tables
	{
		public class Message
		{
			public string Text;

			public bool Error;
		}

		private class DefaultItem
		{
			public string Prefab;

			public string Kind;

			public string Section;

			public int Points;
		}

		public const int OfferingsVersion = 2;

		public static OfferingsTable Offerings = DefaultOfferings();

		public static BlessingsTable Blessings = DefaultBlessings();

		private static DateTime _offLoaded;

		private static DateTime _offSeen;

		private static DateTime _blessLoaded;

		private static DateTime _blessSeen;

		private static readonly string[] Kinds = new string[4] { "boss", "lord", "rare", "item" };

		private const string BlessingsHeader = "# Blot blessings. key = a world modifier (GlobalKeys). multiplier 0 = on/off modifier.\n# minutes count only while someone is online. milestoneOrder repeats; @random = one of randomPool.\n# Live: save and the server uses it within a few seconds. Only the server's copy counts.\n";

		private const string SecBoss = "Bosses, by biome - full value the first time an account offers one";

		private const string SecLord = "Biome Lords (the BiomeLords mod) - half the boss of their biome";

		private const string SecRare = "Rare trophies and items - every copy";

		private const string SecTrophy = "Creature trophies - every copy";

		private const string SecOwn = "Your own additions";

		private static readonly string[] Sections = new string[4] { "Bosses, by biome - full value the first time an account offers one", "Biome Lords (the BiomeLords mod) - half the boss of their biome", "Rare trophies and items - every copy", "Creature trophies - every copy" };

		private static List<DefaultItem> _defaults;

		private static readonly Regex PlainKey = new Regex("^[A-Za-z0-9_][A-Za-z0-9_.()-]*$");

		public static string Dir => Path.Combine(Paths.ConfigPath, "Blot");

		public static string OfferingsPath => Path.Combine(Dir, "offerings.yaml");

		public static string BlessingsPath => Path.Combine(Dir, "blessings.yaml");

		public static List<Message> LoadAll()
		{
			return new List<Message>
			{
				ReloadOfferings(),
				ReloadBlessings()
			};
		}

		public static List<Message> CheckForChanges()
		{
			List<Message> list = null;
			DateTime dateTime = WriteTime(OfferingsPath);
			if (dateTime != _offLoaded)
			{
				if (dateTime == _offSeen)
				{
					List<Message> obj = list ?? new List<Message>();
					list = obj;
					obj.Add(ReloadOfferings());
				}
				else
				{
					_offSeen = dateTime;
				}
			}
			DateTime dateTime2 = WriteTime(BlessingsPath);
			if (dateTime2 != _blessLoaded)
			{
				if (dateTime2 == _blessSeen)
				{
					List<Message> obj2 = list ?? new List<Message>();
					list = obj2;
					obj2.Add(ReloadBlessings());
				}
				else
				{
					_blessSeen = dateTime2;
				}
			}
			return list;
		}

		public static Message ReloadOfferings()
		{
			string offeringsPath = OfferingsPath;
			try
			{
				if (!File.Exists(offeringsPath))
				{
					Offerings = DefaultOfferings();
					YamlIO.SaveText(offeringsPath, RenderOfferings(Offerings));
					return Ok($"offerings.yaml written with the default list ({Offerings.Items.Count} items).");
				}
				if (!YamlIO.TryLoad<OfferingsTable>(offeringsPath, out var value, out var error))
				{
					return Fail($"offerings.yaml has a mistake and was NOT loaded - the previous list ({Offerings.Items.Count} items) stays in use. {error}");
				}
				List<string> list = new List<string>();
				bool flag = false;
				if (value.Version < 2)
				{
					int num = 0;
					foreach (KeyValuePair<string, OfferingDef> item in DefaultOfferings().Items)
					{
						if (!value.Items.ContainsKey(item.Key))
						{
							value.Items[item.Key] = item.Value;
							num++;
						}
					}
					if (value.DefaultTrophyPoints == 1)
					{
						value.DefaultTrophyPoints = 0;
					}
					value.Version = 2;
					list.Add($"upgraded to the full list (+{num} items)");
					flag = true;
				}
				Normalize(value, list);
				Offerings = value;
				if (flag)
				{
					YamlIO.SaveText(offeringsPath, RenderOfferings(value));
				}
				return Ok($"offerings.yaml loaded: {value.Items.Count} items" + ((list.Count > 0) ? (" (" + string.Join("; ", list) + ")") : "") + ".");
			}
			finally
			{
				_offLoaded = (_offSeen = WriteTime(offeringsPath));
			}
		}

		private static void Normalize(OfferingsTable t, List<string> notes)
		{
			if (t.Items == null)
			{
				t.Items = new Dictionary<string, OfferingDef>();
			}
			t.RepeatBossPoints = Math.Max(0, t.RepeatBossPoints);
			t.DefaultTrophyPoints = Math.Max(0, t.DefaultTrophyPoints);
			Dictionary<string, OfferingDef> dictionary = new Dictionary<string, OfferingDef>();
			foreach (KeyValuePair<string, OfferingDef> item in t.Items)
			{
				string text = (item.Key ?? "").Trim();
				if (text.Length == 0)
				{
					continue;
				}
				OfferingDef value = item.Value;
				if (value == null)
				{
					notes.Add(text + " has no points - skipped");
					continue;
				}
				value.Kind = (value.Kind ?? "").Trim().ToLowerInvariant();
				if (Array.IndexOf(Kinds, value.Kind) < 0)
				{
					notes.Add(text + ": unknown kind '" + value.Kind + "', using item");
					value.Kind = "item";
				}
				value.Points = Math.Max(0, value.Points);
				dictionary[text] = value;
			}
			t.Items = dictionary;
		}

		public static Message ReloadBlessings()
		{
			string blessingsPath = BlessingsPath;
			try
			{
				if (!File.Exists(blessingsPath))
				{
					Blessings = DefaultBlessings();
					YamlIO.Save(blessingsPath, Blessings, "# Blot blessings. key = a world modifier (GlobalKeys). multiplier 0 = on/off modifier.\n# minutes count only while someone is online. milestoneOrder repeats; @random = one of randomPool.\n# Live: save and the server uses it within a few seconds. Only the server's copy counts.\n");
					return Ok($"blessings.yaml written with the default blessings ({Blessings.Blessings.Count}).");
				}
				if (!YamlIO.TryLoad<BlessingsTable>(blessingsPath, out var value, out var error))
				{
					return Fail("blessings.yaml has a mistake and was NOT loaded - the previous blessings stay in use. " + error);
				}
				if (value.Blessings == null || value.Blessings.Count == 0)
				{
					return Fail("blessings.yaml lists no blessings and was NOT loaded - the previous blessings stay in use.");
				}
				if (value.MilestoneOrder == null)
				{
					value.MilestoneOrder = new List<string>();
				}
				if (value.RandomPool == null)
				{
					value.RandomPool = new List<string>();
				}
				Blessings = value;
				return Ok($"blessings.yaml loaded: {value.Blessings.Count} blessings.");
			}
			finally
			{
				_blessLoaded = (_blessSeen = WriteTime(blessingsPath));
			}
		}

		public static BlessingDef FindBlessing(string idOrName)
		{
			string text = Squash(idOrName);
			if (text.Length == 0)
			{
				return null;
			}
			foreach (BlessingDef blessing in Blessings.Blessings)
			{
				if (Squash(blessing.Id) == text || Squash(blessing.Name) == text)
				{
					return blessing;
				}
			}
			BlessingDef blessingDef = null;
			foreach (BlessingDef blessing2 in Blessings.Blessings)
			{
				if (Squash(blessing2.Id).StartsWith(text) || Squash(blessing2.Name).StartsWith(text))
				{
					if (blessingDef != null)
					{
						return null;
					}
					blessingDef = blessing2;
				}
			}
			return blessingDef;
		}

		private static string Squash(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			string[] array = s.ToLowerInvariant().Replace("'", "").Split(new char[3] { ' ', '_', '-' }, StringSplitOptions.RemoveEmptyEntries);
			StringBuilder stringBuilder = new StringBuilder();
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (text != "the" && text != "of")
				{
					stringBuilder.Append(text);
				}
			}
			return stringBuilder.ToString();
		}

		private static DateTime WriteTime(string path)
		{
			try
			{
				return File.Exists(path) ? File.GetLastWriteTimeUtc(path) : DateTime.MinValue;
			}
			catch
			{
				return DateTime.MinValue;
			}
		}

		private static Message Ok(string text)
		{
			return new Message
			{
				Text = text
			};
		}

		private static Message Fail(string text)
		{
			return new Message
			{
				Text = text,
				Error = true
			};
		}

		private static List<DefaultItem> DefaultItems()
		{
			if (_defaults != null)
			{
				return _defaults;
			}
			List<DefaultItem> l = new List<DefaultItem>();
			Add("Bosses, by biome - full value the first time an account offers one", "TrophyEikthyr", 10, "boss");
			Add("Bosses, by biome - full value the first time an account offers one", "TrophyTheElder", 20, "boss");
			Add("Bosses, by biome - full value the first time an account offers one", "TrophyBonemass", 35, "boss");
			Add("Bosses, by biome - full value the first time an account offers one", "TrophyDragonQueen", 50, "boss");
			Add("Bosses, by biome - full value the first time an account offers one", "TrophyGoblinKing", 70, "boss");
			Add("Bosses, by biome - full value the first time an account offers one", "TrophySeekerQueen", 95, "boss");
			Add("Bosses, by biome - full value the first time an account offers one", "TrophyFader", 125, "boss");
			Add("Bosses, by biome - full value the first time an account offers one", "CrownJewel", 160, "boss");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyNeckLord", 5, "lord");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyGreydwarfShamanLord", 10, "lord");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyDraugrEliteLord", 18, "lord");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyFenringLord", 25, "lord");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyLoxLord", 35, "lord");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophySeekerLord", 48, "lord");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyFallerValkyrieLord", 63, "lord");
			Add("Biome Lords (the BiomeLords mod) - half the boss of their biome", "TrophyGammeltrollLord", 80, "lord");
			string[] array = new string[6] { "TrophySkeletonHildir", "TrophyCultist_Hildir", "TrophyGoblinBruteBrosBrute", "TrophyGoblinBruteBrosShaman", "TrophyJotunWarrior", "TrophyJotunWitch" };
			foreach (string prefab in array)
			{
				Add("Rare trophies and items - every copy", prefab, 5, "rare");
			}
			Add("Rare trophies and items - every copy", "DragonEgg", 15, "item");
			array = new string[59]
			{
				"TrophyAbomination", "TrophyAsksvin", "TrophyBarka", "TrophyBjorn", "TrophyBjornUndead", "TrophyBlob", "TrophyBlob_Frost", "TrophyBlob_Lava", "TrophyBlob_Morkhalla", "TrophyBoar",
				"TrophyBonemawSerpent", "TrophyCharredArcher", "TrophyCharredMage", "TrophyCharredMelee", "TrophyCultist", "TrophyDeathsquito", "TrophyDeer", "TrophyDeerWhite", "TrophyDraugr", "TrophyDraugrElite",
				"TrophyDraugrFem", "TrophyDvergr", "TrophyElaking", "TrophyFallenValkyrie", "TrophyFenring", "TrophyForestTroll", "TrophyFrostTroll", "TrophyGhost", "TrophyGjall", "TrophyGoblin",
				"TrophyGoblinBrute", "TrophyGoblinShaman", "TrophyGreydwarf", "TrophyGreydwarfBrute", "TrophyGreydwarfShaman", "TrophyGrowth", "TrophyHare", "TrophyHatchling", "TrophyKvastur", "TrophyLeech",
				"TrophyLox", "TrophyMole", "TrophyMoose", "TrophyMorgen", "TrophyNeck", "TrophySGolem", "TrophySeal", "TrophySeeker", "TrophySeekerBrute", "TrophySerpent",
				"TrophySkeleton", "TrophySkeletonPoison", "TrophySurtling", "TrophyTick", "TrophyUlv", "TrophyVolture", "TrophyWolf", "TrophyWraith", "TrophyWrithan"
			};
			foreach (string prefab2 in array)
			{
				Add("Creature trophies - every copy", prefab2, 1, "item");
			}
			return _defaults = l;
			void Add(string section, string prefab3, int points, string kind)
			{
				l.Add(new DefaultItem
				{
					Section = section,
					Prefab = prefab3,
					Points = points,
					Kind = kind
				});
			}
		}

		private static OfferingsTable DefaultOfferings()
		{
			OfferingsTable offeringsTable = new OfferingsTable
			{
				Version = 2,
				RepeatBossPoints = 1,
				DefaultTrophyPoints = 0
			};
			foreach (DefaultItem item in DefaultItems())
			{
				offeringsTable.Items[item.Prefab] = new OfferingDef
				{
					Points = item.Points,
					Kind = item.Kind
				};
			}
			return offeringsTable;
		}

		private static string RenderOfferings(OfferingsTable t)
		{
			StringBuilder sb = new StringBuilder();
			sb.Append("# Blot offerings - everything that scores at the Obliterator, and how many points.\n#\n# LIVE: save this file and the server uses it within a few seconds - no restart, no command.\n# If the file has a mistake, the server keeps the last good list and says why in its log\n# (BepInEx/LogOutput.log), in `blot log` and on the admin Discord channel.\n#\n# Only the SERVER's copy counts. On a dedicated server, players' own copies are ignored;\n# a player's copy is only used for worlds that player hosts.\n#\n# One line per item:   PrefabName: { points: N, kind: K }\n#   kind: boss | lord   full value the first time an account offers it, repeatBossPoints after that\n#         rare | item   the points every time\n# Add a line to make an item an offering. Delete its line (or set points: 0) to stop it scoring.\n# PrefabName is the game's internal name (TrophyBoar, not \"Boar Trophy\").\n# Anything not listed scores 0. Delete this file to get the default list back.\n\nversion: " + 2 + "\n\n# Points for a boss or Lord offering after the account's first one.\nrepeatBossPoints: " + t.RepeatBossPoints + "\n\n# Points for an UNLISTED item whose prefab name starts with \"Trophy\" (from other mods).\n# 0 = only the items listed below score.\ndefaultTrophyPoints: " + t.DefaultTrophyPoints + "\n\nitems:\n");
			HashSet<string> known = new HashSet<string>(from d in DefaultItems()
				select d.Prefab);
			int width = Math.Max(28, t.Items.Keys.Select((string k) => Key(k).Length + 1).DefaultIfEmpty(0).Max());
			bool first = true;
			string[] sections = Sections;
			foreach (string s in sections)
			{
				Section(s, from d in DefaultItems()
					where d.Section == s
					select d.Prefab);
			}
			Section("Your own additions", t.Items.Keys.Where((string k) => !known.Contains(k)).OrderBy<string, string>((string k) => k, StringComparer.Ordinal));
			if (t.Items.Count == 0)
			{
				sb.Append("  {}\n");
			}
			return sb.ToString();
			void Section(string title, IEnumerable<string> prefabs)
			{
				List<string> list = prefabs.Where(t.Items.ContainsKey).ToList();
				if (list.Count == 0)
				{
					return;
				}
				if (!first)
				{
					sb.Append("\n");
				}
				first = false;
				sb.Append("  # ").Append(title).Append("\n");
				foreach (string item in list)
				{
					OfferingDef offeringDef = t.Items[item];
					sb.Append("  ").Append((Key(item) + ":").PadRight(width)).Append(" { points: ")
						.Append(offeringDef.Points.ToString().PadLeft(3))
						.Append(", kind: ")
						.Append(offeringDef.Kind)
						.Append(" }\n");
				}
			}
		}

		private static string Key(string prefab)
		{
			if (!PlainKey.IsMatch(prefab))
			{
				return "'" + prefab.Replace("'", "''") + "'";
			}
			return prefab;
		}

		private static BlessingsTable DefaultBlessings()
		{
			BlessingsTable t = new BlessingsTable
			{
				MilestoneOrder = new List<string> { "odins_insight", "freyrs_bounty", "@random" },
				RandomPool = new List<string> { "bifrost_hour", "valkyries_grace", "mercy_of_hel", "pack_mule", "feast_of_valhalla", "truce_of_the_gods" }
			};
			Add("odins_insight", "Odin's Insight", "SkillGainRate", 1.5f, 120f, "XP x1.5");
			Add("freyrs_bounty", "Freyr's Bounty", "ResourceRate", 1.5f, 120f, "Drops x1.5");
			Add("bifrost_hour", "Bifrost Hour", "TeleportAll", 0f, 30f, "Ore and metal pass through portals");
			Add("valkyries_grace", "Valkyrie's Grace", "DeathKeepEquip", 0f, 60f, "Keep equipped gear on death");
			Add("mercy_of_hel", "Mercy of Hel", "SkillReductionRate", 0.5f, 120f, "Death skill loss x0.5");
			Add("pack_mule", "Pack-Mule", "CarryWeightRate", 1.5f, 60f, "Carry weight x1.5");
			Add("feast_of_valhalla", "Feast of Valhalla", "FoodRate", 0.67f, 120f, "Food lasts x1.5");
			Add("truce_of_the_gods", "Truce of the Gods", "PassiveMobs", 0f, 20f, "Monsters do not attack");
			return t;
			void Add(string id, string name, string key, float mult, float minutes, string effect)
			{
				t.Blessings.Add(new BlessingDef
				{
					Id = id,
					Name = name,
					Key = key,
					Multiplier = mult,
					Minutes = minutes,
					Effect = effect
				});
			}
		}
	}
	internal static class Discord
	{
		public static void Public(string text)
		{
			Post("public", text);
		}

		public static void Admin(string text)
		{
			Post("admin", text);
		}

		public static void Post(string channel, string text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return;
			}
			if (text.Length > 1900)
			{
				text = text.Substring(0, 1900) + "...";
			}
			BlotPlugin.Log.LogInfo((object)("[discord:" + channel + "] " + text));
			if (BlotConfig.PreferServerGuard.Value && ServerGuardBridge.PostDiscord(channel, text))
			{
				return;
			}
			string url = ((channel == "admin" && !string.IsNullOrWhiteSpace(BlotConfig.DiscordWebhookUrlAdmin.Value)) ? BlotConfig.DiscordWebhookUrlAdmin.Value : BlotConfig.DiscordWebhookUrl.Value);
			if (string.IsNullOrWhiteSpace(url))
			{
				return;
			}
			string body = "{\"content\":" + JsonString(text) + "}";
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url.Trim());
					httpWebRequest.Method = "POST";
					httpWebRequest.ContentType = "application/json";
					httpWebRequest.Timeout = 10000;
					byte[] bytes = Encoding.UTF8.GetBytes(body);
					using (Stream stream = httpWebRequest.GetRequestStream())
					{
						stream.Write(bytes, 0, bytes.Length);
					}
					using ((HttpWebResponse)httpWebRequest.GetResponse())
					{
					}
				}
				catch (Exception ex)
				{
					BlotPlugin.Log.LogWarning((object)("Discord post failed: " + ex.Message));
				}
			});
		}

		private static string JsonString(string s)
		{
			StringBuilder stringBuilder = new StringBuilder("\"");
			foreach (char c in s)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				case '\r':
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4"));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.Append('"').ToString();
		}
	}
	internal static class ServerGuardBridge
	{
		private static bool _probed;

		private static Type _api;

		private static PropertyInfo _progressionActive;

		private static MethodInfo _postDiscord;

		private static MethodInfo _isStaff;

		public static bool Present
		{
			get
			{
				Probe();
				return _api != null;
			}
		}

		public static bool ProgressionActive
		{
			get
			{
				Probe();
				try
				{
					return _progressionActive != null && (bool)_progressionActive.GetValue(null, null);
				}
				catch
				{
					return false;
				}
			}
		}

		private static void Probe()
		{
			if (_probed)
			{
				return;
			}
			_probed = true;
			try
			{
				_api = Type.GetType("ValheimServerGuard.ServerGuardApi, Valheim-ServerGuard");
				if (!(_api == null))
				{
					FieldInfo field = _api.GetField("ApiVersion");
					if (field == null || (int)field.GetValue(null) < 1)
					{
						_api = null;
						return;
					}
					_progressionActive = _api.GetProperty("ProgressionActive");
					_postDiscord = _api.GetMethod("PostDiscord");
					_isStaff = _api.GetMethod("IsStaff");
					BlotPlugin.Log.LogInfo((object)"ServerGuard API found: Discord posts and staff checks go through ServerGuard.");
				}
			}
			catch (Exception ex)
			{
				BlotPlugin.Log.LogWarning((object)("ServerGuard API probe failed: " + ex.Message));
				_api = null;
			}
		}

		public static bool PostDiscord(string channel, string message)
		{
			Probe();
			try
			{
				return _postDiscord != null && (bool)_postDiscord.Invoke(null, new object[2] { channel, message });
			}
			catch
			{
				return false;
			}
		}

		public static bool IsStaff(string steamId)
		{
			Probe();
			try
			{
				return _isStaff != null && (bool)_isStaff.Invoke(null, new object[1] { steamId });
			}
			catch
			{
				return false;
			}
		}
	}
	public enum Rite
	{
		Restock,
		Regrowth
	}
	internal static class BlotServer
	{
		[HarmonyPatch(typeof(ZoneSystem), "GenerateLocationsIfNeeded")]
		private static class Patch_GenerateLocationsIfNeeded
		{
			private static void Prefix()
			{
				try
				{
					if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
					{
						OnWorldLoaded();
					}
				}
				catch (Exception arg)
				{
					BlotPlugin.Log.LogError((object)$"Blot world load failed: {arg}");
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private static class Patch_ZNet_Shutdown
		{
			private static void Prefix()
			{
				try
				{
					OnShutdown();
				}
				catch (Exception arg)
				{
					BlotPlugin.Log.LogError((object)$"Blot shutdown save failed: {arg}");
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_CharacterID")]
		private static class Patch_ZNet_CharacterID
		{
			private static void Postfix(ZRpc rpc)
			{
				try
				{
					if (Active && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
					{
						ZNetPeer peer = ZNet.instance.GetPeer(rpc);
						if (peer != null)
						{
							SendStateTo(Accounts.FromPeer(peer));
						}
					}
				}
				catch (Exception ex)
				{
					BlotPlugin.Log.LogWarning((object)("Peer ready sync failed: " + ex.Message));
				}
			}
		}

		[HarmonyPatch(typeof(ZoneSystem), "RPC_SetGlobalKey")]
		private static class Patch_RPC_SetGlobalKey
		{
			private static void Postfix()
			{
				try
				{
					if (Active)
					{
						CheckRitesReady();
						MarkDirty(state: true);
					}
				}
				catch (Exception ex)
				{
					BlotPlugin.Log.LogWarning((object)("Rite readiness check failed: " + ex.Message));
				}
			}
		}

		private enum PlaceResult
		{
			Refilled,
			Built,
			Near,
			Skipped,
			Unknown
		}

		public class ScoreLine
		{
			public string Prefab;

			public int Stack;

			public int Points;

			public string Why = "";
		}

		private class RiteVote
		{
			public int Id;

			public Rite Rite;

			public string ProposerAccount = "";

			public string ProposerName = "";

			public double SecondsLeft;

			public readonly Dictionary<string, bool> Votes = new Dictionary<string, bool>();

			public readonly Dictionary<string, bool?> Fake = new Dictionary<string, bool?>();
		}

		public static readonly Rite[] AllRites = new Rite[2]
		{
			Rite.Restock,
			Rite.Regrowth
		};

		public static BlotState State = new BlotState();

		public static ContributionsDoc Contrib = new ContributionsDoc();

		private static bool _dirty;

		private static bool _stateDirty;

		private static float _secondTimer;

		private static float _saveTimer;

		private static float _broadcastTimer;

		private static readonly string[] CombatKeys = new string[4] { "playerdamage", "enemydamage", "enemyspeedsize", "enemyleveluprate" };

		private static int _busyJobs;

		private static readonly object EnterWaiting = new object();

		private static readonly int PlayerHash = StringExtensionMethods.GetStableHashCode("Player");

		private static readonly Dictionary<int, HashSet<int>> LocationPrefabSets = new Dictionary<int, HashSet<int>>();

		private static int _restockTwins;

		private static RiteVote _vote;

		private static int _nextVoteId = 1;

		private static readonly Dictionary<string, DateTime> _voteCooldown = new Dictionary<string, DateTime>();

		public static bool Active { get; private set; }

		public static long WorldUid { get; private set; }

		public static string DataDir { get; private set; } = "";

		public static bool JobRunning => _busyJobs > 0;

		public static string JobStatus { get; private set; } = "";

		private static string P(string file)
		{
			return Path.Combine(DataDir, file);
		}

		public static string DirFor(long worldUid)
		{
			return Path.Combine(Paths.ConfigPath, "Blot", worldUid.ToString(CultureInfo.InvariantCulture));
		}

		private static void OnWorldLoaded()
		{
			World world = ZNet.World;
			if (world == null)
			{
				return;
			}
			WorldUid = world.m_uid;
			DataDir = DirFor(WorldUid);
			Directory.CreateDirectory(DataDir);
			State = YamlIO.Load<BlotState>(P("state.yaml"));
			Contrib = YamlIO.Load<ContributionsDoc>(P("contributions.yaml"));
			State.RitesAnnouncedReady.RemoveAll((string n) => !TryParseRite(n, out var _));
			Active = true;
			foreach (Tables.Message item in Tables.LoadAll())
			{
				ReportTables(item);
			}
			RollBackLegacyRewards();
			RestoreBlessingsAtBoot();
			BlotPlugin.Log.LogInfo((object)$"Blot server active: world {world.m_name} ({WorldUid}), pool {State.Pool}, offered {State.Offered}.");
			MarkDirty(state: true);
		}

		private static void OnShutdown()
		{
			if (Active)
			{
				SaveAll();
				Active = false;
				ClearVote();
				State = new BlotState();
				Contrib = new ContributionsDoc();
				DataDir = "";
				WorldUid = 0L;
			}
		}

		public static void ReportTables(Tables.Message m)
		{
			if (m != null)
			{
				if (m.Error)
				{
					BlotPlugin.Log.LogError((object)m.Text);
					AddLog(m.Text);
					Discord.Admin(":warning: " + m.Text);
				}
				else
				{
					BlotPlugin.Log.LogInfo((object)m.Text);
				}
			}
		}

		public static void SaveAll()
		{
			if (Active && !string.IsNullOrEmpty(DataDir))
			{
				YamlIO.Save(P("state.yaml"), State);
				YamlIO.Save(P("contributions.yaml"), Contrib);
				_dirty = false;
			}
		}

		public static void MarkDirty(bool state = false)
		{
			_dirty = true;
			if (state)
			{
				_stateDirty = true;
			}
		}

		public static void AddLog(string line)
		{
			State.Log.Add($"{DateTime.UtcNow:yyyy-MM-dd HH:mm} {line}");
			if (State.Log.Count > 200)
			{
				State.Log.RemoveRange(0, State.Log.Count - 200);
			}
			_dirty = true;
		}

		public static void Tick()
		{
			if (!Active)
			{
				return;
			}
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				Active = false;
				return;
			}
			float unscaledDeltaTime = Time.unscaledDeltaTime;
			_secondTimer += unscaledDeltaTime;
			_saveTimer += unscaledDeltaTime;
			_broadcastTimer += unscaledDeltaTime;
			if (_secondTimer >= 1f)
			{
				double secs = _secondTimer;
				_secondTimer = 0f;
				TickBlessings(secs);
				TickVote(secs);
				TickJobs();
				List<Tables.Message> list = Tables.CheckForChanges();
				if (list != null)
				{
					foreach (Tables.Message item in list)
					{
						ReportTables(item);
						if (!item.Error)
						{
							AddLog(item.Text);
						}
					}
					MarkDirty(state: true);
				}
			}
			if (_saveTimer >= 5f)
			{
				_saveTimer = 0f;
				if (_dirty)
				{
					SaveAll();
				}
			}
			if ((_stateDirty && _broadcastTimer >= 1f) || _broadcastTimer >= 10f)
			{
				_broadcastTimer = 0f;
				_stateDirty = false;
				BroadcastState();
			}
		}

		public static int Cost(Rite r)
		{
			if (r != Rite.Restock)
			{
				return BlotConfig.RegrowthCost.Value;
			}
			return BlotConfig.RestockCost.Value;
		}

		public static string GateKey(Rite r)
		{
			if (r != Rite.Restock)
			{
				return BlotConfig.RegrowthGate.Value;
			}
			return BlotConfig.RestockGate.Value;
		}

		public static bool GateOpen(Rite r)
		{
			string text = (GateKey(r) ?? "").Trim();
			if (text.Length != 0)
			{
				if ((Object)(object)ZoneSystem.instance != (Object)null)
				{
					return ZoneSystem.instance.GetGlobalKey(text);
				}
				return false;
			}
			return true;
		}

		public static string RiteName(Rite r)
		{
			return r.ToString();
		}

		public static bool TryParseRite(string s, out Rite rite)
		{
			rite = Rite.Restock;
			switch ((s ?? "").Trim().ToLowerInvariant())
			{
			case "restock":
			case "1":
			case "i":
				rite = Rite.Restock;
				return true;
			case "regrowth":
			case "2":
			case "ii":
				rite = Rite.Regrowth;
				return true;
			default:
				return false;
			}
		}

		public static AccountContribution GetContribution(string account, string name, bool create)
		{
			if (string.IsNullOrEmpty(account))
			{
				return null;
			}
			if (!Contrib.Accounts.TryGetValue(account, out var value))
			{
				if (!create)
				{
					return null;
				}
				value = new AccountContribution();
				Contrib.Accounts[account] = value;
			}
			if (!string.IsNullOrEmpty(name))
			{
				value.Name = name;
			}
			return value;
		}

		public static int ContributionOf(string account)
		{
			return GetContribution(account, null, create: false)?.Total ?? 0;
		}

		public static string RiteBlocker(Rite r, string proposerAccount, bool ignoreCost)
		{
			if (JobRunning)
			{
				return "a rite is still running";
			}
			if (!ignoreCost && State.Pool < Cost(r))
			{
				return $"the pool holds {State.Pool}/{Cost(r)}";
			}
			if (!GateOpen(r))
			{
				return "its boss has not fallen (" + GateKey(r) + ")";
			}
			if (proposerAccount != null && ContributionOf(proposerAccount) < BlotConfig.RiteMinContribution.Value)
			{
				return $"you must offer at least {BlotConfig.RiteMinContribution.Value} points first";
			}
			return null;
		}

		public static void BroadcastState()
		{
			foreach (Who item in Accounts.Online())
			{
				SendStateTo(item);
			}
		}

		public static void SendStateTo(Who w)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Expected O, but got Unknown
			if (w == null)
			{
				return;
			}
			ZPackage val = new ZPackage();
			val.Write(4);
			val.Write(State.Pool);
			val.Write(State.Offered);
			val.Write(BlotConfig.MilestoneEvery.Value);
			val.Write(AllRites.Length);
			Rite[] allRites = AllRites;
			foreach (Rite r in allRites)
			{
				val.Write(RiteName(r));
				val.Write(Cost(r));
				val.Write(GateOpen(r));
				val.Write(RiteBlocker(r, w.Account, ignoreCost: false) ?? "");
			}
			val.Write(State.Blessings.Count);
			foreach (ActiveBlessing blessing in State.Blessings)
			{
				BlessingDef blessingDef = Tables.FindBlessing(blessing.Id);
				val.Write((blessingDef != null) ? blessingDef.Name : blessing.Id);
				val.Write((blessingDef != null) ? blessingDef.Effect : "");
				val.Write((float)blessing.SecondsLeft);
			}
			WriteVote(val, w.Account);
			AccountContribution contribution = GetContribution(w.Account, null, create: false);
			val.Write(contribution?.Total ?? 0);
			val.Write(BlotConfig.DailyCapEnabled.Value ? BlotConfig.DailyCapPoints.Value : 0);
			val.Write((contribution != null && contribution.CapDay == CapDay()) ? contribution.CapUsed : 0);
			Net.ToClient(w.Uid, "Blot_State", val);
		}

		public static void Announce(string text)
		{
			Net.NoticeTo(0L, text);
		}

		private static void CheckMilestones(int before, int after)
		{
			int num = Math.Max(1, BlotConfig.MilestoneEvery.Value);
			int num2 = after / num;
			while (State.MilestonesGranted < num2)
			{
				State.MilestonesGranted++;
				BlessingDef blessingDef = MilestoneBlessing(State.MilestonesGranted);
				if (blessingDef != null)
				{
					StartBlessing(blessingDef, blessingDef.Minutes, $"milestone {State.MilestonesGranted * num}");
				}
			}
		}

		public static BlessingDef MilestoneBlessing(int milestone)
		{
			List<string> milestoneOrder = Tables.Blessings.MilestoneOrder;
			if (milestoneOrder == null || milestoneOrder.Count == 0)
			{
				return null;
			}
			string text = milestoneOrder[(milestone - 1) % milestoneOrder.Count];
			if (text == "@random")
			{
				List<BlessingDef> list = (from b in Tables.Blessings.RandomPool.Select(Tables.FindBlessing)
					where b != null
					select b).ToList();
				if (list.Count == 0)
				{
					return null;
				}
				return list[Random.Range(0, list.Count)];
			}
			return Tables.FindBlessing(text);
		}

		public unsafe static string StartBlessing(BlessingDef def, float minutes, string why)
		{
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZoneSystem.instance == (Object)null)
			{
				return "The world is not ready.";
			}
			if (!TryParseKey(def.Key, out var key))
			{
				return "`" + def.Key + "` is not a world modifier.";
			}
			string key2 = ((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();
			ActiveBlessing activeBlessing = State.Blessings.FirstOrDefault((ActiveBlessing b) => b.Key == key2);
			if (activeBlessing != null)
			{
				if (activeBlessing.Id != def.Id)
				{
					return $"Another blessing ({activeBlessing.Id}) already holds {key}.";
				}
				activeBlessing.SecondsLeft += (double)minutes * 60.0;
				AnnounceBlessingStart(def, activeBlessing.SecondsLeft, why, extended: true);
				MarkDirty(state: true);
				return def.Name + " extended to " + Format(activeBlessing.SecondsLeft) + ".";
			}
			ActiveBlessing activeBlessing2 = new ActiveBlessing
			{
				Id = def.Id,
				Key = key2,
				SecondsLeft = (double)minutes * 60.0
			};
			string text = default(string);
			if (ZoneSystem.instance.GetGlobalKey(key, ref text))
			{
				activeBlessing2.HadOriginal = true;
				activeBlessing2.Original = text ?? "";
			}
			if (def.Multiplier > 0f)
			{
				float num = 100f;
				if (activeBlessing2.HadOriginal && float.TryParse(activeBlessing2.Original, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
				{
					num = result;
				}
				ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString() + " " + Math.Round(num * def.Multiplier, 1).ToString(CultureInfo.InvariantCulture));
			}
			else
			{
				if (activeBlessing2.HadOriginal)
				{
					return $"{key} is already on in this world - nothing to bless.";
				}
				ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString());
			}
			State.Blessings.Add(activeBlessing2);
			AddLog("blessing " + def.Id + " started (" + why + ")");
			AnnounceBlessingStart(def, activeBlessing2.SecondsLeft, why, extended: false);
			MarkDirty(state: true);
			return def.Name + " started for " + Format(activeBlessing2.SecondsLeft) + ".";
		}

		public static string EndBlessing(string idOrKey, string why)
		{
			ActiveBlessing activeBlessing = State.Blessings.FirstOrDefault((ActiveBlessing x) => x.Id == idOrKey || x.Key == idOrKey);
			if (activeBlessing == null)
			{
				BlessingDef def0 = Tables.FindBlessing(idOrKey);
				if (def0 != null)
				{
					activeBlessing = State.Blessings.FirstOrDefault((ActiveBlessing x) => x.Id == def0.Id);
				}
			}
			if (activeBlessing == null)
			{
				return "No blessing `" + idOrKey + "` is running.";
			}
			Restore(activeBlessing);
			State.Blessings.Remove(activeBlessing);
			BlessingDef blessingDef = Tables.FindBlessing(activeBlessing.Id);
			string text = ((blessingDef != null) ? blessingDef.Name : activeBlessing.Id);
			AddLog("blessing " + activeBlessing.Id + " ended (" + why + ")");
			Announce("Blot Blessing - " + text + " has faded.");
			Discord.Public(":hourglass: **" + text + "** has faded.");
			MarkDirty(state: true);
			return text + " ended.";
		}

		private unsafe static void Restore(ActiveBlessing b)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZoneSystem.instance == (Object)null || !TryParseKey(b.Key, out var key))
			{
				return;
			}
			if (b.HadOriginal)
			{
				if (b.Original.Length > 0)
				{
					ServerSetKey($"{key} {b.Original}");
				}
				else
				{
					ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString());
				}
			}
			else
			{
				ServerRemoveKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString());
			}
		}

		private static void TickBlessings(double secs)
		{
			if (State.Blessings.Count == 0 || (BlotConfig.OnlineOnlyTimers.Value && Accounts.Online().Count == 0))
			{
				return;
			}
			foreach (ActiveBlessing item in State.Blessings.ToList())
			{
				item.SecondsLeft -= secs;
				if (item.SecondsLeft <= 0.0)
				{
					EndBlessing(item.Id, "time");
				}
			}
			_dirty = true;
		}

		private unsafe static void RestoreBlessingsAtBoot()
		{
			foreach (ActiveBlessing item in State.Blessings.ToList())
			{
				if (item.SecondsLeft <= 0.0)
				{
					Restore(item);
					State.Blessings.Remove(item);
					continue;
				}
				BlessingDef blessingDef = Tables.FindBlessing(item.Id);
				if (blessingDef == null || !TryParseKey(item.Key, out var key))
				{
					Restore(item);
					State.Blessings.Remove(item);
				}
				else if (blessingDef.Multiplier > 0f)
				{
					float num = 100f;
					if (item.HadOriginal && float.TryParse(item.Original, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
					{
						num = result;
					}
					ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString() + " " + Math.Round(num * blessingDef.Multiplier, 1).ToString(CultureInfo.InvariantCulture));
				}
				else
				{
					ServerSetKey(((object)(*(GlobalKeys*)(&key))/*cast due to .constrained prefix*/).ToString());
				}
			}
		}

		private static void RollBackLegacyRewards()
		{
			if ((Object)(object)ZoneSystem.instance == (Object)null)
			{
				return;
			}
			if (State.Permanent != null)
			{
				Restore(State.Permanent);
				AddLog("0.2.x permanent blessing " + State.Permanent.Id + " rolled back");
				BlotPlugin.Log.LogInfo((object)("Rolled back the 0.2.x permanent blessing " + State.Permanent.Id + "."));
				State.Permanent = null;
			}
			if (string.IsNullOrEmpty(State.Difficulty) && State.DifficultyOriginal == null)
			{
				return;
			}
			string[] combatKeys = CombatKeys;
			for (int i = 0; i < combatKeys.Length; i++)
			{
				ServerRemoveKey(combatKeys[i]);
			}
			foreach (string item in State.DifficultyOriginal ?? new List<string>())
			{
				ServerSetKey(item);
			}
			AddLog("0.2.x difficulty " + State.Difficulty + " rolled back to the server's own combat setting");
			BlotPlugin.Log.LogInfo((object)("Rolled back the 0.2.x difficulty '" + State.Difficulty + "' to the server's own combat setting."));
			State.Difficulty = "";
			State.DifficultyOriginal = null;
		}

		private static void AnnounceBlessingStart(BlessingDef def, double seconds, string why, bool extended)
		{
			string text = Format(seconds);
			Announce(extended ? ("Blot Blessing - " + def.Name + " lengthened: " + def.Effect + " for " + text + " of play.") : ("Blot Blessing - " + def.Name + ": " + def.Effect + " for " + text + " of play."));
			Discord.Public(extended ? (":sparkles: **" + def.Name + "** grows longer - " + def.Effect + ", now " + text + " of play.") : $":sparkles: **{def.Name}** is upon the realm - {def.Effect} for {text} of play. Offered by the realm so far: {State.Offered}.");
		}

		public static void ServerSetKey(string line)
		{
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			string item = line.ToLowerInvariant();
			if (!instance.m_globalKeys.Contains(item))
			{
				instance.GlobalKeyAdd(line, true);
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetPeers().Count > 0)
				{
					instance.SendGlobalKeys(0L);
				}
				CheckRitesReady();
			}
		}

		public static void ServerRemoveKey(string name)
		{
			ZoneSystem instance = ZoneSystem.instance;
			if (!((Object)(object)instance == (Object)null) && instance.GlobalKeyRemove(name.ToLowerInvariant(), true) && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetPeers().Count > 0)
			{
				instance.SendGlobalKeys(0L);
			}
		}

		private static bool TryParseKey(string s, out GlobalKeys key)
		{
			//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_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Invalid comparison between Unknown and I4
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected I4, but got Unknown
			key = (GlobalKeys)41;
			if (string.IsNullOrEmpty(s))
			{
				return false;
			}
			foreach (GlobalKeys value in Enum.GetValues(typeof(GlobalKeys)))
			{
				if ((int)value >= 41)
				{
					break;
				}
				if (string.Equals(((object)value/*cast due to .constrained prefix*/).ToString(), s.Trim(), StringComparison.OrdinalIgnoreCase))
				{
					key = (GlobalKeys)(int)value;
					return true;
				}
			}
			return false;
		}

		public static string Format(double seconds)
		{
			if (seconds < 0.0)
			{
				seconds = 0.0;
			}
			TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
			if (timeSpan.TotalHours >= 1.0)
			{
				return $"{(int)timeSpan.TotalHours} h {timeSpan.Minutes:00} min";
			}
			if (timeSpan.TotalMinutes >= 1.0)
			{
				return $"{timeSpan.Minutes} min";
			}
			return $"{timeSpan.Seconds} s";
		}

		public static void OnCommand(long sender, ZPackage pkg)
		{
			Who who = Accounts.FromSender(sender);
			if (who == null)
			{
				return;
			}
			string text = pkg.ReadString() ?? "";
			string text2;
			if (!Active)
			{
				text2 = "Blot is not running on this server yet.";
			}
			else
			{
				try
				{
					text2 = Dispatch(who, text.Trim());
				}
				catch (Exception ex)
				{
					BlotPlugin.Log.LogError((object)$"Command '{text}' from {who.Name}: {ex}");
					text2 = "Error: " + ex.Message;
				}
			}
			Reply(who, text2);
		}

		private static void Reply(Who who, string text)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(text ?? "");
			Net.ToClient(who.Uid, "Blot_Reply", val);
		}

		public static bool IsAdmin(Who who)
		{
			if (who == null)
			{
				return false;
			}
			if (who.IsHost)
			{
				return true;
			}
			try
			{
				if (ZNet.instance.IsAdmin(who.Account))
				{
					return true;
				}
			}
			catch
			{
			}
			return ServerGuardBridge.IsStaff(who.Account);
		}

		private static string Dispatch(Who who, string text)
		{
			List<string> list = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
			if (list.Count > 0 && list[0].Equals("blot", StringComparison.OrdinalIgnoreCase))
			{
				list.RemoveAt(0);
			}
			string text2 = ((list.Count > 0) ? list[0].ToLowerInvariant() : "help");
			List<string> list2 = list.Skip(1).ToList();
			bool flag = IsAdmin(who);
			switch (text2)
			{
			case "help":
				return Help(flag);
			case "me":
				return Me(who);
			case "status":
				return Status(flag);
			case "rite":
			{
				if (list2.Count == 0 || !TryParseRite(list2[0], out var rite))
				{
					return "Usage: blot rite restock|regrowth";
				}
				return Propose(who, rite);
			}
			case "yes":
				return CastVote(who, yes: true);
			case "no":
				return CastVote(who, yes: false);
			default:
				if (!flag)
				{
					return "Unknown or admin-only command `" + text2 + "`. Try `blot help`.";
				}
				if (text2 != "status" && text2 != "log")
				{
					Discord.Admin(":hammer_and_wrench: **" + who.Name + "** ran `blot " + string.Join(" ", list) + "`");
				}
				switch (text2)
				{
				case "points":
					return Points(list2);
				case "perform":
					return AdminPerform(who, list2);
				case "vote":
					if (list2.Count > 0 && list2[0] == "cancel")
					{
						return CancelVote();
					}
					return "Usage: blot vote cancel";
				case "bless":
					return Bless(list2);
				case "reload":
				{
					List<Tables.Message> list3 = Tables.LoadAll();
					foreach (Tables.Message item in list3)
					{
						if (item.Error)
						{
							ReportTables(item);
						}
					}
					MarkDirty(state: true);
					return string.Join("\n", list3.Select((Tables.Message m) => m.Text));
				}
				case "log":
				{
					int result;
					int num = ((list2.Count > 0 && int.TryParse(list2[0], out result)) ? Mathf.Clamp(result, 1, 40) : 15);
					if (State.Log.Count != 0)
					{
						return string.Join("\n", State.Log.Skip(Math.Max(0, State.Log.Count - num)));
					}
					return "The Blot log is empty.";
				}
				case "test":
					return Test(who, list2);
				default:
					return "Unknown command `" + text2 + "`. Try `blot help`.";
				}
			}
		}

		private static string Help(bool admin)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("Blot commands (F5 console, or /blot in chat):");
			stringBuilder.AppendLine("  blot me                         your offerings and today's cap");
			stringBuilder.AppendLine("  blot status                     pool, rites, blessings");
			stringBuilder.AppendLine("  blot rite restock|regrowth      propose a rite (opens a vote)");
			stringBuilder.AppendLine("  blot yes | blot no              vote on the open proposal");
			if (!admin)
			{
				return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
			}
			stringBuilder.AppendLine("Admin:");
			stringBuilder.AppendLine("  blot points add|set <n>         change the pool");
			stringBuilder.AppendLine("  blot perform <rite> [force]     perform now: no vote, no cost (force: no gate either)");
			stringBuilder.AppendLine("  blot vote cancel                close the open vote as failed");
			stringBuilder.AppendLine("  blot bless <blessing> [minutes] | blot bless end <blessing>");
			stringBuilder.AppendLine("  blot reload | blot log [n]");
			stringBuilder.AppendLine("  blot test ...                   test helpers - `blot test` lists them");
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		private static string Me(Who who)
		{
			AccountContribution contribution = GetContribution(who.Account, null, create: false);
			int value = BlotConfig.DailyCapPoints.Valu