Decompiled source of OneMapToRuleThemAll v1.1.0

plugins/OneMapToRuleThemAll.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("One Map To Rule Them All")]
[assembly: AssemblyDescription("Valheim One Map To Rule Them All Mod by DrummerCraig")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("One Map To Rule Them All")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("6A5B7C8D-E9F0-1234-5678-9ABCDEF01234")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: AssemblyVersion("1.0.1.0")]
namespace OneMapToRuleThemAll;

public class SharedPin
{
	public string Name;

	public PinType Type;

	public Vector3 Pos;

	public bool Checked;

	public string OwnerId = "";
}
public static class MapStateRepository
{
	public const int MapSize = 2048;

	public const int MapSizeSquared = 4194304;

	public const string DiscoveryOwnerId = "auto";

	private static List<SharedPin> ServerPins = new List<SharedPin>();

	public static List<SharedPin> ClientPins = new List<SharedPin>();

	public static bool[] Explored;

	public static bool InitialPinsReceived = false;

	public static ZPackage Default()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(1);
		val.Write(2048);
		for (int i = 0; i < 4194304; i++)
		{
			val.Write(false);
		}
		val.Write(0);
		val.SetPos(0);
		return val;
	}

	public static ZPackage GetMapData()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Expected I4, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(2);
		val.Write(2048);
		byte b = 0;
		int num = 0;
		for (int i = 0; i < Explored.Length; i++)
		{
			if (Explored[i])
			{
				b |= (byte)(1 << num);
			}
			num++;
			if (num >= 8)
			{
				val.Write(b);
				b = 0;
				num = 0;
			}
		}
		if (num > 0)
		{
			val.Write(b);
		}
		val.Write(ServerPins.Count);
		foreach (SharedPin serverPin in ServerPins)
		{
			val.Write(serverPin.Name);
			val.Write(serverPin.Pos);
			val.Write((int)serverPin.Type);
			val.Write(serverPin.Checked);
			val.Write(serverPin.OwnerId);
		}
		return val;
	}

	public static void SetMapData(ZPackage mapData)
	{
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		ServerPins.Clear();
		mapData.SetPos(0);
		int num = mapData.ReadInt();
		int num2 = mapData.ReadInt();
		bool[] array = new bool[num2 * num2];
		if (num >= 2)
		{
			for (int i = 0; i < num2 * num2; i += 8)
			{
				byte b = mapData.ReadByte();
				for (int j = 0; j < 8 && i + j < array.Length; j++)
				{
					array[i + j] = (b & (1 << j)) != 0;
				}
			}
		}
		else
		{
			for (int k = 0; k < num2 * num2; k++)
			{
				array[k] = mapData.ReadBool();
			}
		}
		int num3 = mapData.ReadInt();
		for (int l = 0; l < num3; l++)
		{
			ServerPins.Add(new SharedPin
			{
				Name = mapData.ReadString(),
				Pos = mapData.ReadVector3(),
				Type = (PinType)mapData.ReadInt(),
				Checked = mapData.ReadBool(),
				OwnerId = mapData.ReadString()
			});
		}
		Explored = array;
	}

	public static void SetExplored(int x, int y)
	{
		Explored[y * 2048 + x] = true;
		MapFilePersistence.MapDirty = true;
	}

	public static bool[] GetExplorationArray()
	{
		return Explored;
	}

	public static void MergeExplorationArray(bool[] arr, int startIndex, int size)
	{
		for (int i = 0; i < size; i++)
		{
			Explored[startIndex + i] = arr[i] || Explored[startIndex + i];
		}
		MapFilePersistence.MapDirty = true;
	}

	public static ZPackage PackBoolArray(bool[] arr, int chunkId, int startIndex, int size)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(chunkId);
		byte b = 0;
		int num = 0;
		for (int i = startIndex; i < startIndex + size; i++)
		{
			if (arr[i])
			{
				b |= (byte)(1 << num);
			}
			num++;
			if (num >= 8)
			{
				val.Write(b);
				b = 0;
				num = 0;
			}
		}
		if (num > 0)
		{
			val.Write(b);
		}
		return val;
	}

	public static bool[] UnpackBoolArray(ZPackage z, int length)
	{
		bool[] array = new bool[length];
		for (int i = 0; i < length; i += 8)
		{
			byte b = z.ReadByte();
			array[i] = (b & 1) != 0;
			array[i + 1] = (b & 2) != 0;
			array[i + 2] = (b & 4) != 0;
			array[i + 3] = (b & 8) != 0;
			array[i + 4] = (b & 0x10) != 0;
			array[i + 5] = (b & 0x20) != 0;
			array[i + 6] = (b & 0x40) != 0;
			array[i + 7] = (b & 0x80) != 0;
		}
		return array;
	}

	public static ZPackage PackPin(SharedPin pin, bool skipSetPos = false)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Expected I4, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(pin.Name);
		val.Write(pin.Pos);
		val.Write((int)pin.Type);
		val.Write(pin.Checked);
		val.Write(pin.OwnerId);
		if (!skipSetPos)
		{
			val.SetPos(0);
		}
		return val;
	}

	public static SharedPin UnpackPin(ZPackage z)
	{
		//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)
		return new SharedPin
		{
			Name = z.ReadString(),
			Pos = z.ReadVector3(),
			Type = (PinType)z.ReadInt(),
			Checked = z.ReadBool(),
			OwnerId = z.ReadString()
		};
	}

	public static ZPackage PackPins(List<SharedPin> pins)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Expected I4, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(pins.Count);
		foreach (SharedPin pin in pins)
		{
			val.Write(pin.Name);
			val.Write(pin.Pos);
			val.Write((int)pin.Type);
			val.Write(pin.Checked);
			val.Write(pin.OwnerId);
		}
		val.SetPos(0);
		return val;
	}

	public static List<SharedPin> UnpackPins(ZPackage z)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		List<SharedPin> list = new List<SharedPin>();
		int num = z.ReadInt();
		for (int i = 0; i < num; i++)
		{
			list.Add(new SharedPin
			{
				Name = z.ReadString(),
				Pos = z.ReadVector3(),
				Type = (PinType)z.ReadInt(),
				Checked = z.ReadBool(),
				OwnerId = z.ReadString()
			});
		}
		return list;
	}

	public static List<SharedPin> GetPins()
	{
		return ServerPins;
	}

	public static void AddPin(SharedPin pin)
	{
		ServerPins.Add(pin);
		MapFilePersistence.MapDirty = true;
	}

	public static void RemovePin(SharedPin needle)
	{
		ServerPins.RemoveAll((SharedPin p) => ArePinsEqual(p, needle));
		MapFilePersistence.MapDirty = true;
	}

	public static void SetPinState(SharedPin needle, bool state)
	{
		foreach (SharedPin serverPin in ServerPins)
		{
			if (ArePinsEqual(serverPin, needle))
			{
				serverPin.Checked = state;
			}
		}
		MapFilePersistence.MapDirty = true;
	}

	public static bool ArePinsEqual(SharedPin a, SharedPin b)
	{
		//IL_0014: 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_0028: Unknown result type (might be due to invalid IL or missing references)
		if (a.Name == b.Name && a.Type == b.Type)
		{
			return ((Vector3)(ref a.Pos)).Equals(b.Pos);
		}
		return false;
	}

	public static bool ArePinsEqual(SharedPin a, PinData b)
	{
		//IL_0014: 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_0028: Unknown result type (might be due to invalid IL or missing references)
		if (a.Name == b.m_name && a.Type == b.m_type)
		{
			return Utils.DistanceXZ(a.Pos, b.m_pos) < 1f;
		}
		return false;
	}
}
[HarmonyPatch]
public static class ZNetProxy
{
	[HarmonyPatch(typeof(ZNet), "Awake")]
	private static class ZNetAwake
	{
		private static void Postfix(ZNet __instance)
		{
			ZNetInstance = __instance;
			ModSettings.ModActive = true;
			if ((Object)(object)MinimapProxy.Instance != (Object)null && IsServer(__instance))
			{
				MapPinSynchronizer.SendPinsToClient(null);
			}
		}
	}

	public static ZNet ZNetInstance;

	[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
	[HarmonyPatch(typeof(ZNet), "IsServer")]
	public static bool IsServer(ZNet instance)
	{
		throw new NotImplementedException();
	}

	[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
	[HarmonyPatch(typeof(ZNet), "IsDedicated")]
	public static bool IsDedicated(ZNet instance)
	{
		throw new NotImplementedException();
	}

	[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
	[HarmonyPatch(typeof(ZNet), "GetServerRPC")]
	public static ZRpc GetServerRPC(ZNet instance)
	{
		throw new NotImplementedException();
	}
}
[HarmonyPatch]
public static class MinimapProxy
{
	[HarmonyPatch(typeof(Minimap), "Awake")]
	private static class MinimapAwake
	{
		private static void Postfix(Minimap __instance)
		{
			Instance = __instance;
			object value = Traverse.Create((object)__instance).Field("m_fogTexture").GetValue();
			FogTexture = (Texture2D)((value is Texture2D) ? value : null);
		}
	}

	public static Minimap Instance;

	public static Texture2D FogTexture;

	[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
	[HarmonyPatch(typeof(Minimap), "Explore", new Type[]
	{
		typeof(int),
		typeof(int)
	})]
	public static bool Explore(Minimap instance, int x, int y)
	{
		throw new NotImplementedException();
	}

	[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
	[HarmonyPatch(typeof(Minimap), "AddPin", new Type[]
	{
		typeof(Vector3),
		typeof(PinType),
		typeof(string),
		typeof(bool),
		typeof(bool),
		typeof(long),
		typeof(PlatformUserID)
	})]
	public static PinData AddPin(Minimap instance, Vector3 pos, PinType type, string name, bool save, bool isChecked, long owner, PlatformUserID author)
	{
		throw new NotImplementedException();
	}

	[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
	[HarmonyPatch(typeof(Minimap), "RemovePin", new Type[] { typeof(PinData) })]
	public static void RemovePin(Minimap instance, PinData pin)
	{
		throw new NotImplementedException();
	}
}
public static class ExplorationSynchronizer
{
	[HarmonyPatch(typeof(Minimap), "Explore", new Type[]
	{
		typeof(Vector3),
		typeof(float)
	})]
	private class MinimapPatchExploreInterval
	{
		private static void Postfix(Minimap __instance)
		{
			if (_fogDirty)
			{
				_fogDirty = false;
				object value = Traverse.Create((object)__instance).Field("m_fogTexture").GetValue();
				object obj = ((value is Texture2D) ? value : null);
				if (obj != null)
				{
					((Texture2D)obj).Apply();
				}
			}
			if (_pendingCells.Count == 0)
			{
				return;
			}
			if (ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
			{
				foreach (var (x, y) in _pendingCells)
				{
					OnClientExplore(null, x, y);
				}
			}
			else
			{
				FlushBatchToServer(_pendingCells);
			}
			_pendingCells.Clear();
		}
	}

	[HarmonyPatch(typeof(Minimap), "Explore", new Type[]
	{
		typeof(int),
		typeof(int)
	})]
	private class MinimapPatchExplore
	{
		private static void Postfix(int x, int y, bool __result)
		{
			if (__result && !_blockExplore && ModSettings.ModActive)
			{
				_pendingCells.Add((x, y));
			}
		}
	}

	[HarmonyPatch(typeof(Minimap), "SetMapData", new Type[] { typeof(byte[]) })]
	private class MinimapPatchSetMapData
	{
		private static void Prefix()
		{
			_blockExplore = true;
		}

		private static void Postfix()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			_blockExplore = false;
			if (!ModSettings.ModActive)
			{
				return;
			}
			if (ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
			{
				SendChunkToClient(null, 0);
				return;
			}
			ZRpc serverRPC = ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance);
			if (serverRPC != null)
			{
				serverRPC.Invoke("OM_ClientRequestInitialMap", new object[1] { (object)new ZPackage() });
			}
		}
	}

	[HarmonyPatch(typeof(Minimap), "ExploreAll")]
	private class MinimapPatchExploreAll
	{
		private static void Prefix()
		{
			_blockExplore = true;
		}

		private static void Postfix(Minimap __instance)
		{
			_pendingCells.Clear();
			if (ModSettings.ModActive && (Object)(object)ZNetProxy.ZNetInstance != (Object)null)
			{
				if (ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
				{
					if (Traverse.Create((object)__instance).Field("m_explored").GetValue() is bool[] array)
					{
						MapStateRepository.Explored = (bool[])array.Clone();
						MapFilePersistence.MapDirty = true;
					}
					if (Traverse.Create((object)ZNetProxy.ZNetInstance).Field("m_peers").GetValue() is List<ZNetPeer> list)
					{
						foreach (ZNetPeer item in list)
						{
							if (item.IsReady())
							{
								SendChunkToClient(item.m_rpc, 0);
							}
						}
					}
				}
				else
				{
					((MonoBehaviour)ZNetProxy.ZNetInstance).StartCoroutine(SendChunkToServer(ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance), 0));
				}
			}
			_blockExplore = false;
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	private class ZNetPatchRPCPeerInfo
	{
		private static void Postfix(ZRpc rpc, ZNet __instance)
		{
			if (ModSettings.ModActive && __instance.IsServer())
			{
				SendChunkToClient(rpc, 0);
				rpc.Invoke("OM_ServerDiscoveryConfig", new object[1] { AutoPinConfig.PackDiscoveryConfig() });
				rpc.Invoke("OM_ServerRadarConfig", new object[1] { RadarConfig.PackRadarConfig() });
			}
		}
	}

	private static bool _blockExplore = false;

	private static bool _fogDirty = false;

	private const int Chunks = 64;

	private static readonly List<(int x, int y)> _pendingCells = new List<(int, int)>(64);

	public static void OnClientExplore(ZRpc client, int x, int y)
	{
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Expected O, but got Unknown
		if (!ModSettings.ModActive)
		{
			return;
		}
		MapStateRepository.SetExplored(x, y);
		List<ZNetPeer> obj = Traverse.Create((object)ZNet.instance).Field("m_peers").GetValue() as List<ZNetPeer>;
		int num = 0;
		foreach (ZNetPeer item in obj)
		{
			if (item.IsReady() && item.m_rpc != client)
			{
				ZPackage val = new ZPackage();
				val.Write(x);
				val.Write(y);
				item.m_rpc.Invoke("OM_ServerMapData", new object[1] { val });
				num++;
			}
		}
		if ((Object)(object)MinimapProxy.Instance != (Object)null)
		{
			MinimapProxy.Explore(MinimapProxy.Instance, x, y);
			_fogDirty = true;
		}
	}

	public static void OnClientRequestInitialMap(ZRpc client, ZPackage data)
	{
		if (ModSettings.ModActive)
		{
			SendChunkToClient(client, 0);
		}
	}

	public static void OnClientInitialMap(ZRpc client, ZPackage data)
	{
		if (ModSettings.ModActive)
		{
			data.SetPos(0);
			int num = data.ReadInt();
			int num2 = 65536;
			int startIndex = num * num2;
			MapStateRepository.MergeExplorationArray(MapStateRepository.UnpackBoolArray(data, num2), startIndex, num2);
			SendChunkToClient(client, num + 1);
		}
	}

	public static void OnServerMapData(ZRpc rpc, ZPackage data)
	{
		if (ModSettings.ModActive)
		{
			data.SetPos(0);
			int x = data.ReadInt();
			int y = data.ReadInt();
			if (!((Object)(object)MinimapProxy.Instance == (Object)null))
			{
				MinimapProxy.Explore(MinimapProxy.Instance, x, y);
				_fogDirty = true;
			}
		}
	}

	public static void OnServerMapInitial(ZRpc rpc, ZPackage data)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		data.SetPos(0);
		int num = data.ReadInt();
		int num2 = 65536;
		int num3 = num * num2;
		bool[] array = MapStateRepository.UnpackBoolArray(data, num2);
		for (int i = 0; i < array.Length; i++)
		{
			if (array[i])
			{
				MinimapProxy.Explore(MinimapProxy.Instance, (num3 + i) % 2048, (num3 + i) / 2048);
			}
		}
		Texture2D fogTexture = MinimapProxy.FogTexture;
		if (fogTexture != null)
		{
			fogTexture.Apply();
		}
		((MonoBehaviour)ZNetProxy.ZNetInstance).StartCoroutine(SendChunkToServer(rpc, num));
	}

	private static void FlushBatchToServer(List<(int x, int y)> cells)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Expected O, but got Unknown
		ZRpc serverRPC = ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance);
		if (serverRPC == null)
		{
			return;
		}
		ZPackage val = new ZPackage();
		val.Write(cells.Count);
		foreach (var (num, num2) in cells)
		{
			val.Write(num);
			val.Write(num2);
		}
		serverRPC.Invoke("OM_ClientExploreBatch", new object[1] { val });
	}

	public static void OnClientExploreBatch(ZRpc client, ZPackage data)
	{
		if (ModSettings.ModActive)
		{
			data.SetPos(0);
			int num = data.ReadInt();
			for (int i = 0; i < num; i++)
			{
				int x = data.ReadInt();
				int y = data.ReadInt();
				OnClientExplore(client, x, y);
			}
		}
	}

	private static void SendChunkToClient(ZRpc client, int chunk)
	{
		if (chunk < 64)
		{
			int num = 65536;
			int startIndex = chunk * num;
			ZPackage val = MapStateRepository.PackBoolArray(MapStateRepository.GetExplorationArray(), chunk, startIndex, num);
			if (client == null)
			{
				OnServerMapInitial(null, val);
				return;
			}
			client.Invoke("OM_ServerMapInitial", new object[1] { val });
		}
	}

	private static IEnumerator SendChunkToServer(ZRpc serverRpc, int chunk)
	{
		if (chunk >= 64)
		{
			yield break;
		}
		int num = 65536;
		int startIndex = chunk * num;
		bool[] arr = Traverse.Create((object)MinimapProxy.Instance).Field("m_explored").GetValue() as bool[];
		ZPackage z = MapStateRepository.PackBoolArray(arr, chunk, startIndex, num);
		if (serverRpc == null)
		{
			OnClientInitialMap(null, z);
			yield break;
		}
		yield return (object)new WaitUntil((Func<bool>)(() => ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance) != null));
		ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance).Invoke("OM_ClientInitialMap", new object[1] { z });
	}
}
public static class MapPinSynchronizer
{
	[HarmonyPatch(typeof(Minimap), "OnPinTextEntered")]
	private class MinimapPatchOnPinTextEntered
	{
		private static void Prefix(out PinData __state, PinData ___m_namePin)
		{
			__state = ___m_namePin;
		}

		private static void Postfix(PinData __state)
		{
			if (__state != null)
			{
				SendPinToServer(__state);
			}
		}
	}

	[HarmonyPatch(typeof(Minimap), "OnMapRightClick")]
	private class MinimapPatchOnMapRightClick
	{
		private static void Postfix()
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			if (LatestClosestPin == null)
			{
				return;
			}
			SharedPin clientPin = GetClientPin(LatestClosestPin);
			if (clientPin == null)
			{
				return;
			}
			if (clientPin.OwnerId == "auto")
			{
				if ((Object)(object)MinimapProxy.Instance != (Object)null)
				{
					string name = (AutoPinConfig.DiscoveryPinText.Value ? clientPin.Name : "");
					string keywordForLabel = AutoPinConfig.GetKeywordForLabel(clientPin.Name);
					PinData mmPin = MinimapProxy.AddPin(MinimapProxy.Instance, clientPin.Pos, clientPin.Type, name, save: false, clientPin.Checked, 0L, new PlatformUserID(""));
					if (keywordForLabel != null)
					{
						AutoPinConfig.ApplyDiscoveryIcon(mmPin, keywordForLabel);
					}
				}
			}
			else
			{
				RemovePinFromServer(clientPin);
			}
		}
	}

	[HarmonyPatch(typeof(Minimap), "OnMapLeftClick")]
	private class MinimapPatchOnMapLeftClick
	{
		private static void Postfix()
		{
			if (LatestClosestPin != null)
			{
				SharedPin clientPin = GetClientPin(LatestClosestPin);
				if (clientPin != null)
				{
					clientPin.Checked = LatestClosestPin.m_checked;
					CheckPinOnServer(clientPin, clientPin.Checked);
				}
			}
		}
	}

	[HarmonyPatch(typeof(Minimap), "GetClosestPin", new Type[]
	{
		typeof(Vector3),
		typeof(float),
		typeof(bool)
	})]
	private class MinimapPatchGetClosestPin
	{
		private static void Postfix(ref PinData __result, Vector3 pos, float radius)
		{
			//IL_002d: 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_007b: 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)
			if (!ModSettings.ModActive)
			{
				return;
			}
			LatestClosestPin = __result;
			SharedPin sharedPin = null;
			float num = 999999f;
			foreach (SharedPin clientPin in MapStateRepository.ClientPins)
			{
				float num2 = Utils.DistanceXZ(pos, clientPin.Pos);
				if (num2 < radius && (sharedPin == null || num2 < num))
				{
					sharedPin = clientPin;
					num = num2;
				}
			}
			if (sharedPin != null)
			{
				PinData mapPin = GetMapPin(sharedPin);
				if (mapPin != null && (__result == null || Utils.DistanceXZ(pos, __result.m_pos) > num))
				{
					__result = mapPin;
					LatestClosestPin = mapPin;
				}
			}
		}
	}

	[HarmonyPatch(typeof(Minimap), "ClearPins")]
	private class MinimapPatchClearPins
	{
		private static void Postfix()
		{
			AddPinsAfterClear();
		}
	}

	[HarmonyPatch(typeof(Minimap), "UpdatePins")]
	private class MinimapPatchUpdateDiscoveryIcons
	{
		private static void Postfix(Minimap __instance)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			if (!AutoPinConfig.DiscoveryPinObjectIcon.Value || _iconLookup.Count == 0)
			{
				return;
			}
			_iconTimer += Time.deltaTime;
			if (_iconTimer < ModSettings.ActiveDiscoveryIconInterval)
			{
				return;
			}
			_iconTimer = 0f;
			if (!(Traverse.Create((object)__instance).Field("m_pins").GetValue() is List<PinData> list) || list.Count == 0)
			{
				return;
			}
			foreach (PinData item in list)
			{
				if (_iconLookup.TryGetValue((item.m_name, item.m_type), out var value))
				{
					item.m_icon = value;
				}
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	private class ZNetPatchRPCPeerInfo
	{
		private static void Postfix(ZRpc rpc, ZNet __instance)
		{
			if (ModSettings.ModActive && __instance.IsServer())
			{
				SendPinsToClient(rpc);
				rpc.Invoke("OM_ServerDiscoveryConfig", new object[1] { AutoPinConfig.PackDiscoveryConfig() });
				rpc.Invoke("OM_ServerRadarConfig", new object[1] { RadarConfig.PackRadarConfig() });
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "Shutdown")]
	private class ZNetPatchShutdown
	{
		private static void Postfix()
		{
			MapStateRepository.ClientPins.Clear();
			MapStateRepository.InitialPinsReceived = false;
			AutoPinConfig.ResetToLocalConfig();
			RadarConfig.ResetToLocalConfig();
			RadarClusterManager.Clear();
		}
	}

	[HarmonyPatch(typeof(Minimap), "SetMapData", new Type[] { typeof(byte[]) })]
	private class MinimapPatchSetMapData
	{
		private static void Postfix()
		{
			if (ModSettings.ModActive && !((Object)(object)ZNetProxy.ZNetInstance == (Object)null) && ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
			{
				SendPinsToClient(null);
			}
		}
	}

	private static PinData LatestClosestPin = null;

	private static readonly Dictionary<(string name, PinType type), Sprite> _iconLookup = new Dictionary<(string, PinType), Sprite>();

	private static float _iconTimer = 0f;

	private static void RebuildIconLookup()
	{
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		_iconLookup.Clear();
		foreach (SharedPin clientPin in MapStateRepository.ClientPins)
		{
			if (clientPin.OwnerId != "auto")
			{
				continue;
			}
			string keywordForLabel = AutoPinConfig.GetKeywordForLabel(clientPin.Name);
			if (keywordForLabel != null && RadarIconLoader.TryGetIcon(keywordForLabel, out var sprite))
			{
				(string, PinType) key = (clientPin.Name, clientPin.Type);
				if (!_iconLookup.ContainsKey(key))
				{
					_iconLookup[key] = sprite;
				}
			}
		}
	}

	public static void OnClientAddPin(ZRpc client, ZPackage data)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		data.SetPos(0);
		SharedPin pin = MapStateRepository.UnpackPin(data);
		MapStateRepository.AddPin(pin);
		foreach (ZNetPeer item in Traverse.Create((object)ZNet.instance).Field("m_peers").GetValue() as List<ZNetPeer>)
		{
			if (item.IsReady() && item.m_rpc != client)
			{
				item.m_rpc.Invoke("OM_ServerAddPin", new object[1] { MapStateRepository.PackPin(pin) });
			}
		}
		if (client != null && (Object)(object)MinimapProxy.Instance != (Object)null)
		{
			OnServerAddPin(null, MapStateRepository.PackPin(pin));
		}
	}

	public static void OnClientRemovePin(ZRpc client, ZPackage data)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		data.SetPos(0);
		SharedPin sharedPin = MapStateRepository.UnpackPin(data);
		if (client != null && !ClientCanRemovePin(client, sharedPin))
		{
			return;
		}
		MapStateRepository.RemovePin(sharedPin);
		List<ZNetPeer> obj = Traverse.Create((object)ZNet.instance).Field("m_peers").GetValue() as List<ZNetPeer>;
		bool flag = sharedPin.OwnerId == "auto";
		foreach (ZNetPeer item in obj)
		{
			if (item.IsReady() && (flag || item.m_rpc != client))
			{
				item.m_rpc.Invoke("OM_ServerRemovePin", new object[1] { MapStateRepository.PackPin(sharedPin) });
			}
		}
		if (flag || client != null)
		{
			OnServerRemovePin(null, MapStateRepository.PackPin(sharedPin));
		}
	}

	public static void OnClientCheckPin(ZRpc client, ZPackage data)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		data.SetPos(0);
		SharedPin sharedPin = MapStateRepository.UnpackPin(data);
		bool flag = data.ReadBool();
		MapStateRepository.SetPinState(sharedPin, flag);
		foreach (ZNetPeer item in Traverse.Create((object)ZNet.instance).Field("m_peers").GetValue() as List<ZNetPeer>)
		{
			if (item.IsReady() && item.m_rpc != client)
			{
				ZPackage val = MapStateRepository.PackPin(sharedPin, skipSetPos: true);
				val.Write(flag);
				item.m_rpc.Invoke("OM_ServerCheckPin", new object[1] { val });
			}
		}
		if (client != null)
		{
			ZPackage val2 = MapStateRepository.PackPin(sharedPin, skipSetPos: true);
			val2.Write(flag);
			OnServerCheckPin(null, val2);
		}
	}

	private static bool ClientCanRemovePin(ZRpc client, SharedPin pin)
	{
		if (pin.OwnerId == "auto")
		{
			return false;
		}
		if (AdminCommands.IsAdmin(client))
		{
			return true;
		}
		ZNetPeer val = (Traverse.Create((object)ZNet.instance).Field("m_peers").GetValue() as List<ZNetPeer>)?.FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_rpc == client));
		if (val == null)
		{
			return false;
		}
		string text = val.m_uid.ToString();
		if (!(pin.OwnerId == text))
		{
			return pin.OwnerId == string.Empty;
		}
		return true;
	}

	public static void OnServerInitialPins(ZRpc rpc, ZPackage data)
	{
		if (ModSettings.ModActive)
		{
			data.SetPos(0);
			MapStateRepository.ClientPins = MapStateRepository.UnpackPins(data);
			MapStateRepository.InitialPinsReceived = true;
			AppendPins();
		}
	}

	public static void OnServerAddPin(ZRpc rpc, ZPackage data)
	{
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive)
		{
			return;
		}
		data.SetPos(0);
		SharedPin sharedPin = MapStateRepository.UnpackPin(data);
		string text = ZNet.GetUID().ToString();
		MapStateRepository.ClientPins.Add(sharedPin);
		RebuildIconLookup();
		string text2 = null;
		if (sharedPin.OwnerId != "auto")
		{
			if (!ModSettings.ShowOtherPlayerPins.Value && sharedPin.OwnerId != "" && sharedPin.OwnerId != text)
			{
				return;
			}
		}
		else
		{
			text2 = AutoPinConfig.GetKeywordForLabel(sharedPin.Name);
			if (text2 != null && !WorldObjectConfig.IsKeywordDiscoveryVisible(text2))
			{
				return;
			}
		}
		string name = ((sharedPin.OwnerId == "auto" && !AutoPinConfig.DiscoveryPinText.Value) ? "" : sharedPin.Name);
		PinData mmPin = MinimapProxy.AddPin(MinimapProxy.Instance, sharedPin.Pos, sharedPin.Type, name, save: false, sharedPin.Checked, 0L, new PlatformUserID(""));
		if (text2 != null)
		{
			AutoPinConfig.ApplyDiscoveryIcon(mmPin, text2);
		}
	}

	public static void OnServerRemovePin(ZRpc rpc, ZPackage data)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		data.SetPos(0);
		SharedPin pin = MapStateRepository.UnpackPin(data);
		MapStateRepository.ClientPins.RemoveAll((SharedPin p) => MapStateRepository.ArePinsEqual(p, pin));
		RebuildIconLookup();
		if (!((Object)(object)MinimapProxy.Instance == (Object)null) && Traverse.Create((object)MinimapProxy.Instance).Field("m_pins").GetValue() is List<PinData> mmPins)
		{
			PinData mapPin = GetMapPin(pin);
			if (mapPin != null)
			{
				DestroyPinEntry(mmPins, mapPin);
			}
		}
	}

	public static void OnServerCheckPin(ZRpc rpc, ZPackage data)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		data.SetPos(0);
		SharedPin b = MapStateRepository.UnpackPin(data);
		bool @checked = data.ReadBool();
		foreach (SharedPin clientPin in MapStateRepository.ClientPins)
		{
			if (MapStateRepository.ArePinsEqual(clientPin, b))
			{
				clientPin.Checked = @checked;
				PinData mapPin = GetMapPin(clientPin);
				if (mapPin != null)
				{
					mapPin.m_checked = @checked;
				}
			}
		}
	}

	public static void SendPinToServer(PinData pin, bool isDiscovery = false)
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive)
		{
			return;
		}
		SharedPin sharedPin = new SharedPin
		{
			Name = pin.m_name,
			Pos = pin.m_pos,
			Type = pin.m_type,
			Checked = pin.m_checked,
			OwnerId = (isDiscovery ? "auto" : ZNet.GetUID().ToString())
		};
		MapStateRepository.ClientPins.Add(sharedPin);
		ZPackage val = MapStateRepository.PackPin(sharedPin);
		if (ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
		{
			OnClientAddPin(null, val);
			return;
		}
		ZRpc serverRPC = ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance);
		if (serverRPC != null)
		{
			serverRPC.Invoke("OM_ClientAddPin", new object[1] { val });
		}
	}

	public static void RemovePinFromServer(SharedPin pin)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		MapStateRepository.ClientPins.Remove(pin);
		ZPackage val = MapStateRepository.PackPin(pin);
		if (ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
		{
			OnClientRemovePin(null, val);
			return;
		}
		ZRpc serverRPC = ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance);
		if (serverRPC != null)
		{
			serverRPC.Invoke("OM_ClientRemovePin", new object[1] { val });
		}
	}

	public static void CheckPinOnServer(SharedPin pin, bool state)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		ZPackage val = MapStateRepository.PackPin(pin, skipSetPos: true);
		val.Write(state);
		val.SetPos(0);
		if (ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
		{
			OnClientCheckPin(null, val);
			return;
		}
		ZRpc serverRPC = ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance);
		if (serverRPC != null)
		{
			serverRPC.Invoke("OM_ClientCheckPin", new object[1] { val });
		}
	}

	public static void SendPinsToClient(ZRpc client)
	{
		if (ModSettings.ModActive)
		{
			ZPackage val = MapStateRepository.PackPins(MapStateRepository.GetPins());
			if (client == null)
			{
				OnServerInitialPins(null, val);
				return;
			}
			client.Invoke("OM_ServerInitialPins", new object[1] { val });
		}
	}

	public static void AppendPins()
	{
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_018f: Unknown result type (might be due to invalid IL or missing references)
		//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive || (Object)(object)MinimapProxy.Instance == (Object)null || !(Traverse.Create((object)MinimapProxy.Instance).Field("m_pins").GetValue() is List<PinData> list))
		{
			return;
		}
		string text = ZNet.GetUID().ToString();
		foreach (SharedPin pin in MapStateRepository.ClientPins)
		{
			PinData val = ((!(pin.OwnerId == "auto")) ? ((IEnumerable<PinData>)list).FirstOrDefault((Func<PinData, bool>)((PinData p) => MapStateRepository.ArePinsEqual(pin, p))) : ((IEnumerable<PinData>)list).FirstOrDefault((Func<PinData, bool>)((PinData p) => p.m_type == pin.Type && Utils.DistanceXZ(pin.Pos, p.m_pos) < 1f)));
			if (val != null)
			{
				DestroyPinEntry(list, val);
			}
			string text2 = null;
			bool flag;
			if (pin.OwnerId == "auto")
			{
				text2 = AutoPinConfig.GetKeywordForLabel(pin.Name);
				flag = text2 == null || WorldObjectConfig.IsKeywordDiscoveryVisible(text2);
			}
			else
			{
				flag = ModSettings.ShowOtherPlayerPins.Value || pin.OwnerId == "" || pin.OwnerId == text;
			}
			if (flag)
			{
				string name = ((pin.OwnerId == "auto" && !AutoPinConfig.DiscoveryPinText.Value) ? "" : pin.Name);
				PinData mmPin = MinimapProxy.AddPin(MinimapProxy.Instance, pin.Pos, pin.Type, name, save: false, pin.Checked, 0L, new PlatformUserID(""));
				if (text2 != null)
				{
					AutoPinConfig.ApplyDiscoveryIcon(mmPin, text2);
				}
			}
		}
		RebuildIconLookup();
	}

	private static void AddPinsAfterClear()
	{
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: 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)
		if (!ModSettings.ModActive || (Object)(object)MinimapProxy.Instance == (Object)null)
		{
			return;
		}
		string text = ZNet.GetUID().ToString();
		foreach (SharedPin clientPin in MapStateRepository.ClientPins)
		{
			string text2 = null;
			bool flag;
			if (clientPin.OwnerId == "auto")
			{
				text2 = AutoPinConfig.GetKeywordForLabel(clientPin.Name);
				flag = text2 == null || WorldObjectConfig.IsKeywordDiscoveryVisible(text2);
			}
			else
			{
				flag = ModSettings.ShowOtherPlayerPins.Value || clientPin.OwnerId == "" || clientPin.OwnerId == text;
			}
			if (flag)
			{
				string name = ((clientPin.OwnerId == "auto" && !AutoPinConfig.DiscoveryPinText.Value) ? "" : clientPin.Name);
				PinData mmPin = MinimapProxy.AddPin(MinimapProxy.Instance, clientPin.Pos, clientPin.Type, name, save: false, clientPin.Checked, 0L, new PlatformUserID(""));
				if (text2 != null)
				{
					AutoPinConfig.ApplyDiscoveryIcon(mmPin, text2);
				}
			}
		}
		RebuildIconLookup();
	}

	private static void DestroyPinEntry(List<PinData> mmPins, PinData mmPin)
	{
		FieldInfo[] fields = typeof(PinData).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (!(fieldInfo.FieldType != typeof(RectTransform)))
			{
				object? value = fieldInfo.GetValue(mmPin);
				RectTransform val = (RectTransform)((value is RectTransform) ? value : null);
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)val).gameObject);
				}
				break;
			}
		}
		mmPins.Remove(mmPin);
		object value2 = Traverse.Create((object)mmPin).Field("m_NamePinData").GetValue();
		if (value2 != null)
		{
			object value3 = Traverse.Create(value2).Property("PinNameGameObject", (object[])null).GetValue();
			GameObject val2 = (GameObject)((value3 is GameObject) ? value3 : null);
			if ((Object)(object)val2 != (Object)null)
			{
				Object.Destroy((Object)(object)val2);
			}
		}
	}

	private static PinData GetMapPin(SharedPin needle)
	{
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: 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_0099: Unknown result type (might be due to invalid IL or missing references)
		if (!(Traverse.Create((object)MinimapProxy.Instance).Field("m_pins").GetValue() is List<PinData> list))
		{
			return null;
		}
		foreach (PinData item in list)
		{
			if (MapStateRepository.ArePinsEqual(needle, item))
			{
				return item;
			}
		}
		if (needle.OwnerId == "auto")
		{
			foreach (PinData item2 in list)
			{
				if (needle.Type == item2.m_type && Utils.DistanceXZ(needle.Pos, item2.m_pos) < 1f)
				{
					return item2;
				}
			}
		}
		return null;
	}

	private static SharedPin GetClientPin(PinData needle)
	{
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		foreach (SharedPin clientPin in MapStateRepository.ClientPins)
		{
			if (MapStateRepository.ArePinsEqual(clientPin, needle))
			{
				return clientPin;
			}
		}
		foreach (SharedPin clientPin2 in MapStateRepository.ClientPins)
		{
			if (clientPin2.OwnerId == "auto" && clientPin2.Type == needle.m_type && Utils.DistanceXZ(clientPin2.Pos, needle.m_pos) < 1f)
			{
				return clientPin2;
			}
		}
		return null;
	}
}
[BepInPlugin("drummercraig.one_map_to_rule_them_all", "One Map To Rule Them All", "1.1.0")]
public class OneMapToRuleThemAllPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private class ZNetPatchOnNewConnection
	{
		private static void Postfix(ZNetPeer peer, ZNet __instance)
		{
			if (!__instance.IsServer())
			{
				ModSettings.ModActive = true;
			}
			if (ModSettings.ModActive)
			{
				if (__instance.IsServer())
				{
					peer.m_rpc.Register<int, int>("OM_ClientExplore", (Action<ZRpc, int, int>)ExplorationSynchronizer.OnClientExplore);
					peer.m_rpc.Register<ZPackage>("OM_ClientExploreBatch", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnClientExploreBatch);
					peer.m_rpc.Register<ZPackage>("OM_ClientInitialMap", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnClientInitialMap);
					peer.m_rpc.Register<ZPackage>("OM_ClientRequestInitialMap", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnClientRequestInitialMap);
					peer.m_rpc.Register<ZPackage>("OM_ClientAddPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnClientAddPin);
					peer.m_rpc.Register<ZPackage>("OM_ClientRemovePin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnClientRemovePin);
					peer.m_rpc.Register<ZPackage>("OM_ClientCheckPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnClientCheckPin);
					peer.m_rpc.Register<ZPackage>("OM_AdminClearArea", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminClearArea);
					peer.m_rpc.Register<ZPackage>("OM_AdminClearAll", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminClearAll);
					peer.m_rpc.Register<ZPackage>("OM_AdminRenameAbbr", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminRenameAbbr);
					peer.m_rpc.Register<ZPackage>("OM_AdminUpdateDiscoveryConfig", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminUpdateDiscoveryConfig);
				}
				else
				{
					peer.m_rpc.Register<ZPackage>("OM_ServerMapData", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnServerMapData);
					peer.m_rpc.Register<ZPackage>("OM_ServerMapInitial", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnServerMapInitial);
					peer.m_rpc.Register<ZPackage>("OM_ServerInitialPins", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerInitialPins);
					peer.m_rpc.Register<ZPackage>("OM_ServerAddPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerAddPin);
					peer.m_rpc.Register<ZPackage>("OM_ServerRemovePin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerRemovePin);
					peer.m_rpc.Register<ZPackage>("OM_ServerCheckPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerCheckPin);
					peer.m_rpc.Register<ZPackage>("OM_ServerDiscoveryConfig", (Action<ZRpc, ZPackage>)AutoPinConfig.OnServerDiscoveryConfig);
					peer.m_rpc.Register<ZPackage>("OM_ServerRadarConfig", (Action<ZRpc, ZPackage>)RadarConfig.OnServerRadarConfig);
				}
			}
		}
	}

	private void Awake()
	{
		ModSettings.Log = ((BaseUnityPlugin)this).Logger;
		ConfigMigration.MigrateIfNeeded(((BaseUnityPlugin)this).Config);
		Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
		WorldObjectConfig.PrepareCreatureData();
		ModSettings.ClientResetToDefaults = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "ResetToDefaults", false, "Set to true to reset ALL client settings to their default values. Resets itself to false automatically.");
		ModSettings.ClientResetToDefaults.SettingChanged += delegate
		{
			if (!ModSettings.ClientResetToDefaults.Value)
			{
				return;
			}
			foreach (ConfigDefinition key in ((BaseUnityPlugin)this).Config.Keys)
			{
				if (key.Section.Equals("Client", StringComparison.OrdinalIgnoreCase) || key.Section.StartsWith("Client.", StringComparison.OrdinalIgnoreCase))
				{
					((BaseUnityPlugin)this).Config[key].BoxedValue = ((BaseUnityPlugin)this).Config[key].DefaultValue;
				}
			}
		};
		ModSettings.ShowOtherPlayerPins = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "ShowOtherPlayerPins", true, "Show map pins created by other players on your minimap.");
		ModSettings.ExploreFlushInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "ExploreFlushInterval", 1f, "How often (seconds) batched exploration cells are sent to the server. Range: 0.1–5.0.");
		ModSettings.ExploreFlushInterval.SettingChanged += delegate
		{
			ModSettings.ActiveExploreFlushInterval = Mathf.Clamp(ModSettings.ExploreFlushInterval.Value, 0.1f, 5f);
		};
		ModSettings.ActiveExploreFlushInterval = Mathf.Clamp(ModSettings.ExploreFlushInterval.Value, 0.1f, 5f);
		ModSettings.UpdateThrottleInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "UpdateThrottleInterval", 0.25f, "How often (seconds) proximity and radar watchers check for nearby objects. Range: 0.05–1.0.");
		ModSettings.UpdateThrottleInterval.SettingChanged += delegate
		{
			ModSettings.ActiveUpdateThrottleInterval = Mathf.Clamp(ModSettings.UpdateThrottleInterval.Value, 0.05f, 1f);
		};
		ModSettings.ActiveUpdateThrottleInterval = Mathf.Clamp(ModSettings.UpdateThrottleInterval.Value, 0.05f, 1f);
		ModSettings.DiscoveryIconInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "DiscoveryIconInterval", 0f, "How often (seconds) discovery pin icons are reapplied. 0 = every frame (default). Max 0.1s.");
		ModSettings.DiscoveryIconInterval.SettingChanged += delegate
		{
			ModSettings.ActiveDiscoveryIconInterval = Mathf.Clamp(ModSettings.DiscoveryIconInterval.Value, 0f, 0.1f);
		};
		ModSettings.ActiveDiscoveryIconInterval = Mathf.Clamp(ModSettings.DiscoveryIconInterval.Value, 0f, 0.1f);
		AutoPinConfig.BindClientEntries(((BaseUnityPlugin)this).Config);
		RadarConfig.BindClientEntry(((BaseUnityPlugin)this).Config);
		WorldObjectConfig.BindVisibilityEntries(((BaseUnityPlugin)this).Config);
		ModSettings.ResetToDefaults = ((BaseUnityPlugin)this).Config.Bind<bool>("Server.General", "ResetToDefaults", false, "Set to true to reset ALL server settings to their default values. Resets itself to false automatically.");
		ModSettings.ResetToDefaults.SettingChanged += delegate
		{
			if (!ModSettings.ResetToDefaults.Value)
			{
				return;
			}
			foreach (ConfigDefinition key2 in ((BaseUnityPlugin)this).Config.Keys)
			{
				if (key2.Section.StartsWith("Server.", StringComparison.OrdinalIgnoreCase))
				{
					((BaseUnityPlugin)this).Config[key2].BoxedValue = ((BaseUnityPlugin)this).Config[key2].DefaultValue;
				}
			}
		};
		WorldObjectConfig.BindCategoryEntries(((BaseUnityPlugin)this).Config);
		CustomPrefabLoader.LoadAll(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Config);
		AutoPinConfig.BindServerEntries(((BaseUnityPlugin)this).Config);
		RadarConfig.BindServerEntries(((BaseUnityPlugin)this).Config);
		WorldObjectConfig.WireEvents(((BaseUnityPlugin)this).Config);
		AutoPinConfig.WireEvents(((BaseUnityPlugin)this).Config);
		RadarConfig.WireEvents(((BaseUnityPlugin)this).Config);
		WorldObjectConfig.DiscoveryVisibilityChanged += MapPinSynchronizer.AppendPins;
		AutoPinConfig.DiscoveryPinObjectIcon.SettingChanged += delegate
		{
			MapPinSynchronizer.AppendPins();
		};
		AutoPinConfig.DiscoveryPinText.SettingChanged += delegate
		{
			MapPinSynchronizer.AppendPins();
		};
		ModSettings.ShowOtherPlayerPins.SettingChanged += delegate
		{
			MapPinSynchronizer.AppendPins();
		};
		AutoPinConfig.Reload();
	}
}
public static class MapFilePersistence
{
	[HarmonyPatch(typeof(ZNet), "LoadWorld")]
	private class ZNetPatchLoadWorld
	{
		private static void Postfix(ZNet __instance)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			object value = Traverse.Create((object)__instance).Field("m_world").GetValue();
			World val = (World)((value is World) ? value : null);
			if (val == null)
			{
				ManualLogSource log = ModSettings.Log;
				if (log != null)
				{
					log.LogWarning((object)"[MapFilePersistence] LoadWorld — m_world is null, cannot determine save path.");
				}
				return;
			}
			string dBPath = val.GetDBPath();
			string directoryName = Path.GetDirectoryName(dBPath);
			string text = ((string.IsNullOrEmpty(directoryName) || !Directory.Exists(directoryName)) ? Path.ChangeExtension(Utils.GetSaveDataPath((FileSource)1) + dBPath, null) : Path.ChangeExtension(dBPath, null));
			string text2 = (_savePath = text + ".one_map_to_rule_them_all.explored");
			ManualLogSource log2 = ModSettings.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)$"[MapFilePersistence] LoadWorld — savePath={text2} exists={File.Exists(text2)}");
			}
			if (File.Exists(text2))
			{
				try
				{
					MapStateRepository.SetMapData(new ZPackage(File.ReadAllBytes(text2)));
					ManualLogSource log3 = ModSettings.Log;
					if (log3 != null)
					{
						log3.LogInfo((object)$"[MapFilePersistence] Loaded {MapStateRepository.GetPins().Count} pins from save file.");
					}
					return;
				}
				catch (Exception ex)
				{
					ManualLogSource log4 = ModSettings.Log;
					if (log4 != null)
					{
						log4.LogWarning((object)("[MapFilePersistence] Failed to read save file: " + ex.Message));
					}
				}
			}
			ZPackage val2 = TryMigrateFromServerSideMap(text);
			if (val2 != null)
			{
				MapStateRepository.SetMapData(val2);
				File.WriteAllBytes(text2, MapStateRepository.GetMapData().GetArray());
			}
			else
			{
				MapStateRepository.SetMapData(MapStateRepository.Default());
			}
		}
	}

	[HarmonyPatch(typeof(ZNet), "SaveWorldThread")]
	private class ZNetPatchSaveWorldThread
	{
		private static void Postfix()
		{
			SaveMapData();
		}
	}

	[HarmonyPatch(typeof(ZNet), "Shutdown")]
	private class ZNetPatchShutdownSave
	{
		private static void Prefix()
		{
			ManualLogSource log = ModSettings.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[MapFilePersistence] ZNet.Shutdown Prefix — ServerPins={MapStateRepository.GetPins().Count} savePath={_savePath}");
			}
			SaveMapData();
		}
	}

	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	private class ZNetPatchOnDestroySave
	{
		private static void Prefix()
		{
			ManualLogSource log = ModSettings.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[MapFilePersistence] ZNet.OnDestroy Prefix — ServerPins={MapStateRepository.GetPins().Count} savePath={_savePath}");
			}
			SaveMapData();
		}
	}

	private static string _savePath;

	public static bool MapDirty;

	private static void SaveMapData()
	{
		if (MapStateRepository.Explored == null)
		{
			ManualLogSource log = ModSettings.Log;
			if (log != null)
			{
				log.LogWarning((object)"[MapFilePersistence] SaveMapData skipped — Explored is null.");
			}
		}
		else if (_savePath == null)
		{
			ManualLogSource log2 = ModSettings.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)"[MapFilePersistence] SaveMapData skipped — _savePath is null.");
			}
		}
		else
		{
			if (!MapDirty)
			{
				return;
			}
			MapDirty = false;
			try
			{
				List<SharedPin> pins = MapStateRepository.GetPins();
				byte[] array = MapStateRepository.GetMapData().GetArray();
				File.WriteAllBytes(_savePath, array);
				ManualLogSource log3 = ModSettings.Log;
				if (log3 != null)
				{
					log3.LogInfo((object)$"[MapFilePersistence] Saved {pins.Count} pins ({array.Length} bytes) to {_savePath}");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log4 = ModSettings.Log;
				if (log4 != null)
				{
					log4.LogError((object)("[MapFilePersistence] Save failed: " + ex.Message));
				}
			}
		}
	}

	private static ZPackage TryMigrateFromServerSideMap(string basePath)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Expected O, but got Unknown
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Expected O, but got Unknown
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		string path = basePath + ".mod.serversidemap.explored";
		if (!File.Exists(path))
		{
			return null;
		}
		try
		{
			ZPackage val = new ZPackage(File.ReadAllBytes(path));
			val.SetPos(0);
			val.ReadInt();
			int num = val.ReadInt();
			bool[] array = new bool[num * num];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = val.ReadBool();
			}
			int num2 = val.ReadInt();
			string[] array2 = new string[num2];
			Vector3[] array3 = (Vector3[])(object)new Vector3[num2];
			int[] array4 = new int[num2];
			bool[] array5 = new bool[num2];
			for (int j = 0; j < num2; j++)
			{
				array2[j] = val.ReadString();
				array3[j] = val.ReadVector3();
				array4[j] = val.ReadInt();
				array5[j] = val.ReadBool();
			}
			string text = ZNet.GetUID().ToString();
			ZPackage val2 = new ZPackage();
			val2.Write(1);
			val2.Write(num);
			bool[] array6 = array;
			foreach (bool flag in array6)
			{
				val2.Write(flag);
			}
			val2.Write(num2);
			for (int l = 0; l < num2; l++)
			{
				val2.Write(array2[l]);
				val2.Write(array3[l]);
				val2.Write(array4[l]);
				val2.Write(array5[l]);
				val2.Write(text);
			}
			val2.SetPos(0);
			return val2;
		}
		catch
		{
			return null;
		}
	}
}
public static class ModSettings
{
	public static ConfigEntry<bool> ResetToDefaults;

	public static ConfigEntry<bool> ClientResetToDefaults;

	public static ConfigEntry<bool> ShowOtherPlayerPins;

	public static ConfigEntry<float> ExploreFlushInterval;

	public static float ActiveExploreFlushInterval = 1f;

	public static ConfigEntry<float> UpdateThrottleInterval;

	public static float ActiveUpdateThrottleInterval = 0.25f;

	public static ConfigEntry<float> DiscoveryIconInterval;

	public static float ActiveDiscoveryIconInterval = 0f;

	public static bool ModActive = true;

	public static ManualLogSource Log;

	public static bool IsServer()
	{
		return ZNetProxy.IsServer(ZNetProxy.ZNetInstance);
	}
}
public class BiomeCritterCategory
{
	public string CategoryName;

	public ConfigEntry<float> RadarRadiusEntry;

	public List<(string MatchKeyword, string DisplayName, string IconName)> CreatureDefinitions = new List<(string, string, string)>();

	public float ActiveRadarRadius;

	public List<(string InternalId, string DisplayName)> ActiveOrderedCreatures = new List<(string, string)>();

	public string[] Keywords = Array.Empty<string>();
}
public class PickableCategory
{
	public string CategoryName;

	public ConfigEntry<float> DiscoveryRadiusEntry;

	public ConfigEntry<float> RadarRadiusEntry;

	public ConfigEntry<string> TrackedDiscoveryEntry;

	public ConfigEntry<string> TrackedRadarEntry;

	public float ActiveDiscoveryRadius;

	public float ActiveRadarRadius;

	public string[] DiscoveryKeywords = Array.Empty<string>();

	public string[] RadarKeywords = Array.Empty<string>();
}
public static class WorldObjectConfig
{
	public static readonly List<PickableCategory> PickableCategories = new List<PickableCategory>();

	public static ConfigEntry<float> OresDiscoveryRadius;

	public static ConfigEntry<float> OresRadarRadius;

	public static ConfigEntry<string> TrackedOresDiscovery;

	public static ConfigEntry<string> TrackedOresRadar;

	public static ConfigEntry<float> LocationsDiscoveryRadius;

	public static ConfigEntry<float> LocationsRadarRadius;

	public static ConfigEntry<string> TrackedLocationsDiscovery;

	public static ConfigEntry<string> TrackedLocationsRadar;

	public static readonly List<BiomeCritterCategory> BiomeCritterCategories = new List<BiomeCritterCategory>();

	public static float ActiveOresDiscoveryRadius;

	public static float ActiveOresRadarRadius;

	public static float ActiveLocationsDiscoveryRadius;

	public static float ActiveLocationsRadarRadius;

	public static string[] PickablesDiscovery = Array.Empty<string>();

	public static string[] PickablesRadar = Array.Empty<string>();

	public static string[] OresDiscovery = Array.Empty<string>();

	public static string[] OresRadar = Array.Empty<string>();

	public static string[] LocationsDiscovery = Array.Empty<string>();

	public static string[] LocationsRadar = Array.Empty<string>();

	public static readonly HashSet<string> DiscoveryKeywordSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	public static readonly HashSet<string> RadarKeywordSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	internal static readonly HashSet<string> CustomOresKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	internal static readonly HashSet<string> CustomLocationsKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, ConfigEntry<bool>> DiscoveryVisibilityEntries = new Dictionary<string, ConfigEntry<bool>>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, ConfigEntry<bool>> RadarVisibilityEntries = new Dictionary<string, ConfigEntry<bool>>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, ConfigEntry<bool>> _sectionToggleAllEntries = new Dictionary<string, ConfigEntry<bool>>(StringComparer.OrdinalIgnoreCase);

	private static ConfigFile _config;

	public static bool UsingServerConfig = false;

	private static readonly (string Biome, string Defaults)[] PickableBiomeDefaults = new(string, string)[8]
	{
		("Meadows", "Raspberry,Dandelion,Flint,Mushroom"),
		("BlackForest", "Blueberry,Thistle,Carrot"),
		("Swamp", "SurtlingCore,Turnip,BogIron"),
		("Mountain", "Crystal,Onion,DragonEgg"),
		("Plains", "Cloudberry,Barley,Flax,Tar"),
		("Ocean", "Chitin"),
		("Mistlands", "JotunPuff,Magecap,Fiddlehead,RoyalJelly"),
		("AshLands", "BlackCore,Sulfur,VoltureEgg,Ashstone,MoltenCore,Charredskull")
	};

	private static readonly string[] DefaultOreKeywords = new string[9] { "Copper", "Tin", "Iron", "Silver", "Obsidian", "BlackMarble", "Meteorite", "Flametal", "Stone" };

	private static readonly string[] DefaultLocationKeywords = new string[26]
	{
		"Eikthyr", "Hildir", "Runestone", "GDKing", "TrollCave", "Vendor", "Bonemass", "SunkenCrypt", "Crypt", "MudPile",
		"BogWitch", "DragonQueen", "MountainCave", "DrakeNest", "FrostCave", "GoblinKing", "TarPit", "Henge", "GoblinCamp", "WoodFarm",
		"ShipWreck", "SeekerQueen", "InfestedMine", "DvergrTown", "Fader", "GiantSkull"
	};

	public static event Action DiscoveryVisibilityChanged;

	public static event Action RadarVisibilityChanged;

	public static string DeriveMatchKeyword(string iconName)
	{
		if (!iconName.StartsWith("Trophy", StringComparison.OrdinalIgnoreCase))
		{
			return iconName;
		}
		return iconName.Substring(6);
	}

	public static string NormalizeForMatch(string s)
	{
		return s?.Replace("_", "").Replace(" ", "").ToLower() ?? "";
	}

	public static Dictionary<string, string> GetCreatureIconMap()
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			foreach (var creatureDefinition in biomeCritterCategory.CreatureDefinitions)
			{
				string item = creatureDefinition.DisplayName;
				string item2 = creatureDefinition.IconName;
				if (!dictionary.ContainsKey(item))
				{
					dictionary[item] = item2;
				}
			}
		}
		return dictionary;
	}

	public static bool IsKeywordDiscoveryVisible(string keyword)
	{
		if (DiscoveryVisibilityEntries.TryGetValue(keyword, out var value))
		{
			return value.Value;
		}
		return true;
	}

	public static bool IsKeywordRadarVisible(string keyword)
	{
		if (RadarVisibilityEntries.TryGetValue(keyword, out var value))
		{
			return value.Value;
		}
		return true;
	}

	public static (string displayName, BiomeCritterCategory category) MatchCreatureKeyword(string objectName)
	{
		string text = NormalizeForMatch(objectName);
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			foreach (var (s, item) in biomeCritterCategory.ActiveOrderedCreatures)
			{
				if (text.Contains(NormalizeForMatch(s)))
				{
					return (item, biomeCritterCategory);
				}
			}
		}
		return (null, null);
	}

	public static string GetInternalIdForCreature(string displayName)
	{
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			foreach (var activeOrderedCreature in biomeCritterCategory.ActiveOrderedCreatures)
			{
				var (result, _) = activeOrderedCreature;
				if (string.Equals(activeOrderedCreature.DisplayName, displayName, StringComparison.OrdinalIgnoreCase))
				{
					return result;
				}
			}
		}
		return displayName;
	}

	public static float GetPickableDiscoveryRadius(string keyword)
	{
		if (keyword == null)
		{
			return 6f;
		}
		foreach (PickableCategory pickableCategory in PickableCategories)
		{
			string[] discoveryKeywords = pickableCategory.DiscoveryKeywords;
			for (int i = 0; i < discoveryKeywords.Length; i++)
			{
				if (string.Equals(discoveryKeywords[i], keyword, StringComparison.OrdinalIgnoreCase))
				{
					return pickableCategory.ActiveDiscoveryRadius;
				}
			}
			discoveryKeywords = pickableCategory.RadarKeywords;
			for (int i = 0; i < discoveryKeywords.Length; i++)
			{
				if (string.Equals(discoveryKeywords[i], keyword, StringComparison.OrdinalIgnoreCase))
				{
					return pickableCategory.ActiveDiscoveryRadius;
				}
			}
		}
		return 6f;
	}

	public static void EnsureVisibilityEntries()
	{
		string[] discoveryKeywords;
		foreach (PickableCategory pickableCategory in PickableCategories)
		{
			string section = "Client.MapPin.Pickables." + pickableCategory.CategoryName;
			string section2 = "Client.Radar.Pickables." + pickableCategory.CategoryName;
			EnsureToggleAllEntry(section);
			EnsureToggleAllEntry(section2);
			discoveryKeywords = pickableCategory.DiscoveryKeywords;
			for (int i = 0; i < discoveryKeywords.Length; i++)
			{
				EnsureDiscoveryVisibilityEntry(discoveryKeywords[i], section);
			}
			discoveryKeywords = pickableCategory.RadarKeywords;
			for (int i = 0; i < discoveryKeywords.Length; i++)
			{
				EnsureRadarVisibilityEntry(discoveryKeywords[i], section2);
			}
		}
		EnsureToggleAllEntry("Client.MapPin.Ores");
		EnsureToggleAllEntry("Client.Radar.Ores");
		EnsureToggleAllEntry("Client.MapPin.Locations");
		EnsureToggleAllEntry("Client.Radar.Locations");
		discoveryKeywords = OresDiscovery;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureDiscoveryVisibilityEntry(discoveryKeywords[i], "Client.MapPin.Ores");
		}
		discoveryKeywords = OresRadar;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureRadarVisibilityEntry(discoveryKeywords[i], "Client.Radar.Ores");
		}
		discoveryKeywords = LocationsDiscovery;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureDiscoveryVisibilityEntry(discoveryKeywords[i], "Client.MapPin.Locations");
		}
		discoveryKeywords = LocationsRadar;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureRadarVisibilityEntry(discoveryKeywords[i], "Client.Radar.Locations");
		}
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			string section3 = "Client.Radar.Critters." + biomeCritterCategory.CategoryName.Replace(" ", "_");
			EnsureToggleAllEntry(section3);
			discoveryKeywords = biomeCritterCategory.Keywords;
			for (int i = 0; i < discoveryKeywords.Length; i++)
			{
				EnsureRadarVisibilityEntry(discoveryKeywords[i], section3);
			}
		}
	}

	private static void BindToggleAll(ConfigFile config, string section)
	{
		if (!_sectionToggleAllEntries.ContainsKey(section))
		{
			_sectionToggleAllEntries[section] = config.Bind<bool>(section, "_ToggleAll", false, "Set all entries in this section on or off at once.");
		}
	}

	private static void EnsureToggleAllEntry(string section)
	{
		if (!_sectionToggleAllEntries.ContainsKey(section))
		{
			BindToggleAll(_config, section);
		}
	}

	private static void EnsureDiscoveryVisibilityEntry(string keyword, string section)
	{
		if (!DiscoveryVisibilityEntries.ContainsKey(keyword))
		{
			DiscoveryVisibilityEntries[keyword] = _config.Bind<bool>(section, keyword, false, "Show '" + keyword + "' discovery pins on your minimap. Pins are still created and shared; this only controls local visibility.");
		}
	}

	private static void EnsureRadarVisibilityEntry(string keyword, string section)
	{
		if (!RadarVisibilityEntries.ContainsKey(keyword))
		{
			RadarVisibilityEntries[keyword] = _config.Bind<bool>(section, keyword, false, "Show '" + keyword + "' radar pins on your minimap. Client-only — never overridden by the server.");
		}
	}

	public static void PrepareCreatureData()
	{
		string path = Path.Combine(Paths.ConfigPath, "OneMapToRuleThemAll.creatures.txt");
		if (!File.Exists(path))
		{
			WriteDefaultCreaturesFile(path);
		}
		LoadCreaturesFile(path);
	}

	public static void BindVisibilityEntries(ConfigFile clientConfig)
	{
		_config = clientConfig;
		(string, string)[] pickableBiomeDefaults = PickableBiomeDefaults;
		string[] array;
		for (int i = 0; i < pickableBiomeDefaults.Length; i++)
		{
			(string, string) tuple = pickableBiomeDefaults[i];
			string item = tuple.Item1;
			string item2 = tuple.Item2;
			string text = "Client.MapPin.Pickables." + item;
			string text2 = "Client.Radar.Pickables." + item;
			BindToggleAll(clientConfig, text);
			BindToggleAll(clientConfig, text2);
			array = AutoPinConfig.ParseKeywordList(item2);
			foreach (string text3 in array)
			{
				DiscoveryVisibilityEntries[text3] = clientConfig.Bind<bool>(text, text3, false, "Show '" + text3 + "' discovery pins on your minimap. Pins are still created and shared; this only controls local visibility.");
				RadarVisibilityEntries[text3] = clientConfig.Bind<bool>(text2, text3, false, "Show '" + text3 + "' radar pins on your minimap. Client-only — never overridden by the server.");
			}
		}
		BindToggleAll(clientConfig, "Client.MapPin.Ores");
		BindToggleAll(clientConfig, "Client.Radar.Ores");
		array = DefaultOreKeywords;
		foreach (string text4 in array)
		{
			DiscoveryVisibilityEntries[text4] = clientConfig.Bind<bool>("Client.MapPin.Ores", text4, false, "Show '" + text4 + "' discovery pins on your minimap. Pins are still created and shared; this only controls local visibility.");
			RadarVisibilityEntries[text4] = clientConfig.Bind<bool>("Client.Radar.Ores", text4, false, "Show '" + text4 + "' radar pins on your minimap. Client-only — never overridden by the server.");
		}
		BindToggleAll(clientConfig, "Client.MapPin.Locations");
		BindToggleAll(clientConfig, "Client.Radar.Locations");
		array = DefaultLocationKeywords;
		foreach (string text5 in array)
		{
			DiscoveryVisibilityEntries[text5] = clientConfig.Bind<bool>("Client.MapPin.Locations", text5, false, "Show '" + text5 + "' discovery pins on your minimap. Pins are still created and shared; this only controls local visibility.");
			RadarVisibilityEntries[text5] = clientConfig.Bind<bool>("Client.Radar.Locations", text5, false, "Show '" + text5 + "' radar pins on your minimap. Client-only — never overridden by the server.");
		}
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			string text6 = "Client.Radar.Critters." + biomeCritterCategory.CategoryName.Replace(" ", "_");
			BindToggleAll(clientConfig, text6);
			foreach (var creatureDefinition in biomeCritterCategory.CreatureDefinitions)
			{
				string item3 = creatureDefinition.DisplayName;
				RadarVisibilityEntries[item3] = clientConfig.Bind<bool>(text6, item3, false, "Show '" + item3 + "' radar pins on your minimap. Client-only — never overridden by the server.");
			}
		}
	}

	public static void BindCategoryEntries(ConfigFile serverConfig)
	{
		PickableCategories.Clear();
		(string, string)[] pickableBiomeDefaults = PickableBiomeDefaults;
		for (int i = 0; i < pickableBiomeDefaults.Length; i++)
		{
			(string, string) tuple = pickableBiomeDefaults[i];
			string item = tuple.Item1;
			string item2 = tuple.Item2;
			string text = "Server.Pickables." + item;
			PickableCategory pickableCategory = new PickableCategory
			{
				CategoryName = item
			};
			pickableCategory.DiscoveryRadiusEntry = serverConfig.Bind<float>(text, "DiscoveryRadius", 6f, "How close (meters) the player must approach a " + item + " pickable to trigger a discovery pin. Maximum 150.");
			pickableCategory.RadarRadiusEntry = serverConfig.Bind<float>(text, "RadarRadius", 50f, "How close (meters) the player must be to a " + item + " pickable for its radar pin to appear. Maximum 150.");
			pickableCategory.TrackedDiscoveryEntry = serverConfig.Bind<string>(text, "TrackedDiscovery", item2, "Comma-separated keywords for " + item + " pickables that trigger a permanent discovery pin.");
			pickableCategory.TrackedRadarEntry = serverConfig.Bind<string>(text, "TrackedRadar", item2, "Comma-separated keywords for " + item + " pickables that appear as radar (transient proximity) pins.");
			pickableCategory.ActiveDiscoveryRadius = pickableCategory.DiscoveryRadiusEntry.Value;
			pickableCategory.ActiveRadarRadius = pickableCategory.RadarRadiusEntry.Value;
			pickableCategory.DiscoveryKeywords = AutoPinConfig.ParseKeywordList(pickableCategory.TrackedDiscoveryEntry.Value);
			pickableCategory.RadarKeywords = AutoPinConfig.ParseKeywordList(pickableCategory.TrackedRadarEntry.Value);
			PickableCategories.Add(pickableCategory);
		}
		OresDiscoveryRadius = serverConfig.Bind<float>("Server.Ores", "DiscoveryRadius", 6f, "How close (meters) the player must approach an ore deposit to trigger a discovery pin. Maximum 150.");
		OresRadarRadius = serverConfig.Bind<float>("Server.Ores", "RadarRadius", 50f, "How close (meters) the player must be to an ore deposit for its radar pin to appear. Maximum 150.");
		TrackedOresDiscovery = serverConfig.Bind<string>("Server.Ores", "TrackedDiscovery", "Copper,Tin,Iron,Silver,Obsidian,BlackMarble,Meteorite,Flametal,Stone", "Comma-separated keywords for ore deposits that trigger a permanent discovery pin.");
		TrackedOresRadar = serverConfig.Bind<string>("Server.Ores", "TrackedRadar", "Copper,Tin,Iron,Silver,Obsidian,BlackMarble,Meteorite,Flametal,Stone", "Comma-separated keywords for ore deposits that appear as radar pins.");
		LocationsDiscoveryRadius = serverConfig.Bind<float>("Server.Locations", "DiscoveryRadius", 6f, "How close (meters) the player must approach a location to trigger a discovery pin. Maximum 150.");
		LocationsRadarRadius = serverConfig.Bind<float>("Server.Locations", "RadarRadius", 50f, "How close (meters) the player must be to a location for its radar pin to appear. Maximum 150.");
		TrackedLocationsDiscovery = serverConfig.Bind<string>("Server.Locations", "TrackedDiscovery", "Eikthyr,Hildir,Runestone,GDKing,TrollCave,Vendor,Bonemass,SunkenCrypt,Crypt,MudPile,BogWitch,DragonQueen,MountainCave,DrakeNest,FrostCave,GoblinKing,TarPit,Henge,GoblinCamp,WoodFarm,ShipWreck,SeekerQueen,InfestedMine,DvergrTown,Fader,GiantSkull", "Comma-separated keywords for locations that trigger a permanent discovery pin.");
		TrackedLocationsRadar = serverConfig.Bind<string>("Server.Locations", "TrackedRadar", "Eikthyr,Hildir,Runestone,GDKing,TrollCave,Vendor,Bonemass,SunkenCrypt,Crypt,MudPile,BogWitch,DragonQueen,MountainCave,DrakeNest,FrostCave,GoblinKing,TarPit,Henge,GoblinCamp,WoodFarm,ShipWreck,SeekerQueen,InfestedMine,DvergrTown,Fader,GiantSkull", "Comma-separated keywords for locations that appear as radar pins.");
		Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase)
		{
			{ "Meadows", 50f },
			{ "BlackForest", 50f },
			{ "Swamp", 50f },
			{ "Mountain", 50f },
			{ "Plains", 50f },
			{ "Mistlands", 50f },
			{ "AshLands", 50f },
			{ "Ocean", 50f },
			{ "Bosses", 50f },
			{ "Minibosses", 50f },
			{ "General", 50f }
		};
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			string text2 = "Server.Critters." + biomeCritterCategory.CategoryName.Replace(" ", "_");
			float value;
			float num = (dictionary.TryGetValue(biomeCritterCategory.CategoryName, out value) ? value : 40f);
			biomeCritterCategory.RadarRadiusEntry = serverConfig.Bind<float>(text2, "RadarRadius", num, "Detection radius (meters) for " + biomeCritterCategory.CategoryName + " creatures to show as radar pins. Maximum 150.");
			biomeCritterCategory.ActiveRadarRadius = biomeCritterCategory.RadarRadiusEntry.Value;
			RebuildBiomeCreatureState(biomeCritterCategory);
		}
	}

	public static void WireEvents(ConfigFile config)
	{
		config.SettingChanged += delegate(object _, SettingChangedEventArgs e)
		{
			string section = e.ChangedSetting.Definition.Section;
			if (e.ChangedSetting.Definition.Key == "_ToggleAll")
			{
				bool value = (bool)e.ChangedSetting.BoxedValue;
				foreach (ConfigEntry<bool> value2 in DiscoveryVisibilityEntries.Values)
				{
					if (((ConfigEntryBase)value2).Definition.Section == section)
					{
						value2.Value = value;
					}
				}
				{
					foreach (ConfigEntry<bool> value3 in RadarVisibilityEntries.Values)
					{
						if (((ConfigEntryBase)value3).Definition.Section == section)
						{
							value3.Value = value;
						}
					}
					return;
				}
			}
			if (section.StartsWith("Client.MapPin."))
			{
				WorldObjectConfig.DiscoveryVisibilityChanged?.Invoke();
			}
			if (section.StartsWith("Client.Radar."))
			{
				WorldObjectConfig.RadarVisibilityChanged?.Invoke();
			}
		};
	}

	public static void Reload()
	{
		if (OresDiscoveryRadius.Value > 150f)
		{
			OresDiscoveryRadius.Value = 150f;
		}
		if (OresRadarRadius.Value > 150f)
		{
			OresRadarRadius.Value = 150f;
		}
		if (LocationsDiscoveryRadius.Value > 150f)
		{
			LocationsDiscoveryRadius.Value = 150f;
		}
		if (LocationsRadarRadius.Value > 150f)
		{
			LocationsRadarRadius.Value = 150f;
		}
		foreach (PickableCategory pickableCategory in PickableCategories)
		{
			if (pickableCategory.DiscoveryRadiusEntry != null)
			{
				if (pickableCategory.DiscoveryRadiusEntry.Value > 150f)
				{
					pickableCategory.DiscoveryRadiusEntry.Value = 150f;
				}
				if (!UsingServerConfig)
				{
					pickableCategory.ActiveDiscoveryRadius = pickableCategory.DiscoveryRadiusEntry.Value;
				}
			}
			if (pickableCategory.RadarRadiusEntry != null)
			{
				if (pickableCategory.RadarRadiusEntry.Value > 150f)
				{
					pickableCategory.RadarRadiusEntry.Value = 150f;
				}
				if (!UsingServerConfig)
				{
					pickableCategory.ActiveRadarRadius = pickableCategory.RadarRadiusEntry.Value;
				}
			}
			if (!UsingServerConfig)
			{
				if (pickableCategory.TrackedDiscoveryEntry != null)
				{
					pickableCategory.DiscoveryKeywords = AutoPinConfig.ParseKeywordList(pickableCategory.TrackedDiscoveryEntry.Value);
				}
				if (pickableCategory.TrackedRadarEntry != null)
				{
					pickableCategory.RadarKeywords = AutoPinConfig.ParseKeywordList(pickableCategory.TrackedRadarEntry.Value);
				}
			}
		}
		PickablesDiscovery = PickableCategories.SelectMany((PickableCategory c) => c.DiscoveryKeywords).ToArray();
		PickablesRadar = PickableCategories.SelectMany((PickableCategory c) => c.RadarKeywords).ToArray();
		if (!UsingServerConfig)
		{
			ActiveOresDiscoveryRadius = OresDiscoveryRadius.Value;
			ActiveOresRadarRadius = OresRadarRadius.Value;
			OresDiscovery = AutoPinConfig.ParseKeywordList(TrackedOresDiscovery.Value).Concat(CustomOresKeywords).Distinct<string>(StringComparer.OrdinalIgnoreCase)
				.ToArray();
			OresRadar = AutoPinConfig.ParseKeywordList(TrackedOresRadar.Value).Concat(CustomOresKeywords).Distinct<string>(StringComparer.OrdinalIgnoreCase)
				.ToArray();
			ActiveLocationsDiscoveryRadius = LocationsDiscoveryRadius.Value;
			ActiveLocationsRadarRadius = LocationsRadarRadius.Value;
			LocationsDiscovery = AutoPinConfig.ParseKeywordList(TrackedLocationsDiscovery.Value).Concat(CustomLocationsKeywords).Distinct<string>(StringComparer.OrdinalIgnoreCase)
				.ToArray();
			LocationsRadar = AutoPinConfig.ParseKeywordList(TrackedLocationsRadar.Value).Concat(CustomLocationsKeywords).Distinct<string>(StringComparer.OrdinalIgnoreCase)
				.ToArray();
		}
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			if (biomeCritterCategory.RadarRadiusEntry != null)
			{
				if (biomeCritterCategory.RadarRadiusEntry.Value > 150f)
				{
					biomeCritterCategory.RadarRadiusEntry.Value = 150f;
				}
				if (!UsingServerConfig)
				{
					biomeCritterCategory.ActiveRadarRadius = biomeCritterCategory.RadarRadiusEntry.Value;
				}
			}
			if (!UsingServerConfig)
			{
				RebuildBiomeCreatureState(biomeCritterCategory);
			}
		}
		RebuildKeywordSets();
		EnsureVisibilityEntries();
		WorldObjectPatches.ReScanForKeywordChanges();
	}

	public static void ResetToLocalConfig()
	{
		UsingServerConfig = false;
		Reload();
	}

	private static void RebuildBiomeCreatureState(BiomeCritterCategory cat)
	{
		cat.ActiveOrderedCreatures.Clear();
		List<string> list = new List<string>();
		foreach (var (text, text2, _) in cat.CreatureDefinitions)
		{
			if (!string.IsNullOrEmpty(text))
			{
				cat.ActiveOrderedCreatures.Add((text, text2));
				list.Add(text2);
			}
		}
		cat.Keywords = list.ToArray();
	}

	private static void RebuildKeywordSets()
	{
		DiscoveryKeywordSet.Clear();
		RadarKeywordSet.Clear();
		string[] pickablesDiscovery = PickablesDiscovery;
		foreach (string item in pickablesDiscovery)
		{
			DiscoveryKeywordSet.Add(item);
		}
		pickablesDiscovery = OresDiscovery;
		foreach (string item2 in pickablesDiscovery)
		{
			DiscoveryKeywordSet.Add(item2);
		}
		pickablesDiscovery = LocationsDiscovery;
		foreach (string item3 in pickablesDiscovery)
		{
			DiscoveryKeywordSet.Add(item3);
		}
		pickablesDiscovery = PickablesRadar;
		foreach (string item4 in pickablesDiscovery)
		{
			RadarKeywordSet.Add(item4);
		}
		pickablesDiscovery = OresRadar;
		foreach (string item5 in pickablesDiscovery)
		{
			RadarKeywordSet.Add(item5);
		}
		pickablesDiscovery = LocationsRadar;
		foreach (string item6 in pickablesDiscovery)
		{
			RadarKeywordSet.Add(item6);
		}
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			pickablesDiscovery = biomeCritterCategory.Keywords;
			foreach (string item7 in pickablesDiscovery)
			{
				RadarKeywordSet.Add(item7);
			}
		}
	}

	public static void PackCategoryData(ZPackage z)
	{
		z.Write(PickableCategories.Count);
		foreach (PickableCategory pickableCategory in PickableCategories)
		{
			z.Write(pickableCategory.CategoryName);
			z.Write(pickableCategory.DiscoveryRadiusEntry?.Value ?? pickableCategory.ActiveDiscoveryRadius);
			z.Write(pickableCategory.RadarRadiusEntry?.Value ?? pickableCategory.ActiveRadarRadius);
			z.Write(pickableCategory.TrackedDiscoveryEntry?.Value ?? string.Join(",", pickableCategory.DiscoveryKeywords));
			z.Write(pickableCategory.TrackedRadarEntry?.Value ?? string.Join(",", pickableCategory.RadarKeywords));
		}
		z.Write(OresDiscoveryRadius.Value);
		z.Write(OresRadarRadius.Value);
		z.Write(TrackedOresDiscovery.Value);
		z.Write(TrackedOresRadar.Value);
		z.Write(LocationsDiscoveryRadius.Value);
		z.Write(LocationsRadarRadius.Value);
		z.Write(TrackedLocationsDiscovery.Value);
		z.Write(TrackedLocationsRadar.Value);
		z.Write(BiomeCritterCategories.Count);
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			z.Write(biomeCritterCategory.CategoryName);
			z.Write(biomeCritterCategory.RadarRadiusEntry?.Value ?? biomeCritterCategory.ActiveRadarRadius);
			z.Write(biomeCritterCategory.CreatureDefinitions.Count);
			foreach (var (text, text2, text3) in biomeCritterCategory.CreatureDefinitions)
			{
				z.Write(text2);
				z.Write(text);
				z.Write(text3);
			}
		}
	}

	public static void UnpackCategoryDataIntoEntries(ZPackage z)
	{
		int num = z.ReadInt();
		for (int i = 0; i < num; i++)
		{
			string name2 = z.ReadString();
			float value = z.ReadSingle();
			float value2 = z.ReadSingle();
			string value3 = z.ReadString();
			string value4 = z.ReadString();
			PickableCategory pickableCategory = PickableCategories.FirstOrDefault((PickableCategory c) => string.Equals(c.CategoryName, name2, StringComparison.OrdinalIgnoreCase));
			if (pickableCategory != null)
			{
				if (pickableCategory.DiscoveryRadiusEntry != null)
				{
					pickableCategory.DiscoveryRadiusEntry.Value = value;
				}
				if (pickableCategory.RadarRadiusEntry != null)
				{
					pickableCategory.RadarRadiusEntry.Value = value2;
				}
				if (pickableCategory.TrackedDiscoveryEntry != null)
				{
					pickableCategory.TrackedDiscoveryEntry.Value = value3;
				}
				if (pickableCategory.TrackedRadarEntry != null)
				{
					pickableCategory.TrackedRadarEntry.Value = value4;
				}
			}
		}
		OresDiscoveryRadius.Value = z.ReadSingle();
		OresRadarRadius.Value = z.ReadSingle();
		TrackedOresDiscovery.Value = z.ReadString();
		TrackedOresRadar.Value = z.ReadString();
		LocationsDiscoveryRadius.Value = z.ReadSingle();
		LocationsRadarRadius.Value = z.ReadSingle();
		TrackedLocationsDiscovery.Value = z.ReadString();
		TrackedLocationsRadar.Value = z.ReadString();
		int num2 = z.ReadInt();
		for (int j = 0; j < num2; j++)
		{
			string name = z.ReadString();
			float value5 = z.ReadSingle();
			int num3 = z.ReadInt();
			for (int k = 0; k < num3; k++)
			{
				z.ReadString();
				z.ReadString();
				z.ReadString();
			}
			BiomeCritterCategory biomeCritterCategory = BiomeCritterCategories.FirstOrDefault((BiomeCritterCategory c) => string.Equals(c.CategoryName, name, StringComparison.OrdinalIgnoreCase));
			if (biomeCritterCategory?.RadarRadiusEntry != null)
			{
				biomeCritterCategory.RadarRadiusEntry.Value = value5;
			}
		}
	}

	public static void ApplyCategoryData(ZPackage z)
	{
		if (!ModSettings.IsServer())
		{
			UsingServerConfig = true;
		}
		int num = z.ReadInt();
		PickableCategories.Clear();
		for (int i = 0; i < num; i++)
		{
			string categoryName = z.ReadString();
			float activeDiscoveryRadius = z.ReadSingle();
			float activeRadarRadius = z.ReadSingle();
			string csv = z.ReadString();
			string csv2 = z.ReadString();
			PickableCategory item = new PickableCategory
			{
				CategoryName = categoryName,
				ActiveDiscoveryRadius = activeDiscoveryRadius,
				ActiveRadarRadius = activeRadarRadius,
				DiscoveryKeywords = AutoPinConfig.ParseKeywordList(csv),
				RadarKeywords = AutoPinConfig.ParseKeywordList(csv2)
			};
			PickableCategories.Add(item);
		}
		PickablesDiscovery = PickableCategories.SelectMany((PickableCategory c) => c.DiscoveryKeywords).ToArray();
		PickablesRadar = PickableCategories.SelectMany((PickableCategory c) => c.RadarKeywords).ToArray();
		ActiveOresDiscoveryRadius = z.ReadSingle();
		ActiveOresRadarRadius = z.ReadSingle();
		OresDiscovery = AutoPinConfig.ParseKeywordList(z.ReadString());
		OresRadar = AutoPinConfig.ParseKeywordList(z.ReadString());
		ActiveLocationsDiscoveryRadius = z.ReadSingle();
		ActiveLocationsRadarRadius = z.ReadSingle();
		LocationsDiscovery = AutoPinConfig.ParseKeywordList(z.ReadString());
		LocationsRadar = AutoPinConfig.ParseKeywordList(z.ReadString());
		int num2 = z.ReadInt();
		BiomeCritterCategories.Clear();
		for (int j = 0; j < num2; j++)
		{
			string categoryName2 = z.ReadString();
			float activeRadarRadius2 = z.ReadSingle();
			BiomeCritterCategory biomeCritterCategory = new BiomeCritterCategory
			{
				CategoryName = categoryName2,
				ActiveRadarRadius = activeRadarRadius2
			};
			int num3 = z.ReadInt();
			for (int k = 0; k < num3; k++)
			{
				string item2 = z.ReadString();
				string text = z.ReadString();
				string item3 = z.ReadString();
				biomeCritterCategory.CreatureDefinitions.Add((text, item2, item3));
				if (!string.IsNullOrEmpty(text))
				{
					biomeCritterCategory.ActiveOrderedCreatures.Add((text, item2));
				}
			}
			biomeCritterCategory.Keywords = biomeCritterCategory.ActiveOrderedCreatures.Select(((string InternalId, string DisplayName) x) => x.DisplayName).ToArray();
			BiomeCritterCategories.Add(biomeCritterCategory);
		}
		EnsureVisibilityEntries();
		RebuildKeywordSets();
		WorldObjectPatches.ReScanForKeywordChanges();
	}

	private static void LoadCreaturesFile(string path)
	{
		BiomeCritterCategories.Clear();
		Dictionary<string, BiomeCritterCategory> dictionary = new Dictionary<string, BiomeCritterCategory>(StringComparer.OrdinalIgnoreCase);
		List<string> list = new List<string>();
		string[] array = File.ReadAllLines(path);
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim();
			if (string.IsNullOrEmpty(text) || text.StartsWith("#"))
			{
				continue;
			}
			string[] array2 = text.Split(new char[1] { ':' });
			if (array2.Length < 3)
			{
				continue;
			}
			string text2 = array2[0].Trim();
			string text3 = array2[1].Trim();
			string text4 = array2[2].Trim();
			string item = ((array2.Length >= 4) ? array2[3].Trim() : text4);
			if (!string.IsNullOrEmpty(text2) && !string.IsNullOrEmpty(text3) && !string.IsNullOrEmpty(text4))
			{
				string item2 = DeriveMatchKeyword(text4);
				if (!dictionary.TryGetValue(text2, out var value))
				{
					value = (dictionary[text2] = new BiomeCritterCategory
					{
						CategoryName = text2
					});
					list.Add(text2);
				}
				value.CreatureDefinitions.Add((item2, text3, item));
			}
		}
		foreach (string item3 in list)
		{
			BiomeCritterCategories.Add(dictionary[item3]);
		}
	}

	private static void WriteDefaultCreaturesFile(string path)
	{
		File.WriteAllText(path, "# OneMapToRuleThemAll — Creature Definitions\n# Format:  category:displayname:internalname[:iconoverride]\n#\n# internalname  = used to derive the match keyword ('Trophy' prefix stripped if present).\n# iconoverride  = optional 4th field; ObjectDB item name for the icon when the creature\n#                 has no trophy. If omitted, internalname is also used for the icon lookup.\n# Matching is normalized (case-insensitive, underscores/spaces ignored), so\n# 'GreydwarfShaman' matches the game object 'Greydwarf_Shaman'.\n#\n# ORDER MATTERS: more-specific entries must appear before less-specific ones within\n# and across categories. Bosses and Minibosses are listed first so their specific\n# prefab names are checked before generic biome keywords.\n#\n# Radar radius per category is configured in One_Map_To_Rule_Them_All_Settings.cfg\n# under [Critters.<CategoryName>] RadarRadius.\n\n# ── Bosses ────────────────────────────────────────────────────────────────────\nBosses:Eikthyr:TrophyEikthyr\nBosses:The Elder:TrophyTheElder\nBosses:Bonemass:TrophyBonemass\nBosses:Moder:TrophyDragonQueen\nBosses:Yagluth:TrophyGoblinKing\nBosses:The Queen:TrophySeekerQueen\nBosses:Fader:TrophyFader\n\n# ── Minibosses ────────────────────────────────────────────────────────────────\n# Listed before biome categories so specific prefab names match before generic ones.\n# e.g. GoblinBruteBros must match before GoblinBrute (Fuling Berserker).\nMinibosses:Brenna:TrophySkeletonHildir\nMinibosses:Geirrhafa:TrophyCultist_Hildir\nMinibosses:Zil & Thungr:GoblinBruteBros\nMinibosses:Lord Reto:Charred_Melee_Dyrnwyn\n\n# ── Meadows ───────────────────────────────────────────────────────────────────\nMeadows:Deer:TrophyDeer\nMeadows:Boar:TrophyBoar\nMeadows:Neck:TrophyNeck\nMeadows:Greyling:Greyling:TrophyGreydwarf\n\n# ── Black Forest ──────────────────────────────────────────────────────────────\n# Category name 'BlackForest' (no space) matches the existing config section [Critters.BlackForest].\n# Greydwarf shaman must appear before Greydwarf (shaman's keyword is a substring check).\n# Rancid remains (SkeletonPoison) must appear before Skeleton.\nBlackForest:Greydwarf shaman:TrophyGreydwarfShaman\nBlackForest:Greydwarf:TrophyGreydwarf\nBlackForest:Rancid remains:TrophySkeletonPoison\nBlackForest:Skeleton:TrophySkeleton\nBlackForest:Troll:TrophyFrostTroll\nBlackForest:Ghost:TrophyGhost\nBlackForest:Bear:TrophyBjorn\n\n# ── Swamp ─────────────────────────────────────────────────────────────────────\n# Oozer (BlobElite) before Blob. Draugr elite before Draugr.\nSwamp:Abomination:TrophyAbomination\nSwamp:Oozer:BlobElite\nSwamp:Blob:TrophyBlob\nSwamp:Draugr elite:TrophyDraugrElite\nSwamp:Draugr:TrophyDraugr\nSwamp:Leech:TrophyLeech\nSwamp:Leech (cave variant):TrophyLeech\nSwamp:Surtling:TrophySurtling\nSwamp:Wraith:TrophyWraith\nSwamp:Kvastur:BogWitchKvastur:TrophyWraith\n\n# ── Mountain ──────────────────────────────────────────────────────────────────\n# Cultist (Fenring_Cultist) must appear before Fenring.\nMountain:Wolf:TrophyWolf\nMountain:Drake:TrophyHatchling\nMountain:Stone Golem:Golem:TrophySGolem\nMountain:Cultist:TrophyCultist\nMountain:Fenring:TrophyFenring\nMountain:Ulv:TrophyUlv\n\n# ── Plains ────────────────────────────────────────────────────────────────────\n# Fuling Berserker and Fuling shaman must appear before generic Fuling (Goblin).\nPlains:Fuling Berserker:TrophyGoblinBrute\nPlains:Fuling shaman:TrophyGoblinShaman\nPlains:Fuling:TrophyGoblin\nPlains:Deathsquito:TrophyDeathsquito\nPlains:Lox:TrophyLox\nPlains:Growth:TrophyGrowth\nPlains:Vile:TrophyBjornUndead\n\n# ── Mistlands ─────────────────────────────────────────────────────────────────\n# Seeker Soldier and Seeker Brood must appear before generic Seeker.\n# All Dvergr entries derive the same match keyword; first listed wins for all variants.\nMistlands:Seeker Soldier:TrophySeekerBrute\nMistlands:Seeker Brood:SeekerBrood\nMistlands:Seeker:TrophySeeker\nMistlands:Gjall:TrophyGjall\nMistlands:Tick:TrophyTick\nMistlands:Dvergr mage (Fire Variant):TrophyDvergr\nMistlands:Dvergr mage (Ice Variant):TrophyDvergr\nMistlands:Dvergr mage (Support Variant):TrophyDvergr\nMistlands:Dvergr mage:TrophyDvergr\nMistlands:Dvergr rogue:TrophyDvergr\nMistlands:Hare:TrophyHare\n\n# ── Ashlands ──────────────────────────────────────────────────────────────────\n# Category name 'AshLands' matches the existing config section [Critters.AshLands].\n# Specific Charred variants before any generic 'Charred' entry.\nAshLands:Charred Warlock:TrophyCharredMage\nAshLands:Charred Warrior:TrophyCharredMelee\nAshLands:Charred Marksman:TrophyCharredArcher\nAshLands:Charred Twitcher:Charred_Twitcher\nAshLands:Bonemaw:TrophyBonemawSerpent\nAshLands:Lava Blob:BlobLava\nAshLands:Fallen Valkyrie:TrophyFallenValkyrie\nAshLands:Volture:TrophyVolture\nAshLands:Morgen:TrophyMorgen\nAshLands:Asksvin:TrophyAsksvin\nAshLands:Asksvin hatchling:Asksvin_hatchling:TrophyAsksvin\nAshLands:Unbjorn:Unbjorn:TrophyMorgen\n# Skugg: icon is a building piece; if Skugg is not detected, check its in-game prefab name.\nAshLands:Skugg:piece_Charred_Balista\n\n# ── Ocean ─────────────────────────────────────────────────────────────────────\n# Fish10-12 must appear before Fish1 (Fish10 contains 'Fish1' as a substring).\nOcean:Northern salmon:Fish10\nOcean:Magmafish:Fish11\nOcean:Pufferfish:Fish12\nOcean:Perch:Fish1\nOcean:Pike:Fish2\nOcean:Tuna:Fish3\nOcean:Tetra:Fish4_cave\nOcean:Trollfish:Fish5\nOcean:Giant herring:Fish6\nOcean:Grouper:Fish7\nOcean:Coral cod:Fish8\nOcean:Anglerfish:Fish9\nOcean:Serpent:TrophySerpent\nOcean:Leviathan:Leviathan\n\n# ── General ───────────────────────────────────────────────────────────────────\nGeneral:Chicken:Chicken\nGeneral:Hen:Hen\n");
	}
}
public static class AutoPinConfig
{
	public static ConfigEntry<bool> Enabled;

	public static ConfigEntry<float> QuantitySearchRadius;

	public static bool ActiveEnabled;

	public static float ActiveQuantitySearchRadius;

	public static bool UsingServerConfig = false;

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

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

	public static ConfigEntry<bool> DiscoveryPinObjectIcon;

	public static ConfigEntry<bool> DiscoveryPinText;

	private static bool _reloading = false;

	public static void BindClientEntries(ConfigFile clientConfig)
	{
		DiscoveryPinObjectIcon = clientConfig.Bind<bool>("Client", "DiscoveryPinObjectIcon", true, "Replace the standard pin icon on discovery pins with the item sprite from ObjectDB. Client-only — does not affect other players.");
		DiscoveryPinText = clientConfig.Bind<bool>("Client", "DiscoveryPinText", true, "Show the abbreviation label on auto-discovery pins. Set to false to show only the pin icon. Client-only — does not affect other players.");
	}

	public static void BindServerEntries(ConfigFile serverConfig)
	{
		Enabled = serverConfig.Bind<bool>("Server.Discovery", "Enabled", true, "Master switch for the automatic discovery pin system.");
		QuantitySearchRadius = serverConfig.Bind<float>("Server.Discovery", "QuantitySearchRadius", 16f, "Radius (centered on the discovered object) searched for nearby objects of the same type. The count is appended to the pin label, e.g. 'RB 4'. Also defines the cluster boundary — objects within this radius share one pin. Keep larger than the per-category DiscoveryRadius.");
		BindAbbreviations(serverConfig);
	}

	public static void WireEvents(ConfigFile config)
	{
		config.SettingChanged += delegate
		{
			if (WorldObjectConfig.UsingServerConfig)
			{
				PushDiscoveryConfigToServer();
			}
			else
			{
				Reload();
			}
		};
	}

	private static void BindAbbreviations(ConfigFile serverConfig)
	{
		(string, string)[] array = new(string, string)[111]
		{
			("Raspberry", "Rb"),
			("Blueberry", "Bb"),
			("Mushroom", "Mu"),
			("Thistle", "Th"),
			("Cloudberry", "Cb"),
			("Barley", "Br"),
			("Flax", "Fl"),
			("Onion", "On"),
			("Carrot", "Ca"),
			("Turnip", "Tu"),
			("Chitin", "Ch"),
			("Crystal", "Cr"),
			("SurtlingCore", "Su"),
			("BlackCore", "Bc"),
			("JotunPuff", "JP"),
			("Magecap", "Mc"),
			("Dandelion", "Dan"),
			("Flint", "Fli"),
			("BogIron", "BI"),
			("DragonEgg", "DEgg"),
			("Tar", "Tar"),
			("Fiddlehead", "Fern"),
			("RoyalJelly", "RJly"),
			("Sulfur", "Sulf"),
			("VoltureEgg", "VEgg"),
			("Ashstone", "Ash"),
			("MoltenCore", "MCor"),
			("Charredskull", "CSku"),
			("Copper", "Cu"),
			("Tin", "Sn"),
			("Silver", "Ag"),
			("Iron", "Fe"),
			("Obsidian", "Ob"),
			("Flametal", "Fm"),
			("Meteorite", "Met"),
			("BlackMarble", "BkM"),
			("Stone", "Stn"),
			("SunkenCrypt", "SCrypt"),
			("Crypt", "Crypt"),
			("MountainCave", "MCave"),
			("TrollCave", "TCave"),
			("MudPile", "Mud"),
			("Vendor", "Shop"),
			("DrakeNest", "DNest"),
			("TarPit", "Tar"),
			("Henge", "Henge"),
			("Eikthyr", "Eik"),
			("GDKing", "Elder"),
			("Bonemass", "Bone"),
			("DragonQueen", "Moder"),
			("GoblinKing", "Yag"),
			("SeekerQueen", "Queen"),
			("Fader", "Fader"),
			("FrostCave", "FCave"),
			("InfestedMine", "IMine"),
			("Hildir", "Hild"),
			("BogWitch", "Witch"),
			("GoblinCamp", "GCamp"),
			("WoodFarm", "WFarm"),
			("DvergrTown", "Dtown"),
			("ShipWreck", "Ship"),
			("Runestone", "Rune"),
			("GiantSkull", "GSku"),
			("Beehive", "BHive"),
			("Greyling", "Grl"),
			("Boar", "Boar"),
			("Deer", "Deer"),
			("Neck", "Neck"),
			("Chicken", "Hen"),
			("Hare", "Hare"),
			("GreydwarfElite", "GdwE"),
			("GreydwarfShaman", "GdwS"),
			("Greydwarf", "Gdw"),
			("Troll", "Trl"),
			("Skeleton", "Skel"),
			("DraugrElite", "DrE"),
			("Draugr", "Dr"),
			("Leech", "Lch"),
			("Blob", "Blob"),
			("Wraith", "Wra"),
			("Surtling", "Srt"),
			("Abomination", "Abom"),
			("Ghost", "Gst"),
			("Wolf", "Wolf"),
			("Drake", "Drk"),
			("Stone Golem", "Glm"),
			("Fenring", "Fen"),
			("Cultist", "Cult"),
			("Bat", "Bat"),
			("FulingBerserker", "FulB"),
			("FulingShaman", "FulS"),
			("FulingArcher", "FulA"),
			("Fuling", "Ful"),
			("Deathsquito", "Dsq"),
			("Lox", "Lox"),
			("SeekerBrood", "SkrB"),
			("Seeker", "Skr"),
			("Gjall", "Gjl"),
			("Tick", "Tick"),
			("DvergrMage", "DvrM"),
			("Dvergr", "Dvr"),
			("Ulv", "Ulv"),
			("Charred", "Chr"),
			("Morgen", "Mrg"),
			("Asksvin", "Ask"),
			("Volture", "Vlt"),
			("Unbjorn", "Ubj"),
			("Kvastur", "Kvas"),
			("Serpent", "Spt"),
			("Leviathan", "Lev"),
			("Fish", "Fish")
		};
		for (int i = 0; i < array.Length; i++)
		{
			(string, string) tuple = array[i];
			string item = tuple.Item1;
			string item2 = tuple.Item2;
			ConfigEntry<string> value = serverConfig.Bind<string>("Server.Discovery.Abbreviations", item, item2, "Pin-label abbreviation used when a '" + item + "' object is discovered.");
			AbbreviationEntries[item] = value;
		}
	}

	public static void EnsureAbbreviationEntry(string keyword, string suggestedAbbr, ConfigFile serverConfig)
	{
		if (!string.IsNullOrEmpty(keyword) && !AbbreviationEntries.ContainsKey(keyword))
		{
			string text = ((!string.IsNullOrWhiteSpace(suggestedAbbr)) ? suggestedAbbr : ((keyword.Length <= 6) ? keyword : keyword.Substring(0, 6)));
			ConfigEntry<string> val = serverConfig.Bind<string>("Server.Discovery.Abbreviations", keyword, text, "Pin-label abbreviation used when a '" + keyword + "' object is discovered.");
			AbbreviationEntries[keyword] = val;
			Abbreviations[keyword] = val.Value;
		}
	}

	public static string GetKeywordForLabel(string pinLabel)
	{
		foreach (KeyValuePair<string, string> abbreviation in Abbreviations)
		{
			string value = abbreviation.Value;
			if (string.Equals(pinLabel, value, StringComparison.OrdinalIgnoreCase) || pinLabel.StartsWith(value + " ", StringComparison.OrdinalIgnoreCase))
			{
				return abbreviation.Key;
			}
		}
		return null;
	}

	public static void ApplyDiscoveryIcon(PinData mmPin, string keyword)
	{
		if (mmPin != null && DiscoveryPinObjectIcon.Value && RadarIconLoader.TryGetIcon(keyword, out var sprite))
		{
			mmPin.m_icon = sprite;
		}
	}

	public static void Reload()
	{
		if (_reloading)
		{
			return;
		}
		_reloading = true;
		try
		{
			ReloadImpl();
		}
		finally
		{
			_reloading = false;
		}
	}

	private static void ReloadImpl()
	{
		if (QuantitySearchRadius.Value > 150f)
		{
			QuantitySearchRadius.Value = 150f;
		}
		WorldObjectConfig.Reload();
		if (!UsingServerConfig)
		{
			ActiveEnabled = Enabled.Value;
			ActiveQuantitySearchRadius = QuantitySearchRadius.Value;
			Abbreviations.Clear();
			foreach (KeyValuePair<string, ConfigEntry<string>> abbreviationEntry in AbbreviationEntries)
			{
				Abbreviations[abbreviationEntry.Key] = abbreviationEntry.Value.Value;
			}
		}
		if (!ModSettings.ModActive || !ModSettings.IsServer() || !((Object)(object)ZNetProxy.ZNetInstance != (Object)null) || !(Traverse.Create((object)ZNetProxy.ZNetInstance).Field("m_peers").GetValue() is List<ZNetPeer> list))
		{
			return;
		}
		foreach (ZNetPeer item in list)
		{
			if (item.IsReady())
			{
				item.m_rpc.Invoke("OM_ServerDiscoveryConfig", new object[1] { PackDiscoveryConfig() });
			}
		}
	}

	public static string GetAbbreviation(string keyword)
	{
		if (Abbreviations.TryGetValue(keyword, out var value) && !string.IsNullOrWhiteSpace(value))
		{
			return value;
		}
		if (keyword.Length <= 6)
		{
			return keyword;
		}
		return keyword.Substring(0, 6);
	}

	public static string MatchKeyword(string objectName, string[] keywords)
	{
		foreach (string text in keywords)
		{
			if (objectName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return text;
			}
		}
		return null;
	}

	internal static string[] ParseKeywordList(string csv)
	{
		return (from s in csv.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
			select s.Trim() into s
			where s.Length > 0
			select s).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
	}

	public static ZPackage PackDiscoveryConfig()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(Enabled.Value);
		val.Write(QuantitySearchRadius.Value);
		WorldObjectConfig.PackCategoryData(val);
		val.Write(AbbreviationEntries.Count);
		foreach (KeyValuePair<string, ConfigEntry<string>> abbreviationEntry in AbbreviationEntries)
		{
			val.Write(abbreviationEntry.Key);
			val.Write(abbreviationEntry.Value.Value);
		}
		val.SetPos(0);
		return val;
	}

	private static void PushDiscoveryConfigToServer()
	{
		ZRpc serverRPC = ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance);
		if (serverRPC != null)
		{
			serverRPC.Invoke("OM_AdminUpdateDiscoveryConfig", new object[1] { PackDiscoveryConfig() });
		}
	}

	public static void ApplyPackedConfigToEntries(ZPackage z)
	{
		Enabled.Value = z.ReadBool();
		QuantitySearchRadius.Value = z.ReadSingle();
		WorldObjectConfig.UnpackCategoryDataIntoEntries(z);
		int num = z.ReadInt();
		for (int i = 0; i < num; i++)
		{
			string key = z.ReadString();
			string value = z.ReadString();
			if (AbbreviationEntries.TryGetValue(key, out var value2))
			{
				value2.Value = value;
			}
		}
	}

	public static void OnServerDiscoveryConfig(ZRpc rpc, ZPackage z)
	{
		z.SetPos(0);
		if (!ModSettings.IsServer())
		{
			UsingServerConfig = true;
		}
		ActiveEnabled = z.ReadBool();
		ActiveQuantitySearchRadius = z.ReadSingle();
		WorldObjectConfig.ApplyCategoryData(z);
		int num = z.ReadInt();
		Abbreviations.Clear();
		for (int i = 0; i < num; i++)
		{
			Abbreviations[z.ReadString()] = z.ReadString();
		}
	}

	public static void ResetToLocalConfig()
	{
		UsingServerConfig = false;
		WorldObjectConfig.ResetToLocalConfig();
		Reload();
	}
}
public class ProximityWatcher : MonoBehaviour
{
	private string _keyword;

	private float _detectionRadius;

	private bool _inRange;

	private float _updateTimer;

	public string Keyword => _keyword;

	public void Initialize(string keyword, float detectionRadius)
	{
		_keyword = keyword;
		_detectionRadius = detectionRadius;
		_updateTimer = Random.Range(0f, ModSettings.ActiveUpdateThrottleInterval);
	}

	private void Update()
	{
		//IL_0050: 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_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (migh