Decompiled source of RestockRegen v0.19.0

plugins/RestockRegen.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("RestockRegen")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.19.0.0")]
[assembly: AssemblyInformationalVersion("0.19.0+69842f6b1567c8788fad48857eca4647fe6abdae")]
[assembly: AssemblyProduct("RestockRegen")]
[assembly: AssemblyTitle("RestockRegen")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.19.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RestockRegen
{
	[HarmonyPatch(typeof(ZNet), "RPC_Kick")]
	internal static class AdminCommands
	{
		private static bool Prefix(ZNet __instance, ZRpc rpc, string user)
		{
			string text = (user ?? "").Trim();
			if (!text.StartsWith("restock-", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			string command = text.Substring("restock-".Length);
			ZNetPeer peer = __instance.GetPeer(rpc);
			if (!__instance.ListContainsId(__instance.m_adminList, rpc.GetSocket().GetHostName()))
			{
				__instance.RemotePrint(rpc, "RestockRegen commands are for server admins");
				return false;
			}
			RestockRegenPlugin.Log.LogInfo((object)("admin command from " + (peer?.m_playerName ?? "?") + ": " + text));
			try
			{
				string[] array = Handle(command);
				foreach (string text2 in array)
				{
					__instance.RemotePrint(rpc, text2);
					if (peer != null)
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { 1, text2 });
					}
				}
			}
			catch (Exception arg)
			{
				RestockRegenPlugin.Log.LogError((object)$"admin command '{text}' failed: {arg}");
				__instance.RemotePrint(rpc, "RestockRegen: command failed, see the server log");
			}
			return false;
		}

		private static string[] Handle(string command)
		{
			string text = command.ToLowerInvariant();
			switch (text)
			{
			case "status":
				return new string[1] { RestockRegenPlugin.Instance.Status() };
			case "regen-cores":
				RestockRegenPlugin.Instance.RegenDungeonsNow(RestockRegenPlugin.BlackCoreKind);
				return new string[1] { "RestockRegen: regenerating every free infested mine with taken black cores now (details in the server log)" };
			case "regen-hole":
				RestockRegenPlugin.Instance.RegenDungeonsNow(RestockRegenPlugin.HoleKind);
				return new string[1] { "RestockRegen: regenerating every free Hole's nests and trash piles now (details in the server log)" };
			case "regen-citadel":
				RestockRegenPlugin.Instance.RegenDungeonsNow(RestockRegenPlugin.CitadelKind);
				return new string[1] { "RestockRegen: resetting every free infested citadel whose Queen is dead now (details in the server log)" };
			case "regen-morkhalla":
				RestockRegenPlugin.Instance.RegenDungeonsNow(RestockRegenPlugin.MorkhallaKind);
				return new string[1] { "RestockRegen: regenerating the Morkhalla dungeon's gems and coin piles now, if nobody is inside (details in the server log)" };
			case "regen-crystals":
				RestockRegenPlugin.Instance.RegenDungeonsNow(RestockRegenPlugin.CrystalKind);
				return new string[1] { "RestockRegen: regenerating every free frost cave with picked crystals now (details in the server log)" };
			default:
				if (text.StartsWith("regen-"))
				{
					string id = text.Substring("regen-".Length);
					if (RestockRegenPlugin.Instance.RegrowNow(id))
					{
						return new string[1] { "RestockRegen: restoring every damaged or gone " + Regrow.Find(id).Label + " spot with nobody near and no build on it, now (details in the server log)" };
					}
					return new string[1] { "RestockRegen: use restock-regen (crypts), restock-regen-crystals, restock-regen-cores, restock-regen-morkhalla, restock-regen-hole, restock-regen-citadel, restock-regen-armor, restock-regen-obsidian, restock-regen-flametal or restock-regen-ice" };
				}
				if (text == "regen")
				{
					RestockRegenPlugin.Instance.RegenDungeonsNow(RestockRegenPlugin.MudKind);
					return new string[1] { "RestockRegen: regenerating every free sunken crypt with mined piles now (details in the server log)" };
				}
				if (text.StartsWith("get:"))
				{
					ConfigEntryBase val = Find(command.Substring(4));
					return new string[1] { (val == null) ? ("RestockRegen: no setting '" + command.Substring(4) + "'") : (val.Definition.Key + " = " + val.GetSerializedValue()) };
				}
				if (text.StartsWith("set:"))
				{
					string[] array = command.Substring(4).Split(new char[1] { '=' }, 2);
					ConfigEntryBase val2 = ((array.Length == 2) ? Find(array[0]) : null);
					if (val2 != null)
					{
						string serializedValue = ((val2.SettingType == typeof(string)) ? array[1].Replace('_', ' ') : array[1]);
						val2.SetSerializedValue(serializedValue);
						return new string[1] { val2.Definition.Key + " = " + val2.GetSerializedValue() };
					}
					return new string[1] { "RestockRegen: use restock-set:Setting=value (restock-help lists them)" };
				}
				return new string[3]
				{
					"RestockRegen commands (in chat: /kick <command>, in the F5 console: kick <command>):",
					"  restock-status   restock-regen   restock-regen-crystals   restock-regen-cores   restock-regen-morkhalla   restock-regen-hole   restock-regen-citadel   restock-regen-armor   restock-regen-obsidian   restock-regen-flametal   restock-regen-ice   restock-get:Setting   restock-set:Setting=value",
					"  In text settings type _ for a space. Settings: " + string.Join(", ", ((BaseUnityPlugin)RestockRegenPlugin.Instance).Config.Keys.Select((ConfigDefinition k) => k.Key))
				};
			}
		}

		private static ConfigEntryBase Find(string key)
		{
			ConfigFile config = ((BaseUnityPlugin)RestockRegenPlugin.Instance).Config;
			ConfigDefinition val = ((IEnumerable<ConfigDefinition>)config.Keys).FirstOrDefault((Func<ConfigDefinition, bool>)((ConfigDefinition k) => k.Key.Equals(key.Trim(), StringComparison.OrdinalIgnoreCase)));
			if (!(val == (ConfigDefinition)null))
			{
				return config[val];
			}
			return null;
		}
	}
	internal class DungeonRegrow
	{
		internal class Kind
		{
			public string Label;

			public string DungeonLabel;

			public string Generator;

			public string Singular;

			public string Plural;

			public string SpotsKey;

			public string VisitKey;

			public string[] Names;

			public ConfigEntry<bool> On;

			public ConfigEntry<bool> DryRun;

			public ConfigEntry<bool> RegenNow;

			public ConfigEntry<int> Days;

			public ConfigEntry<string> NotifyText;

			public ConfigEntry<string> EntranceText;

			public ConfigEntry<string> DueText;

			public ConfigEntry<float> EntranceRadius;

			public ConfigEntry<float> Radius;

			public string Boss;

			public string BossRoom;

			public string Door;

			public Vector3 BossOffset;

			public float BossYaw;

			public ConfigEntry<bool> CloseDoor;
		}

		private struct Spot
		{
			public int Prefab;

			public Vector3 Pos;

			public Vector3 Rot;
		}

		private class Dungeon
		{
			public ZDOID Generator;

			public Vector3 Pos;

			public Vector3 Entrance;

			public ZDOID Door;

			public readonly Dictionary<(int, int, int), Spot> Spots = new Dictionary<(int, int, int), Spot>();

			public int LastVisit = -1;

			public bool SpotsDirty;

			public bool VisitDirty;
		}

		internal struct Result
		{
			public int Dungeons;

			public int Waiting;

			public int Occupied;

			public int Regenerated;

			public int Rebuilt;

			public int Replaced;
		}

		[HarmonyPatch(typeof(ZDO), "Deserialize")]
		private static class SeeThings
		{
			private static void Postfix(ZDO __instance)
			{
				foreach (DungeonRegrow item in All)
				{
					if (item.m_ready && item.m_intactOf.ContainsKey(__instance.GetPrefab()) && item.K.On.Value)
					{
						item.Remember(item.SpotOf(__instance));
					}
				}
			}
		}

		private const float InteriorHeight = 4000f;

		internal static readonly List<DungeonRegrow> All = new List<DungeonRegrow>();

		internal readonly Kind K;

		private readonly int m_generator;

		private readonly int m_spotsKey;

		private readonly int m_visitKey;

		private readonly int m_boss;

		private readonly int m_bossRoom;

		private readonly int m_door;

		private readonly Dictionary<int, int> m_intactOf = new Dictionary<int, int>();

		private readonly HashSet<int> m_intact = new HashSet<int>();

		private readonly Dictionary<ZDOID, Dungeon> m_dungeons = new Dictionary<ZDOID, Dungeon>();

		private readonly List<Vector3> m_otherDungeons = new List<Vector3>();

		private readonly Dictionary<(int, int, int), Spot> m_seen = new Dictionary<(int, int, int), Spot>();

		private readonly Dictionary<long, (ZDOID Dungeon, int Stage)> m_welcomed = new Dictionary<long, (ZDOID, int)>();

		private readonly List<ZDO> m_nearby = new List<ZDO>();

		private bool m_ready;

		internal DungeonRegrow(Kind kind)
		{
			K = kind;
			m_generator = StringExtensionMethods.GetStableHashCode(kind.Generator);
			m_spotsKey = StringExtensionMethods.GetStableHashCode(kind.SpotsKey);
			m_visitKey = StringExtensionMethods.GetStableHashCode(kind.VisitKey);
			if (kind.Boss != null)
			{
				m_boss = StringExtensionMethods.GetStableHashCode(kind.Boss);
				m_bossRoom = StringExtensionMethods.GetStableHashCode(kind.BossRoom);
				m_door = StringExtensionMethods.GetStableHashCode(kind.Door);
			}
		}

		private static (int, int, int) Key(Vector3 p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			return (Mathf.RoundToInt(p.x * 10f), Mathf.RoundToInt(p.y * 10f), Mathf.RoundToInt(p.z * 10f));
		}

		private static float Flat(Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = new Vector2(a.x - b.x, a.z - b.z);
			return ((Vector2)(ref val)).magnitude;
		}

		private bool IsWhole(ZDO zdo)
		{
			if (m_intact.Contains(zdo.GetPrefab()))
			{
				return !zdo.GetBool(ZDOVars.s_picked, false);
			}
			return false;
		}

		internal void Init()
		{
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_055b: Unknown result type (might be due to invalid IL or missing references)
			//IL_056c: Unknown result type (might be due to invalid IL or missing references)
			//IL_057d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0593: Unknown result type (might be due to invalid IL or missing references)
			//IL_0598: Unknown result type (might be due to invalid IL or missing references)
			m_intactOf.Clear();
			m_intact.Clear();
			HashSet<int> hashSet = new HashSet<int>();
			foreach (GameObject prefab3 in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab3 != (Object)null && (Object)(object)prefab3.GetComponent<DungeonGenerator>() != (Object)null)
				{
					hashSet.Add(StringExtensionMethods.GetStableHashCode(((Object)prefab3).name));
				}
			}
			hashSet.Add(m_generator);
			string[] names = K.Names;
			foreach (string text in names)
			{
				if ((Object)(object)ZNetScene.instance.GetPrefab(text) == (Object)null)
				{
					continue;
				}
				int stableHashCode = StringExtensionMethods.GetStableHashCode(text);
				m_intact.Add(stableHashCode);
				m_intactOf[stableHashCode] = stableHashCode;
				string[] array = new string[2] { "_frac", "_destruction" };
				foreach (string text2 in array)
				{
					if ((Object)(object)ZNetScene.instance.GetPrefab(text + text2) != (Object)null)
					{
						m_intactOf[StringExtensionMethods.GetStableHashCode(text + text2)] = stableHashCode;
					}
				}
			}
			m_dungeons.Clear();
			m_otherDungeons.Clear();
			List<ZDO> list = new List<ZDO>();
			List<ZDO> list2 = new List<ZDO>();
			List<ZDO> list3 = new List<ZDO>();
			foreach (ZDO value in ZDOMan.instance.m_objectsByID.Values)
			{
				int prefab = value.GetPrefab();
				if (prefab == m_generator)
				{
					AddDungeon(value);
				}
				else if (hashSet.Contains(prefab))
				{
					m_otherDungeons.Add(value.GetPosition());
				}
				else if (m_boss != 0 && prefab == m_door)
				{
					list2.Add(value);
				}
				else if (m_boss != 0 && prefab == m_boss)
				{
					list3.Add(value);
				}
				else if (m_intactOf.ContainsKey(prefab))
				{
					list.Add(value);
				}
			}
			int num = 0;
			foreach (ZDO item in list)
			{
				if (Remember(SpotOf(item)))
				{
					num++;
				}
			}
			m_ready = true;
			int num2 = m_dungeons.Values.Sum((Dungeon c) => c.Spots.Count);
			int num3 = list.Count(IsWhole);
			names = K.Names;
			foreach (string text3 in names)
			{
				GameObject prefab2 = ZNetScene.instance.GetPrefab(text3);
				Pickable val = ((prefab2 != null) ? prefab2.GetComponent<Pickable>() : null);
				if ((Object)(object)val != (Object)null)
				{
					RestockRegenPlugin.Log.LogInfo((object)($"{K.Label}: {text3} is a pickable, game respawn {val.m_respawnTimeMinutes} min, " + (((Object)(object)val.m_hideWhenPicked != (Object)null) ? "stays in the world when picked" : "deleted when picked")));
				}
			}
			RestockRegenPlugin.Log.LogInfo((object)($"{K.Label}: {m_dungeons.Count} {K.DungeonLabel}s, {num2} spots remembered ({num} new this start), " + string.Format("{0} whole and {1} damaged in the world now{2}{3}", num3, list.Count - num3, K.On.Value ? "" : " (off)", K.DryRun.Value ? " (dry run)" : "")));
			if (m_boss == 0)
			{
				return;
			}
			FindDoors(list2);
			HashSet<Dungeon> hashSet2 = new HashSet<Dungeon>(from b in list3
				select DungeonAt(b.GetPosition()) into d
				where d != null
				select d);
			foreach (Dungeon value2 in m_dungeons.Values)
			{
				RestockRegenPlugin.Log.LogInfo((object)(string.Format("{0} at ({1:0}, {2:0}): {3} {4}, ", K.DungeonLabel, value2.Pos.x, value2.Pos.z, K.Boss, hashSet2.Contains(value2) ? "alive" : "gone") + (BossSpot(value2, out var spot) ? $"its place is ({spot.Pos.x:0.0}, {spot.Pos.y:0.0}, {spot.Pos.z:0.0})" : ("its room " + K.BossRoom + " was not found, so it cannot be reset")) + ((value2.Door != ZDOID.None) ? $", door at ({value2.Entrance.x:0}, {value2.Entrance.z:0})" : ", door not found")));
			}
		}

		private void FindDoors(List<ZDO> doors)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			foreach (Dungeon dungeon in m_dungeons.Values)
			{
				ZDO val = (from d in doors
					where Flat(d.GetPosition(), dungeon.Pos) < 100f
					orderby Flat(d.GetPosition(), dungeon.Pos)
					select d).FirstOrDefault();
				if (val != null)
				{
					dungeon.Door = val.m_uid;
					dungeon.Entrance = val.GetPosition();
				}
			}
		}

		private bool BossSpot(Dungeon dungeon, out Spot spot)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			spot = default(Spot);
			ZDO zDO = ZDOMan.instance.GetZDO(dungeon.Generator);
			byte[] array = ((zDO != null) ? zDO.GetByteArray(ZDOVars.s_roomData, (byte[])null) : null);
			if (array == null)
			{
				return false;
			}
			ZPackage val = new ZPackage(array);
			int num = val.ReadInt();
			for (int i = 0; i < num; i++)
			{
				int num2 = val.ReadInt();
				Vector3 val2 = val.ReadVector3();
				Quaternion val3 = Quaternion.Euler(val.ReadVector3());
				if (num2 == m_bossRoom)
				{
					Spot spot2 = new Spot
					{
						Prefab = m_boss,
						Pos = val2 + val3 * K.BossOffset
					};
					Quaternion val4 = val3 * Quaternion.Euler(0f, K.BossYaw, 0f);
					spot2.Rot = ((Quaternion)(ref val4)).eulerAngles;
					spot = spot2;
					return true;
				}
			}
			return false;
		}

		private Dungeon AddDungeon(ZDO generator)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			if (m_dungeons.TryGetValue(generator.m_uid, out var value))
			{
				return value;
			}
			value = new Dungeon
			{
				Generator = generator.m_uid,
				Pos = generator.GetPosition(),
				Entrance = generator.GetPosition()
			};
			int num = generator.GetInt(m_visitKey, 0);
			value.LastVisit = ((num > 0) ? (num - 1) : (-1));
			byte[] byteArray = generator.GetByteArray(m_spotsKey, (byte[])null);
			if (byteArray != null)
			{
				ZPackage val = new ZPackage(byteArray);
				if (val.ReadInt() == 1)
				{
					int num2 = val.ReadInt();
					for (int i = 0; i < num2; i++)
					{
						Spot value2 = new Spot
						{
							Prefab = val.ReadInt(),
							Pos = val.ReadVector3(),
							Rot = val.ReadVector3()
						};
						value.Spots[Key(value2.Pos)] = value2;
					}
				}
			}
			m_dungeons[generator.m_uid] = value;
			return value;
		}

		private Spot SpotOf(ZDO thing)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: 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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			Spot result = new Spot
			{
				Prefab = m_intactOf[thing.GetPrefab()],
				Pos = thing.GetPosition()
			};
			Quaternion rotation = thing.GetRotation();
			result.Rot = ((Quaternion)(ref rotation)).eulerAngles;
			return result;
		}

		private Dungeon DungeonAt(Vector3 p)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			if (p.y < 4000f)
			{
				return null;
			}
			Dungeon dungeon = null;
			float bestDist = K.Radius.Value;
			foreach (Dungeon value in m_dungeons.Values)
			{
				float num = Flat(p, value.Pos);
				if (num < bestDist && Mathf.Abs(p.y - value.Pos.y) < 500f)
				{
					dungeon = value;
					bestDist = num;
				}
			}
			if (dungeon != null && m_otherDungeons.Any((Vector3 o) => o.y > 4000f && Flat(p, o) < bestDist))
			{
				return null;
			}
			return dungeon;
		}

		private Dungeon EntranceAt(Vector3 p, float radius)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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)
			if (p.y >= 4000f)
			{
				return null;
			}
			Dungeon result = null;
			float num = radius;
			foreach (Dungeon value in m_dungeons.Values)
			{
				float num2 = Flat(p, value.Entrance);
				if (num2 < num)
				{
					result = value;
					num = num2;
				}
			}
			return result;
		}

		private bool Remember(Spot spot)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			Dungeon dungeon = DungeonAt(spot.Pos);
			if (dungeon == null)
			{
				m_seen[Key(spot.Pos)] = spot;
				return false;
			}
			(int, int, int) key = Key(spot.Pos);
			if (dungeon.Spots.ContainsKey(key))
			{
				return false;
			}
			dungeon.Spots[key] = spot;
			dungeon.SpotsDirty = true;
			return true;
		}

		internal void Tick(int today)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			if (!m_ready || !K.On.Value)
			{
				return;
			}
			float value = K.EntranceRadius.Value;
			HashSet<long> present = new HashSet<long>();
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				present.Add(peer.m_uid);
				Vector3 refPos = peer.GetRefPos();
				Dungeon dungeon = DungeonAt(refPos);
				if (dungeon != null && dungeon.LastVisit != today)
				{
					dungeon.LastVisit = today;
					dungeon.VisitDirty = true;
				}
				Dungeon dungeon2 = dungeon ?? EntranceAt(refPos, value);
				if (dungeon2 == null)
				{
					if (m_welcomed.TryGetValue(peer.m_uid, out (ZDOID, int) value2) && (!m_dungeons.TryGetValue(value2.Item1, out var value3) || !((refPos.y < 4000f) ? (Flat(refPos, value3.Entrance) < value * 2f) : (DungeonAt(refPos) == value3))))
					{
						m_welcomed.Remove(peer.m_uid);
					}
					continue;
				}
				int num = ((dungeon == null) ? 1 : 2);
				if (!m_welcomed.TryGetValue(peer.m_uid, out (ZDOID, int) value4) || value4.Item1 != dungeon2.Generator || num > value4.Item2)
				{
					m_welcomed[peer.m_uid] = (dungeon2.Generator, num);
					Welcome(peer, dungeon2, dungeon != null, today);
				}
			}
			foreach (long item in m_welcomed.Keys.Where((long uid) => !present.Contains(uid)).ToList())
			{
				m_welcomed.Remove(item);
			}
			Flush();
		}

		private void Welcome(ZNetPeer peer, Dungeon dungeon, bool inside, int today)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if (!RestockRegenPlugin.Notify.Value || (m_boss == 0 && dungeon.Spots.Count == 0))
			{
				return;
			}
			m_nearby.Clear();
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(dungeon.Pos), new SimulationDistance(4, 0, true), m_nearby, (List<ZDO>)null);
			HashSet<(int, int, int)> intact = new HashSet<(int, int, int)>(from z in m_nearby.Where(IsWhole)
				select Key(z.GetPosition()));
			int num = dungeon.Spots.Keys.Count(((int, int, int) k) => !intact.Contains(k));
			if (!((m_boss != 0) ? m_nearby.Any((ZDO z) => z.GetPrefab() == m_boss && DungeonAt(z.GetPosition()) == dungeon) : (num == 0)))
			{
				int num2 = Mathf.Max(1, K.Days.Value);
				if (!inside && dungeon.LastVisit >= 0)
				{
					num2 -= today - dungeon.LastVisit;
				}
				string text = (inside ? K.NotifyText.Value : ((num2 > 0) ? K.EntranceText.Value : K.DueText.Value)).Replace("{mined}", num.ToString()).Replace("{piles}", (num == 1) ? K.Singular : K.Plural).Replace("{days}", (num2 == 1) ? "1 day" : $"{num2} days");
				ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { 2, text });
				if (RestockRegenPlugin.Verbose.Value)
				{
					RestockRegenPlugin.Log.LogInfo((object)("told " + peer.m_playerName + ": " + text));
				}
			}
		}

		internal void FlushPending()
		{
			if (m_ready)
			{
				Flush();
			}
		}

		internal string Summary()
		{
			if (!m_ready)
			{
				return K.Label + " not started";
			}
			return $"{m_dungeons.Count} {K.DungeonLabel}s, {m_dungeons.Values.Sum((Dungeon c) => c.Spots.Count)} {K.Label} spots known" + (K.On.Value ? "" : " (off)") + (K.DryRun.Value ? " (DRY RUN)" : "");
		}

		private void Flush()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			if (K.DryRun.Value)
			{
				return;
			}
			foreach (Dungeon value in m_dungeons.Values)
			{
				if (!value.SpotsDirty && !value.VisitDirty)
				{
					continue;
				}
				ZDO zDO = ZDOMan.instance.GetZDO(value.Generator);
				if (zDO == null || WorldObjects.HeldByPlayer(zDO))
				{
					continue;
				}
				if (value.VisitDirty)
				{
					zDO.Set(m_visitKey, value.LastVisit + 1, false);
					value.VisitDirty = false;
				}
				if (!value.SpotsDirty)
				{
					continue;
				}
				ZPackage val = new ZPackage();
				val.Write(1);
				val.Write(value.Spots.Count);
				foreach (Spot value2 in value.Spots.Values)
				{
					val.Write(value2.Prefab);
					val.Write(value2.Pos);
					val.Write(value2.Rot);
				}
				zDO.Set(m_spotsKey, val.GetArray());
				value.SpotsDirty = false;
			}
		}

		internal Result Sweep(int today)
		{
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0427: Unknown result type (might be due to invalid IL or missing references)
			//IL_0433: Unknown result type (might be due to invalid IL or missing references)
			//IL_0474: Unknown result type (might be due to invalid IL or missing references)
			//IL_047b: Unknown result type (might be due to invalid IL or missing references)
			//IL_048d: Unknown result type (might be due to invalid IL or missing references)
			Result result = default(Result);
			if (!m_ready || !K.On.Value)
			{
				return result;
			}
			bool value = K.DryRun.Value;
			bool value2 = K.RegenNow.Value;
			bool value3 = RestockRegenPlugin.Verbose.Value;
			int num = Mathf.Max(1, K.Days.Value);
			long sessionID = ZDOMan.GetSessionID();
			Dictionary<(int, int, int), ZDO> standing = new Dictionary<(int, int, int), ZDO>();
			List<ZDO> list = new List<ZDO>();
			List<ZDO> list2 = new List<ZDO>();
			foreach (ZDO value7 in ZDOMan.instance.m_objectsByID.Values)
			{
				int prefab = value7.GetPrefab();
				if (prefab == m_generator)
				{
					AddDungeon(value7);
				}
				else if (m_boss != 0 && prefab == m_door)
				{
					list.Add(value7);
				}
				else if (m_boss != 0 && prefab == m_boss)
				{
					list2.Add(value7);
				}
				else if (m_intactOf.ContainsKey(prefab))
				{
					(int, int, int) key = Key(value7.GetPosition());
					if (!standing.TryGetValue(key, out var value4) || !IsWhole(value4))
					{
						standing[key] = value7;
					}
				}
			}
			foreach (ZDO value8 in standing.Values)
			{
				Remember(SpotOf(value8));
			}
			foreach (Spot item in m_seen.Values.ToList())
			{
				m_seen.Remove(Key(item.Pos));
				Remember(item);
			}
			FindDoors(list);
			HashSet<Dungeon> hashSet = new HashSet<Dungeon>(from b in list2
				select DungeonAt(b.GetPosition()) into d
				where d != null
				select d);
			foreach (Dungeon value9 in m_dungeons.Values)
			{
				result.Dungeons++;
				if (value9.LastVisit < 0 && !value2)
				{
					value9.LastVisit = today;
					value9.VisitDirty = true;
					continue;
				}
				Spot spot = default(Spot);
				if (m_boss != 0 && (hashSet.Contains(value9) || !BossSpot(value9, out spot)))
				{
					continue;
				}
				int num2 = ((value9.LastVisit < 0) ? num : (today - value9.LastVisit));
				if (num2 < num && !value2)
				{
					result.Waiting++;
					continue;
				}
				ZDO value6;
				List<KeyValuePair<(int, int, int), Spot>> list3 = value9.Spots.Where((KeyValuePair<(int, int, int), Spot> kv) => !standing.TryGetValue(kv.Key, out value6) || !IsWhole(value6)).ToList();
				if (list3.Count == 0 && m_boss == 0)
				{
					continue;
				}
				if (Occupied(value9, standing, sessionID))
				{
					result.Occupied++;
					if (value2)
					{
						value9.LastVisit = today - num;
						value9.VisitDirty = true;
					}
					continue;
				}
				int num3 = 0;
				int num4 = 0;
				foreach (KeyValuePair<(int, int, int), Spot> item2 in list3)
				{
					if (standing.TryGetValue(item2.Key, out var value5))
					{
						num3++;
						if (m_intact.Contains(value5.GetPrefab()))
						{
							if (!value)
							{
								value5.Set(ZDOVars.s_picked, false);
								value5.Set(ZDOVars.s_pickedTime, 0L);
							}
							continue;
						}
						if (!value)
						{
							WorldObjects.Remove(value5);
						}
					}
					else
					{
						num4++;
					}
					if (!value)
					{
						WorldObjects.Create(item2.Value.Prefab, item2.Value.Pos, item2.Value.Rot);
					}
				}
				if (m_boss != 0)
				{
					num4++;
					if (!value)
					{
						WorldObjects.Create(spot.Prefab, spot.Pos, spot.Rot);
						ZDO zDO = ZDOMan.instance.GetZDO(value9.Door);
						if (zDO != null && K.CloseDoor.Value)
						{
							zDO.Set(ZDOVars.s_state, 0, false);
						}
					}
				}
				result.Regenerated++;
				result.Replaced += num3;
				result.Rebuilt += num4;
				value9.LastVisit = today;
				value9.VisitDirty = true;
				if (value3)
				{
					RestockRegenPlugin.Log.LogInfo((object)(string.Format("{0} {1} at ({2:0}, {3:0}) ", value ? "dry run: would regenerate" : "regenerated", K.DungeonLabel, value9.Pos.x, value9.Pos.z) + (value2 ? "(regenerate now): " : $"after {num2} days unvisited: ") + $"{num3} damaged {K.Plural} replaced, {num4} gone {K.Plural} rebuilt, " + $"{value9.Spots.Count - list3.Count} untouched"));
				}
			}
			Flush();
			if (value2 && !value)
			{
				K.RegenNow.Value = false;
				RestockRegenPlugin.Log.LogInfo((object)(K.Label + ": regenerate-now done, " + ((ConfigEntryBase)K.RegenNow).Definition.Key + " set back to false"));
			}
			return result;
		}

		private bool Occupied(Dungeon dungeon, Dictionary<(int, int, int), ZDO> standing, long server)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (ZNet.instance.GetPeers().Any((ZNetPeer p) => DungeonAt(p.GetRefPos()) == dungeon))
			{
				return true;
			}
			if (WorldObjects.HeldByPlayer(ZDOMan.instance.GetZDO(dungeon.Generator)) || WorldObjects.HeldByPlayer(ZDOMan.instance.GetZDO(dungeon.Door)))
			{
				return true;
			}
			foreach (var key in dungeon.Spots.Keys)
			{
				if (standing.TryGetValue(key, out var value) && WorldObjects.HeldByPlayer(value))
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class History
	{
		internal class Record
		{
			public string FirstBy = "";

			public string LastBy = "";

			public int FirstDay;

			public int LastDay;

			public bool Empty => LastBy.Length == 0;
		}

		private static readonly int s_firstBy = StringExtensionMethods.GetStableHashCode("restockregen_firstby");

		private static readonly int s_firstDay = StringExtensionMethods.GetStableHashCode("restockregen_firstday");

		private static readonly int s_lastBy = StringExtensionMethods.GetStableHashCode("restockregen_lastby");

		private static readonly int s_lastDay = StringExtensionMethods.GetStableHashCode("restockregen_lastday");

		private static readonly Dictionary<ZDOID, Record> s_pending = new Dictionary<ZDOID, Record>();

		internal static Record Get(ZDO zdo)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			if (s_pending.TryGetValue(zdo.m_uid, out var value))
			{
				return value;
			}
			return new Record
			{
				FirstBy = zdo.GetString(s_firstBy, ""),
				FirstDay = zdo.GetInt(s_firstDay, 0) - 1,
				LastBy = zdo.GetString(s_lastBy, ""),
				LastDay = zdo.GetInt(s_lastDay, 0) - 1
			};
		}

		internal static Record Opened(ZDO zdo, string player, int today)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			Record record = Get(zdo);
			Record value = new Record
			{
				FirstBy = ((record.FirstBy.Length > 0) ? record.FirstBy : player),
				FirstDay = ((record.FirstBy.Length > 0) ? record.FirstDay : today),
				LastBy = player,
				LastDay = today
			};
			s_pending[zdo.m_uid] = value;
			return record;
		}

		internal static void Flush()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			if (s_pending.Count == 0)
			{
				return;
			}
			foreach (ZDOID item in s_pending.Keys.ToList())
			{
				ZDO zDO = ZDOMan.instance.GetZDO(item);
				if (zDO == null)
				{
					s_pending.Remove(item);
				}
				else if (!WorldObjects.HeldByPlayer(zDO))
				{
					Record record = s_pending[item];
					s_pending.Remove(item);
					if (!RestockRegenPlugin.DryRun.Value)
					{
						zDO.Set(s_firstBy, record.FirstBy);
						zDO.Set(s_firstDay, record.FirstDay + 1, false);
						zDO.Set(s_lastBy, record.LastBy);
						zDO.Set(s_lastDay, record.LastDay + 1, false);
					}
				}
			}
		}

		internal static string Message(Record before, string player, int today)
		{
			if (before.Empty)
			{
				return null;
			}
			string newValue = ((before.LastBy == player) ? "you" : before.LastBy);
			string newValue2 = ((before.FirstBy == player) ? "you" : before.FirstBy);
			return ((before.FirstBy == before.LastBy && before.FirstDay == before.LastDay) ? RestockRegenPlugin.HistoryTextOnce.Value : RestockRegenPlugin.HistoryText.Value).Replace("{last}", newValue).Replace("{lastago}", Ago(today - before.LastDay)).Replace("{first}", newValue2)
				.Replace("{firstago}", Ago(today - before.FirstDay));
		}

		private static string Ago(int days)
		{
			if (days <= 0)
			{
				return "today";
			}
			if (days == 1)
			{
				return "yesterday";
			}
			return $"{days} days ago";
		}
	}
	internal static class LootChests
	{
		internal static readonly Dictionary<int, Container> Prefabs = new Dictionary<int, Container>();

		internal static void FindPrefabs()
		{
			Prefabs.Clear();
			HashSet<string> hashSet = new HashSet<string>(from s in RestockRegenPlugin.Exclude.Value.Split(new char[1] { ',' })
				select s.Trim() into s
				where s.Length > 0
				select s);
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				Container component = prefab.GetComponent<Container>();
				if ((Object)(object)component == (Object)null || component.m_defaultItems == null || component.m_defaultItems.m_drops.Count == 0)
				{
					continue;
				}
				if (hashSet.Contains(((Object)prefab).name))
				{
					RestockRegenPlugin.Log.LogInfo((object)("loot prefab " + ((Object)prefab).name + ": excluded by config"));
					continue;
				}
				Prefabs[StringExtensionMethods.GetStableHashCode(((Object)prefab).name)] = component;
				if (component.m_autoDestroyEmpty)
				{
					RestockRegenPlugin.Log.LogWarning((object)("loot prefab " + ((Object)prefab).name + ": deletes itself when emptied, cannot be restocked"));
				}
				if (RestockRegenPlugin.Verbose.Value)
				{
					RestockRegenPlugin.Log.LogInfo((object)($"loot prefab {((Object)prefab).name}: {component.m_width}x{component.m_height}, " + $"{component.m_defaultItems.m_drops.Count} drops, rolls {component.m_defaultItems.m_dropMin}-{component.m_defaultItems.m_dropMax}" + (component.m_autoDestroyEmpty ? ", autoDestroyEmpty" : "")));
				}
			}
		}

		internal static int? ItemCount(ZDO zdo)
		{
			byte[] byteArray = zdo.GetByteArray(ZDOVars.s_items, (byte[])null);
			if (byteArray == null || byteArray.Length < 4)
			{
				return null;
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(byteArray));
			if (binaryReader.ReadInt32() >= 108)
			{
				return (byteArray.Length >= 6) ? new int?(binaryReader.ReadUInt16()) : ((int?)null);
			}
			return (byteArray.Length >= 8) ? new int?(binaryReader.ReadInt32()) : ((int?)null);
		}

		internal static void Census()
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			FindPrefabs();
			Dictionary<int, int[]> dictionary = new Dictionary<int, int[]>();
			foreach (ZDO value3 in ZDOMan.instance.m_objectsByID.Values)
			{
				int prefab = value3.GetPrefab();
				if (Prefabs.ContainsKey(prefab))
				{
					if (!dictionary.TryGetValue(prefab, out var value))
					{
						value = (dictionary[prefab] = new int[5]);
					}
					value[0]++;
					int? num = ItemCount(value3);
					if (!num.HasValue)
					{
						value[3]++;
					}
					else if (num == 0)
					{
						value[1]++;
					}
					else
					{
						value[2]++;
					}
					if (value3.GetLong(ZDOVars.s_creator, 0L) != 0L)
					{
						value[4]++;
					}
				}
			}
			int num2 = dictionary.Values.Sum((int[] c) => c[0]);
			int num3 = dictionary.Values.Sum((int[] c) => c[1]);
			RestockRegenPlugin.Log.LogInfo((object)($"census: {Prefabs.Count} loot chest prefabs, {num2} chests in the world, {num3} empty, " + $"day {EnvMan.instance.GetDay()}, {stopwatch.ElapsedMilliseconds} ms over {ZDOMan.instance.m_objectsByID.Count} objects"));
			foreach (KeyValuePair<int, int[]> item in dictionary.OrderByDescending((KeyValuePair<int, int[]> kv) => kv.Value[0]))
			{
				int[] value2 = item.Value;
				RestockRegenPlugin.Log.LogInfo((object)$"census: {((Object)((Component)Prefabs[item.Key]).gameObject).name,-32} total {value2[0],4}  empty {value2[1],4}  items {value2[2],4}  never loaded {value2[3],3}  player-built {value2[4],3}");
			}
		}
	}
	[HarmonyPatch(typeof(ZDO), "Deserialize")]
	internal static class Notifier
	{
		private static readonly HashSet<ZDOID> s_open = new HashSet<ZDOID>();

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

		internal static readonly Dictionary<ZDOID, int> ClockStart = new Dictionary<ZDOID, int>();

		private static void Postfix(ZDO __instance)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: 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_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			if (!RestockRegenPlugin.Enabled.Value || LootChests.Prefabs.Count == 0 || !LootChests.Prefabs.ContainsKey(__instance.GetPrefab()) || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || __instance.GetLong(ZDOVars.s_creator, 0L) != 0L)
			{
				return;
			}
			ZDOID uid = __instance.m_uid;
			int num = (((Object)(object)EnvMan.instance != (Object)null) ? EnvMan.instance.GetDay() : 0);
			int? num2 = LootChests.ItemCount(__instance);
			bool flag = num2 == 0;
			bool value = RestockRegenPlugin.ResetOpened.Value;
			bool flag2 = !ClockStart.ContainsKey(uid) && __instance.GetInt(Restocker.s_emptySince, 0) == 0;
			if (flag && flag2)
			{
				ClockStart[uid] = num;
			}
			else if (!flag && !value)
			{
				s_announced.Remove(uid);
				ClockStart.Remove(uid);
			}
			if (__instance.GetInt(ZDOVars.s_inUse, 0) != 1)
			{
				s_open.Remove(uid);
				s_announced.Remove(uid);
				return;
			}
			long owner = __instance.GetOwner();
			ZNetPeer val = ((owner != 0L && owner != ZDOMan.GetSessionID()) ? ZNet.instance.GetPeer(owner) : null);
			if (val == null)
			{
				return;
			}
			bool num3 = s_open.Add(uid);
			if (num3 && value && flag2 && num2.HasValue)
			{
				ClockStart[uid] = num;
			}
			if (num3 && RestockRegenPlugin.ShowHistory.Value)
			{
				string text = History.Message(History.Opened(__instance, val.m_playerName, num), val.m_playerName, num);
				if (text != null)
				{
					Send(val, (MessageType)1, text);
				}
			}
			if ((flag || value) && num2.HasValue && RestockRegenPlugin.Notify.Value && s_announced.Add(uid))
			{
				int num4 = Restocker.DaysLeft(__instance, num);
				string text2 = (flag ? RestockRegenPlugin.NotifyText.Value : RestockRegenPlugin.NotifyLeftoversText.Value);
				Send(val, (MessageType)2, (num4 > 0) ? text2.Replace("{days}", (num4 == 1) ? "1 day" : $"{num4} days") : RestockRegenPlugin.NotifyDueText.Value);
			}
		}

		private static void Send(ZNetPeer peer, MessageType type, string text)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected I4, but got Unknown
			ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2]
			{
				(int)type,
				text
			});
			if (RestockRegenPlugin.Verbose.Value)
			{
				RestockRegenPlugin.Log.LogInfo((object)("told " + peer.m_playerName + ": " + text));
			}
		}
	}
	internal static class Regrow
	{
		internal class Group
		{
			public string Id;

			public string Label;

			public string[] Names;

			public bool Drifts;

			public ConfigEntry<bool> On;

			public ConfigEntry<bool> DryRun;

			public ConfigEntry<int> Days;

			public ConfigEntry<float> Radius;

			public ConfigEntry<float> Clearance;

			public bool Enabled => On.Value;

			public int DayCount => Mathf.Max(1, Days.Value);
		}

		private class BiomeNotice
		{
			public Biome Biome;

			public ConfigEntry<string> Text;

			public ConfigEntry<float> Cooldown;

			public Group[] Groups;

			public readonly Dictionary<long, bool> Inside = new Dictionary<long, bool>();

			public readonly Dictionary<long, float> LastTold = new Dictionary<long, float>();
		}

		private struct Spot
		{
			public int Prefab;

			public Vector3 Pos;

			public Vector3 Rot;
		}

		internal struct Result
		{
			public int Missing;

			public int Waiting;

			public int Near;

			public int Built;

			public int Restored;

			public int Replaced;
		}

		[HarmonyPatch(typeof(ZDO), "Deserialize")]
		private static class SeeObjects
		{
			private static void Postfix(ZDO __instance)
			{
				if (s_ready && s_intactOf.ContainsKey(__instance.GetPrefab()))
				{
					Remember(__instance);
				}
			}
		}

		internal static readonly List<Group> Groups = new List<Group>();

		private static readonly List<BiomeNotice> s_notices = new List<BiomeNotice>();

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

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

		private static readonly Dictionary<int, Group> s_groupOf = new Dictionary<int, Group>();

		private static readonly Dictionary<(int, int, int), Spot> s_spots = new Dictionary<(int, int, int), Spot>();

		private static readonly Dictionary<Vector2i, int> s_zoneVisit = new Dictionary<Vector2i, int>();

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

		private static readonly int s_health = StringExtensionMethods.GetStableHashCode("health");

		private static readonly int[] s_chunkHealth = (from i in Enumerable.Range(0, 64)
			select StringExtensionMethods.GetStableHashCode("Health" + i)).ToArray();

		private const float DriftRadius = 6f;

		private static bool s_ready;

		private static bool s_dirty;

		private static string s_file;

		internal static void Configure(ConfigFile config)
		{
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			Groups.Clear();
			s_notices.Clear();
			Group obj = Bind(config, "AncientArmor", "armor", "ancient armor", new string[4] { "giant_helmet1", "giant_helmet2", "giant_sword1", "giant_sword2" }, "the giant's helmets and swords in the Mistlands");
			Group obj2 = Bind(config, "Obsidian", "obsidian", "obsidian", new string[1] { "MineRock_Obsidian" }, "obsidian deposits in the Mountains");
			Group obj3 = Bind(config, "Flametal", "flametal", "flametal", new string[1] { "LeviathanLava" }, "flametal spires in the Ashlands' lava");
			obj3.Drifts = true;
			Group obj4 = Bind(config, "Ice", "ice", "ice", new string[2] { "IcePond_rock", "IceShore_1" }, "ice ponds and the ice along the shore in the Deep North");
			Groups.AddRange(new Group[4] { obj, obj2, obj3, obj4 });
			s_notices.Add(new BiomeNotice
			{
				Biome = (Biome)512,
				Groups = new Group[1] { obj },
				Text = config.Bind<string>("AncientArmor", "AncientArmorNotifyText", "The spirits will restore ancient armor after {days} without visitors", "Shown to a player entering the Mistlands (needs Notify on). {days} is AncientArmorDays."),
				Cooldown = config.Bind<float>("AncientArmor", "AncientArmorNotifyCooldown", 10f, "Minutes. A player is told at most once in this long, so walking along the Mistlands border does not repeat it.")
			});
			s_notices.Add(new BiomeNotice
			{
				Biome = (Biome)4,
				Groups = new Group[1] { obj2 },
				Text = config.Bind<string>("Mountains", "MountainsNotifyText", "The spirits will restore obsidian after {days} without visitors", "Shown to a player entering the Mountains while Obsidian is on (needs Notify on). {days} is ObsidianDays."),
				Cooldown = config.Bind<float>("Mountains", "MountainsNotifyCooldown", 10f, "Minutes. A player is told at most once in this long.")
			});
			s_notices.Add(new BiomeNotice
			{
				Biome = (Biome)32,
				Groups = new Group[1] { obj3 },
				Text = config.Bind<string>("Ashlands", "AshlandsNotifyText", "The spirits will restore flametal after {days} without visitors", "Shown to a player entering the Ashlands while Flametal is on (needs Notify on). {days} is FlametalDays."),
				Cooldown = config.Bind<float>("Ashlands", "AshlandsNotifyCooldown", 10f, "Minutes. A player is told at most once in this long.")
			});
			s_notices.Add(new BiomeNotice
			{
				Biome = (Biome)64,
				Groups = new Group[1] { obj4 },
				Text = config.Bind<string>("DeepNorth", "DeepNorthNotifyText", "The spirits will restore ice after {days} without visitors", "Shown to a player entering the Deep North while Ice is on (needs Notify on). {days} is IceDays."),
				Cooldown = config.Bind<float>("DeepNorth", "DeepNorthNotifyCooldown", 10f, "Minutes. A player is told at most once in this long.")
			});
		}

		private static Group Bind(ConfigFile config, string section, string id, string label, string[] names, string what)
		{
			Group obj = new Group();
			obj.Id = id;
			obj.Label = label;
			obj.Names = names;
			obj.On = config.Bind<bool>(section, section, true, "Restore " + what + " where no player has been for " + section + "Days in-game days. Only objects the mod has seen, whole or damaged, can come back.");
			obj.Days = config.Bind<int>(section, section + "Days", 30, "In-game days with no player within " + section + "VisitRadius before a " + label + " spot is restored. Any visit starts the count again.");
			obj.DryRun = config.Bind<bool>(section, section + "DryRun", false, "Log what would be restored, but create and remove nothing. Spots and visits are still recorded.");
			obj.Radius = config.Bind<float>(section, section + "VisitRadius", 100f, "Metres. A player this close to a spot counts as a visit, and nothing is restored with a player this close.");
			obj.Clearance = config.Bind<float>(section, section + "BuildClearance", 8f, "Metres, measured flat. Nothing is restored if anything a player built stands this close to the spot. 0 turns the check off.");
			return obj;
		}

		private static (int, int, int) Key(Vector3 p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			return (Mathf.RoundToInt(p.x * 10f), Mathf.RoundToInt(p.y * 10f), Mathf.RoundToInt(p.z * 10f));
		}

		private static (int, int, int)? SpotKeyAt(Vector3 p, int wholePrefab)
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			if (s_groupOf.TryGetValue(wholePrefab, out var value) && value.Drifts)
			{
				foreach (KeyValuePair<(int, int, int), Spot> s_spot in s_spots)
				{
					if (s_spot.Value.Prefab == wholePrefab && Flat(s_spot.Value.Pos, p) < 6f)
					{
						return s_spot.Key;
					}
				}
				return null;
			}
			(int, int, int) tuple = Key(p);
			if (s_spots.ContainsKey(tuple))
			{
				return tuple;
			}
			for (int i = -2; i <= 2; i++)
			{
				for (int j = -2; j <= 2; j++)
				{
					for (int k = -2; k <= 2; k++)
					{
						(int, int, int) tuple2 = (tuple.Item1 + i, tuple.Item2 + j, tuple.Item3 + k);
						if (s_spots.TryGetValue(tuple2, out var value2))
						{
							Vector3 val = value2.Pos - p;
							if (((Vector3)(ref val)).sqrMagnitude < 0.04f)
							{
								return tuple2;
							}
						}
					}
				}
			}
			return null;
		}

		private static Vector2i Zone(Vector3 p)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			Vector2s zone = ZoneSystem.GetZone(p);
			return new Vector2i((int)zone.x, (int)zone.y);
		}

		private static float Flat(Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = new Vector2(a.x - b.x, a.z - b.z);
			return ((Vector2)(ref val)).magnitude;
		}

		private static bool IsWhole(ZDO zdo)
		{
			if (!s_intact.Contains(zdo.GetPrefab()) || !string.IsNullOrEmpty(zdo.GetString(s_health, "")) || zdo.GetBool(ZDOVars.s_dead, false))
			{
				return false;
			}
			if (s_groupOf.TryGetValue(zdo.GetPrefab(), out var value) && value.Drifts)
			{
				int[] array = s_chunkHealth;
				foreach (int num in array)
				{
					if (zdo.GetFloat(num, float.MaxValue) != float.MaxValue)
					{
						return false;
					}
				}
			}
			return true;
		}

		internal static void Init(int today)
		{
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
			s_intactOf.Clear();
			s_intact.Clear();
			s_groupOf.Clear();
			foreach (Group group in Groups)
			{
				string[] names = group.Names;
				foreach (string text in names)
				{
					if ((Object)(object)ZNetScene.instance.GetPrefab(text) == (Object)null)
					{
						RestockRegenPlugin.Log.LogWarning((object)(group.Label + ": prefab " + text + " not found, skipped"));
						continue;
					}
					int stableHashCode = StringExtensionMethods.GetStableHashCode(text);
					s_intact.Add(stableHashCode);
					s_intactOf[stableHashCode] = stableHashCode;
					s_groupOf[stableHashCode] = group;
					string[] array = new string[2] { "_destruction", "_frac" };
					foreach (string text2 in array)
					{
						if ((Object)(object)ZNetScene.instance.GetPrefab(text + text2) != (Object)null)
						{
							s_intactOf[StringExtensionMethods.GetStableHashCode(text + text2)] = stableHashCode;
						}
					}
				}
			}
			s_file = Path.Combine(SaveSystem.GetWorldsSaveRootPath(ZNet.m_world.m_fileSource), ZNet.instance.GetWorldName() + ".restockregen.txt");
			Load();
			foreach (var item in (from kv in s_spots
				where !s_groupOf.ContainsKey(kv.Value.Prefab)
				select kv.Key).ToList())
			{
				s_spots.Remove(item);
				s_dirty = true;
			}
			Dictionary<Group, int> dictionary = Groups.ToDictionary((Group g) => g, (Group g) => SpotsOf(g));
			Dictionary<Group, int> dictionary2 = Groups.ToDictionary((Group g) => g, (Group g) => 0);
			Dictionary<Group, int> dictionary3 = Groups.ToDictionary((Group g) => g, (Group g) => 0);
			foreach (ZDO value2 in ZDOMan.instance.m_objectsByID.Values)
			{
				if (s_intactOf.TryGetValue(value2.GetPrefab(), out var value))
				{
					Remember(value2);
					if (IsWhole(value2))
					{
						dictionary2[s_groupOf[value]]++;
					}
					else
					{
						dictionary3[s_groupOf[value]]++;
					}
				}
			}
			foreach (Spot value3 in s_spots.Values)
			{
				Vector2i key = Zone(value3.Pos);
				if (!s_zoneVisit.ContainsKey(key))
				{
					s_zoneVisit[key] = today;
					s_dirty = true;
				}
			}
			s_ready = true;
			Save();
			foreach (Group group2 in Groups)
			{
				int num = SpotsOf(group2);
				RestockRegenPlugin.Log.LogInfo((object)($"{group2.Label}: {num} spots remembered ({num - dictionary[group2]} new this start), {dictionary2[group2]} whole and {dictionary3[group2]} damaged in the world now, " + string.Format("{0} gone{1}{2}", num - dictionary2[group2] - dictionary3[group2], group2.Enabled ? "" : " (off)", group2.DryRun.Value ? " (dry run)" : "")));
			}
			RestockRegenPlugin.Log.LogInfo((object)$"regrow records: {s_zoneVisit.Count} zones, in {s_file}");
		}

		private static int SpotsOf(Group g)
		{
			Group value;
			return s_spots.Values.Count((Spot s) => s_groupOf.TryGetValue(s.Prefab, out value) && value == g);
		}

		private static void Remember(ZDO zdo)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: 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)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (!SpotKeyAt(zdo.GetPosition(), s_intactOf[zdo.GetPrefab()]).HasValue)
			{
				Dictionary<(int, int, int), Spot> dictionary = s_spots;
				(int, int, int) key = Key(zdo.GetPosition());
				Spot value = new Spot
				{
					Prefab = s_intactOf[zdo.GetPrefab()],
					Pos = zdo.GetPosition()
				};
				Quaternion rotation = zdo.GetRotation();
				value.Rot = ((Quaternion)(ref rotation)).eulerAngles;
				dictionary[key] = value;
				Vector2i key2 = Zone(zdo.GetPosition());
				if (!s_zoneVisit.ContainsKey(key2) && (Object)(object)EnvMan.instance != (Object)null)
				{
					s_zoneVisit[key2] = EnvMan.instance.GetDay();
				}
				s_dirty = true;
			}
		}

		internal static void Tick(int today)
		{
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: 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)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			if (!s_ready || !Groups.Any((Group g) => g.Enabled))
			{
				return;
			}
			float num = Groups.Where((Group g) => g.Enabled).Max((Group g) => g.Radius.Value);
			int num2 = Mathf.CeilToInt(num / 64f);
			HashSet<long> present = new HashSet<long>();
			Vector2i val2 = default(Vector2i);
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				present.Add(peer.m_uid);
				Vector3 refPos = peer.GetRefPos();
				Vector2i val = Zone(refPos);
				for (int num3 = -num2; num3 <= num2; num3++)
				{
					for (int num4 = -num2; num4 <= num2; num4++)
					{
						((Vector2i)(ref val2))..ctor(val.x + num3, val.y + num4);
						if (s_zoneVisit.TryGetValue(val2, out var value) && value != today && ZoneNear(val2, refPos, num))
						{
							s_zoneVisit[val2] = today;
							s_dirty = true;
						}
					}
				}
				Biome val3 = (Biome)((refPos.y < 4000f && WorldGenerator.instance != null) ? ((int)WorldGenerator.instance.GetBiome(refPos)) : 0);
				foreach (BiomeNotice s_notice in s_notices)
				{
					bool flag = val3 == s_notice.Biome;
					bool value2;
					bool flag2 = s_notice.Inside.TryGetValue(peer.m_uid, out value2) && value2;
					s_notice.Inside[peer.m_uid] = flag;
					if (flag && !flag2)
					{
						Welcome(peer, s_notice);
					}
				}
			}
			foreach (BiomeNotice s_notice2 in s_notices)
			{
				foreach (long item in s_notice2.Inside.Keys.Where((long uid) => !present.Contains(uid)).ToList())
				{
					s_notice2.Inside.Remove(item);
				}
			}
			Save();
		}

		private static bool ZoneNear(Vector2i zone, Vector3 p, float radius)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			Vector3 zonePos = ZoneSystem.GetZonePos(new Vector2s((short)zone.x, (short)zone.y));
			float num = 32f;
			float num2 = Mathf.Max(0f, Mathf.Abs(p.x - zonePos.x) - num);
			float num3 = Mathf.Max(0f, Mathf.Abs(p.z - zonePos.z) - num);
			return num2 * num2 + num3 * num3 <= radius * radius;
		}

		private static void Welcome(ZNetPeer peer, BiomeNotice notice)
		{
			Group obj = notice.Groups.FirstOrDefault((Group g) => g.Enabled);
			if (obj != null && RestockRegenPlugin.Notify.Value && (!notice.LastTold.TryGetValue(peer.m_uid, out var value) || !(Time.time - value < notice.Cooldown.Value * 60f)))
			{
				notice.LastTold[peer.m_uid] = Time.time;
				int dayCount = obj.DayCount;
				string text = notice.Text.Value.Replace("{days}", (dayCount == 1) ? "1 day" : $"{dayCount} days");
				ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "ShowMessage", new object[2] { 2, text });
				if (RestockRegenPlugin.Verbose.Value)
				{
					RestockRegenPlugin.Log.LogInfo((object)("told " + peer.m_playerName + ": " + text));
				}
			}
		}

		internal static Dictionary<Group, Result> Sweep(int today, Group only = null, bool now = false)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_027e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_030b: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<Group, Result> dictionary = Groups.ToDictionary((Group g) => g, (Group g) => default(Result));
			if (!s_ready)
			{
				return dictionary;
			}
			bool value = RestockRegenPlugin.Verbose.Value;
			Dictionary<(int, int, int), ZDO> dictionary2 = new Dictionary<(int, int, int), ZDO>();
			foreach (ZDO value7 in ZDOMan.instance.m_objectsByID.Values)
			{
				if (s_intactOf.ContainsKey(value7.GetPrefab()))
				{
					Remember(value7);
					(int, int, int) key = SpotKeyAt(value7.GetPosition(), s_intactOf[value7.GetPrefab()]) ?? Key(value7.GetPosition());
					if (!dictionary2.TryGetValue(key, out var value2) || !IsWhole(value2))
					{
						dictionary2[key] = value7;
					}
				}
			}
			List<Vector3> source = (from p in ZNet.instance.GetPeers()
				select p.GetRefPos()).ToList();
			foreach (KeyValuePair<(int, int, int), Spot> s_spot in s_spots)
			{
				if (!s_groupOf.TryGetValue(s_spot.Value.Prefab, out var group) || !group.Enabled || (only != null && group != only))
				{
					continue;
				}
				dictionary2.TryGetValue(s_spot.Key, out var value3);
				if (value3 != null && IsWhole(value3))
				{
					continue;
				}
				Result value4 = dictionary[group];
				value4.Missing++;
				Spot spot = s_spot.Value;
				int value5;
				int num = (s_zoneVisit.TryGetValue(Zone(spot.Pos), out value5) ? value5 : today);
				if (!now && today - num < group.DayCount)
				{
					value4.Waiting++;
				}
				else if (source.Any((Vector3 p) => Flat(p, spot.Pos) < group.Radius.Value) || WorldObjects.HeldByPlayer(value3))
				{
					value4.Near++;
				}
				else if (BuiltNear(spot.Pos, group.Clearance.Value))
				{
					value4.Built++;
				}
				else
				{
					if (value3 != null)
					{
						value4.Replaced++;
					}
					else
					{
						value4.Restored++;
					}
					bool value6 = group.DryRun.Value;
					if (!value6)
					{
						if (value3 != null)
						{
							WorldObjects.Remove(value3);
						}
						WorldObjects.Create(spot.Prefab, spot.Pos, spot.Rot);
					}
					if (value)
					{
						ManualLogSource log = RestockRegenPlugin.Log;
						object[] obj = new object[4]
						{
							value6 ? "dry run: would restore" : "restored",
							null,
							null,
							null
						};
						GameObject prefab = ZNetScene.instance.GetPrefab(spot.Prefab);
						obj[1] = ((prefab != null) ? ((Object)prefab).name : null);
						obj[2] = spot.Pos.x;
						obj[3] = spot.Pos.z;
						log.LogInfo((object)(string.Format("{0} {1} at ({2:0}, {3:0})", obj) + ((value3 != null) ? " (replacing a damaged one)" : " (was gone)") + (now ? " (regenerate now)" : $" after {today - num} days without visitors")));
					}
				}
				dictionary[group] = value4;
			}
			Save();
			return dictionary;
		}

		private static bool BuiltNear(Vector3 pos, float clearance)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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)
			if (clearance <= 0f)
			{
				return false;
			}
			s_nearby.Clear();
			ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(pos), new SimulationDistance(1, 0, true), s_nearby, (List<ZDO>)null);
			foreach (ZDO item in s_nearby)
			{
				if (Flat(item.GetPosition(), pos) < clearance && Mathf.Abs(item.GetPosition().y - pos.y) < 30f && item.GetLong(ZDOVars.s_creator, 0L) != 0L)
				{
					return true;
				}
			}
			return false;
		}

		internal static string Summary()
		{
			if (!s_ready)
			{
				return "regrow not started";
			}
			return string.Join(", ", Groups.Select((Group g) => string.Format("{0} {1} spots{2}{3}", SpotsOf(g), g.Label, g.Enabled ? "" : " (off)", g.DryRun.Value ? " (DRY RUN)" : "")));
		}

		internal static Group Find(string id)
		{
			return Groups.FirstOrDefault((Group g) => g.Id == id);
		}

		private static void Load()
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: 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)
			s_spots.Clear();
			s_zoneVisit.Clear();
			if (!File.Exists(s_file))
			{
				return;
			}
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			string[] array = File.ReadAllLines(s_file);
			foreach (string text in array)
			{
				string[] array2 = text.Split(new char[1] { ' ' });
				try
				{
					if (array2[0] == "S" && array2.Length == 8)
					{
						Spot value = new Spot
						{
							Prefab = int.Parse(array2[1], invariantCulture),
							Pos = new Vector3(float.Parse(array2[2], invariantCulture), float.Parse(array2[3], invariantCulture), float.Parse(array2[4], invariantCulture)),
							Rot = new Vector3(float.Parse(array2[5], invariantCulture), float.Parse(array2[6], invariantCulture), float.Parse(array2[7], invariantCulture))
						};
						if (!SpotKeyAt(value.Pos, value.Prefab).HasValue)
						{
							s_spots[Key(value.Pos)] = value;
						}
						else
						{
							s_dirty = true;
						}
					}
					else if (array2[0] == "V" && array2.Length == 4)
					{
						s_zoneVisit[new Vector2i(int.Parse(array2[1], invariantCulture), int.Parse(array2[2], invariantCulture))] = int.Parse(array2[3], invariantCulture);
					}
				}
				catch (Exception)
				{
					RestockRegenPlugin.Log.LogWarning((object)("regrow: skipped a bad line in " + s_file + ": " + text));
				}
			}
		}

		internal static void Save()
		{
			if (s_dirty && s_file != null)
			{
				CultureInfo inv = CultureInfo.InvariantCulture;
				List<string> list = new List<string> { "# RestockRegen regrow records. S = spot (prefab x y z rx ry rz), V = zone last visited (zone x, zone y, day)." };
				list.AddRange(s_spots.Values.Select((Spot s) => string.Format(inv, "S {0} {1:R} {2:R} {3:R} {4:R} {5:R} {6:R}", s.Prefab, s.Pos.x, s.Pos.y, s.Pos.z, s.Rot.x, s.Rot.y, s.Rot.z)));
				list.AddRange(s_zoneVisit.Select((KeyValuePair<Vector2i, int> v) => string.Format(inv, "V {0} {1} {2}", v.Key.x, v.Key.y, v.Value)));
				string text = s_file + ".tmp";
				File.WriteAllLines(text, list);
				if (File.Exists(s_file))
				{
					File.Delete(s_file);
				}
				File.Move(text, s_file);
				s_dirty = false;
			}
		}
	}
	internal static class Restocker
	{
		internal struct Result
		{
			public int Chests;

			public int Stamped;

			public int Cleared;

			public int Restocked;

			public int Waiting;

			public int Loaded;

			public int Forced;

			public int RolledNothing;
		}

		internal static readonly int s_emptySince = StringExtensionMethods.GetStableHashCode("restockregen_emptysince");

		private static readonly Dictionary<ZDOID, int> s_dryStamps = new Dictionary<ZDOID, int>();

		private static int GetStamp(ZDO zdo, bool dryRun)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (dryRun)
			{
				if (!s_dryStamps.TryGetValue(zdo.m_uid, out var value))
				{
					return zdo.GetInt(s_emptySince, 0);
				}
				return value;
			}
			return zdo.GetInt(s_emptySince, 0);
		}

		private static void SetStamp(ZDO zdo, int value, bool dryRun)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (dryRun)
			{
				s_dryStamps[zdo.m_uid] = value;
			}
			else
			{
				zdo.Set(s_emptySince, value, false);
			}
		}

		internal static int DaysLeft(ZDO zdo, int today)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			int num = Mathf.Max(1, RestockRegenPlugin.Days.Value);
			int stamp = GetStamp(zdo, RestockRegenPlugin.DryRun.Value);
			if (stamp != 0)
			{
				return num - (today - (stamp - 1));
			}
			if (!Notifier.ClockStart.TryGetValue(zdo.m_uid, out var value))
			{
				return num;
			}
			return num - (today - value);
		}

		internal static Result Sweep(int today, bool dryRun, bool verbose)
		{
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			Result result = default(Result);
			long sessionID = ZDOMan.GetSessionID();
			int num = Mathf.Max(1, RestockRegenPlugin.Days.Value);
			foreach (ZDO value3 in ZDOMan.instance.m_objectsByID.Values)
			{
				if (!LootChests.Prefabs.TryGetValue(value3.GetPrefab(), out var value))
				{
					continue;
				}
				result.Chests++;
				if (value3.GetLong(ZDOVars.s_creator, 0L) != 0L)
				{
					continue;
				}
				int? num2 = LootChests.ItemCount(value3);
				if (!num2.HasValue)
				{
					continue;
				}
				if (value3.HasOwner() && value3.GetOwner() != sessionID)
				{
					if (!RestockRegenPlugin.RestockLoaded.Value || value3.GetInt(ZDOVars.s_inUse, 0) == 1)
					{
						result.Loaded++;
						continue;
					}
					result.Forced++;
				}
				int num3 = GetStamp(value3, dryRun);
				int value2;
				bool flag = Notifier.ClockStart.TryGetValue(value3.m_uid, out value2);
				if (num2 > 0 && !RestockRegenPlugin.ResetOpened.Value)
				{
					Notifier.ClockStart.Remove(value3.m_uid);
					if (num3 != 0)
					{
						result.Cleared++;
						SetStamp(value3, 0, dryRun);
					}
					continue;
				}
				if (num3 == 0)
				{
					if (num2 > 0 && !flag)
					{
						continue;
					}
					if (!flag || value2 > today)
					{
						value2 = today;
					}
					Notifier.ClockStart.Remove(value3.m_uid);
					result.Stamped++;
					SetStamp(value3, value2 + 1, dryRun);
					num3 = value2 + 1;
					if (today - value2 < num)
					{
						continue;
					}
				}
				int num4 = today - (num3 - 1);
				string rolled;
				if (num4 < num)
				{
					result.Waiting++;
				}
				else if (dryRun)
				{
					result.Restocked++;
					SetStamp(value3, 0, dryRun);
					if (verbose)
					{
						RestockRegenPlugin.Log.LogInfo((object)$"dry run: would restock {((Object)value).name} at {Where(value3)} after {num4} days, replacing {num2} stacks");
					}
				}
				else if (Restock(value3, value, out rolled))
				{
					value3.Set(s_emptySince, 0, false);
					result.Restocked++;
					if (verbose)
					{
						RestockRegenPlugin.Log.LogInfo((object)$"restocked {((Object)value).name} at {Where(value3)} after {num4} days, replacing {num2} stacks: {rolled}");
					}
				}
				else
				{
					result.RolledNothing++;
				}
			}
			return result;
		}

		private static bool Restock(ZDO zdo, Container prefab, out string rolled)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			rolled = "";
			Inventory val = new Inventory(prefab.m_name, (Sprite)null, prefab.m_width, prefab.m_height);
			List<string> list = new List<string>();
			foreach (ItemData dropListItem in prefab.m_defaultItems.GetDropListItems())
			{
				GameObject dropPrefab = dropListItem.m_dropPrefab;
				list.Add($"{((dropPrefab != null) ? ((Object)dropPrefab).name : null)}x{dropListItem.m_stack}");
				val.AddItem(dropListItem);
			}
			if (val.NrOfItems() == 0)
			{
				return false;
			}
			ZPackage val2 = new ZPackage();
			val.Save(val2);
			zdo.Set(ZDOVars.s_items, val2.GetArray());
			rolled = string.Join(" ", list);
			return true;
		}

		private static string Where(ZDO zdo)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = zdo.GetPosition();
			return $"({position.x:0}, {position.z:0})";
		}
	}
	[BepInPlugin("liekos47.restockregen", "RestockRegen", "0.19.0")]
	public class RestockRegenPlugin : BaseUnityPlugin
	{
		public const string Guid = "liekos47.restockregen";

		public const string Name = "RestockRegen";

		public const string Version = "0.19.0";

		internal static ManualLogSource Log;

		internal static RestockRegenPlugin Instance;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<int> Days;

		internal static ConfigEntry<bool> Verbose;

		internal static ConfigEntry<bool> DryRun;

		internal static ConfigEntry<string> Exclude;

		internal static ConfigEntry<bool> Notify;

		internal static ConfigEntry<string> NotifyText;

		internal static ConfigEntry<string> NotifyDueText;

		internal static ConfigEntry<string> NotifyLeftoversText;

		internal static ConfigEntry<bool> ResetOpened;

		internal static ConfigEntry<bool> RestockLoaded;

		internal static ConfigEntry<bool> MudPileRegenNow;

		internal static ConfigEntry<bool> Crystals;

		internal static ConfigEntry<int> CrystalDays;

		internal static ConfigEntry<bool> CrystalDryRun;

		internal static ConfigEntry<bool> CrystalRegenNow;

		internal static ConfigEntry<string> CrystalNotifyText;

		internal static ConfigEntry<string> CrystalEntranceText;

		internal static ConfigEntry<string> CrystalDueText;

		internal static ConfigEntry<float> CaveEntranceRadius;

		internal static ConfigEntry<float> CaveRadius;

		internal static ConfigEntry<bool> BlackCores;

		internal static ConfigEntry<int> BlackCoreDays;

		internal static ConfigEntry<bool> BlackCoreDryRun;

		internal static ConfigEntry<bool> BlackCoreRegenNow;

		internal static ConfigEntry<string> BlackCoreNotifyText;

		internal static ConfigEntry<string> BlackCoreEntranceText;

		internal static ConfigEntry<string> BlackCoreDueText;

		internal static ConfigEntry<float> MineEntranceRadius;

		internal static ConfigEntry<float> MineRadius;

		internal static ConfigEntry<bool> Morkhalla;

		internal static ConfigEntry<int> MorkhallaDays;

		internal static ConfigEntry<bool> MorkhallaDryRun;

		internal static ConfigEntry<bool> MorkhallaRegenNow;

		internal static ConfigEntry<string> MorkhallaNotifyText;

		internal static ConfigEntry<string> MorkhallaEntranceText;

		internal static ConfigEntry<string> MorkhallaDueText;

		internal static ConfigEntry<float> MorkhallaEntranceRadius;

		internal static ConfigEntry<float> MorkhallaRadius;

		internal static ConfigEntry<bool> TheHole;

		internal static ConfigEntry<int> HoleDays;

		internal static ConfigEntry<bool> HoleDryRun;

		internal static ConfigEntry<bool> HoleRegenNow;

		internal static ConfigEntry<string> HoleNotifyText;

		internal static ConfigEntry<string> HoleEntranceText;

		internal static ConfigEntry<string> HoleDueText;

		internal static ConfigEntry<float> HoleEntranceRadius;

		internal static ConfigEntry<float> HoleRadius;

		internal static ConfigEntry<bool> Citadel;

		internal static ConfigEntry<int> CitadelDays;

		internal static ConfigEntry<bool> CitadelDryRun;

		internal static ConfigEntry<bool> CitadelRegenNow;

		internal static ConfigEntry<bool> CitadelCloseDoor;

		internal static ConfigEntry<string> CitadelNotifyText;

		internal static ConfigEntry<string> CitadelEntranceText;

		internal static ConfigEntry<string> CitadelDueText;

		internal static ConfigEntry<float> CitadelEntranceRadius;

		internal static ConfigEntry<float> CitadelRadius;

		internal static DungeonRegrow MudKind;

		internal static DungeonRegrow CrystalKind;

		internal static DungeonRegrow BlackCoreKind;

		internal static DungeonRegrow MorkhallaKind;

		internal static DungeonRegrow HoleKind;

		internal static DungeonRegrow CitadelKind;

		internal static ConfigEntry<bool> MudPiles;

		internal static ConfigEntry<int> MudPileDays;

		internal static ConfigEntry<bool> MudPileDryRun;

		internal static ConfigEntry<float> CryptRadius;

		internal static ConfigEntry<string> MudPileNotifyText;

		internal static ConfigEntry<string> MudPileEntranceText;

		internal static ConfigEntry<string> MudPileDueText;

		internal static ConfigEntry<int> VisitCheckSeconds;

		internal static ConfigEntry<float> EntranceRadius;

		internal static ConfigEntry<bool> ShowHistory;

		internal static ConfigEntry<string> HistoryText;

		internal static ConfigEntry<string> HistoryTextOnce;

		private Harmony harmony;

		private bool censusDone;

		private bool mudFailed;

		private bool regrowFailed;

		private float nextMudTick;

		private int lastSweepDay = -1;

		private float nextCheck;

		private void Awake()
		{
			//IL_0f92: Unknown result type (might be due to invalid IL or missing references)
			//IL_0f97: Unknown result type (might be due to invalid IL or missing references)
			//IL_0fee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ff8: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Off leaves the plugin loaded and doing nothing.");
			Days = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Days", 30, "In-game days an empty loot chest must stay empty before it refills. A Valheim day is 20 minutes, and on a dedicated server time only moves while someone is online, so 30 days is about 10 hours of play.");
			Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Verbose", false, "Log one line per chest prefab and per restocked chest.");
			DryRun = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DryRun", false, "Count and log what would happen, but write nothing to the world. Empty-since dates are kept in memory instead, so a dry run still comes due; they reset on restart.");
			ResetOpened = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ResetOpened", true, "On: a loot chest resets Days after a player first opens it, even if items are left in it, and whatever is inside then is replaced by a fresh roll. Off: only chests that are completely empty are on a clock, and putting anything back in stops it.");
			RestockLoaded = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RestockLoaded", true, "Restock a chest even while a player has its area loaded (a chest near a base, for example), as long as nobody has it open. Off: wait until no player is near it.");
			Exclude = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Exclude", "TreasureChest_forestcrypt_hildir,TreasureChest_mountaincave_hildir,TreasureChest_plainsfortress_hildir", "Comma-separated chest prefab names never to restock. The default is Hildir's three quest chests, which each hold one of her quest items. Read once at world load: changing it needs a restart.");
			Notify = ((BaseUnityPlugin)this).Config.Bind<bool>("Notify", "Notify", true, "Show a message in the middle of the screen when a player opens an empty loot chest, or takes the last item out of one, saying when it restocks.");
			NotifyText = ((BaseUnityPlugin)this).Config.Bind<string>("Notify", "NotifyText", "The spirits will refill this chest in {days}", "The message. {days} becomes \"1 day\" or \"N days\" (in-game days).");
			NotifyLeftoversText = ((BaseUnityPlugin)this).Config.Bind<string>("Notify", "NotifyLeftoversText", "The spirits will refill this chest in {days}. Anything left inside will be lost", "The message when the chest still has items in it (only with ResetOpened).");
			NotifyDueText = ((BaseUnityPlugin)this).Config.Bind<string>("Notify", "NotifyDueText", "The spirits will refill this chest at the next dawn, once no one is near", "The message for a chest whose time is already up. It restocks at the next daily sweep that finds no player holding it.");
			ShowHistory = ((BaseUnityPlugin)this).Config.Bind<bool>("History", "History", true, "Remember who first opened each loot chest and who opened it last, and tell the next player who opens it (top-left message).");
			HistoryText = ((BaseUnityPlugin)this).Config.Bind<string>("History", "HistoryText", "Last opened by {last} {lastago}, first by {first} {firstago}", "{last}/{first} are character names (\"you\" for the player reading it), {lastago}/{firstago} are \"today\", \"yesterday\" or \"N days ago\" in in-game days.");
			HistoryTextOnce = ((BaseUnityPlugin)this).Config.Bind<string>("History", "HistoryTextOnce", "Last opened by {last} {lastago}", "Used instead when only one opening has been recorded so far.");
			MudPiles = ((BaseUnityPlugin)this).Config.Bind<bool>("MudPiles", "MudPiles", true, "Regenerate muddy scrap piles in sunken crypts that no player has visited for MudPileDays in-game days. Only piles the mod has seen, intact or partly mined, can come back: one mined out before it was installed left no trace.");
			MudPileDays = ((BaseUnityPlugin)this).Config.Bind<int>("MudPiles", "MudPileDays", 30, "In-game days a sunken crypt must go unvisited before its piles regenerate. Any visit starts the count again.");
			MudPileDryRun = ((BaseUnityPlugin)this).Config.Bind<bool>("MudPiles", "MudPileDryRun", false, "Log which crypts would regenerate, but create and remove nothing and write nothing. Visits are then tracked in memory only.");
			MudPileNotifyText = ((BaseUnityPlugin)this).Config.Bind<string>("MudPiles", "MudPileNotifyText", "The spirits will restore muddy scrap piles after {days} without visitors", "Shown to a player inside a sunken crypt with mined piles (needs Notify on). Their visit has just restarted the clock, so {days} is the full MudPileDays. {mined} is how many, {piles} is \"muddy scrap pile(s)\", {days} is MudPileDays. Every visit restarts the count, so it is always the full number of days.");
			MudPileEntranceText = ((BaseUnityPlugin)this).Config.Bind<string>("MudPiles", "MudPileEntranceText", "The spirits will restore muddy scrap piles in {days} if no one enters", "Shown at the entrance of a sunken crypt with mined piles. {days} is what is left of the crypt's clock; going in restarts it.");
			MudPileDueText = ((BaseUnityPlugin)this).Config.Bind<string>("MudPiles", "MudPileDueText", "The spirits will restore muddy scrap piles at the next dawn if no one enters", "Shown at the entrance when the crypt's time is already up. It regenerates at the next daily sweep with nobody nearby.");
			MudPileRegenNow = ((BaseUnityPlugin)this).Config.Bind<bool>("MudPiles", "MudPileRegenNow", false, "One-shot: at the next daily sweep, every sunken crypt with mined piles regenerates whatever its clock says, then this sets itself back to false. A crypt with a player in or near it then is done at the first sweep that finds it free.");
			VisitCheckSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("MudPiles", "VisitCheckSeconds", 30, "Real seconds between checks for players at or inside sunken crypts. A visit shorter than this can go unnoticed, and the message can arrive up to this long after a player gets there.");
			EntranceRadius = ((BaseUnityPlugin)this).Config.Bind<float>("MudPiles", "EntranceRadius", 30f, "Metres, measured flat, around a sunken crypt's entrance within which a player on the surface is told about its mined piles. Standing there does not count as a visit.");
			CryptRadius = ((BaseUnityPlugin)this).Config.Bind<float>("MudPiles", "CryptRadius", 200f, "Metres, measured flat from a sunken crypt's generator, within which a player up at dungeon height counts as inside that crypt, and a pile belongs to it.");
			Instance = this;
			Regrow.Configure(((BaseUnityPlugin)this).Config);
			Crystals = ((BaseUnityPlugin)this).Config.Bind<bool>("FrostCaves", "FrostCaveCrystals", true, "Restore the crystals in Mountain frost caves that no player has been inside for CrystalDays in-game days. Only crystals the mod has seen can come back: one picked before it was installed left no trace.");
			CrystalDays = ((BaseUnityPlugin)this).Config.Bind<int>("FrostCaves", "CrystalDays", 30, "In-game days a frost cave must go without anyone inside before its crystals come back. Any visit starts the count again.");
			CrystalDryRun = ((BaseUnityPlugin)this).Config.Bind<bool>("FrostCaves", "CrystalDryRun", false, "Log which frost caves would regenerate, but create and remove nothing and write nothing. Visits are then tracked in memory only.");
			CrystalRegenNow = ((BaseUnityPlugin)this).Config.Bind<bool>("FrostCaves", "CrystalRegenNow", false, "One-shot: at the next daily sweep, every frost cave with picked crystals regenerates whatever its clock says, then this sets itself back to false.");
			CrystalNotifyText = ((BaseUnityPlugin)this).Config.Bind<string>("FrostCaves", "CrystalNotifyText", "The spirits will restore the crystals after {days} without visitors", "Shown to a player inside a frost cave with picked crystals (needs Notify on). {days} is CrystalDays; {mined} is how many were picked.");
			CrystalEntranceText = ((BaseUnityPlugin)this).Config.Bind<string>("FrostCaves", "CrystalEntranceText", "The spirits will restore the crystals in {days} if no one enters", "Shown at the entrance of a frost cave with picked crystals. {days} is what is left of the cave's clock; going in restarts it.");
			CrystalDueText = ((BaseUnityPlugin)this).Config.Bind<string>("FrostCaves", "CrystalDueText", "The spirits will restore the crystals at the next dawn if no one enters", "Shown at the entrance when the cave's time is already up.");
			CaveEntranceRadius = ((BaseUnityPlugin)this).Config.Bind<float>("FrostCaves", "CaveEntranceRadius", 50f, "Metres, measured flat, around a frost cave's centre within which a player on the surface gets the entrance message. A frost cave's surface location stands about 30 m from its interior's centre. Standing there does not count as a visit.");
			CaveRadius = ((BaseUnityPlugin)this).Config.Bind<float>("FrostCaves", "CaveRadius", 200f, "Metres from a frost cave's centre that count as inside it.");
			BlackCores = ((BaseUnityPlugin)this).Config.Bind<bool>("InfestedMines", "BlackCores", true, "Restore the black cores in Mistlands infested mines that no player has been inside for BlackCoreDays in-game days. A taken core's stand stays in the world marked as picked, so every taken core can come back, including ones taken before the mod was installed.");
			BlackCoreDays = ((BaseUnityPlugin)this).Config.Bind<int>("InfestedMines", "BlackCoreDays", 30, "In-game days an infested mine must go without anyone inside before its black cores come back. Any visit starts the count again.");
			BlackCoreDryRun = ((BaseUnityPlugin)this).Config.Bind<bool>("InfestedMines", "BlackCoreDryRun", false, "Log which infested mines would regenerate, but change nothing and write nothing. Visits are then tracked in memory only.");
			BlackCoreRegenNow = ((BaseUnityPlugin)this).Config.Bind<bool>("InfestedMines", "BlackCoreRegenNow", false, "One-shot: at the next daily sweep, every infested mine with taken black cores regenerates whatever its clock says, then this sets itself back to false.");
			BlackCoreNotifyText = ((BaseUnityPlugin)this).Config.Bind<string>("InfestedMines", "BlackCoreNotifyText", "The spirits will restore the black cores after {days} without visitors", "Shown to a player inside an infested mine with taken black cores (needs Notify on). {days} is BlackCoreDays; {mined} is how many were taken.");
			BlackCoreEntranceText = ((BaseUnityPlugin)this).Config.Bind<string>("InfestedMines", "BlackCoreEntranceText", "The spirits will restore the black cores in {days} if no one enters", "Shown at the entrance of an infested mine with taken black cores. {days} is what is left of the mine's clock; going in restarts it.");
			BlackCoreDueText = ((BaseUnityPlugin)this).Config.Bind<string>("InfestedMines", "BlackCoreDueText", "The spirits will restore the black cores at the next dawn if no one enters", "Shown at the entrance when the mine's time is already up.");
			MineEntranceRadius = ((BaseUnityPlugin)this).Config.Bind<float>("InfestedMines", "MineEntranceRadius", 50f, "Metres, measured flat, around an infested mine's centre within which a player on the surface gets the entrance message. A mine's surface location stands about 30 m from its interior's centre. Standing there does not count as a visit.");
			MineRadius = ((BaseUnityPlugin)this).Config.Bind<float>("InfestedMines", "MineRadius", 200f, "Metres from an infested mine's centre that count as inside it.");
			Morkhalla = ((BaseUnityPlugin)this).Config.Bind<bool>("Morkhalla", "Morkhalla", true, "Restore the gems and coin piles in the Deep North's Morkhalla dungeon once no player has been inside it for MorkhallaDays in-game days: the gemstones in the statue eyes, the rubble piles that drop ancient coins, the treasure piles, and the black ice block that starts a jotun invasion. Eyes whose gem was taken before the mod was installed come back too; piles broken or taken before then cannot.");
			MorkhallaDays = ((BaseUnityPlugin)this).Config.Bind<int>("Morkhalla", "MorkhallaDays", 30, "In-game days the dungeon must go without anyone inside before its gems and coin piles come back. Any visit starts the count again.");
			MorkhallaDryRun = ((BaseUnityPlugin)this).Config.Bind<bool>("Morkhalla", "MorkhallaDryRun", false, "Log what would be restored, but change nothing and write nothing. Visits are then tracked in memory only.");
			MorkhallaRegenNow = ((BaseUnityPlugin)this).Config.Bind<bool>("Morkhalla", "MorkhallaRegenNow", false, "One-shot: at the next daily sweep the dungeon regenerates whatever its clock says, then this sets itself back to false.");
			MorkhallaNotifyText = ((BaseUnityPlugin)this).Config.Bind<string>("Morkhalla", "MorkhallaNotifyText", "The spirits will restore the gems and coin piles after {days} without visitors", "Shown to a player inside the dungeon when gems or piles are missing (needs Notify on). {days} is MorkhallaDays; {mined} is how many are missing.");
			MorkhallaEntranceText = ((BaseUnityPlugin)this).Config.Bind<string>("Morkhalla", "MorkhallaEntranceText", "The spirits will restore the gems and coin piles in {days} if no one enters", "Shown at the dungeon's entrance when gems or piles are missing. {days} is what is left of its clock; going in restarts it.");
			MorkhallaDueText = ((BaseUnityPlugin)this).Config.Bind<string>("Morkhalla", "MorkhallaDueText", "The spirits will restore the gems and coin piles at the next dawn if no one enters", "Shown at the entrance when the dungeon's time is already up.");
			MorkhallaEntranceRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Morkhalla", "MorkhallaEntranceRadius", 50f, "Metres, measured flat, around the dungeon's centre within which a player on the surface gets the entrance message. Its surface location stands about 30 m from its interior's centre. Standing there does not count as a visit.");
			MorkhallaRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Morkhalla", "MorkhallaRadius", 200f, "Metres from the dungeon's centre that count as inside it.");
			TheHole = ((BaseUnityPlugin)this).Config.Bind<bool>("TheHole", "TheHole", true, "Restore the trash piles and the spawner nests in the Deep North's Holes once no player has been inside a Hole for HoleDays in-game days. Only ones the mod has seen can come back: one broken before it was installed left no trace. Glow worms are left to the game, which respawns them itself; roots are not restored.");
			HoleDays = ((BaseUnityPlugin)this).Config.Bind<int>("TheHole", "HoleDays", 30, "In-game days a Hole must go without anyone inside before its trash piles and nests come back. Any visit starts the count again.");
			HoleDryRun = ((BaseUnityPlugin)this).Config.Bind<bool>("TheHole", "HoleDryRun", false, "Log which Holes would regenerate, but create nothing and write nothing. Visits are then tracked in memory only.");
			HoleRegenNow = ((BaseUnityPlugin)this).Config.Bind<bool>("TheHole", "HoleRegenNow", false, "One-shot: at the next daily sweep, every Hole with broken piles or nests regenerates whatever its clock says, then this sets itself back to false.");
			HoleNotifyText = ((BaseUnityPlugin)this).Config.Bind<string>("TheHole", "HoleNotifyText", "The spirits will restore the nests and trash piles after {days} without visitors", "Shown to a player inside a Hole with broken piles or nests (needs Notify on). {days} is HoleDays; {mined} is how many are missing.");
			HoleEntranceText = ((BaseUnityPlugin)this).Config.Bind<string>("TheHole", "HoleEntranceText", "The spirits will restore the nests and trash piles in {days} if no one enters", "Shown at the entrance of a Hole with broken piles or nests. {days} is what is left of the Hole's clock; going in restarts it.");
			HoleDueText = ((BaseUnityPlugin)this).Config.Bind<string>("TheHole", "HoleDueText", "The spirits will restore the nests and trash piles at the next dawn if no one enters", "Shown at the entrance when the Hole's time is already up.");
			HoleEntranceRadius = ((BaseUnityPlugin)this).Config.Bind<float>("TheHole", "HoleEntranceRadius", 40f, "Metres, measured flat, around a Hole's centre within which a player on the surface gets the entrance message. A Hole's surface location stands about 20 m from its interior's centre. Standing there does not count as a visit.");
			HoleRadius = ((BaseUnityPlugin)this).Config.Bind<float>("TheHole", "HoleRadius", 150f, "Metres from a Hole's centre that count as inside it. Its piles and nests are within about 50 m; the next Hole is over 300 m away.");
			Citadel = ((BaseUnityPlugin)this).Config.Bind<bool>("Citadel", "Citadel", true, "Reset a Mistlands infested citadel whose Queen has been killed, once no player has been inside it for CitadelDays in-game days: the Queen is put back in her place, and the seeker eggs and creep blocks the mod has seen are restored. A citadel whose Queen is alive is never touched. Queens killed before the mod was installed come back too.");
			CitadelDays = ((BaseUnityPlugin)this).Config.Bind<int>("Citadel", "CitadelDays", 30, "In-game days a citadel with a dead Queen must go without anyone inside before it resets. Any visit starts the count again.");
			CitadelDryRun = ((BaseUnityPlugin)this).Config.Bind<bool>("Citadel", "CitadelDryRun", false, "Log which citadels would reset, but create nothing and write nothing. Visits are then tracked in memory only.");
			CitadelRegenNow = ((BaseUnityPlugin)this).Config.Bind<bool>("Citadel", "CitadelRegenNow", false, "One-shot: at the next daily sweep, every citadel with a dead Queen resets whatever its clock says, then this sets itself back to false.");
			CitadelCloseDoor = ((BaseUnityPlugin)this).Config.Bind<bool>("Citadel", "CitadelCloseDoor", true, "Close the citadel's sealed door when it resets, so it takes a Sealbreaker to get in again. The game does not use up the Sealbreaker.");
			CitadelNotifyText = ((BaseUnityPlugin)this).Config.Bind<string>("Citadel", "CitadelNotifyText", "Th