Decompiled source of PortalTracking v1.0.0

BepInEx\plugins\PortalTracking.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("PortalTracking")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+31490a69edb7828a6a46bce36461cbbde547abc4")]
[assembly: AssemblyProduct("PortalTracking")]
[assembly: AssemblyTitle("PortalTracking")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PortalTracking
{
	internal static class EffectiveConfig
	{
		private static bool _fromServer;

		private static bool _clusterHubs;

		private static float _hubRadius;

		private static HubIconType _hubIcon;

		internal static bool ClusterHubs
		{
			get
			{
				if (!_fromServer)
				{
					return PortalTrackingPlugin.ClusterHubs.Value;
				}
				return _clusterHubs;
			}
		}

		internal static float HubRadius => Mathf.Clamp(_fromServer ? _hubRadius : PortalTrackingPlugin.HubRadius.Value, 0f, 500f);

		internal static HubIconType HubIcon
		{
			get
			{
				if (!_fromServer)
				{
					return PortalTrackingPlugin.HubIcon.Value;
				}
				return _hubIcon;
			}
		}

		internal static void ClearServerOverrides()
		{
			_fromServer = false;
		}

		internal static void ApplyFromServer(bool overridesEnabled, bool clusterHubs, float hubRadius, HubIconType hubIcon)
		{
			if (!overridesEnabled)
			{
				_fromServer = false;
				return;
			}
			_fromServer = true;
			_clusterHubs = clusterHubs;
			_hubRadius = hubRadius;
			_hubIcon = hubIcon;
		}
	}
	internal static class PortalCatalog
	{
		internal readonly struct PrefabInfo
		{
			public readonly string Prefab;

			public readonly string Label;

			public readonly int Hash;

			public PrefabInfo(string prefab, string label)
			{
				Prefab = prefab;
				Label = label;
				Hash = StringExtensionMethods.GetStableHashCode(prefab);
			}
		}

		internal static readonly PrefabInfo[] Prefabs = new PrefabInfo[3]
		{
			new PrefabInfo("portal_wood", "Portal"),
			new PrefabInfo("portal_stone", "Stone portal"),
			new PrefabInfo("portal", "Portal")
		};

		private static readonly Dictionary<int, PrefabInfo> ByHash = BuildLookup();

		private static Dictionary<int, PrefabInfo> BuildLookup()
		{
			Dictionary<int, PrefabInfo> dictionary = new Dictionary<int, PrefabInfo>();
			PrefabInfo[] prefabs = Prefabs;
			for (int i = 0; i < prefabs.Length; i++)
			{
				PrefabInfo value = prefabs[i];
				dictionary[value.Hash] = value;
			}
			return dictionary;
		}

		internal static bool TryGet(int prefabHash, out PrefabInfo info)
		{
			return ByHash.TryGetValue(prefabHash, out info);
		}

		internal static bool IsPortalPrefab(int prefabHash)
		{
			if (ByHash.ContainsKey(prefabHash))
			{
				return true;
			}
			try
			{
				if ((Object)(object)Game.instance != (Object)null && Game.instance.PortalPrefabHash != null)
				{
					return Game.instance.PortalPrefabHash.Contains(prefabHash);
				}
			}
			catch
			{
			}
			return false;
		}

		internal static string TypeLabel(int prefabHash)
		{
			if (TryGet(prefabHash, out var info))
			{
				return info.Label;
			}
			return "Portal";
		}

		internal static string DisplayName(string tag)
		{
			if (!string.IsNullOrWhiteSpace(tag))
			{
				return tag.Trim();
			}
			return string.Empty;
		}

		internal static int HubIconPrefabHash()
		{
			return StringExtensionMethods.GetStableHashCode((EffectiveConfig.HubIcon == HubIconType.Stone) ? "portal_stone" : "portal_wood");
		}
	}
	internal sealed class DisplayPin
	{
		public ZDOID Key;

		public Vector3 Position;

		public string Name = string.Empty;

		public int PrefabHash;

		public int MemberCount = 1;
	}
	internal static class PortalHubs
	{
		internal static List<DisplayPin> Build(IReadOnlyList<PortalSnapshot> portals)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			List<DisplayPin> list = new List<DisplayPin>();
			if (portals == null || portals.Count == 0)
			{
				return list;
			}
			List<PortalSnapshot> list2 = new List<PortalSnapshot>(portals.Count);
			foreach (PortalSnapshot portal in portals)
			{
				if (IsSanePosition(portal.Position))
				{
					list2.Add(portal);
				}
			}
			if (list2.Count == 0)
			{
				return list;
			}
			float hubRadius = EffectiveConfig.HubRadius;
			if (hubRadius <= 0f || !EffectiveConfig.ClusterHubs)
			{
				foreach (PortalSnapshot item in list2)
				{
					list.Add(ToSingle(item));
				}
				return list;
			}
			int count = list2.Count;
			int[] parent = new int[count];
			for (int i = 0; i < count; i++)
			{
				parent[i] = i;
			}
			float num = hubRadius * hubRadius;
			for (int j = 0; j < count; j++)
			{
				for (int k = j + 1; k < count; k++)
				{
					if (HorizontalDistanceSq(list2[j].Position, list2[k].Position) <= num)
					{
						Union(j, k);
					}
				}
			}
			Dictionary<int, List<int>> dictionary = new Dictionary<int, List<int>>();
			for (int l = 0; l < count; l++)
			{
				int key = Find(l);
				if (!dictionary.TryGetValue(key, out var value))
				{
					value = (dictionary[key] = new List<int>());
				}
				value.Add(l);
			}
			foreach (List<int> value2 in dictionary.Values)
			{
				if (value2.Count == 1)
				{
					list.Add(ToSingle(list2[value2[0]]));
					continue;
				}
				Vector3 val = Vector3.zero;
				ZDOID id = list2[value2[0]].Id;
				foreach (int item2 in value2)
				{
					val += list2[item2].Position;
				}
				list.Add(new DisplayPin
				{
					Key = id,
					Position = val / (float)value2.Count,
					Name = "Hub",
					PrefabHash = PortalCatalog.HubIconPrefabHash(),
					MemberCount = value2.Count
				});
			}
			return list;
			int Find(int num2)
			{
				while (parent[num2] != num2)
				{
					parent[num2] = parent[parent[num2]];
					num2 = parent[num2];
				}
				return num2;
			}
			void Union(int a, int b)
			{
				a = Find(a);
				b = Find(b);
				if (a != b)
				{
					parent[b] = a;
				}
			}
		}

		private static DisplayPin ToSingle(PortalSnapshot portal)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			return new DisplayPin
			{
				Key = portal.Id,
				Position = portal.Position,
				Name = (portal.Name ?? string.Empty),
				PrefabHash = portal.PrefabHash,
				MemberCount = 1
			};
		}

		private static float HorizontalDistanceSq(Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			float num = a.x - b.x;
			float num2 = a.z - b.z;
			return num * num + num2 * num2;
		}

		private static bool IsSanePosition(Vector3 pos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (float.IsNaN(pos.x) || float.IsNaN(pos.y) || float.IsNaN(pos.z))
			{
				return false;
			}
			if (float.IsInfinity(pos.x) || float.IsInfinity(pos.y) || float.IsInfinity(pos.z))
			{
				return false;
			}
			if (((Vector3)(ref pos)).sqrMagnitude < 0.01f)
			{
				return false;
			}
			return true;
		}
	}
	internal static class PortalPins
	{
		private sealed class MarkerState
		{
			public Vector3 Pos;

			public string Name = string.Empty;

			public int PrefabHash;

			public GameObject? Marker;
		}

		private static readonly Dictionary<ZDOID, MarkerState> Markers = new Dictionary<ZDOID, MarkerState>();

		private static readonly Dictionary<int, Sprite?> SpriteCache = new Dictionary<int, Sprite>();

		private static Sprite? _fallbackSprite;

		private static int _lastLoggedCount = -1;

		private static bool _loggedDrawError;

		private static Font? _mapFont;

		internal static void Clear()
		{
			foreach (MarkerState value in Markers.Values)
			{
				DestroyMarker(value);
			}
			Markers.Clear();
			_lastLoggedCount = -1;
		}

		internal static void Apply(IReadOnlyList<PortalSnapshot> portals)
		{
			try
			{
				ApplyInternal(portals);
			}
			catch (Exception arg)
			{
				PortalTrackingPlugin.Log.LogError((object)$"PortalPins.Apply failed: {arg}");
			}
		}

		private static void ApplyInternal(IReadOnlyList<PortalSnapshot> portals)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			if (!PortalTrackingPlugin.ShowPins.Value)
			{
				Clear();
				return;
			}
			List<DisplayPin> list = PortalHubs.Build(portals);
			HashSet<ZDOID> hashSet = new HashSet<ZDOID>();
			foreach (DisplayPin item in list)
			{
				hashSet.Add(item.Key);
				if (!Markers.TryGetValue(item.Key, out MarkerState value))
				{
					value = new MarkerState();
					Markers[item.Key] = value;
				}
				value.Pos = item.Position;
				value.Name = item.Name ?? string.Empty;
				value.PrefabHash = item.PrefabHash;
			}
			List<ZDOID> list2 = new List<ZDOID>();
			foreach (ZDOID key in Markers.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list2.Add(key);
				}
			}
			foreach (ZDOID item2 in list2)
			{
				RemoveTracked(item2);
			}
			if (Markers.Count == _lastLoggedCount)
			{
				return;
			}
			_lastLoggedCount = Markers.Count;
			PortalTrackingPlugin.Log.LogInfo((object)$"Map markers tracked: {Markers.Count} (from {portals.Count} portal(s))");
			foreach (KeyValuePair<ZDOID, MarkerState> marker in Markers)
			{
				string arg = (string.IsNullOrWhiteSpace(marker.Value.Name) ? PortalCatalog.TypeLabel(marker.Value.PrefabHash) : marker.Value.Name);
				PortalTrackingPlugin.Log.LogInfo((object)$"  {arg} @ {marker.Value.Pos}");
			}
		}

		internal static void UpdateDrawnMarkers()
		{
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!PortalTrackingPlugin.ShowPins.Value || !Object.op_Implicit((Object)(object)Minimap.instance) || Markers.Count == 0)
				{
					if (Markers.Count == 0)
					{
						return;
					}
					{
						foreach (MarkerState value in Markers.Values)
						{
							DestroyMarker(value);
						}
						return;
					}
				}
				Minimap instance = Minimap.instance;
				GameObject field = GetField<GameObject>(instance, "m_largeRoot");
				RawImage field2 = GetField<RawImage>(instance, "m_mapImageLarge");
				RawImage field3 = GetField<RawImage>(instance, "m_mapImageSmall");
				RectTransform field4 = GetField<RectTransform>(instance, "m_pinRootLarge");
				RectTransform field5 = GetField<RectTransform>(instance, "m_pinRootSmall");
				GameObject field6 = GetField<GameObject>(instance, "m_pinPrefab");
				if (!Object.op_Implicit((Object)(object)field) || !Object.op_Implicit((Object)(object)field2) || !Object.op_Implicit((Object)(object)field3) || !Object.op_Implicit((Object)(object)field4) || !Object.op_Implicit((Object)(object)field5) || !Object.op_Implicit((Object)(object)field6))
				{
					return;
				}
				bool activeSelf = field.activeSelf;
				RawImage val = (activeSelf ? field2 : field3);
				RectTransform val2 = (activeSelf ? field4 : field5);
				if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)((Graphic)val).rectTransform))
				{
					return;
				}
				float num = (activeSelf ? GetField<float>(instance, "m_pinSizeLarge") : GetField<float>(instance, "m_pinSizeSmall"));
				if (num <= 0f)
				{
					num = (activeSelf ? 32f : 16f);
				}
				foreach (MarkerState item in new List<MarkerState>(Markers.Values))
				{
					if (item != null)
					{
						if (!IsPointVisible(item.Pos, val, instance))
						{
							DestroyMarker(item);
						}
						else
						{
							DrawMarker(item, num, val2, val, field6, activeSelf, instance);
						}
					}
				}
			}
			catch (Exception arg)
			{
				if (!_loggedDrawError)
				{
					_loggedDrawError = true;
					PortalTrackingPlugin.Log.LogWarning((object)$"UpdateDrawnMarkers: {arg}");
				}
			}
		}

		private static void DrawMarker(MarkerState state, float size, RectTransform parent, RawImage rawImage, GameObject pinPrefab, bool large, Minimap mm)
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: 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)
			GameObject val = state.Marker;
			if (!Object.op_Implicit((Object)(object)val) || (Object)(object)val.transform.parent != (Object)(object)parent)
			{
				if (Object.op_Implicit((Object)(object)val))
				{
					Object.Destroy((Object)(object)val);
				}
				val = (state.Marker = Object.Instantiate<GameObject>(pinPrefab));
				val.transform.SetParent((Transform)(object)parent, false);
				Image component = val.GetComponent<Image>();
				if (Object.op_Implicit((Object)(object)component))
				{
					component.sprite = ResolveSprite(state.PrefabHash) ?? component.sprite;
				}
				Transform transform = val.transform;
				RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null);
				if (Object.op_Implicit((Object)(object)val2))
				{
					val2.SetSizeWithCurrentAnchors((Axis)0, size * 1.25f);
					val2.SetSizeWithCurrentAnchors((Axis)1, size * 1.25f);
				}
			}
			WorldToMapPoint(state.Pos, mm, out var mx, out var my);
			Vector2 anchoredPosition = MapPointToLocalGuiPos(mx, my, rawImage);
			Transform transform2 = val.transform;
			RectTransform val3 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null);
			if (Object.op_Implicit((Object)(object)val3))
			{
				val3.anchoredPosition = anchoredPosition;
			}
			Transform val4 = val.transform.Find("Checked");
			if (Object.op_Implicit((Object)(object)val4))
			{
				((Component)val4).gameObject.SetActive(false);
			}
			SetMarkerName(val, state.Name, large, size);
		}

		private static void SetMarkerName(GameObject marker, string name, bool large, float pinSize)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			Transform val = marker.transform.Find("Name");
			if (Object.op_Implicit((Object)(object)val))
			{
				((Component)val).gameObject.SetActive(false);
			}
			Transform val2 = marker.transform.Find("PortalTrackingName");
			Text val4;
			if (!Object.op_Implicit((Object)(object)val2))
			{
				GameObject val3 = new GameObject("PortalTrackingName");
				val2 = val3.transform;
				val2.SetParent(marker.transform, false);
				LayoutLabelRect(val3.AddComponent<RectTransform>(), pinSize);
				val4 = val3.AddComponent<Text>();
				val4.font = GetMapFont();
				val4.fontSize = Mathf.Clamp(Mathf.RoundToInt(pinSize * 0.35f), 10, 14);
				val4.fontStyle = (FontStyle)1;
				val4.alignment = (TextAnchor)1;
				((Graphic)val4).color = new Color(0.75f, 0.9f, 1f, 1f);
				val4.horizontalOverflow = (HorizontalWrapMode)1;
				val4.verticalOverflow = (VerticalWrapMode)1;
				((Graphic)val4).raycastTarget = false;
				val4.supportRichText = false;
				Outline obj = val3.AddComponent<Outline>();
				((Shadow)obj).effectColor = new Color(0f, 0f, 0f, 0.9f);
				((Shadow)obj).effectDistance = new Vector2(1.2f, -1.2f);
			}
			else
			{
				val4 = ((Component)val2).GetComponent<Text>();
				RectTransform val5 = (RectTransform)(object)((val2 is RectTransform) ? val2 : null);
				if (val5 != null)
				{
					LayoutLabelRect(val5, pinSize);
				}
			}
			((Component)val2).gameObject.SetActive(large && !string.IsNullOrWhiteSpace(name));
			if (large && !((Object)(object)val4 == (Object)null))
			{
				val4.text = ((Localization.instance != null) ? Localization.instance.Localize(name) : name);
			}
		}

		private static void LayoutLabelRect(RectTransform rt, float pinSize)
		{
			//IL_000b: 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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			rt.sizeDelta = new Vector2(220f, 48f);
			rt.pivot = new Vector2(0.5f, 1f);
			rt.anchorMin = new Vector2(0.5f, 0.5f);
			rt.anchorMax = new Vector2(0.5f, 0.5f);
			rt.anchoredPosition = new Vector2(0f, (0f - pinSize) * 0.4f);
			((Transform)rt).localScale = Vector3.one;
		}

		private static Font GetMapFont()
		{
			if (Object.op_Implicit((Object)(object)_mapFont))
			{
				return _mapFont;
			}
			try
			{
				if (Object.op_Implicit((Object)(object)Minimap.instance))
				{
					Text[] componentsInChildren = ((Component)Minimap.instance).GetComponentsInChildren<Text>(true);
					foreach (Text val in componentsInChildren)
					{
						if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.font))
						{
							_mapFont = val.font;
							return _mapFont;
						}
					}
				}
			}
			catch
			{
			}
			_mapFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
			if (!Object.op_Implicit((Object)(object)_mapFont))
			{
				_mapFont = Font.CreateDynamicFontFromOSFont("Arial", 16);
			}
			return _mapFont;
		}

		private static Sprite? ResolveSprite(int prefabHash)
		{
			if (SpriteCache.TryGetValue(prefabHash, out Sprite value))
			{
				return value ?? GetFallbackSprite();
			}
			Sprite val = null;
			try
			{
				if ((Object)(object)ZNetScene.instance != (Object)null)
				{
					GameObject prefab = ZNetScene.instance.GetPrefab(prefabHash);
					if (Object.op_Implicit((Object)(object)prefab))
					{
						Piece component = prefab.GetComponent<Piece>();
						if (Object.op_Implicit((Object)(object)component))
						{
							object? obj = AccessTools.Field(typeof(Piece), "m_icon")?.GetValue(component);
							val = (Sprite)((obj is Sprite) ? obj : null);
						}
					}
				}
			}
			catch
			{
			}
			SpriteCache[prefabHash] = val;
			return val ?? GetFallbackSprite();
		}

		private static Sprite? GetFallbackSprite()
		{
			if (Object.op_Implicit((Object)(object)_fallbackSprite))
			{
				return _fallbackSprite;
			}
			try
			{
				Minimap instance = Minimap.instance;
				if (!Object.op_Implicit((Object)(object)instance))
				{
					return null;
				}
				if (GetField<object>(instance, "m_icons") is IList list)
				{
					foreach (object item in list)
					{
						if (item == null)
						{
							continue;
						}
						string text = item.GetType().GetField("m_name")?.GetValue(item)?.ToString() ?? item.GetType().GetField("m_type")?.GetValue(item)?.ToString();
						object? obj = item.GetType().GetField("m_icon")?.GetValue(item);
						Sprite val = (Sprite)(((obj is Sprite) ? obj : null) ?? ((object)/*isinst with value type is only supported in some contexts*/));
						if (!((Object)(object)val == (Object)null))
						{
							if (text != null && (text.Contains("Icon") || text.Contains("Death")))
							{
								_fallbackSprite = val;
								return _fallbackSprite;
							}
							if (_fallbackSprite == null)
							{
								_fallbackSprite = val;
							}
						}
					}
				}
			}
			catch
			{
			}
			return _fallbackSprite;
		}

		private static bool IsPointVisible(Vector3 p, RawImage map, Minimap mm)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			WorldToMapPoint(p, mm, out var mx, out var my);
			Rect uvRect = map.uvRect;
			if (mx > ((Rect)(ref uvRect)).xMin && mx < ((Rect)(ref uvRect)).xMax && my > ((Rect)(ref uvRect)).yMin)
			{
				return my < ((Rect)(ref uvRect)).yMax;
			}
			return false;
		}

		private static void WorldToMapPoint(Vector3 p, Minimap mm, out float mx, out float my)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			int field = GetField<int>(mm, "m_textureSize");
			float field2 = GetField<float>(mm, "m_pixelSize");
			if (field <= 0 || field2 <= 0f)
			{
				mx = 0f;
				my = 0f;
				return;
			}
			int num = field / 2;
			mx = p.x / field2 + (float)num;
			my = p.z / field2 + (float)num;
			mx /= field;
			my /= field;
		}

		private static Vector2 MapPointToLocalGuiPos(float mx, float my, RawImage img)
		{
			//IL_0015: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_009d: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)img) || !Object.op_Implicit((Object)(object)((Graphic)img).rectTransform))
			{
				return Vector2.zero;
			}
			Rect uvRect = img.uvRect;
			if (((Rect)(ref uvRect)).width <= 0f || ((Rect)(ref uvRect)).height <= 0f)
			{
				return Vector2.zero;
			}
			Vector2 result = default(Vector2);
			((Vector2)(ref result))..ctor((mx - ((Rect)(ref uvRect)).xMin) / ((Rect)(ref uvRect)).width, (my - ((Rect)(ref uvRect)).yMin) / ((Rect)(ref uvRect)).height);
			Rect rect = ((Graphic)img).rectTransform.rect;
			result.x *= ((Rect)(ref rect)).width;
			result.y *= ((Rect)(ref rect)).height;
			return result;
		}

		private static T? GetField<T>(object obj, string name)
		{
			FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), name);
			if (fieldInfo == null)
			{
				return default(T);
			}
			object value = fieldInfo.GetValue(obj);
			if (value is T)
			{
				return (T)value;
			}
			if (typeof(T).IsValueType && value != null)
			{
				return (T)value;
			}
			return default(T);
		}

		private static void RemoveTracked(ZDOID id)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if (Markers.TryGetValue(id, out MarkerState value))
			{
				DestroyMarker(value);
				Markers.Remove(id);
			}
		}

		private static void DestroyMarker(MarkerState state)
		{
			if (Object.op_Implicit((Object)(object)state.Marker))
			{
				Object.Destroy((Object)(object)state.Marker);
				state.Marker = null;
			}
		}

		private static bool IsSanePosition(Vector3 pos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (float.IsNaN(pos.x) || float.IsNaN(pos.y) || float.IsNaN(pos.z))
			{
				return false;
			}
			if (float.IsInfinity(pos.x) || float.IsInfinity(pos.y) || float.IsInfinity(pos.z))
			{
				return false;
			}
			if (((Vector3)(ref pos)).sqrMagnitude < 0.01f)
			{
				return false;
			}
			return true;
		}
	}
	internal sealed class PortalSnapshot
	{
		public ZDOID Id;

		public Vector3 Position;

		public string Name = string.Empty;

		public int PrefabHash;
	}
	internal static class PortalSync
	{
		private static float _timer;

		private static readonly List<PortalSnapshot> Latest = new List<PortalSnapshot>();

		private static readonly object Gate = new object();

		private static int _lastLoggedCount = -1;

		private static bool _loggedProtocolMismatch;

		internal static IReadOnlyList<PortalSnapshot> GetLatest()
		{
			lock (Gate)
			{
				return Latest.ToArray();
			}
		}

		internal static void Tick(float dt)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null || !ZNet.instance.IsServer())
			{
				return;
			}
			float num = Mathf.Clamp(PortalTrackingPlugin.SyncInterval.Value, 1f, 30f);
			_timer += dt;
			if (_timer < num)
			{
				return;
			}
			_timer = 0f;
			try
			{
				BroadcastNow();
			}
			catch (Exception arg)
			{
				PortalTrackingPlugin.Log.LogError((object)$"Portal sync failed: {arg}");
			}
		}

		internal static void BroadcastNow()
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZDOMan.instance != null)
			{
				List<PortalSnapshot> list = CollectPortals();
				ZPackage val = WritePackage(list);
				lock (Gate)
				{
					Latest.Clear();
					Latest.AddRange(list);
				}
				if (list.Count != _lastLoggedCount)
				{
					_lastLoggedCount = list.Count;
					PortalTrackingPlugin.Log.LogInfo((object)$"Broadcasting {list.Count} portal pin(s) to clients.");
				}
				if (!ZNet.instance.IsDedicated() && Object.op_Implicit((Object)(object)Minimap.instance))
				{
					PortalPins.Apply(list);
				}
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "PortalTracking.Sync", new object[1] { val });
			}
		}

		internal static void RequestFromServer()
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null)
			{
				return;
			}
			if (ZNet.instance.IsServer())
			{
				BroadcastNow();
				return;
			}
			long serverPeerIdSafe = GetServerPeerIdSafe();
			if (serverPeerIdSafe != 0L)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerIdSafe, "PortalTracking.RequestSync", Array.Empty<object>());
			}
		}

		internal static void HandleRequestSync(long sender)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				ZPackage val = WritePackage(CollectPortals());
				ZRoutedRpc.instance.InvokeRoutedRPC(sender, "PortalTracking.Sync", new object[1] { val });
			}
		}

		internal static void HandleSync(long sender, ZPackage pkg)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsDedicated() || pkg == null || pkg.Size() == 0)
			{
				return;
			}
			try
			{
				pkg.SetPos(0);
				if (TryReadPackage(pkg, out List<PortalSnapshot> portals))
				{
					lock (Gate)
					{
						Latest.Clear();
						Latest.AddRange(portals);
					}
					if (portals.Count != _lastLoggedCount)
					{
						_lastLoggedCount = portals.Count;
						PortalTrackingPlugin.Log.LogInfo((object)$"Received {portals.Count} portal pin(s) from server.");
					}
					PortalPins.Apply(portals);
				}
			}
			catch (Exception arg)
			{
				PortalTrackingPlugin.Log.LogError((object)$"Portal sync receive failed: {arg}");
			}
		}

		private static long GetServerPeerIdSafe()
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null);
				if (methodInfo != null && ZRoutedRpc.instance != null && methodInfo.Invoke(ZRoutedRpc.instance, null) is long result)
				{
					return result;
				}
			}
			catch (Exception ex)
			{
				PortalTrackingPlugin.Log.LogWarning((object)("GetServerPeerID reflection failed: " + ex.Message));
			}
			try
			{
				ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetServerPeer() : null);
				if (val != null)
				{
					FieldInfo fieldInfo = AccessTools.Field(((object)val).GetType(), "m_uid");
					if (fieldInfo != null && fieldInfo.GetValue(val) is long result2)
					{
						return result2;
					}
				}
			}
			catch (Exception ex2)
			{
				PortalTrackingPlugin.Log.LogWarning((object)("GetServerPeer fallback failed: " + ex2.Message));
			}
			return 0L;
		}

		private static List<PortalSnapshot> CollectPortals()
		{
			List<PortalSnapshot> result = new List<PortalSnapshot>();
			ZDOMan instance = ZDOMan.instance;
			if (instance == null)
			{
				return result;
			}
			HashSet<ZDOID> seen = new HashSet<ZDOID>();
			CollectFromPortalList(instance, Consider);
			if (result.Count == 0)
			{
				CollectFromObjectsById(instance, Consider);
			}
			if (result.Count == 0)
			{
				CollectFromPrefabIterative(instance, Consider);
			}
			return result;
			void Consider(ZDO? zdo)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				//IL_0060: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: 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)
				if (zdo != null && zdo.IsValid() && seen.Add(zdo.m_uid))
				{
					int prefab = zdo.GetPrefab();
					if (PortalCatalog.IsPortalPrefab(prefab))
					{
						string text = ReadPortalTag(zdo);
						if (PortalTrackingPlugin.ShowUntagged.Value || !string.IsNullOrWhiteSpace(text))
						{
							Vector3 position = zdo.GetPosition();
							result.Add(new PortalSnapshot
							{
								Id = zdo.m_uid,
								Position = position,
								Name = PortalCatalog.DisplayName(text),
								PrefabHash = prefab
							});
						}
					}
				}
			}
		}

		private static string ReadPortalTag(ZDO zdo)
		{
			try
			{
				return zdo.GetString(ZDOVars.s_tag, string.Empty) ?? string.Empty;
			}
			catch
			{
				return zdo.GetString("tag", string.Empty) ?? string.Empty;
			}
		}

		private static void CollectFromPortalList(ZDOMan zdoMan, Action<ZDO?> consider)
		{
			try
			{
				List<ZDO> portalList = zdoMan.GetPortalList();
				if (portalList == null)
				{
					return;
				}
				foreach (ZDO item in portalList)
				{
					consider(item);
				}
			}
			catch (Exception ex)
			{
				PortalTrackingPlugin.Log.LogWarning((object)("GetPortalList failed: " + ex.Message));
			}
		}

		private static void CollectFromObjectsById(ZDOMan zdoMan, Action<ZDO?> consider)
		{
			try
			{
				object obj = AccessTools.Field(typeof(ZDOMan), "m_objectsByID")?.GetValue(zdoMan);
				if (obj is IDictionary dictionary)
				{
					{
						foreach (object value2 in dictionary.Values)
						{
							ZDO val = (ZDO)((value2 is ZDO) ? value2 : null);
							if (val != null)
							{
								consider(val);
							}
						}
						return;
					}
				}
				if (!(obj is IEnumerable enumerable))
				{
					return;
				}
				foreach (object item in enumerable)
				{
					ZDO val2 = (ZDO)((item is ZDO) ? item : null);
					if (val2 != null)
					{
						consider(val2);
					}
					else if (item is DictionaryEntry dictionaryEntry)
					{
						object? value = dictionaryEntry.Value;
						ZDO val3 = (ZDO)((value is ZDO) ? value : null);
						if (val3 != null)
						{
							consider(val3);
						}
					}
				}
			}
			catch (Exception ex)
			{
				PortalTrackingPlugin.Log.LogWarning((object)("ZDO id scan failed: " + ex.Message));
			}
		}

		private static void CollectFromPrefabIterative(ZDOMan zdoMan, Action<ZDO?> consider)
		{
			List<ZDO> list = new List<ZDO>();
			PortalCatalog.PrefabInfo[] prefabs = PortalCatalog.Prefabs;
			for (int i = 0; i < prefabs.Length; i++)
			{
				PortalCatalog.PrefabInfo prefabInfo = prefabs[i];
				list.Clear();
				int num = 0;
				int num2 = 0;
				while (!zdoMan.GetAllZDOsWithPrefabIterative(prefabInfo.Prefab, list, ref num))
				{
					if (++num2 > 100000)
					{
						PortalTrackingPlugin.Log.LogWarning((object)("Prefab scan aborted for " + prefabInfo.Prefab));
						break;
					}
				}
				foreach (ZDO item in list)
				{
					consider(item);
				}
			}
		}

		private static ZPackage WritePackage(List<PortalSnapshot> portals)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			val.Write(2);
			val.Write(PortalTrackingPlugin.ServerOverridesClient.Value);
			val.Write(PortalTrackingPlugin.ClusterHubs.Value);
			val.Write(Mathf.Clamp(PortalTrackingPlugin.HubRadius.Value, 0f, 500f));
			val.Write((int)PortalTrackingPlugin.HubIcon.Value);
			val.Write(portals.Count);
			foreach (PortalSnapshot portal in portals)
			{
				val.Write(portal.Id);
				val.Write(portal.Position);
				val.Write(portal.Name ?? string.Empty);
				val.Write(portal.PrefabHash);
			}
			return val;
		}

		private static bool TryReadPackage(ZPackage pkg, out List<PortalSnapshot> portals)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			portals = new List<PortalSnapshot>();
			int num = pkg.ReadInt();
			if (num != 2)
			{
				NotifyProtocolMismatch(num);
				return false;
			}
			bool overridesEnabled = pkg.ReadBool();
			bool clusterHubs = pkg.ReadBool();
			float hubRadius = pkg.ReadSingle();
			HubIconType hubIcon = ((pkg.ReadInt() == 1) ? HubIconType.Stone : HubIconType.Wood);
			EffectiveConfig.ApplyFromServer(overridesEnabled, clusterHubs, hubRadius, hubIcon);
			int num2 = pkg.ReadInt();
			if (num2 < 0 || num2 > 1024)
			{
				PortalTrackingPlugin.Log.LogWarning((object)$"Portal sync rejected: unreasonable portal count {num2}.");
				return false;
			}
			portals = new List<PortalSnapshot>(num2);
			for (int i = 0; i < num2; i++)
			{
				portals.Add(new PortalSnapshot
				{
					Id = pkg.ReadZDOID(),
					Position = pkg.ReadVector3(),
					Name = (pkg.ReadString() ?? string.Empty),
					PrefabHash = pkg.ReadInt()
				});
			}
			return true;
		}

		private static void NotifyProtocolMismatch(int serverVersion)
		{
			if (_loggedProtocolMismatch)
			{
				return;
			}
			_loggedProtocolMismatch = true;
			int num = 2;
			string text = ((serverVersion >= num) ? $"PortalTracking: your mod is outdated (server protocol {serverVersion}, you have {num}). Update PortalTracking to match the server." : $"PortalTracking: server mod is outdated (server protocol {serverVersion}, you have {num}). Ask the host to update PortalTracking.");
			PortalTrackingPlugin.Log.LogWarning((object)text);
			try
			{
				if (Object.op_Implicit((Object)(object)MessageHud.instance))
				{
					MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
				}
			}
			catch (Exception ex)
			{
				PortalTrackingPlugin.Log.LogWarning((object)("Could not show protocol mismatch HUD: " + ex.Message));
			}
			try
			{
				if (Object.op_Implicit((Object)(object)Chat.instance))
				{
					((Terminal)Chat.instance).AddString("PortalTracking", text, (Type)1, false);
				}
			}
			catch
			{
			}
		}
	}
	public enum HubIconType
	{
		Wood,
		Stone
	}
	[BepInPlugin("matthb1.portaltracking", "PortalTracking", "1.0.0")]
	public class PortalTrackingPlugin : BaseUnityPlugin
	{
		public const string GUID = "matthb1.portaltracking";

		public const string NAME = "PortalTracking";

		public const string VERSION = "1.0.0";

		public const int ProtocolVersion = 2;

		public const string RpcSync = "PortalTracking.Sync";

		public const string RpcRequestSync = "PortalTracking.RequestSync";

		internal static PortalTrackingPlugin Instance;

		internal static ConfigEntry<bool> ShowPins;

		internal static ConfigEntry<bool> ShowUntagged;

		internal static ConfigEntry<float> SyncInterval;

		internal static ConfigEntry<bool> ClusterHubs;

		internal static ConfigEntry<float> HubRadius;

		internal static ConfigEntry<HubIconType> HubIcon;

		internal static ConfigEntry<bool> ServerOverridesClient;

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ShowPins = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowPins", true, "Show portal pins on the minimap and world map.");
			ShowUntagged = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowUntagged", true, "Show pins for portals that have no tag/name yet.");
			SyncInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SyncInterval", 3f, "Seconds between server portal syncs (1-30).");
			ClusterHubs = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ClusterHubs", true, "Merge nearby portals into a single Hub pin.");
			HubRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "HubRadius", 50f, "Horizontal meters - portals within this range share one Hub pin (0-500).");
			HubIcon = ((BaseUnityPlugin)this).Config.Bind<HubIconType>("General", "HubIcon", HubIconType.Wood, "Map icon for Hub pins: Wood or Stone portal.");
			ServerOverridesClient = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ServerOverridesClient", true, "Server only: push ClusterHubs, HubRadius, and HubIcon to clients (overrides their local values).");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"PortalTracking 1.0.0 loaded - server syncs portal map pins (wood + stone).");
			new Harmony("matthb1.portaltracking").PatchAll();
		}

		private void Update()
		{
			PortalSync.Tick(Time.deltaTime);
		}
	}
}
namespace PortalTracking.Patches
{
	[HarmonyPatch(typeof(Game), "Start")]
	internal static class GameStartPatch
	{
		private static void Prefix()
		{
			ZRoutedRpc.instance.Register("PortalTracking.RequestSync", (Action<long>)PortalSync.HandleRequestSync);
			ZRoutedRpc.instance.Register<ZPackage>("PortalTracking.Sync", (Action<long, ZPackage>)PortalSync.HandleSync);
		}
	}
	[HarmonyPatch(typeof(Game), "Logout")]
	internal static class GameLogoutPatch
	{
		private static void Prefix()
		{
			PortalPins.Clear();
			EffectiveConfig.ClearServerOverrides();
		}
	}
	[HarmonyPatch(typeof(Minimap), "OnDestroy")]
	internal static class MinimapDestroyPatch
	{
		private static void Postfix()
		{
			PortalPins.Clear();
		}
	}
	[HarmonyPatch(typeof(Minimap), "Awake")]
	internal static class MinimapAwakePatch
	{
		private static void Postfix()
		{
			PortalSync.RequestFromServer();
		}
	}
	[HarmonyPatch(typeof(Minimap), "UpdatePins")]
	internal static class MinimapUpdatePinsPatch
	{
		private static void Postfix()
		{
			PortalPins.UpdateDrawnMarkers();
		}
	}
	[HarmonyPatch(typeof(Minimap), "UpdateMap")]
	internal static class MinimapUpdateRefreshPatch
	{
		private static float _next;

		private static void Postfix()
		{
			if (!(Time.time < _next))
			{
				_next = Time.time + 5f;
				IReadOnlyList<PortalSnapshot> latest = PortalSync.GetLatest();
				if (latest.Count > 0)
				{
					PortalPins.Apply(latest);
				}
			}
		}
	}
	[HarmonyPatch(typeof(TeleportWorld), "RPC_SetTag")]
	internal static class PortalTagSyncPatch
	{
		private static void Postfix()
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			try
			{
				PortalSync.BroadcastNow();
			}
			catch (Exception ex)
			{
				PortalTrackingPlugin.Log.LogWarning((object)("Tag sync broadcast failed: " + ex.Message));
			}
		}
	}
}