Decompiled source of OneMapToRuleThemAll v1.2.8

plugins/OneMapToRuleThemAll.dll

Decompiled 6 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.2.8")]
[assembly: AssemblyVersion("1.2.8.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 ZPackage PackPinsChunk(List<SharedPin> pins, int start, int count, int totalPins)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Expected I4, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(totalPins);
		val.Write(start);
		val.Write(count);
		for (int i = 0; i < count; i++)
		{
			SharedPin sharedPin = pins[start + i];
			val.Write(sharedPin.Name);
			val.Write(sharedPin.Pos);
			val.Write((int)sharedPin.Type);
			val.Write(sharedPin.Checked);
			val.Write(sharedPin.OwnerId);
		}
		val.SetPos(0);
		return val;
	}

	public static List<SharedPin> UnpackPinsChunk(ZPackage z, out int totalPins, out int chunkStart)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		totalPins = z.ReadInt();
		chunkStart = z.ReadInt();
		int num = z.ReadInt();
		List<SharedPin> list = new List<SharedPin>(num);
		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;
	}

	public static int RemoveDuplicateDiscoveryPins()
	{
		float num = AutoPinConfig.ActiveQuantitySearchRadius;
		if (num <= 0f)
		{
			num = 16f;
		}
		float num2 = num * num;
		List<SharedPin> list = new List<SharedPin>(ServerPins.Count);
		int num3 = 0;
		foreach (SharedPin serverPin in ServerPins)
		{
			if (serverPin.OwnerId != "auto")
			{
				list.Add(serverPin);
				continue;
			}
			string a = ExtractAbbreviation(serverPin.Name);
			bool flag = false;
			foreach (SharedPin item in list)
			{
				if (!(item.OwnerId != "auto") && string.Equals(a, ExtractAbbreviation(item.Name), StringComparison.OrdinalIgnoreCase))
				{
					float num4 = item.Pos.x - serverPin.Pos.x;
					float num5 = item.Pos.z - serverPin.Pos.z;
					if (num4 * num4 + num5 * num5 <= num2)
					{
						flag = true;
						break;
					}
				}
			}
			if (flag)
			{
				num3++;
			}
			else
			{
				list.Add(serverPin);
			}
		}
		if (num3 > 0)
		{
			ServerPins = list;
		}
		return num3;
	}

	private static string ExtractAbbreviation(string pinName)
	{
		if (string.IsNullOrEmpty(pinName))
		{
			return "";
		}
		int num = pinName.IndexOf(' ');
		if (num < 0)
		{
			return pinName;
		}
		return pinName.Substring(0, num);
	}
}
[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 && MapSyncConfig.ActiveEnabled)
			{
				_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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			_blockExplore = false;
			if (!ModSettings.ModActive || !MapSyncConfig.ActiveEnabled)
			{
				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())
			{
				rpc.Invoke("OM_ServerMapConfig", new object[1] { MapSyncConfig.PackMapConfig() });
				rpc.Invoke("OM_ServerDiscoveryConfig", new object[1] { AutoPinConfig.PackDiscoveryConfig() });
				rpc.Invoke("OM_ServerRadarConfig", new object[1] { RadarConfig.PackRadarConfig() });
				if (MapSyncConfig.ActiveEnabled)
				{
					SendChunkToClient(rpc, 0);
				}
			}
		}
	}

	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_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Expected O, but got Unknown
		if (!ModSettings.ModActive || !MapSyncConfig.ActiveEnabled)
		{
			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 && MapSyncConfig.ActiveEnabled)
		{
			SendChunkToClient(client, 0);
		}
	}

	public static void OnClientInitialMap(ZRpc client, ZPackage data)
	{
		if (ModSettings.ModActive && MapSyncConfig.ActiveEnabled)
		{
			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 && MapSyncConfig.ActiveEnabled)
		{
			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()
		{
			_cachedMmPins = null;
			AddPinsAfterClear();
		}
	}

	[HarmonyPatch(typeof(Minimap), "UpdatePins")]
	private class MinimapPatchUpdateDiscoveryIcons
	{
		private static void Postfix(Minimap __instance)
		{
			//IL_009c: 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 (_cachedMmPins == null)
			{
				_cachedMmPins = Traverse.Create((object)__instance).Field("m_pins").GetValue() as List<PinData>;
			}
			if (_cachedMmPins == null || _cachedMmPins.Count == 0)
			{
				return;
			}
			foreach (PinData cachedMmPin in _cachedMmPins)
			{
				if (_iconLookup.TryGetValue((cachedMmPin.m_name, cachedMmPin.m_type), out var value))
				{
					cachedMmPin.m_icon = value;
				}
			}
			StutterMonitor.AddIconStampPass(_cachedMmPins.Count);
		}
	}

	[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()
		{
			_cachedMmPins = null;
			MapStateRepository.ClientPins.Clear();
			AutoPinPlacer.InvalidatePinIndex();
			MapStateRepository.InitialPinsReceived = false;
			AutoPinConfig.ResetToLocalConfig();
			RadarConfig.ResetToLocalConfig();
			MapSyncConfig.ResetToLocalConfig();
			RadarClusterManager.Clear();
		}
	}

	[HarmonyPatch(typeof(Minimap), "SetMapData", new Type[] { typeof(byte[]) })]
	private class MinimapPatchSetMapData
	{
		private static void Postfix()
		{
			_cachedMmPins = null;
			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 List<PinData> _cachedMmPins = null;

	private const int InitialPinsChunkSize = 256;

	private static List<SharedPin> _initialPinsBuffer;

	private static int _initialPinsExpected = -1;

	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;
				}
			}
		}
		AutoPinPlacer.InvalidatePinIndex();
	}

	public static void OnClientAddPin(ZRpc client, ZPackage data)
	{
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive || !MapSyncConfig.ActiveEnabled)
		{
			return;
		}
		data.SetPos(0);
		SharedPin sharedPin = MapStateRepository.UnpackPin(data);
		if (sharedPin.OwnerId == "auto" && IsDuplicateDiscoveryPin(sharedPin))
		{
			ManualLogSource log = ModSettings.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[OnClientAddPin] dropped duplicate discovery pin '{sharedPin.Name}' at {sharedPin.Pos}");
			}
			return;
		}
		MapStateRepository.AddPin(sharedPin);
		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(sharedPin) });
			}
		}
		if (client != null && (Object)(object)MinimapProxy.Instance != (Object)null)
		{
			OnServerAddPin(null, MapStateRepository.PackPin(sharedPin));
		}
	}

	private static bool IsDuplicateDiscoveryPin(SharedPin incoming)
	{
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrEmpty(incoming.Name))
		{
			return false;
		}
		int num = incoming.Name.IndexOf(' ');
		string a = ((num >= 0) ? incoming.Name.Substring(0, num) : incoming.Name);
		float activeQuantitySearchRadius = AutoPinConfig.ActiveQuantitySearchRadius;
		if (activeQuantitySearchRadius <= 0f)
		{
			return false;
		}
		foreach (SharedPin pin in MapStateRepository.GetPins())
		{
			if (!(pin.OwnerId != "auto") && !string.IsNullOrEmpty(pin.Name))
			{
				int num2 = pin.Name.IndexOf(' ');
				string b = ((num2 >= 0) ? pin.Name.Substring(0, num2) : pin.Name);
				if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase) && Utils.DistanceXZ(pin.Pos, incoming.Pos) <= activeQuantitySearchRadius)
				{
					return true;
				}
			}
		}
		return false;
	}

	public static void OnClientRemovePin(ZRpc client, ZPackage data)
	{
		if (!ModSettings.ModActive || !MapSyncConfig.ActiveEnabled)
		{
			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 || !MapSyncConfig.ActiveEnabled)
		{
			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 OnServerInitialPinsChunk(ZRpc rpc, ZPackage data)
	{
		if (!ModSettings.ModActive)
		{
			return;
		}
		StutterMonitor.AddChunkedSendPinsCall();
		data.SetPos(0);
		int totalPins;
		int chunkStart;
		List<SharedPin> list = MapStateRepository.UnpackPinsChunk(data, out totalPins, out chunkStart);
		if (_initialPinsBuffer == null || _initialPinsExpected != totalPins)
		{
			_initialPinsBuffer = new List<SharedPin>(totalPins);
			for (int i = 0; i < totalPins; i++)
			{
				_initialPinsBuffer.Add(null);
			}
			_initialPinsExpected = totalPins;
		}
		for (int j = 0; j < list.Count; j++)
		{
			int num = chunkStart + j;
			if (num >= 0 && num < _initialPinsBuffer.Count)
			{
				_initialPinsBuffer[num] = list[j];
			}
		}
		for (int k = 0; k < _initialPinsBuffer.Count; k++)
		{
			if (_initialPinsBuffer[k] == null)
			{
				return;
			}
		}
		MapStateRepository.ClientPins = _initialPinsBuffer;
		MapStateRepository.InitialPinsReceived = true;
		_initialPinsBuffer = null;
		_initialPinsExpected = -1;
		AppendPins();
		ManualLogSource log = ModSettings.Log;
		if (log != null)
		{
			log.LogInfo((object)$"[OM_ServerInitialPinsChunk] initial pin sync complete: {MapStateRepository.ClientPins.Count} pins received.");
		}
	}

	public static void OnServerAddPin(ZRpc rpc, ZPackage data)
	{
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive)
		{
			return;
		}
		StutterMonitor.AddAddPinCall();
		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;
		}
		StutterMonitor.AddRemovePinCall();
		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;
		}
		StutterMonitor.AddCheckPinCall();
		data.SetPos(0);
		SharedPin b = MapStateRepository.UnpackPin(data);
		bool flag = data.ReadBool();
		foreach (SharedPin clientPin in MapStateRepository.ClientPins)
		{
			if (MapStateRepository.ArePinsEqual(clientPin, b))
			{
				clientPin.Checked = flag;
				PinData mapPin = GetMapPin(clientPin);
				if (mapPin != null)
				{
					mapPin.m_checked = flag;
				}
			}
		}
	}

	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);
		AutoPinPlacer.InvalidatePinIndex();
		if (!MapSyncConfig.ActiveEnabled)
		{
			return;
		}
		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);
		AutoPinPlacer.InvalidatePinIndex();
		if (!MapSyncConfig.ActiveEnabled)
		{
			return;
		}
		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 || !MapSyncConfig.ActiveEnabled)
		{
			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 || !MapSyncConfig.ActiveEnabled)
		{
			return;
		}
		List<SharedPin> pins = MapStateRepository.GetPins();
		int count = pins.Count;
		if (count == 0)
		{
			ZPackage val = MapStateRepository.PackPinsChunk(pins, 0, 0, 0);
			if (client == null)
			{
				OnServerInitialPinsChunk(null, val);
				return;
			}
			client.Invoke("OM_ServerInitialPinsChunk", new object[1] { val });
			return;
		}
		for (int i = 0; i < count; i += 256)
		{
			int count2 = Math.Min(256, count - i);
			ZPackage val2 = MapStateRepository.PackPinsChunk(pins, i, count2, count);
			if (client == null)
			{
				OnServerInitialPinsChunk(null, val2);
				continue;
			}
			client.Invoke("OM_ServerInitialPinsChunk", new object[1] { val2 });
		}
		ManualLogSource log = ModSettings.Log;
		if (log != null)
		{
			log.LogInfo((object)string.Format("[SendPinsToClient] dispatched {0} pins in {1} chunk(s) to {2}.", count, (count + 256 - 1) / 256, (client == null) ? "local" : "remote client"));
		}
	}

	public static void AppendPins()
	{
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive || (Object)(object)MinimapProxy.Instance == (Object)null)
		{
			return;
		}
		StutterMonitor.AddAppendPinsCall();
		if (!(Traverse.Create((object)MinimapProxy.Instance).Field("m_pins").GetValue() is List<PinData> list))
		{
			return;
		}
		string text = ZNet.GetUID().ToString();
		foreach (SharedPin clientPin in MapStateRepository.ClientPins)
		{
			PinData val = null;
			if (clientPin.OwnerId == "auto")
			{
				Vector3 pos = clientPin.Pos;
				PinType type = clientPin.Type;
				foreach (PinData item in list)
				{
					if (item.m_type == type && Utils.DistanceXZ(pos, item.m_pos) < 1f)
					{
						val = item;
						break;
					}
				}
			}
			else
			{
				foreach (PinData item2 in list)
				{
					if (MapStateRepository.ArePinsEqual(clientPin, item2))
					{
						val = item2;
						break;
					}
				}
			}
			if (val != null)
			{
				DestroyPinEntry(list, val);
			}
			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 AddPinsAfterClear()
	{
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive || (Object)(object)MinimapProxy.Instance == (Object)null)
		{
			return;
		}
		StutterMonitor.AddAppendPinsCall();
		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.2.8")]
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_ServerInitialPinsChunk", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerInitialPinsChunk);
					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);
					peer.m_rpc.Register<ZPackage>("OM_ServerMapConfig", (Action<ZRpc, ZPackage>)MapSyncConfig.OnServerMapConfig);
				}
			}
		}
	}

	private void Awake()
	{
		ModSettings.Log = ((BaseUnityPlugin)this).Logger;
		ConfigMigration.MigrateIfNeeded(((BaseUnityPlugin)this).Config);
		try
		{
			ConfigMigration.MigrateFilesToSubfolder();
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("[ConfigMigration] File migration failed: " + ex.Message));
		}
		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.ClientDiscoveryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "DiscoveryEnabled", true, "Master client switch for the auto-discovery pin feature. When the server has Server.Discovery.Enabled = true, this acts as a local opt-out: turning this off hides and stops generating auto pins on this client without affecting other players. When the server has Server.Discovery.Enabled = false, this becomes the sole gate — auto pins generated while this is true stay client-local until the server re-enables the feature.");
		ModSettings.ClientDiscoveryEnabled.SettingChanged += delegate
		{
			WorldObjectPatches.ReScanForKeywordChanges();
		};
		ModSettings.ClientRadarEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "RadarEnabled", true, "Master client switch for the radar feature. Effective gate is (Server.Radar.Enabled AND Client.RadarEnabled). Turn this off to disable radar entirely on this client even when the server allows it. Radar pins are always client-only — never sent to other players.");
		ModSettings.ClientRadarEnabled.SettingChanged += delegate
		{
			RadarClusterManager.ClearAndReset();
			WorldObjectPatches.ReScanForKeywordChanges();
		};
		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", 1f, "How often (seconds) proximity and radar watchers check for nearby objects. Default 1.0 — at running speed a player covers ~5 m per second, so a 1 Hz check still trips every detection radius. Lower this only if you want pins to appear instantly the moment you cross a boundary; higher values reduce per-frame CPU. Range: 0.05–5.0.");
		ModSettings.UpdateThrottleInterval.SettingChanged += delegate
		{
			ModSettings.ActiveUpdateThrottleInterval = Mathf.Clamp(ModSettings.UpdateThrottleInterval.Value, 0.05f, 5f);
		};
		ModSettings.ActiveUpdateThrottleInterval = Mathf.Clamp(ModSettings.UpdateThrottleInterval.Value, 0.05f, 5f);
		ModSettings.DiscoveryIconInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "DiscoveryIconInterval", 1f, "How often (seconds) discovery pin icons are reapplied after Valheim's per-frame icon reset. Default 1.0 — high enough that the per-frame restamp work is negligible but low enough that a freshly-opened map has correct icons within one tick. Lower values (down to 0 = every frame) restamp more aggressively but cost CPU. Range: 0–5.0.");
		ModSettings.DiscoveryIconInterval.SettingChanged += delegate
		{
			ModSettings.ActiveDiscoveryIconInterval = Mathf.Clamp(ModSettings.DiscoveryIconInterval.Value, 0f, 5f);
		};
		ModSettings.ActiveDiscoveryIconInterval = Mathf.Clamp(ModSettings.DiscoveryIconInterval.Value, 0f, 5f);
		ModSettings.QuantityRecountInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "QuantityRecountInterval", 10f, "Minimum time (seconds) before re-running the nearby-object Physics query for an already-discovered pin. Prevents redundant OverlapSphere calls when the player oscillates near a detection boundary. Higher values reduce Physics overhead; lower values keep resource counts fresher. Range: 1–120.");
		ModSettings.QuantityRecountInterval.SettingChanged += delegate
		{
			ModSettings.ActiveQuantityRecountInterval = Mathf.Clamp(ModSettings.QuantityRecountInterval.Value, 1f, 120f);
		};
		ModSettings.ActiveQuantityRecountInterval = Mathf.Clamp(ModSettings.QuantityRecountInterval.Value, 1f, 120f);
		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);
		MapSyncConfig.BindServerEntries(((BaseUnityPlugin)this).Config);
		StutterMonitor.BindServerEntries(((BaseUnityPlugin)this).Config);
		WorldObjectConfig.WireEvents(((BaseUnityPlugin)this).Config);
		AutoPinConfig.WireEvents(((BaseUnityPlugin)this).Config);
		RadarConfig.WireEvents(((BaseUnityPlugin)this).Config);
		MapSyncConfig.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();
		StutterMonitor.Install();
		if (ModSettings.UpdateThrottleInterval != null && ModSettings.UpdateThrottleInterval.Value < 0.5f)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)($"[OneMap] UpdateThrottleInterval={ModSettings.UpdateThrottleInterval.Value} is below the v1.2.6 default of 1.0. " + "The lower value places more per-frame work on this client (proximity watchers + radar trackers fire faster). If you are not deliberately running a low interval for instant pin placement, edit BepInEx/config/drummercraig.one_map_to_rule_them_all.cfg and bump UpdateThrottleInterval to 1.0 (or remove the line to accept the default)."));
		}
	}
}
public static class MapFilePersistence
{
	[HarmonyPatch(typeof(ZNet), "LoadWorld")]
	private class ZNetPatchLoadWorld
	{
		private static void Postfix(ZNet __instance)
		{
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: 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.");
					}
					int num = MapStateRepository.RemoveDuplicateDiscoveryPins();
					if (num > 0)
					{
						ManualLogSource log4 = ModSettings.Log;
						if (log4 != null)
						{
							log4.LogInfo((object)$"[MapFilePersistence] Removed {num} duplicate auto-discovery pin(s) on load.");
						}
						MapDirty = true;
					}
					return;
				}
				catch (Exception ex)
				{
					ManualLogSource log5 = ModSettings.Log;
					if (log5 != null)
					{
						log5.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
{
	private static string _modFolder;

	public static ConfigEntry<bool> ResetToDefaults;

	public static ConfigEntry<bool> ClientResetToDefaults;

	public static ConfigEntry<bool> ShowOtherPlayerPins;

	public static ConfigEntry<bool> ClientDiscoveryEnabled;

	public static ConfigEntry<bool> ClientRadarEnabled;

	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 = 0.05f;

	public static ConfigEntry<float> QuantityRecountInterval;

	public static float ActiveQuantityRecountInterval = 10f;

	public static bool ModActive = true;

	public static ManualLogSource Log;

	public static string ModFolder
	{
		get
		{
			if (_modFolder == null)
			{
				_modFolder = Path.Combine(Paths.ConfigPath, "OneMapToRuleThemAll");
				Directory.CreateDirectory(_modFolder);
			}
			return _modFolder;
		}
	}

	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 (displayName: item, category: biomeCritterCategory);
				}
			}
		}
		return (displayName: null, category: 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(ModSettings.ModFolder, "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 name = 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, name, 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 num3 = 0; num3 < num2; num3++)
		{
			string name2 = z.ReadString();
			float value5 = z.ReadSingle();
			int num4 = z.ReadInt();
			for (int num5 = 0; num5 < num4; num5++)
			{
				z.ReadString();
				z.ReadString();
				z.ReadString();
			}
			BiomeCritterCategory biomeCritterCategory = BiomeCritterCategories.FirstOrDefault((BiomeCritterCategory c) => string.Equals(c.CategoryName, name2, 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 num3 = 0; num3 < num2; num3++)
		{
			string categoryName2 = z.ReadString();
			float activeRadarRadius2 = z.ReadSingle();
			BiomeCritterCategory biomeCritterCategory = new BiomeCritterCategory
			{
				CategoryName = categoryName2,
				ActiveRadarRadius = activeRadarRadius2
			};
			int num4 = z.ReadInt();
			for (int num5 = 0; num5 < num4; num5++)
			{
				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: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:Troph