Decompiled source of OneMapToRuleThemAll v1.0.0

plugins/OneMapToRuleThemAll.dll

Decompiled 3 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("One Map To Rule Them All")]
[assembly: AssemblyDescription("Valheim One Map To Rule Them All Mod by DrummerCraig")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("One Map To Rule Them All")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("6A5B7C8D-E9F0-1234-5678-9ABCDEF01234")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: AssemblyVersion("1.0.0.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_006e: 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_0085: Expected I4, but got Unknown
		ZPackage val = new ZPackage();
		val.Write(1);
		val.Write(2048);
		bool[] explored = Explored;
		foreach (bool flag in explored)
		{
			val.Write(flag);
		}
		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_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		ServerPins.Clear();
		mapData.SetPos(0);
		mapData.ReadInt();
		int num = mapData.ReadInt();
		bool[] array = new bool[num * num];
		for (int i = 0; i < num * num; i++)
		{
			array[i] = mapData.ReadBool();
		}
		int num2 = mapData.ReadInt();
		for (int j = 0; j < num2; j++)
		{
			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;
	}

	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];
		}
	}

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

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

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

	public static SharedPin UnpackPin(ZPackage z)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		return new SharedPin
		{
			Name = z.ReadString(),
			Pos = z.ReadVector3(),
			Type = (PinType)z.ReadInt(),
			Checked = z.ReadBool(),
			OwnerId = z.ReadString()
		};
	}

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

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

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

	public static void AddPin(SharedPin pin)
	{
		ServerPins.Add(pin);
	}

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

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

	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_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 ((Vector3)(ref a.Pos)).Equals(b.m_pos);
		}
		return false;
	}
}
[HarmonyPatch]
public static class ZNetProxy
{
	[HarmonyPatch(typeof(ZNet), "Awake")]
	private static class ZNetAwake
	{
		private static void Postfix(ZNet __instance)
		{
			ZNetInstance = __instance;
			ModSettings.ModActive = true;
			if ((Object)(object)MinimapProxy.Instance != (Object)null && IsServer(__instance))
			{
				MapPinSynchronizer.SendPinsToClient(null);
			}
		}
	}

	public static ZNet ZNetInstance;

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

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

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

	public static Minimap Instance;

	public static Texture2D FogTexture;

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

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

	[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
	[HarmonyPatch(typeof(Minimap), "RemovePin", new Type[] { typeof(PinData) })]
	public static void RemovePin(Minimap instance, PinData pin)
	{
		throw new NotImplementedException();
	}
}
public static class ExplorationSynchronizer
{
	[HarmonyPatch(typeof(Minimap), "Explore", new Type[]
	{
		typeof(Vector3),
		typeof(float)
	})]
	private class MinimapPatchExploreInterval
	{
		private static void Postfix(Minimap __instance)
		{
			if (_fogDirty)
			{
				_fogDirty = false;
				object value = Traverse.Create((object)__instance).Field("m_fogTexture").GetValue();
				object obj = ((value is Texture2D) ? value : null);
				if (obj != null)
				{
					((Texture2D)obj).Apply();
				}
			}
		}
	}

	[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)
			{
				return;
			}
			if (ZNetProxy.IsServer(ZNetProxy.ZNetInstance))
			{
				OnClientExplore(null, x, y);
				return;
			}
			ZRpc serverRPC = ZNetProxy.GetServerRPC(ZNetProxy.ZNetInstance);
			if (serverRPC != null)
			{
				serverRPC.Invoke("OM_ClientExplore", new object[2] { x, y });
			}
		}
	}

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

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

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

	private static bool _blockExplore;

	private static bool _fogDirty;

	private const int Chunks = 64;

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

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

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

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

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

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

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

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

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

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

	[HarmonyPatch(typeof(Minimap), "GetClosestPin", new Type[]
	{
		typeof(Vector3),
		typeof(float),
		typeof(bool)
	})]
	private class MinimapPatchGetClosestPin
	{
		private static void Postfix(ref PinData __result, Vector3 pos, float radius)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			if (!ModSettings.ModActive)
			{
				return;
			}
			LatestClosestPin = __result;
			SharedPin sharedPin = null;
			float num = 999999f;
			foreach (SharedPin clientPin in MapStateRepository.ClientPins)
			{
				float num2 = Utils.DistanceXZ(pos, clientPin.Pos);
				if (num2 < radius && (sharedPin == null || num2 < num))
				{
					sharedPin = clientPin;
					num = num2;
				}
			}
			if (sharedPin != null)
			{
				PinData mapPin = GetMapPin(sharedPin);
				if (mapPin != null && (__result == null || Utils.DistanceXZ(pos, __result.m_pos) > num))
				{
					__result = mapPin;
					LatestClosestPin = mapPin;
				}
			}
		}
	}

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

	[HarmonyPatch(typeof(Minimap), "UpdatePins")]
	private class MinimapPatchUpdateDiscoveryIcons
	{
		private static void Postfix(Minimap __instance)
		{
			if (!AutoPinConfig.DiscoveryPinObjectIcon.Value || MapStateRepository.ClientPins.Count == 0 || !(Traverse.Create((object)__instance).Field("m_pins").GetValue() is List<PinData> list) || list.Count == 0)
			{
				return;
			}
			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))
				{
					continue;
				}
				foreach (PinData item in list)
				{
					if (MapStateRepository.ArePinsEqual(clientPin, item))
					{
						item.m_icon = sprite;
						break;
					}
				}
			}
		}
	}

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

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

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

	private static PinData LatestClosestPin;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	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);
	}

	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.0.0")]
public class OneMapToRuleThemAllPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private class ZNetPatchOnNewConnection
	{
		private static void Postfix(ZNetPeer peer, ZNet __instance)
		{
			if (!__instance.IsServer())
			{
				ModSettings.ModActive = true;
			}
			if (ModSettings.ModActive)
			{
				if (__instance.IsServer())
				{
					peer.m_rpc.Register<int, int>("OM_ClientExplore", (Action<ZRpc, int, int>)ExplorationSynchronizer.OnClientExplore);
					peer.m_rpc.Register<ZPackage>("OM_ClientInitialMap", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnClientInitialMap);
					peer.m_rpc.Register<ZPackage>("OM_ClientRequestInitialMap", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnClientRequestInitialMap);
					peer.m_rpc.Register<ZPackage>("OM_ClientAddPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnClientAddPin);
					peer.m_rpc.Register<ZPackage>("OM_ClientRemovePin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnClientRemovePin);
					peer.m_rpc.Register<ZPackage>("OM_ClientCheckPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnClientCheckPin);
					peer.m_rpc.Register<ZPackage>("OM_AdminClearArea", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminClearArea);
					peer.m_rpc.Register<ZPackage>("OM_AdminClearAll", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminClearAll);
					peer.m_rpc.Register<ZPackage>("OM_AdminRenameAbbr", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminRenameAbbr);
					peer.m_rpc.Register<ZPackage>("OM_AdminUpdateDiscoveryConfig", (Action<ZRpc, ZPackage>)AdminCommands.OnAdminUpdateDiscoveryConfig);
				}
				else
				{
					peer.m_rpc.Register<ZPackage>("OM_ServerMapData", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnServerMapData);
					peer.m_rpc.Register<ZPackage>("OM_ServerMapInitial", (Action<ZRpc, ZPackage>)ExplorationSynchronizer.OnServerMapInitial);
					peer.m_rpc.Register<ZPackage>("OM_ServerInitialPins", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerInitialPins);
					peer.m_rpc.Register<ZPackage>("OM_ServerAddPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerAddPin);
					peer.m_rpc.Register<ZPackage>("OM_ServerRemovePin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerRemovePin);
					peer.m_rpc.Register<ZPackage>("OM_ServerCheckPin", (Action<ZRpc, ZPackage>)MapPinSynchronizer.OnServerCheckPin);
					peer.m_rpc.Register<ZPackage>("OM_ServerDiscoveryConfig", (Action<ZRpc, ZPackage>)AutoPinConfig.OnServerDiscoveryConfig);
					peer.m_rpc.Register<ZPackage>("OM_ServerRadarConfig", (Action<ZRpc, ZPackage>)RadarConfig.OnServerRadarConfig);
				}
			}
		}
	}

	private void Awake()
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Expected O, but got Unknown
		ModSettings.Log = ((BaseUnityPlugin)this).Logger;
		Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
		ConfigFile serverConfig = new ConfigFile(Path.Combine(Paths.ConfigPath, "One_Map_To_Rule_Them_All_Settings.cfg"), true);
		ModSettings.ResetToDefaults = serverConfig.Bind<bool>("General", "ResetToDefaults", false, "Set to true to reset ALL settings to their default values. Resets itself to false automatically.");
		ModSettings.ResetToDefaults.SettingChanged += delegate
		{
			if (!ModSettings.ResetToDefaults.Value)
			{
				return;
			}
			foreach (ConfigDefinition key in serverConfig.Keys)
			{
				serverConfig[key].BoxedValue = serverConfig[key].DefaultValue;
			}
		};
		ModSettings.ShowOtherPlayerPins = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "ShowOtherPlayerPins", true, "Show map pins created by other players on your minimap.");
		AutoPinConfig.Bind(serverConfig, ((BaseUnityPlugin)this).Config);
		WorldObjectConfig.DiscoveryVisibilityChanged += MapPinSynchronizer.AppendPins;
		AutoPinConfig.DiscoveryPinObjectIcon.SettingChanged += delegate
		{
			MapPinSynchronizer.AppendPins();
		};
		AutoPinConfig.DiscoveryPinText.SettingChanged += delegate
		{
			MapPinSynchronizer.AppendPins();
		};
		RadarConfig.Bind(serverConfig, ((BaseUnityPlugin)this).Config);
		ModSettings.ShowOtherPlayerPins.SettingChanged += delegate
		{
			MapPinSynchronizer.AppendPins();
		};
	}
}
public static class MapFilePersistence
{
	[HarmonyPatch(typeof(ZNet), "LoadWorld")]
	private class ZNetPatchLoadWorld
	{
		private static void Postfix(ZNet __instance)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected O, but got Unknown
			object value = Traverse.Create(typeof(ZNet)).Field("m_world").GetValue();
			string dBPath = ((World)((value is World) ? value : null)).GetDBPath();
			string text = Path.ChangeExtension(Utils.GetSaveDataPath((FileSource)1) + dBPath, null);
			string text2 = (_savePath = text + ".one_map_to_rule_them_all.explored");
			ManualLogSource log = ModSettings.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[MapFilePersistence] LoadWorld — savePath={text2} exists={File.Exists(text2)}");
			}
			if (File.Exists(text2))
			{
				try
				{
					MapStateRepository.SetMapData(new ZPackage(File.ReadAllBytes(text2)));
					ManualLogSource log2 = ModSettings.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)$"[MapFilePersistence] Loaded {MapStateRepository.GetPins().Count} pins from save file.");
					}
					return;
				}
				catch (Exception ex)
				{
					ManualLogSource log3 = ModSettings.Log;
					if (log3 != null)
					{
						log3.LogWarning((object)("[MapFilePersistence] Failed to read save file: " + ex.Message));
					}
				}
			}
			ZPackage val = TryMigrateFromServerSideMap(text);
			if (val != null)
			{
				MapStateRepository.SetMapData(val);
				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;

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

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

	public static ConfigEntry<bool> ShowOtherPlayerPins;

	public static bool ModActive = true;

	public static ManualLogSource Log;

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

	public ConfigEntry<float> RadarRadiusEntry;

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

	public float ActiveRadarRadius;

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

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

	public ConfigEntry<float> DiscoveryRadiusEntry;

	public ConfigEntry<float> RadarRadiusEntry;

	public ConfigEntry<string> TrackedDiscoveryEntry;

	public ConfigEntry<string> TrackedRadarEntry;

	public float ActiveDiscoveryRadius;

	public float ActiveRadarRadius;

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

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

	public static ConfigEntry<float> OresDiscoveryRadius;

	public static ConfigEntry<float> OresRadarRadius;

	public static ConfigEntry<string> TrackedOresDiscovery;

	public static ConfigEntry<string> TrackedOresRadar;

	public static ConfigEntry<float> LocationsDiscoveryRadius;

	public static ConfigEntry<float> LocationsRadarRadius;

	public static ConfigEntry<string> TrackedLocationsDiscovery;

	public static ConfigEntry<string> TrackedLocationsRadar;

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

	public static float ActiveOresDiscoveryRadius;

	public static float ActiveOresRadarRadius;

	public static float ActiveLocationsDiscoveryRadius;

	public static float ActiveLocationsRadarRadius;

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

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

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

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

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

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

	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;

	public static event Action DiscoveryVisibilityChanged;

	public static event Action RadarVisibilityChanged;

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

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

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

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

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

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

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

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

	public static void EnsureVisibilityEntries()
	{
		string[] discoveryKeywords;
		foreach (PickableCategory pickableCategory in PickableCategories)
		{
			string section = "MapPin.Pickables." + pickableCategory.CategoryName;
			string section2 = "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("MapPin.Ores");
		EnsureToggleAllEntry("Radar.Ores");
		EnsureToggleAllEntry("MapPin.Locations");
		EnsureToggleAllEntry("Radar.Locations");
		discoveryKeywords = OresDiscovery;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureDiscoveryVisibilityEntry(discoveryKeywords[i], "MapPin.Ores");
		}
		discoveryKeywords = OresRadar;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureRadarVisibilityEntry(discoveryKeywords[i], "Radar.Ores");
		}
		discoveryKeywords = LocationsDiscovery;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureDiscoveryVisibilityEntry(discoveryKeywords[i], "MapPin.Locations");
		}
		discoveryKeywords = LocationsRadar;
		for (int i = 0; i < discoveryKeywords.Length; i++)
		{
			EnsureRadarVisibilityEntry(discoveryKeywords[i], "Radar.Locations");
		}
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			string section3 = "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", true, "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, true, "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, true, "Show '" + keyword + "' radar pins on your minimap. Client-only — never overridden by the server.");
		}
	}

	public static void Bind(ConfigFile serverConfig, ConfigFile clientConfig)
	{
		_config = clientConfig;
		PickableCategories.Clear();
		(string, string)[] array = new(string, string)[8]
		{
			("Meadows", "Raspberry,Dandelion,Flint,Mushroom"),
			("BlackForest", "Blueberry,Thistle,Carrot"),
			("Swamp", "SurtlingCore,Turnip"),
			("Mountain", "Crystal,Onion"),
			("Plains", "Cloudberry,Barley,Flax"),
			("Ocean", "Chitin"),
			("Mistlands", "JotunPuff,Magecap"),
			("AshLands", "BlackCore")
		};
		for (int i = 0; i < array.Length; i++)
		{
			(string, string) tuple = array[i];
			string item = tuple.Item1;
			string item2 = tuple.Item2;
			string text = "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>("Ores", "DiscoveryRadius", 6f, "How close (meters) the player must approach an ore deposit to trigger a discovery pin. Maximum 150.");
		OresRadarRadius = serverConfig.Bind<float>("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>("Ores", "TrackedDiscovery", "Copper,Tin,Iron,Silver,Obsidian,BlackMarble,Meteorite,Flametal", "Comma-separated keywords for ore deposits that trigger a permanent discovery pin.");
		TrackedOresRadar = serverConfig.Bind<string>("Ores", "TrackedRadar", "Copper,Tin,Iron,Silver,Obsidian,BlackMarble,Meteorite,Flametal", "Comma-separated keywords for ore deposits that appear as radar pins.");
		LocationsDiscoveryRadius = serverConfig.Bind<float>("Locations", "DiscoveryRadius", 6f, "How close (meters) the player must approach a location to trigger a discovery pin. Maximum 150.");
		LocationsRadarRadius = serverConfig.Bind<float>("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>("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", "Comma-separated keywords for locations that trigger a permanent discovery pin.");
		TrackedLocationsRadar = serverConfig.Bind<string>("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", "Comma-separated keywords for locations that appear as radar pins.");
		string path = Path.Combine(Paths.ConfigPath, "OneMapToRuleThemAll.creatures.txt");
		if (!File.Exists(path))
		{
			WriteDefaultCreaturesFile(path);
		}
		LoadCreaturesFile(path);
		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 = "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);
		}
		BindDiscoveryVisibility(clientConfig);
		BindRadarVisibility(clientConfig);
		clientConfig.SettingChanged += delegate(object _, SettingChangedEventArgs e)
		{
			string section = e.ChangedSetting.Definition.Section;
			if (e.ChangedSetting.Definition.Key == "_ToggleAll")
			{
				bool value2 = (bool)e.ChangedSetting.BoxedValue;
				foreach (ConfigEntry<bool> value3 in DiscoveryVisibilityEntries.Values)
				{
					if (((ConfigEntryBase)value3).Definition.Section == section)
					{
						value3.Value = value2;
					}
				}
				{
					foreach (ConfigEntry<bool> value4 in RadarVisibilityEntries.Values)
					{
						if (((ConfigEntryBase)value4).Definition.Section == section)
						{
							value4.Value = value2;
						}
					}
					return;
				}
			}
			if (section.StartsWith("MapPin."))
			{
				WorldObjectConfig.DiscoveryVisibilityChanged?.Invoke();
			}
			if (section.StartsWith("Radar."))
			{
				WorldObjectConfig.RadarVisibilityChanged?.Invoke();
			}
			Reload();
		};
		serverConfig.SettingChanged += delegate
		{
			Reload();
		};
		Reload();
	}

	private static void BindDiscoveryVisibility(ConfigFile config)
	{
		string[] discoveryKeywords;
		foreach (PickableCategory pickableCategory in PickableCategories)
		{
			string text = "MapPin.Pickables." + pickableCategory.CategoryName;
			BindToggleAll(config, text);
			discoveryKeywords = pickableCategory.DiscoveryKeywords;
			foreach (string text2 in discoveryKeywords)
			{
				DiscoveryVisibilityEntries[text2] = config.Bind<bool>(text, text2, true, "Show '" + text2 + "' discovery pins on your minimap. Pins are still created and shared; this only controls local visibility.");
			}
		}
		string[] array = new string[8] { "Copper", "Tin", "Iron", "Silver", "Obsidian", "BlackMarble", "Meteorite", "Flametal" };
		string[] array2 = new string[25]
		{
			"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"
		};
		BindToggleAll(config, "MapPin.Ores");
		discoveryKeywords = array;
		foreach (string text3 in discoveryKeywords)
		{
			DiscoveryVisibilityEntries[text3] = config.Bind<bool>("MapPin.Ores", text3, true, "Show '" + text3 + "' discovery pins on your minimap. Pins are still created and shared; this only controls local visibility.");
		}
		BindToggleAll(config, "MapPin.Locations");
		discoveryKeywords = array2;
		foreach (string text4 in discoveryKeywords)
		{
			DiscoveryVisibilityEntries[text4] = config.Bind<bool>("MapPin.Locations", text4, true, "Show '" + text4 + "' discovery pins on your minimap. Pins are still created and shared; this only controls local visibility.");
		}
	}

	private static void BindRadarVisibility(ConfigFile config)
	{
		string[] radarKeywords;
		foreach (PickableCategory pickableCategory in PickableCategories)
		{
			string text = "Radar.Pickables." + pickableCategory.CategoryName;
			BindToggleAll(config, text);
			radarKeywords = pickableCategory.RadarKeywords;
			foreach (string text2 in radarKeywords)
			{
				RadarVisibilityEntries[text2] = config.Bind<bool>(text, text2, true, "Show '" + text2 + "' radar pins on your minimap. Client-only — never overridden by the server.");
			}
		}
		string[] array = new string[8] { "Copper", "Tin", "Iron", "Silver", "Obsidian", "BlackMarble", "Meteorite", "Flametal" };
		string[] array2 = new string[25]
		{
			"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"
		};
		BindToggleAll(config, "Radar.Ores");
		radarKeywords = array;
		foreach (string text3 in radarKeywords)
		{
			RadarVisibilityEntries[text3] = config.Bind<bool>("Radar.Ores", text3, true, "Show '" + text3 + "' radar pins on your minimap. Client-only — never overridden by the server.");
		}
		BindToggleAll(config, "Radar.Locations");
		radarKeywords = array2;
		foreach (string text4 in radarKeywords)
		{
			RadarVisibilityEntries[text4] = config.Bind<bool>("Radar.Locations", text4, true, "Show '" + text4 + "' radar pins on your minimap. Client-only — never overridden by the server.");
		}
		foreach (BiomeCritterCategory biomeCritterCategory in BiomeCritterCategories)
		{
			string text5 = "Radar.Critters." + biomeCritterCategory.CategoryName.Replace(" ", "_");
			BindToggleAll(config, text5);
			foreach (var creatureDefinition in biomeCritterCategory.CreatureDefinitions)
			{
				string item = creatureDefinition.DisplayName;
				RadarVisibilityEntries[item] = config.Bind<bool>(text5, item, true, "Show '" + item + "' radar pins on your minimap. Client-only — never overridden by the server.");
			}
		}
	}

	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);
			OresRadar = AutoPinConfig.ParseKeywordList(TrackedOresRadar.Value);
			ActiveLocationsDiscoveryRadius = LocationsDiscoveryRadius.Value;
			ActiveLocationsRadarRadius = LocationsRadarRadius.Value;
			LocationsDiscovery = AutoPinConfig.ParseKeywordList(TrackedLocationsDiscovery.Value);
			LocationsRadar = AutoPinConfig.ParseKeywordList(TrackedLocationsRadar.Value);
		}
		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);
			}
		}
		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();
	}

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

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

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

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

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

	public static ConfigEntry<float> QuantitySearchRadius;

	public static bool ActiveEnabled;

	public static float ActiveQuantitySearchRadius;

	public static bool UsingServerConfig = false;

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

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

	public static ConfigEntry<bool> DiscoveryPinObjectIcon;

	public static ConfigEntry<bool> DiscoveryPinText;

	public static void Bind(ConfigFile serverConfig, ConfigFile clientConfig)
	{
		WorldObjectConfig.Bind(serverConfig, clientConfig);
		DiscoveryPinObjectIcon = clientConfig.Bind<bool>("Client", "DiscoveryPinObjectIcon", true, "Replace the standard pin icon on discovery pins with the item sprite from ObjectDB. Client-only — does not affect other players.");
		DiscoveryPinText = clientConfig.Bind<bool>("Client", "DiscoveryPinText", true, "Show the abbreviation label on auto-discovery pins. Set to false to show only the pin icon. Client-only — does not affect other players.");
		Enabled = serverConfig.Bind<bool>("Discovery", "Enabled", true, "Master switch for the automatic discovery pin system.");
		QuantitySearchRadius = serverConfig.Bind<float>("Discovery", "QuantitySearchRadius", 16f, "Radius (centered on the discovered object) searched for nearby objects of the same type. The count is appended to the pin label, e.g. 'RB 4'. Also defines the cluster boundary — objects within this radius share one pin. Keep larger than the per-category DiscoveryRadius.");
		BindAbbreviations(serverConfig);
		serverConfig.SettingChanged += delegate
		{
			if (WorldObjectConfig.UsingServerConfig)
			{
				PushDiscoveryConfigToServer();
			}
			else
			{
				Reload();
			}
		};
		clientConfig.SettingChanged += delegate
		{
			Reload();
		};
		Reload();
	}

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

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

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

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

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

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

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

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

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

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

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

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

	private float _detectionRadius;

	private bool _inRange;

	public string Keyword => _keyword;

	public void Initialize(string keyword, float detectionRadius)
	{
		_keyword = keyword;
		_detectionRadius = detectionRadius;
	}

	private void Update()
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: 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_00fd: Unknown result type (might be due to invalid IL or missing references)
		if (!ModSettings.ModActive || !AutoPinConfig.ActiveEnabled)
		{
			return;
		}
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return;
		}
		Vector3 val = ((Component)localPlayer).transform.position - ((Component)this).transform.position;
		if (((Vector3)(ref val)).sqrMagnitude > _detectionRadius * _detectionRadius)
		{
			_inRange = false;
		}
		else
		{
			if (_inRange || !MapStateRepository.InitialPinsReceived)
			{
				return;
			}
			_inRange = true;
			SharedPin sharedPin = AutoPinPlacer.FindNearbyPin(((Component)this).transform.position, _keyword);
			int num = AutoPinPlacer.CountNearbyObjects(sharedPin?.Pos ?? ((Component)this).transform.position, _keyword, AutoPinConfig.ActiveQuantitySearchRadius);
			string abbreviation = AutoPinConfig.GetAbbreviation(_keyword);
			string text = ((num > 1) ? $"{abbreviation} {num}" : abbreviation);
			if (sharedPin != null)
			{
				if (sharedPin.Name != text)
				{
					AutoPinPlacer.UpdateDiscoveryPin(sharedPin, text, _keyword);
				}
			}
			else
			{
				AutoPinPlacer.PlaceDiscoveryPin(((Component)this).transform.position, text, _keyword);
			}
		}
	}
}
public static class AutoPinPlacer
{
	private static readonly Collider[] _overlapBuffer = (Collider[])(object)new Collider[512];

	public static SharedPin FindNearbyPin(Vector3 position, string keyword)
	{
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		float activeQuantitySearchRadius = AutoPinConfig.ActiveQuantitySearchRadius;
		string abbreviation = AutoPinConfig.GetAbbreviation(keyword);
		SharedPin result = null;
		float num = float.MaxValue;
		foreach (SharedPin clientPin in MapStateRepository.ClientPins)
		{
			if (LabelMatchesAbbreviation(clientPin.Name, abbreviation))
			{
				float num2 = Vector3.Distance(clientPin.Pos, position);
				if (num2 < activeQuantitySearchRadius && num2 < num)
				{
					result = clientPin;
					num = num2;
				}
			}
		}
		return result;
	}

	public static void UpdateDiscoveryPin(SharedPin existing, string newLabel, string keyword)
	{
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		if (ModSettings.ModActive)
		{
			if ((Object)(object)MinimapProxy.Instance != (Object)null)
			{
				PinData val = (Traverse.Create((object)MinimapProxy.Instance).Field("m_pins").GetValue() as List<PinData>)?.FirstOrDefault((Func<PinData, bool>)((PinData p) => MapStateRepository.ArePinsEqual(existing, p)));
				if (val != null)
				{
					Traverse.Create((object)MinimapProxy.Instance).Method("RemovePin", new object[1] { val }).GetValue();
				}
			}
			MapPinSynchronizer.RemovePinFromServer(existing);
		}
		else
		{
			MapStateRepository.ClientPins.Remove(existing);
			if ((Object)(object)MinimapProxy.Instance != (Object)null)
			{
				PinData val2 = (Traverse.Create((object)MinimapProxy.Instance).Field("m_pins").GetValue() as List<PinData>)?.FirstOrDefault((Func<PinData, bool>)((PinData p) => MapStateRepository.ArePinsEqual(existing, p)));
				if (val2 != null)
				{
					Traverse.Create((object)MinimapProxy.Instance).Method("RemovePin", new object[1] { val2 }).GetValue();
				}
			}
		}
		PlaceDiscoveryPin(existing.Pos, newLabel, keyword);
	}

	public static int CountNearbyObjects(Vector3 origin, string keyword, float radius)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		string internalIdForCreature = WorldObjectConfig.GetInternalIdForCreature(keyword);
		int num = Physics.OverlapSphereNonAlloc(origin, radius, _overlapBuffer);
		HashSet<GameObject> hashSet = new HashSet<GameObject>();
		for (int i = 0; i < num; i++)
		{
			GameObject gameObject = ((Component)((Component)_overlapBuffer[i]).transform.root).gameObject;
			if (((Object)gameObject).name.IndexOf(internalIdForCreature, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				hashSet.Add(gameObject);
			}
		}
		return Mathf.Max(1, hashSet.Count);
	}

	public static void PlaceDiscoveryPin(Vector3 position, string label, string keyword)
	{
		//IL_0098: 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)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Expected O, but got Unknown
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)MinimapProxy.Instance == (Object)null)
		{
			return;
		}
		bool flag = WorldObjectConfig.IsKeywordDiscoveryVisible(keyword);
		if (ModSettings.ModActive)
		{
			MapPinSynchronizer.SendPinToServer(new PinData
			{
				m_name = label,
				m_pos = position,
				m_type = (PinType)3,
				m_checked = false,
				m_ownerID = 0L
			}, isDiscovery: true);
			if (flag)
			{
				AutoPinConfig.ApplyDiscoveryIcon(MinimapProxy.AddPin(MinimapProxy.Instance, position, (PinType)3, AutoPinConfig.DiscoveryPinText.Value ? label : "", save: false, isChecked: false, 0L, new PlatformUserID("")), keyword);
			}
		}
		else
		{
			MapStateRepository.ClientPins.Add(new SharedPin
			{
				Name = label,
				Pos = position,
				Type = (PinType)3,
				Checked = false,
				OwnerId = "auto"
			});
			if (flag)
			{
				AutoPinConfig.ApplyDiscoveryIcon(MinimapProxy.AddPin(MinimapProxy.Instance, position, (PinType)3, AutoPinConfig.DiscoveryPinText.Value ? label : "", save: true, isChecked: false, 0L, new PlatformUserID("")), keyword);
			}
		}
	}

	public static void SetNearbyPinChecked(Vector3 position, string keyword, bool state)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		SharedPin pin = FindNearbyPin(position, keyword);
		if (pin == null || pin.Checked == state)
		{
			return;
		}
		pin.Checked = state;
		if ((Object)(object)MinimapProxy.Instance != (Object)null)
		{
			PinData val = (Traverse.Create((object)MinimapProxy.Instance).Field("m_pins").GetValue() as List<PinData>)?.FirstOrDefault((Func<PinData, bool>)((PinData p) => MapStateRepository.ArePinsEqual(pin, p)));
			if (val != null)
			{
				val.m_checked = state;
			}
		}
		MapPinSynchronizer.CheckPinOnServer(pin, state);
	}

	private static bool LabelMatchesAbbreviation(string pinLabel, string abbr)
	{
		if (!string.Equals(pinLabel, abbr, StringComparison.OrdinalIgnoreCase))
		{
			return pinLabel.StartsWith(abbr + " ", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}
}
public static class WorldObjectPatches
{
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	private class ObjectDBAwakeHook
	{
		private static void Postfix(ObjectDB __instance)
		{
			RadarIconLoader.LoadAll(__instance);
		}
	}

	[HarmonyPatch(typeof(Pickable), "Awake")]
	private class PickableAwakeHook
	{
		private static void Postfix(Pickable __instance)
		{
			if (__instance.CanBePicked())
			{
				string text = AutoPinConfig.MatchKeyword(((Object)__instance).name, WorldObjectConfig.PickablesDiscovery);
				string text2 = AutoPinConfig.MatchKeyword(((Object)__instance).name, WorldObjectConfig.PickablesRadar);
				if (text != null || text2 != null)
				{
					AttachTrackers(((Component)__instance).gameObject, text, text2, WorldObjectConfig.GetPickableDiscoveryRadius(text ?? text2));
				}
			}
		}
	}

	[HarmonyPatch(typeof(Pickable), "SetPicked")]
	private class PickableSetPickedHook
	{
		private static void Postfix(Pickable __instance, bool picked)
		{
			//IL_0085: 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)
			string text = AutoPinConfig.MatchKeyword(((Object)__instance).name, WorldObjectConfig.PickablesDiscovery);
			string text2 = AutoPinConfig.MatchKeyword(((Object)__instance).name, WorldObjectConfig.PickablesRadar);
			if (text == null && text2 == null)
			{
				return;
			}
			if (picked)
			{
				ProximityWatcher component = ((Component)__instance).GetComponent<ProximityWatcher>();
				if ((Object)(object)component != (Object)null)
				{
					Object.Destroy((Object)(object)component);
				}
				RadarPinComponent component2 = ((Component)__instance).GetComponent<RadarPinComponent>();
				if ((Object)(object)component2 != (Object)null)
				{
					Object.Destroy((Object)(object)component2);
				}
				if (text != null && AutoPinConfig.ActiveEnabled)
				{
					AutoPinPlacer.SetNearbyPinChecked(((Component)__instance).transform.position, text, state: true);
				}
			}
			else
			{
				if (text != null && AutoPinConfig.ActiveEnabled)
				{
					AutoPinPlacer.SetNearbyPinChecked(((Component)__instance).transform.position, text, state: false);
				}
				AttachTrackers(((Component)__instance).gameObject, text, text2, WorldObjectConfig.GetPickableDiscoveryRadius(text ?? text2));
			}
		}
	}

	[HarmonyPatch(typeof(Destructible), "Awake")]
	private class DestructibleAwakeHook
	{
		private static void Postfix(Destructible __instance)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			string text = AutoPinConfig.MatchKeyword(((Object)__instance).name, WorldObjectConfig.OresDiscovery);
			string text2 = AutoPinConfig.MatchKeyword(((Object)__instance).name, WorldObjectConfig.OresRadar);
			if (text != null || text2 != null)
			{
				if (text != null && AutoPinConfig.ActiveEnabled)
				{
					AutoPinPlacer.SetNearbyPinChecked(((Component)__instance).transform.position, text, state: false);
				}
				AttachTrackers(((Component)__instance).gameObject, text, text2, WorldObjectConfig.ActiveOresDiscoveryRadius);
			}
		}
	}

	[HarmonyPatch(typeof(Destructible), "Destroy")]
	private class D