Decompiled source of Joeys Wayfinder v1.1.1

JoeyBadManners.Wayfinder.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JoeyBadManners.Wayfinder.Core;
using JoeyBadManners.Wayfinder.Discovery;
using JoeyBadManners.Wayfinder.HUD;
using JoeyBadManners.Wayfinder.Icons;
using JoeyBadManners.Wayfinder.Management;
using JoeyBadManners.Wayfinder.Map;
using JoeyBadManners.Wayfinder.Patches;
using JoeyBadManners.Wayfinder.Pins;
using JoeyBadManners.Wayfinder.Tracking;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace JoeyBadManners.Wayfinder
{
	[BepInPlugin("com.joeybadmanners.wayfinder", "Joey's Wayfinder", "1.1.1")]
	public sealed class WayfinderPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.joeybadmanners.wayfinder";

		public const string PluginName = "Joey's Wayfinder";

		public const string PluginVersion = "1.1.1";

		private const float DiscoverySliceSpacing = 0.005f;

		private const double SlowDiscoveryWarningMs = 4.0;

		private long _loadedWorldUid = long.MinValue;

		private float _nextBootstrapCheck;

		private float _nextDiscoverySliceTime;

		private int _discoverySlicePhase;

		private bool _runtimeSuspended;

		internal static WayfinderPlugin Instance { get; private set; }

		internal static Harmony Harmony { get; private set; }

		internal static WayfinderConfig Settings { get; private set; }

		internal static RuntimeIconRegistry Icons { get; private set; }

		internal static WayfinderPinDatabase Pins { get; private set; }

		internal static WayfinderPinManagementService PinManager { get; private set; }

		internal static WayfinderPinManagerUI PinManagerUI { get; private set; }

		internal static WayfinderMapController Map { get; private set; }

		internal static ResourceDiscovery Discovery { get; private set; }

		internal static AreaScanner Scanner { get; private set; }

		internal static StructureDiscovery Structures { get; private set; }

		internal static TraderDiscovery Traders { get; private set; }

		internal static SpawnerDiscovery Spawners { get; private set; }

		internal static RunestoneDiscovery Runestones { get; private set; }

		internal static PortalDiscovery Portals { get; private set; }

		internal static VehicleDiscovery Vehicles { get; private set; }

		internal static EnemySightingDiscovery Sightings { get; private set; }

		internal static OreDiagnosticScanner OreDiagnostics { get; private set; }

		internal static DiscoveryArtConsistency ArtConsistency { get; private set; }

		internal static TrackedBeaconController TrackingBeacons { get; private set; }

		internal static WayfinderCompassHUD CompassHUD { get; private set; }

		internal static GravestoneIntegration Gravestones { get; private set; }

		private void Awake()
		{
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Expected O, but got Unknown
			Instance = this;
			Settings = new WayfinderConfig(((BaseUnityPlugin)this).Config);
			Icons = new RuntimeIconRegistry(((BaseUnityPlugin)this).Logger, Settings);
			Pins = new WayfinderPinDatabase(((BaseUnityPlugin)this).Logger, Settings);
			PinManager = new WayfinderPinManagementService(Settings, Pins);
			Map = new WayfinderMapController(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins, PinManager);
			PinManagerUI = new WayfinderPinManagerUI(((BaseUnityPlugin)this).Logger, Settings, PinManager, Icons);
			Discovery = new ResourceDiscovery(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins);
			Scanner = new AreaScanner(((BaseUnityPlugin)this).Logger, Settings, Discovery);
			Structures = new StructureDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins, Icons);
			Traders = new TraderDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins);
			Spawners = new SpawnerDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins, Icons);
			Runestones = new RunestoneDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins);
			Portals = new PortalDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins);
			Vehicles = new VehicleDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins);
			Sightings = new EnemySightingDiscovery(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins);
			OreDiagnostics = new OreDiagnosticScanner(((BaseUnityPlugin)this).Logger, Settings);
			ArtConsistency = new DiscoveryArtConsistency(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins);
			TrackingBeacons = new TrackedBeaconController(((BaseUnityPlugin)this).Logger, Settings, PinManager);
			CompassHUD = new WayfinderCompassHUD(((BaseUnityPlugin)this).Logger, Settings, PinManager, Icons);
			Gravestones = new GravestoneIntegration(((BaseUnityPlugin)this).Logger, Settings, Icons);
			Harmony = new Harmony("com.joeybadmanners.wayfinder");
			Harmony.PatchAll(typeof(WayfinderPlugin).Assembly);
			int num = WayfinderGameplayInputCaptureInstaller.Install(Harmony);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Joey's Wayfinder 1.1.1 loaded.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Client-side Wayfinder systems initialized. No server installation is required. Runtime-safe input hooks installed: " + num + "."));
		}

		private void Update()
		{
			if (Settings == null || !Settings.Enabled.Value)
			{
				SuspendRuntime();
				return;
			}
			if (_runtimeSuspended)
			{
				_runtimeSuspended = false;
				_nextBootstrapCheck = 0f;
				_nextDiscoverySliceTime = 0f;
				if (Map != null)
				{
					Map.ForceRefresh();
				}
				if (Vehicles != null)
				{
					Vehicles.ResetSession();
				}
			}
			if (PinManagerUI != null)
			{
				PinManagerUI.TickInput();
			}
			if (TrackingBeacons != null)
			{
				TrackingBeacons.Tick();
			}
			if (Vehicles != null)
			{
				Vehicles.Tick();
			}
			if (Gravestones != null)
			{
				Gravestones.Tick();
			}
			if (Map != null)
			{
				Map.RefreshDynamicVehiclePositions();
				Map.Tick();
			}
			TickDiscoveryWorkSlice();
			if (Time.unscaledTime < _nextBootstrapCheck)
			{
				return;
			}
			_nextBootstrapCheck = Time.unscaledTime + 1f;
			try
			{
				BootstrapWorldState();
				BootstrapIconRegistry();
				Discovery.RepairKnownResourceIcons();
				Discovery.ApplyLiveFilterCleanup();
				if (ArtConsistency != null)
				{
					ArtConsistency.Tick();
				}
				Portals.Tick();
				OreDiagnostics.Tick();
				Pins.Tick();
			}
			catch (Exception ex)
			{
				if (Settings.DebugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Bootstrap check failed: " + ex));
				}
			}
		}

		private void TickDiscoveryWorkSlice()
		{
			if (Settings == null || !Settings.Enabled.Value || Time.unscaledTime < _nextDiscoverySliceTime)
			{
				return;
			}
			_nextDiscoverySliceTime = Time.unscaledTime + 0.005f;
			double previousFrameMs = (double)Time.unscaledDeltaTime * 1000.0;
			double adaptiveDiscoveryBudgetMs = GetAdaptiveDiscoveryBudgetMs(previousFrameMs);
			if (adaptiveDiscoveryBudgetMs <= 0.0)
			{
				return;
			}
			string text = string.Empty;
			bool flag = false;
			long timestamp = Stopwatch.GetTimestamp();
			try
			{
				switch (_discoverySlicePhase)
				{
				case 0:
					text = "AreaScanner";
					if (Scanner != null)
					{
						flag = Scanner.TickBudgeted(adaptiveDiscoveryBudgetMs);
					}
					break;
				case 1:
					text = "EnemySightings";
					if (Sightings != null)
					{
						flag = Sightings.TickBudgeted(adaptiveDiscoveryBudgetMs);
					}
					break;
				case 2:
					text = "Structures";
					if (Structures != null)
					{
						flag = Structures.TickBudgeted(adaptiveDiscoveryBudgetMs);
					}
					break;
				case 3:
					text = "Spawners";
					if (Spawners != null)
					{
						flag = Spawners.TickBudgeted(adaptiveDiscoveryBudgetMs);
					}
					break;
				case 4:
					text = "Runestones";
					if (Runestones != null)
					{
						flag = Runestones.TickBudgeted(adaptiveDiscoveryBudgetMs);
					}
					break;
				default:
					text = "Traders";
					if (Traders != null)
					{
						flag = Traders.TickBudgeted(adaptiveDiscoveryBudgetMs);
					}
					break;
				}
			}
			catch (Exception ex)
			{
				if (Settings != null && Settings.DebugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Discovery slice " + text + " failed: " + ex));
				}
			}
			finally
			{
				_discoverySlicePhase = (_discoverySlicePhase + 1) % 6;
			}
			if (Settings == null || !Settings.DebugLogging.Value)
			{
				return;
			}
			double num = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency;
			if (num >= 4.0)
			{
				string text2 = string.Empty;
				if (text == "AreaScanner" && Scanner != null)
				{
					text2 = Scanner.GetPerfDetail();
				}
				((BaseUnityPlugin)this).Logger.LogWarning((object)("JW PERF: " + text + " slice took " + num.ToString("0.00") + " ms (budget=" + adaptiveDiscoveryBudgetMs.ToString("0.00") + "ms, pending=" + flag + ", prevFrame=" + previousFrameMs.ToString("0.00") + "ms)." + text2));
			}
		}

		private static double GetAdaptiveDiscoveryBudgetMs(double previousFrameMs)
		{
			if (previousFrameMs >= 40.0)
			{
				return 0.0;
			}
			if (previousFrameMs >= 25.0)
			{
				return 0.75;
			}
			if (previousFrameMs >= 18.0)
			{
				return 1.0;
			}
			if (previousFrameMs >= 14.0)
			{
				return 1.4;
			}
			if (previousFrameMs >= 10.0)
			{
				return 2.0;
			}
			return 2.75;
		}

		private void BootstrapWorldState()
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				CloseWorldSessionIfNeeded();
				return;
			}
			long num = 0L;
			try
			{
				num = ZNet.instance.GetWorldUID();
			}
			catch
			{
				CloseWorldSessionIfNeeded();
				return;
			}
			if (num == 0L)
			{
				CloseWorldSessionIfNeeded();
			}
			else if ((num != _loadedWorldUid || Pins.WorldUid != num) && Pins.LoadWorld(num, ZNet.instance.GetWorldName()))
			{
				_loadedWorldUid = num;
				LiveDiscoveryRegistry.ResetForWorld(num);
				PhysicalSpawnerIndex.Reset();
				LiveDiscoveryRegistry.SyncFromLoadedLocations();
				LiveDiscoveryRegistry.SyncVehiclesFromGameInstances();
				LiveDiscoveryRegistry.SeedLoadedPortals();
				if (Vehicles != null)
				{
					Vehicles.ResetSession();
				}
				if (Portals != null)
				{
					Portals.ResetSession();
				}
				if (Traders != null)
				{
					Traders.ResetSession();
				}
				if (ArtConsistency != null)
				{
					ArtConsistency.ResetSession();
				}
				if (Gravestones != null)
				{
					Gravestones.ResetSession();
				}
				Map.ForceRefresh();
			}
		}

		private void CloseWorldSessionIfNeeded()
		{
			if ((_loadedWorldUid != long.MinValue || Pins.WorldUid != 0L) && Pins.UnloadWorld())
			{
				if (TrackingBeacons != null)
				{
					TrackingBeacons.Clear();
				}
				if (Vehicles != null)
				{
					Vehicles.ClearSession();
				}
				if (Portals != null)
				{
					Portals.ClearSession();
				}
				if (Traders != null)
				{
					Traders.ResetSession();
				}
				if (ArtConsistency != null)
				{
					ArtConsistency.ResetSession();
				}
				if (Gravestones != null)
				{
					Gravestones.ClearSession();
				}
				LiveDiscoveryRegistry.ResetForWorld(0L);
				PhysicalSpawnerIndex.Reset();
				_loadedWorldUid = long.MinValue;
				Map.ForceRefresh();
			}
		}

		private void BootstrapIconRegistry()
		{
			if (!((Object)(object)ObjectDB.instance == (Object)null) && ObjectDB.instance.m_items != null && ObjectDB.instance.m_items.Count != 0 && (!Icons.IsBuilt || Icons.IsDirty))
			{
				Icons.Build(ObjectDB.instance);
				Discovery.RepairKnownResourceIcons(force: true);
				Map.ForceRefresh();
			}
		}

		private void LateUpdate()
		{
			if (Settings != null && Settings.Enabled.Value && Map != null && (Object)(object)Minimap.instance != (Object)null)
			{
				Map.EnforceNativeShipMarkerVisibility(Minimap.instance);
				Map.EnforceVanillaPersistentPinUiVisibility(Minimap.instance);
			}
		}

		private void OnGUI()
		{
			if (Settings != null && Settings.Enabled.Value)
			{
				if (Map != null)
				{
					Map.DrawLargeMapNameLabels();
				}
				if (CompassHUD != null)
				{
					CompassHUD.Draw();
				}
				if (PinManagerUI != null)
				{
					PinManagerUI.DrawMapButton();
					PinManagerUI.Draw();
				}
			}
		}

		private void SuspendRuntime()
		{
			if (!_runtimeSuspended)
			{
				_runtimeSuspended = true;
				if (TrackingBeacons != null)
				{
					TrackingBeacons.Clear();
				}
				if (Vehicles != null)
				{
					Vehicles.ClearSession();
				}
				if (Map != null)
				{
					Map.Suspend();
				}
			}
		}

		private void OnDestroy()
		{
			try
			{
				if (TrackingBeacons != null)
				{
					TrackingBeacons.Clear();
				}
				if (Pins != null)
				{
					Pins.Save();
				}
				if (Harmony != null)
				{
					Harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
		}
	}
}
namespace JoeyBadManners.Wayfinder.Tracking
{
	internal sealed class TrackedBeaconController
	{
		private sealed class Beam
		{
			internal GameObject GameObject;

			internal LineRenderer Line;

			internal Material Material;
		}

		private readonly ManualLogSource _log;

		private readonly WayfinderConfig _config;

		private readonly WayfinderPinManagementService _manager;

		private readonly Dictionary<string, Beam> _beams = new Dictionary<string, Beam>(StringComparer.Ordinal);

		internal TrackedBeaconController(ManualLogSource log, WayfinderConfig config, WayfinderPinManagementService manager)
		{
			_log = log;
			_config = config;
			_manager = manager;
		}

		internal void Tick()
		{
			if (!_config.Enabled.Value || !_config.BeaconEnabled.Value || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				Clear();
				return;
			}
			List<WayfinderPinRecord> trackedPins = _manager.GetTrackedPins();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < trackedPins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = trackedPins[i];
				if (wayfinderPinRecord == null || string.IsNullOrEmpty(wayfinderPinRecord.id))
				{
					continue;
				}
				hashSet.Add(wayfinderPinRecord.id);
				if (!_beams.TryGetValue(wayfinderPinRecord.id, out var value) || value == null || (Object)(object)value.GameObject == (Object)null || (Object)(object)value.Line == (Object)null)
				{
					value = CreateBeam(wayfinderPinRecord.id);
					if (value == null)
					{
						continue;
					}
					_beams[wayfinderPinRecord.id] = value;
				}
				UpdateBeam(value, wayfinderPinRecord);
			}
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Beam> beam in _beams)
			{
				if (!hashSet.Contains(beam.Key))
				{
					list.Add(beam.Key);
				}
			}
			for (int j = 0; j < list.Count; j++)
			{
				DestroyBeam(_beams[list[j]]);
				_beams.Remove(list[j]);
			}
		}

		internal void Clear()
		{
			foreach (Beam value in _beams.Values)
			{
				DestroyBeam(value);
			}
			_beams.Clear();
		}

		private Beam CreateBeam(string id)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			Shader val = Shader.Find("Sprites/Default");
			if ((Object)(object)val == (Object)null)
			{
				val = Shader.Find("Unlit/Transparent");
			}
			if ((Object)(object)val == (Object)null)
			{
				if (_config.DebugLogging.Value)
				{
					_log.LogWarning((object)"Tracked beacon could not find a transparent Unity shader.");
				}
				return null;
			}
			GameObject val2 = new GameObject("Wayfinder Tracked Beacon " + id);
			LineRenderer val3 = val2.AddComponent<LineRenderer>();
			Material val4 = new Material(val);
			((Object)val4).name = "Wayfinder Tracked Beacon Material";
			val4.renderQueue = 4000;
			((Renderer)val3).material = val4;
			val3.positionCount = 2;
			val3.useWorldSpace = true;
			val3.loop = false;
			val3.numCapVertices = 4;
			val3.textureMode = (LineTextureMode)0;
			val3.alignment = (LineAlignment)0;
			((Renderer)val3).receiveShadows = false;
			return new Beam
			{
				GameObject = val2,
				Line = val3,
				Material = val4
			};
		}

		private void UpdateBeam(Beam beam, WayfinderPinRecord record)
		{
			//IL_009b: 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_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			if (beam != null && !((Object)(object)beam.Line == (Object)null) && record != null)
			{
				float num = Mathf.Clamp01(_config.BeaconOpacity.Value);
				bool num2 = WayfinderDynamicState.IsLastKnownVehicle(record);
				if (num2)
				{
					num *= Mathf.Clamp01(_config.LastKnownVehicleOpacity.Value);
				}
				float num3 = Mathf.Max(0.08f, _config.BeaconWidth.Value);
				float num4 = Mathf.Max(5f, _config.BeaconHeight.Value);
				if (num2)
				{
					num3 *= 0.82f;
				}
				Color color = TrackedColorPalette.GetColor(record.trackSlot, num);
				Color color2 = TrackedColorPalette.GetColor(record.trackSlot, num * 0.15f);
				beam.Line.startWidth = num3;
				beam.Line.endWidth = num3 * 0.55f;
				beam.Line.startColor = color;
				beam.Line.endColor = color2;
				beam.Line.SetPosition(0, record.Position + Vector3.up * 0.5f);
				beam.Line.SetPosition(1, record.Position + Vector3.up * num4);
				if ((Object)(object)beam.Material != (Object)null)
				{
					beam.Material.color = color;
				}
			}
		}

		private static void DestroyBeam(Beam beam)
		{
			if (beam != null)
			{
				if ((Object)(object)beam.GameObject != (Object)null)
				{
					Object.Destroy((Object)(object)beam.GameObject);
				}
				if ((Object)(object)beam.Material != (Object)null)
				{
					Object.Destroy((Object)(object)beam.Material);
				}
			}
		}
	}
	internal static class TrackedColorPalette
	{
		internal const int SlotCount = 5;

		internal static Color GetColor(int slot, float alpha)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			Color result = (Color)(slot switch
			{
				0 => new Color(0.32f, 1f, 0.34f, 1f), 
				1 => new Color(0.18f, 0.82f, 1f, 1f), 
				2 => new Color(1f, 0.72f, 0.18f, 1f), 
				3 => new Color(0.82f, 0.38f, 1f, 1f), 
				4 => new Color(1f, 0.28f, 0.22f, 1f), 
				_ => new Color(0.32f, 1f, 0.34f, 1f), 
			});
			result.a = Mathf.Clamp01(alpha);
			return result;
		}

		internal static string GetName(int slot)
		{
			return slot switch
			{
				0 => "Green", 
				1 => "Cyan", 
				2 => "Gold", 
				3 => "Violet", 
				4 => "Red", 
				_ => "Green", 
			};
		}
	}
}
namespace JoeyBadManners.Wayfinder.Pins
{
	[Serializable]
	internal sealed class WayfinderClusterMember
	{
		public string key;

		public float x;

		public float y;

		public float z;

		public Vector3 Position => new Vector3(x, y, z);

		public WayfinderClusterMember()
		{
		}

		public WayfinderClusterMember(string memberKey, Vector3 position)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			key = memberKey ?? string.Empty;
			x = position.x;
			y = position.y;
			z = position.z;
		}
	}
	internal static class WayfinderDynamicState
	{
		private const long UnixEpochTicks = 621355968000000000L;

		private const long TicksPerSecond = 10000000L;

		internal static bool IsDynamicVehicle(WayfinderPinRecord record)
		{
			if (record != null && record.category == WayfinderPinCategory.Vehicle)
			{
				return record.source == WayfinderPinSource.Dynamic;
			}
			return false;
		}

		internal static bool IsLiveVehicle(WayfinderPinRecord record)
		{
			if (IsDynamicVehicle(record))
			{
				return record.dynamicLive;
			}
			return false;
		}

		internal static bool IsLastKnownVehicle(WayfinderPinRecord record)
		{
			if (IsDynamicVehicle(record) && !record.dynamicLive)
			{
				return record.lastSeenUnix > 0;
			}
			return false;
		}

		internal static long NowUnixSeconds()
		{
			try
			{
				long num = DateTime.UtcNow.Ticks - 621355968000000000L;
				return (num <= 0) ? 0 : (num / 10000000);
			}
			catch
			{
				return 0L;
			}
		}

		internal static long AgeSeconds(WayfinderPinRecord record)
		{
			if (record == null || record.lastSeenUnix <= 0)
			{
				return -1L;
			}
			long num = NowUnixSeconds();
			if (num <= 0)
			{
				return -1L;
			}
			long num2 = num - record.lastSeenUnix;
			if (num2 >= 0)
			{
				return num2;
			}
			return 0L;
		}

		internal static string FormatLastSeen(WayfinderPinRecord record)
		{
			if (record == null)
			{
				return "unknown";
			}
			if (record.dynamicLive)
			{
				return "now";
			}
			long num = AgeSeconds(record);
			if (num < 0)
			{
				return "unknown";
			}
			if (num < 10)
			{
				return "just now";
			}
			if (num < 60)
			{
				return num + "s ago";
			}
			long num2 = num / 60;
			if (num2 < 60)
			{
				return num2 + "m ago";
			}
			long num3 = num2 / 60;
			if (num3 < 24)
			{
				return num3 + "h ago";
			}
			long num4 = num3 / 24;
			if (num4 < 30)
			{
				return num4 + "d ago";
			}
			long num5 = num4 / 30;
			if (num5 < 12)
			{
				return num5 + "mo ago";
			}
			return num4 / 365 + "y ago";
		}

		internal static string GetVehicleStateLabel(WayfinderPinRecord record)
		{
			if (!IsDynamicVehicle(record))
			{
				return string.Empty;
			}
			if (record.dynamicLive)
			{
				return "LIVE";
			}
			return "LAST KNOWN";
		}
	}
	internal enum WayfinderPinCategory
	{
		Resource,
		Creature,
		Boss,
		Dungeon,
		PointOfInterest,
		Spawner,
		Habitat,
		Sighting,
		Vehicle,
		Trader,
		Portal,
		Base,
		Outpost,
		Farm,
		Dock,
		Road,
		Bridge,
		Camp,
		Storage,
		Danger,
		Custom,
		Unknown
	}
	internal sealed class WayfinderPinDatabase
	{
		private const float DuplicatePositionTolerance = 0.25f;

		private const string PortalRightClickProtectionMigrationSentinel = "__jw_1350_portal_rightclick_protection__";

		private readonly ManualLogSource _log;

		private readonly WayfinderConfig _config;

		private WayfinderWorldData _data = new WayfinderWorldData();

		private string _filePath;

		private bool _dirty;

		private float _nextScheduledSaveTime;

		private readonly HashSet<string> _suppressedKeyCache = new HashSet<string>(StringComparer.Ordinal);

		private readonly List<WayfinderPinRecord> _visibleRecords = new List<WayfinderPinRecord>();

		private int _visibleRecordsRevision = -1;

		internal IReadOnlyList<WayfinderPinRecord> Records
		{
			get
			{
				if (_visibleRecordsRevision != Revision)
				{
					_visibleRecords.Clear();
					if (_data != null && _data.pins != null)
					{
						for (int i = 0; i < _data.pins.Count; i++)
						{
							WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
							if (wayfinderPinRecord != null && (wayfinderPinRecord.category != WayfinderPinCategory.Portal || wayfinderPinRecord.portalVerified))
							{
								_visibleRecords.Add(wayfinderPinRecord);
							}
						}
					}
					_visibleRecordsRevision = Revision;
				}
				return _visibleRecords;
			}
		}

		internal long WorldUid => _data.worldUid;

		internal int Revision { get; private set; }

		internal WayfinderPinDatabase(ManualLogSource log, WayfinderConfig config)
		{
			_log = log;
			_config = config;
		}

		internal bool LoadWorld(long worldUid, string worldName)
		{
			if (!Save())
			{
				_log.LogWarning((object)"Wayfinder postponed world switch because the previous world database could not be saved. The in-memory records were kept and the save/switch will retry automatically.");
				return false;
			}
			string text = Path.Combine(Paths.ConfigPath, "JoeysWayfinder", "worlds");
			Directory.CreateDirectory(text);
			string text2 = Path.Combine(text, worldUid + ".wfd");
			WayfinderWorldData wayfinderWorldData = new WayfinderWorldData
			{
				worldUid = worldUid,
				worldName = (worldName ?? string.Empty)
			};
			bool flag = false;
			bool flag2 = false;
			bool flag3 = false;
			if (File.Exists(text2))
			{
				WayfinderWorldData wayfinderWorldData2 = null;
				try
				{
					wayfinderWorldData2 = WayfinderSaveCodec.Read(text2, worldUid, worldName);
				}
				catch (Exception ex)
				{
					flag3 = true;
					_log.LogWarning((object)("Could not read the primary Wayfinder world database: " + ex.Message));
				}
				if (wayfinderWorldData2 == null)
				{
					flag3 = true;
				}
				if (wayfinderWorldData2 == null)
				{
					string text3 = text2 + ".bak";
					if (File.Exists(text3))
					{
						try
						{
							wayfinderWorldData2 = WayfinderSaveCodec.Read(text3, worldUid, worldName);
							if (wayfinderWorldData2 != null && wayfinderWorldData2.worldUid == worldUid)
							{
								flag = true;
								try
								{
									File.Copy(text3, text2, overwrite: true);
								}
								catch (Exception ex2)
								{
									flag2 = true;
									_log.LogWarning((object)("Wayfinder loaded the backup database but could not restore it over the primary file yet: " + ex2.Message));
								}
							}
						}
						catch (Exception ex3)
						{
							_log.LogWarning((object)("Could not read the Wayfinder backup database: " + ex3.Message));
						}
					}
				}
				if (wayfinderWorldData2 != null && wayfinderWorldData2.worldUid == worldUid)
				{
					wayfinderWorldData = wayfinderWorldData2;
				}
				else if (flag3)
				{
					PreserveUnreadableWorldFile(text2);
					_log.LogWarning((object)"Wayfinder could not validate either the primary world database or its backup. The unreadable primary was preserved before starting a clean in-memory database for this world.");
				}
			}
			else
			{
				string path = Path.Combine(text, worldUid + ".json");
				if (File.Exists(path))
				{
					try
					{
						WayfinderWorldData wayfinderWorldData3 = JsonUtility.FromJson<WayfinderWorldData>(File.ReadAllText(path));
						if (wayfinderWorldData3 != null && wayfinderWorldData3.worldUid == worldUid && wayfinderWorldData3.suppressedKeys != null)
						{
							wayfinderWorldData.suppressedKeys = new List<string>(wayfinderWorldData3.suppressedKeys);
						}
					}
					catch (Exception ex4)
					{
						_log.LogWarning((object)("Could not migrate legacy Wayfinder suppression data: " + ex4.Message));
					}
				}
			}
			_filePath = text2;
			_data = wayfinderWorldData;
			if (_data.pins == null)
			{
				_data.pins = new List<WayfinderPinRecord>();
			}
			if (_data.suppressedKeys == null)
			{
				_data.suppressedKeys = new List<string>();
			}
			if (_data.schemaVersion < 2)
			{
				_data.schemaVersion = 2;
			}
			_dirty = false;
			_nextScheduledSaveTime = 0f;
			if (flag2)
			{
				_dirty = true;
				ScheduleSave();
			}
			int repairedIds;
			int repairedMembers;
			int repairedSuppressions;
			int num = RepairLoadedPersistenceIntegrity(out repairedIds, out repairedMembers, out repairedSuppressions);
			if (num > 0)
			{
				_dirty = true;
				ScheduleSave();
			}
			int mergedResourceRecords;
			int removedStaticPointRecords;
			int num2 = RepairLoadedDiscoveryDuplicates(out mergedResourceRecords, out removedStaticPointRecords);
			if (num2 > 0)
			{
				_dirty = true;
				ScheduleSave();
			}
			int num3 = RepairPre1350PortalRightClickSuppressions();
			if (num3 >= 0)
			{
				_dirty = true;
				ScheduleSave();
			}
			int preservedCustomNames;
			int num4 = RepairPre1351VehicleNaming(out preservedCustomNames);
			if (num4 >= 0)
			{
				_data.schemaVersion = 10;
				_dirty = true;
				ScheduleSave();
			}
			int num5 = MigratePortalVerification();
			if (num5 >= 0)
			{
				_dirty = true;
				ScheduleSave();
			}
			RebuildSuppressionCache();
			Revision++;
			_log.LogInfo((object)("Wayfinder world database ready: " + _data.pins.Count + " records for " + (_data.worldName ?? worldUid.ToString()) + "."));
			if (flag)
			{
				_log.LogWarning((object)"Recovered this world's Wayfinder database from the .bak safety copy after the primary file failed validation.");
			}
			if (num > 0)
			{
				_log.LogInfo((object)("Persistence integrity repaired " + repairedIds + " pin id(s), " + repairedMembers + " invalid/duplicate member(s), and " + repairedSuppressions + " invalid/duplicate suppression key(s)."));
			}
			if (num2 > 0)
			{
				_log.LogInfo((object)("Discovery regression cleanup merged " + mergedResourceRecords + " exact duplicate resource cluster record(s) and removed " + removedStaticPointRecords + " exact duplicate generated-location/spawner record(s)."));
			}
			if (num3 > 0)
			{
				_log.LogInfo((object)("Portal recovery restored " + num3 + " previously suppressed portal pin(s). Map right-click can no longer permanently suppress managed portals; deliberate removal remains available from the Pins panel."));
			}
			if (num5 > 0)
			{
				_log.LogWarning((object)("Portal migration quarantined " + num5 + " legacy position-only portal record(s). They are preserved in the .wfd file but hidden until a valid live portal confirms them; matching records recover their names, favorites, icon overrides, and tracking state in place."));
			}
			if (num4 > 0 || preservedCustomNames > 0)
			{
				_log.LogInfo((object)("Vehicle-name migration repaired " + num4 + " automatic vehicle label(s) and preserved " + preservedCustomNames + " existing custom label(s)."));
			}
			return true;
		}

		private void PreserveUnreadableWorldFile(string path)
		{
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				return;
			}
			try
			{
				string text = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss");
				string destFileName = path + ".corrupt-" + text;
				File.Copy(path, destFileName, overwrite: false);
			}
			catch
			{
			}
		}

		private int RepairLoadedPersistenceIntegrity(out int repairedIds, out int repairedMembers, out int repairedSuppressions)
		{
			repairedIds = 0;
			repairedMembers = 0;
			repairedSuppressions = 0;
			if (_data == null)
			{
				return 0;
			}
			if (_data.pins == null)
			{
				_data.pins = new List<WayfinderPinRecord>();
			}
			if (_data.suppressedKeys == null)
			{
				_data.suppressedKeys = new List<string>();
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord == null)
				{
					continue;
				}
				if (string.IsNullOrEmpty(wayfinderPinRecord.id) || hashSet.Contains(wayfinderPinRecord.id))
				{
					wayfinderPinRecord.id = Guid.NewGuid().ToString("N");
					repairedIds++;
				}
				hashSet.Add(wayfinderPinRecord.id);
				if (wayfinderPinRecord.members == null)
				{
					wayfinderPinRecord.members = new List<WayfinderClusterMember>();
					continue;
				}
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
				bool flag = false;
				for (int num = wayfinderPinRecord.members.Count - 1; num >= 0; num--)
				{
					string text = wayfinderPinRecord.members[num]?.key;
					if (string.IsNullOrEmpty(text) || hashSet2.Contains(text))
					{
						wayfinderPinRecord.members.RemoveAt(num);
						repairedMembers++;
						flag = true;
					}
					else
					{
						hashSet2.Add(text);
					}
				}
				if (flag && (wayfinderPinRecord.category == WayfinderPinCategory.Resource || wayfinderPinRecord.category == WayfinderPinCategory.Sighting))
				{
					RecalculateCluster(wayfinderPinRecord);
				}
			}
			HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
			for (int num2 = _data.suppressedKeys.Count - 1; num2 >= 0; num2--)
			{
				string text2 = _data.suppressedKeys[num2];
				if (string.IsNullOrEmpty(text2) || hashSet3.Contains(text2))
				{
					_data.suppressedKeys.RemoveAt(num2);
					repairedSuppressions++;
				}
				else
				{
					hashSet3.Add(text2);
				}
			}
			return repairedIds + repairedMembers + repairedSuppressions;
		}

		private int RepairLoadedDiscoveryDuplicates(out int mergedResourceRecords, out int removedStaticPointRecords)
		{
			mergedResourceRecords = 0;
			removedStaticPointRecords = 0;
			if (_data == null || _data.pins == null || _data.pins.Count < 2)
			{
				return 0;
			}
			Dictionary<string, WayfinderPinRecord> ownerByMember = new Dictionary<string, WayfinderPinRecord>(StringComparer.OrdinalIgnoreCase);
			Dictionary<string, WayfinderPinRecord> dictionary = new Dictionary<string, WayfinderPinRecord>(StringComparer.OrdinalIgnoreCase);
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (!IsRepairableAutoDiscoveryRecord(wayfinderPinRecord) || wayfinderPinRecord.members == null || wayfinderPinRecord.members.Count == 0)
				{
					continue;
				}
				if (wayfinderPinRecord.category == WayfinderPinCategory.Resource)
				{
					WayfinderPinRecord wayfinderPinRecord2 = FindExistingResourceOwner(wayfinderPinRecord, ownerByMember);
					if (wayfinderPinRecord2 != null && wayfinderPinRecord2 != wayfinderPinRecord)
					{
						MergeExactDuplicateResourceRecord(wayfinderPinRecord2, wayfinderPinRecord);
						_data.pins.RemoveAt(i);
						i--;
						mergedResourceRecords++;
						IndexResourceMembers(wayfinderPinRecord2, ownerByMember);
					}
					else
					{
						IndexResourceMembers(wayfinderPinRecord, ownerByMember);
					}
				}
				else
				{
					if (!IsStaticGeneratedOrSpawnerRecord(wayfinderPinRecord))
					{
						continue;
					}
					string exactStaticIdentityKey = GetExactStaticIdentityKey(wayfinderPinRecord);
					if (!string.IsNullOrEmpty(exactStaticIdentityKey))
					{
						if (!dictionary.TryGetValue(exactStaticIdentityKey, out var value))
						{
							dictionary[exactStaticIdentityKey] = wayfinderPinRecord;
						}
						else if (value != wayfinderPinRecord)
						{
							PreserveDuplicateUserState(value, wayfinderPinRecord);
							_data.pins.RemoveAt(i);
							i--;
							removedStaticPointRecords++;
						}
					}
				}
			}
			return mergedResourceRecords + removedStaticPointRecords;
		}

		private static bool IsRepairableAutoDiscoveryRecord(WayfinderPinRecord record)
		{
			if (record != null && record.source != WayfinderPinSource.Manual && record.source != WayfinderPinSource.Imported && record.source != WayfinderPinSource.Vanilla)
			{
				return record.source != WayfinderPinSource.Dynamic;
			}
			return false;
		}

		private static bool IsStaticGeneratedOrSpawnerRecord(WayfinderPinRecord record)
		{
			if (record == null || record.members == null)
			{
				return false;
			}
			for (int i = 0; i < record.members.Count; i++)
			{
				string text = ((record.members[i] == null) ? null : record.members[i].key);
				if (!string.IsNullOrEmpty(text) && (text.StartsWith("location:", StringComparison.OrdinalIgnoreCase) || text.StartsWith("spawner:", StringComparison.OrdinalIgnoreCase)))
				{
					return true;
				}
			}
			return false;
		}

		private static string GetExactStaticIdentityKey(WayfinderPinRecord record)
		{
			if (record == null || record.members == null)
			{
				return null;
			}
			for (int i = 0; i < record.members.Count; i++)
			{
				string text = ((record.members[i] == null) ? null : record.members[i].key);
				if (!string.IsNullOrEmpty(text) && (text.StartsWith("location:", StringComparison.OrdinalIgnoreCase) || text.StartsWith("spawner:", StringComparison.OrdinalIgnoreCase)))
				{
					return text;
				}
			}
			return null;
		}

		private static string BuildResourceMemberIdentity(WayfinderPinRecord record, WayfinderClusterMember member)
		{
			if (record == null || member == null || string.IsNullOrEmpty(member.key))
			{
				return null;
			}
			return (record.subtype ?? string.Empty).ToLowerInvariant() + "|" + member.key;
		}

		private static WayfinderPinRecord FindExistingResourceOwner(WayfinderPinRecord record, Dictionary<string, WayfinderPinRecord> ownerByMember)
		{
			if (record == null || record.members == null || ownerByMember == null)
			{
				return null;
			}
			for (int i = 0; i < record.members.Count; i++)
			{
				string text = BuildResourceMemberIdentity(record, record.members[i]);
				if (!string.IsNullOrEmpty(text) && ownerByMember.TryGetValue(text, out var value) && value != null && value != record)
				{
					return value;
				}
			}
			return null;
		}

		private static void IndexResourceMembers(WayfinderPinRecord record, Dictionary<string, WayfinderPinRecord> ownerByMember)
		{
			if (record == null || record.members == null || ownerByMember == null)
			{
				return;
			}
			for (int i = 0; i < record.members.Count; i++)
			{
				string text = BuildResourceMemberIdentity(record, record.members[i]);
				if (!string.IsNullOrEmpty(text))
				{
					ownerByMember[text] = record;
				}
			}
		}

		private void MergeExactDuplicateResourceRecord(WayfinderPinRecord keep, WayfinderPinRecord duplicate)
		{
			if (keep == null || duplicate == null)
			{
				return;
			}
			if (keep.members == null)
			{
				keep.members = new List<WayfinderClusterMember>();
			}
			if (duplicate.members != null)
			{
				for (int i = 0; i < duplicate.members.Count; i++)
				{
					WayfinderClusterMember wayfinderClusterMember = duplicate.members[i];
					if (wayfinderClusterMember != null && !string.IsNullOrEmpty(wayfinderClusterMember.key) && !HasResourceMember(keep, wayfinderClusterMember.key))
					{
						keep.members.Add(wayfinderClusterMember);
					}
				}
			}
			PreserveDuplicateUserState(keep, duplicate);
			if (duplicate.source == WayfinderPinSource.InteractionDiscovery)
			{
				keep.source = WayfinderPinSource.InteractionDiscovery;
			}
			if (string.IsNullOrEmpty(keep.displayName) && !string.IsNullOrEmpty(duplicate.displayName))
			{
				keep.displayName = duplicate.displayName;
			}
			if (string.IsNullOrEmpty(keep.iconKey) && !string.IsNullOrEmpty(duplicate.iconKey))
			{
				keep.iconKey = duplicate.iconKey;
			}
			RecalculateCluster(keep);
		}

		private static bool HasResourceMember(WayfinderPinRecord record, string memberKey)
		{
			if (record == null || record.members == null || string.IsNullOrEmpty(memberKey))
			{
				return false;
			}
			for (int i = 0; i < record.members.Count; i++)
			{
				WayfinderClusterMember wayfinderClusterMember = record.members[i];
				if (wayfinderClusterMember != null && string.Equals(wayfinderClusterMember.key, memberKey, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static void PreserveDuplicateUserState(WayfinderPinRecord keep, WayfinderPinRecord duplicate)
		{
			if (keep == null || duplicate == null)
			{
				return;
			}
			if (duplicate.favorite)
			{
				keep.favorite = true;
			}
			if (duplicate.iconOverride && !keep.iconOverride)
			{
				keep.iconOverride = true;
				keep.iconOverrideKey = duplicate.iconOverrideKey ?? string.Empty;
			}
			if (duplicate.tracked)
			{
				if (!keep.tracked)
				{
					keep.tracked = true;
					keep.trackSlot = duplicate.trackSlot;
				}
				else if (duplicate.trackSlot >= 0 && (keep.trackSlot < 0 || duplicate.trackSlot < keep.trackSlot))
				{
					keep.trackSlot = duplicate.trackSlot;
				}
			}
		}

		private void RebuildSuppressionCache()
		{
			_suppressedKeyCache.Clear();
			if (_data == null || _data.suppressedKeys == null)
			{
				return;
			}
			for (int i = 0; i < _data.suppressedKeys.Count; i++)
			{
				string text = _data.suppressedKeys[i];
				if (!string.IsNullOrEmpty(text))
				{
					_suppressedKeyCache.Add(text);
				}
			}
		}

		private int RepairPre1350PortalRightClickSuppressions()
		{
			if (_data == null || _data.suppressedKeys == null)
			{
				return -1;
			}
			for (int i = 0; i < _data.suppressedKeys.Count; i++)
			{
				if (string.Equals(_data.suppressedKeys[i], "__jw_1350_portal_rightclick_protection__", StringComparison.Ordinal))
				{
					return -1;
				}
			}
			int num = 0;
			for (int num2 = _data.suppressedKeys.Count - 1; num2 >= 0; num2--)
			{
				string text = _data.suppressedKeys[num2];
				if (!string.IsNullOrEmpty(text) && text.StartsWith("portal:", StringComparison.OrdinalIgnoreCase))
				{
					_data.suppressedKeys.RemoveAt(num2);
					num++;
				}
			}
			_data.suppressedKeys.Add("__jw_1350_portal_rightclick_protection__");
			return num;
		}

		private int MigratePortalVerification()
		{
			if (_data == null || _data.pins == null || _data.schemaVersion >= 11)
			{
				return -1;
			}
			int num = 0;
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Portal)
				{
					wayfinderPinRecord.portalVerified = HasStablePortalIdentity(wayfinderPinRecord);
					if (!wayfinderPinRecord.portalVerified)
					{
						num++;
					}
				}
			}
			_data.schemaVersion = 11;
			return num;
		}

		private static bool HasStablePortalIdentity(WayfinderPinRecord record)
		{
			if (record == null || record.members == null)
			{
				return false;
			}
			for (int i = 0; i < record.members.Count; i++)
			{
				WayfinderClusterMember wayfinderClusterMember = record.members[i];
				if (wayfinderClusterMember != null && !string.IsNullOrEmpty(wayfinderClusterMember.key) && wayfinderClusterMember.key.StartsWith("portal:zdo:", StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private int RepairPre1351VehicleNaming(out int preservedCustomNames)
		{
			preservedCustomNames = 0;
			if (_data == null || _data.pins == null || _data.schemaVersion >= 9)
			{
				return -1;
			}
			int num = 0;
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord == null || wayfinderPinRecord.category != WayfinderPinCategory.Vehicle || wayfinderPinRecord.source != WayfinderPinSource.Dynamic)
				{
					continue;
				}
				if (WayfinderVehicleNaming.LooksLikeAutomaticOrLegacyName(wayfinderPinRecord))
				{
					wayfinderPinRecord.vehicleNameOverride = false;
					string automaticDisplayName = WayfinderVehicleNaming.GetAutomaticDisplayName(wayfinderPinRecord);
					if (!string.Equals(wayfinderPinRecord.displayName ?? string.Empty, automaticDisplayName, StringComparison.Ordinal))
					{
						wayfinderPinRecord.displayName = automaticDisplayName;
						num++;
					}
				}
				else
				{
					wayfinderPinRecord.vehicleNameOverride = true;
					preservedCustomNames++;
				}
			}
			return num;
		}

		internal bool UnloadWorld()
		{
			int num = ((_data != null && _data.pins != null) ? _data.pins.Count : 0);
			long num2 = ((_data == null) ? 0 : _data.worldUid);
			if (!Save())
			{
				return false;
			}
			_data = new WayfinderWorldData();
			_filePath = null;
			_dirty = false;
			_suppressedKeyCache.Clear();
			_nextScheduledSaveTime = 0f;
			Revision++;
			if (num2 != 0L)
			{
				_log.LogInfo((object)("Wayfinder world session closed after saving " + num + " records."));
			}
			return true;
		}

		internal void Tick()
		{
			if (_dirty && !(_nextScheduledSaveTime <= 0f) && Time.unscaledTime >= _nextScheduledSaveTime)
			{
				Save();
			}
		}

		private void ScheduleSave()
		{
			if (_dirty && !string.IsNullOrEmpty(_filePath) && !(_nextScheduledSaveTime > 0f))
			{
				_nextScheduledSaveTime = Time.unscaledTime + Mathf.Max(0.5f, _config.PersistenceFlushInterval.Value);
			}
		}

		internal bool Save()
		{
			if (!_dirty || string.IsNullOrEmpty(_filePath))
			{
				_nextScheduledSaveTime = 0f;
				return true;
			}
			try
			{
				WayfinderSaveCodec.Write(_filePath, _data);
				_dirty = false;
				_nextScheduledSaveTime = 0f;
				if (_config.DebugLogging.Value && _config.VerboseRuntimeLogging.Value)
				{
					_log.LogInfo((object)("Wayfinder persisted " + _data.pins.Count + " pins to " + _filePath));
				}
				return true;
			}
			catch (Exception ex)
			{
				_dirty = true;
				_nextScheduledSaveTime = Time.unscaledTime + Mathf.Max(1f, _config.PersistenceFlushInterval.Value);
				_log.LogWarning((object)("Could not save Wayfinder world database; keeping changes in memory and retrying automatically: " + ex.Message));
				return false;
			}
		}

		internal WayfinderPinRecord AddResourceObservation(string resourceKey, string displayName, Vector3 position, string iconKey, WayfinderPinSource source, string memberKey = null, float? linkDistanceOverride = null)
		{
			//IL_0026: 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_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(resourceKey))
			{
				throw new ArgumentException("resourceKey is required", "resourceKey");
			}
			memberKey = (string.IsNullOrEmpty(memberKey) ? BuildPositionKey(resourceKey, position) : memberKey);
			if (IsSuppressed(memberKey))
			{
				return null;
			}
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord.category != WayfinderPinCategory.Resource || wayfinderPinRecord.subtype != resourceKey || wayfinderPinRecord.members == null)
				{
					continue;
				}
				for (int j = 0; j < wayfinderPinRecord.members.Count; j++)
				{
					WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[j];
					if (wayfinderClusterMember.key == memberKey || HorizontalDistance(wayfinderClusterMember.Position, position) <= 0.25f)
					{
						RepairResourceMetadata(wayfinderPinRecord, displayName, iconKey, source);
						return wayfinderPinRecord;
					}
				}
			}
			float num = linkDistanceOverride ?? _config.ResourceClusterLinkDistance.Value;
			if (!_config.ResourceClustering.Value)
			{
				num = 0f;
			}
			List<WayfinderPinRecord> list = new List<WayfinderPinRecord>();
			if (num > 0f)
			{
				for (int k = 0; k < _data.pins.Count; k++)
				{
					WayfinderPinRecord wayfinderPinRecord2 = _data.pins[k];
					if (wayfinderPinRecord2.category == WayfinderPinCategory.Resource && !(wayfinderPinRecord2.subtype != resourceKey) && ClusterTouches(wayfinderPinRecord2, position, num))
					{
						list.Add(wayfinderPinRecord2);
					}
				}
			}
			WayfinderPinRecord wayfinderPinRecord3;
			if (list.Count == 0)
			{
				wayfinderPinRecord3 = NewResourceRecord(resourceKey, displayName, position, iconKey, source);
				wayfinderPinRecord3.members.Add(new WayfinderClusterMember(memberKey, position));
				_data.pins.Add(wayfinderPinRecord3);
			}
			else
			{
				wayfinderPinRecord3 = list[0];
				wayfinderPinRecord3.members.Add(new WayfinderClusterMember(memberKey, position));
				if (!string.IsNullOrEmpty(displayName))
				{
					wayfinderPinRecord3.displayName = displayName;
				}
				if (!string.IsNullOrEmpty(iconKey))
				{
					wayfinderPinRecord3.iconKey = iconKey;
				}
				if (source == WayfinderPinSource.InteractionDiscovery)
				{
					wayfinderPinRecord3.source = source;
				}
				for (int l = 1; l < list.Count; l++)
				{
					WayfinderPinRecord wayfinderPinRecord4 = list[l];
					if (wayfinderPinRecord4.members != null)
					{
						wayfinderPinRecord3.members.AddRange(wayfinderPinRecord4.members);
					}
					_data.pins.Remove(wayfinderPinRecord4);
				}
			}
			RecalculateCluster(wayfinderPinRecord3);
			_dirty = true;
			Revision++;
			ScheduleSave();
			return wayfinderPinRecord3;
		}

		internal WayfinderPinRecord AddSightingObservation(string creatureKey, string displayName, Vector3 position, string iconKey, string memberKey, float linkDistance)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(creatureKey))
			{
				return null;
			}
			memberKey = (string.IsNullOrEmpty(memberKey) ? BuildPositionKey("sighting:" + creatureKey, position) : memberKey);
			if (IsSuppressed(memberKey))
			{
				return null;
			}
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord == null || wayfinderPinRecord.category != WayfinderPinCategory.Sighting || !string.Equals(wayfinderPinRecord.subtype, creatureKey, StringComparison.OrdinalIgnoreCase) || wayfinderPinRecord.members == null)
				{
					continue;
				}
				for (int j = 0; j < wayfinderPinRecord.members.Count; j++)
				{
					WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[j];
					if (wayfinderClusterMember != null && wayfinderClusterMember.key == memberKey)
					{
						RepairPointMetadata(wayfinderPinRecord, displayName, iconKey, WayfinderPinSource.Dynamic);
						return wayfinderPinRecord;
					}
				}
			}
			List<WayfinderPinRecord> list = new List<WayfinderPinRecord>();
			for (int k = 0; k < _data.pins.Count; k++)
			{
				WayfinderPinRecord wayfinderPinRecord2 = _data.pins[k];
				if (wayfinderPinRecord2 != null && wayfinderPinRecord2.category == WayfinderPinCategory.Sighting && string.Equals(wayfinderPinRecord2.subtype, creatureKey, StringComparison.OrdinalIgnoreCase) && ClusterTouches(wayfinderPinRecord2, position, linkDistance))
				{
					list.Add(wayfinderPinRecord2);
				}
			}
			WayfinderPinRecord wayfinderPinRecord3;
			if (list.Count == 0)
			{
				wayfinderPinRecord3 = new WayfinderPinRecord
				{
					id = Guid.NewGuid().ToString("N"),
					displayName = (displayName ?? creatureKey),
					subtype = creatureKey,
					iconKey = (iconKey ?? string.Empty),
					category = WayfinderPinCategory.Sighting,
					source = WayfinderPinSource.Dynamic,
					Position = position,
					count = 1,
					isCluster = false,
					scale = 0.9f
				};
				wayfinderPinRecord3.members.Add(new WayfinderClusterMember(memberKey, position));
				_data.pins.Add(wayfinderPinRecord3);
			}
			else
			{
				wayfinderPinRecord3 = list[0];
				wayfinderPinRecord3.members.Add(new WayfinderClusterMember(memberKey, position));
				if (!string.IsNullOrEmpty(displayName))
				{
					wayfinderPinRecord3.displayName = displayName;
				}
				if (!string.IsNullOrEmpty(iconKey))
				{
					wayfinderPinRecord3.iconKey = iconKey;
				}
				for (int l = 1; l < list.Count; l++)
				{
					WayfinderPinRecord wayfinderPinRecord4 = list[l];
					if (wayfinderPinRecord4.members != null)
					{
						wayfinderPinRecord3.members.AddRange(wayfinderPinRecord4.members);
					}
					_data.pins.Remove(wayfinderPinRecord4);
				}
			}
			RecalculateCluster(wayfinderPinRecord3);
			_dirty = true;
			Revision++;
			ScheduleSave();
			return wayfinderPinRecord3;
		}

		internal int CollapseNearbyAutoPoints(Func<WayfinderPinRecord, string> groupSelector, float radius)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			if (groupSelector == null || radius <= 0f)
			{
				return 0;
			}
			float num = radius * radius;
			float num2 = 1f / radius;
			Dictionary<string, Dictionary<long, List<WayfinderPinRecord>>> dictionary = new Dictionary<string, Dictionary<long, List<WayfinderPinRecord>>>(StringComparer.OrdinalIgnoreCase);
			HashSet<WayfinderPinRecord> hashSet = new HashSet<WayfinderPinRecord>();
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord == null || wayfinderPinRecord.category == WayfinderPinCategory.Portal || wayfinderPinRecord.source == WayfinderPinSource.Manual || wayfinderPinRecord.source == WayfinderPinSource.Imported || wayfinderPinRecord.source == WayfinderPinSource.Dynamic)
				{
					continue;
				}
				string text = null;
				try
				{
					text = groupSelector(wayfinderPinRecord);
				}
				catch
				{
				}
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				int num3 = Mathf.FloorToInt(wayfinderPinRecord.Position.x * num2);
				int num4 = Mathf.FloorToInt(wayfinderPinRecord.Position.z * num2);
				if (!dictionary.TryGetValue(text, out var value))
				{
					Dictionary<long, List<WayfinderPinRecord>> dictionary2 = (dictionary[text] = new Dictionary<long, List<WayfinderPinRecord>>());
					value = dictionary2;
				}
				bool flag = false;
				for (int j = -1; j <= 1; j++)
				{
					if (flag)
					{
						break;
					}
					for (int k = -1; k <= 1; k++)
					{
						if (flag)
						{
							break;
						}
						long key = BuildSpatialCellKey(num3 + j, num4 + k);
						if (!value.TryGetValue(key, out var value2))
						{
							continue;
						}
						for (int l = 0; l < value2.Count; l++)
						{
							WayfinderPinRecord wayfinderPinRecord2 = value2[l];
							if (wayfinderPinRecord2 != null)
							{
								float num5 = wayfinderPinRecord2.Position.x - wayfinderPinRecord.Position.x;
								float num6 = wayfinderPinRecord2.Position.z - wayfinderPinRecord.Position.z;
								if (num5 * num5 + num6 * num6 <= num)
								{
									flag = true;
									break;
								}
							}
						}
					}
				}
				if (flag)
				{
					hashSet.Add(wayfinderPinRecord);
					continue;
				}
				long key2 = BuildSpatialCellKey(num3, num4);
				if (!value.TryGetValue(key2, out var value3))
				{
					List<WayfinderPinRecord> list = (value[key2] = new List<WayfinderPinRecord>());
					value3 = list;
				}
				value3.Add(wayfinderPinRecord);
			}
			int num7 = 0;
			if (hashSet.Count > 0)
			{
				for (int num8 = _data.pins.Count - 1; num8 >= 0; num8--)
				{
					if (hashSet.Contains(_data.pins[num8]))
					{
						_data.pins.RemoveAt(num8);
						num7++;
					}
				}
			}
			if (num7 > 0)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num7;
		}

		private static long BuildSpatialCellKey(int x, int z)
		{
			return ((long)x << 32) ^ (uint)z;
		}

		internal WayfinderPinRecord AddPointObservation(string subtype, string displayName, Vector3 position, string iconKey, WayfinderPinCategory category, WayfinderPinSource source, string memberKey)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: 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)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(subtype))
			{
				return null;
			}
			memberKey = (string.IsNullOrEmpty(memberKey) ? BuildPositionKey(subtype, position) : memberKey);
			if (IsSuppressed(memberKey))
			{
				return null;
			}
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord == null || wayfinderPinRecord.category != category || wayfinderPinRecord.subtype != subtype)
				{
					continue;
				}
				if (wayfinderPinRecord.members != null)
				{
					for (int j = 0; j < wayfinderPinRecord.members.Count; j++)
					{
						WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[j];
						if (wayfinderClusterMember.key == memberKey || HorizontalDistance(wayfinderClusterMember.Position, position) <= 0.75f)
						{
							RepairPointMetadata(wayfinderPinRecord, displayName, iconKey, source);
							return wayfinderPinRecord;
						}
					}
				}
				if (HorizontalDistance(wayfinderPinRecord.Position, position) <= 0.75f)
				{
					RepairPointMetadata(wayfinderPinRecord, displayName, iconKey, source);
					return wayfinderPinRecord;
				}
			}
			WayfinderPinRecord wayfinderPinRecord2 = new WayfinderPinRecord();
			wayfinderPinRecord2.id = Guid.NewGuid().ToString("N");
			wayfinderPinRecord2.displayName = displayName ?? subtype;
			wayfinderPinRecord2.subtype = subtype;
			wayfinderPinRecord2.iconKey = iconKey ?? string.Empty;
			wayfinderPinRecord2.category = category;
			wayfinderPinRecord2.source = source;
			wayfinderPinRecord2.Position = position;
			wayfinderPinRecord2.count = 1;
			wayfinderPinRecord2.isCluster = false;
			float scale;
			switch (category)
			{
			default:
				scale = 1f;
				break;
			case WayfinderPinCategory.Dungeon:
			case WayfinderPinCategory.PointOfInterest:
				scale = 0.95f;
				break;
			case WayfinderPinCategory.Boss:
				scale = Mathf.Max(1f, _config.BossPinScale.Value);
				break;
			}
			wayfinderPinRecord2.scale = scale;
			WayfinderPinRecord wayfinderPinRecord3 = wayfinderPinRecord2;
			wayfinderPinRecord3.members.Add(new WayfinderClusterMember(memberKey, position));
			_data.pins.Add(wayfinderPinRecord3);
			_dirty = true;
			Revision++;
			ScheduleSave();
			return wayfinderPinRecord3;
		}

		internal void BeginPortalObservationPass()
		{
			if (_data == null || _data.pins == null)
			{
				return;
			}
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Portal)
				{
					wayfinderPinRecord.portalLive = false;
				}
			}
		}

		internal WayfinderPinRecord UpsertPortalObservation(string subtype, string portalTag, Vector3 position, string stableMemberKey, string positionMemberKey, bool connectionKnown, bool connected, bool syncDisplayName, out string action)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03af: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03db: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_046a: Unknown result type (might be due to invalid IL or missing references)
			//IL_049e: Unknown result type (might be due to invalid IL or missing references)
			//IL_048a: Unknown result type (might be due to invalid IL or missing references)
			action = "rejected";
			if (string.IsNullOrEmpty(subtype))
			{
				subtype = "Portal";
			}
			positionMemberKey = (string.IsNullOrEmpty(positionMemberKey) ? BuildPositionKey("portal-pos:" + subtype, position) : positionMemberKey);
			if (string.IsNullOrEmpty(stableMemberKey) || IsSuppressed(stableMemberKey) || IsSuppressed(positionMemberKey))
			{
				action = "suppressed";
				return null;
			}
			WayfinderPinRecord wayfinderPinRecord = null;
			float num = float.MaxValue;
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord2 = _data.pins[i];
				if (wayfinderPinRecord2 != null && wayfinderPinRecord2.category == WayfinderPinCategory.Portal)
				{
					float num2 = HorizontalDistance(wayfinderPinRecord2.Position, position);
					if ((RecordHasMemberKey(wayfinderPinRecord2, stableMemberKey) && num2 <= 3f) || RecordHasMemberKey(wayfinderPinRecord2, positionMemberKey))
					{
						wayfinderPinRecord = wayfinderPinRecord2;
						break;
					}
					float num3 = num2;
					float num4 = (wayfinderPinRecord2.portalVerified ? 1f : 3f);
					if (num3 <= num4 && num3 < num && string.Equals(wayfinderPinRecord2.subtype ?? string.Empty, subtype, StringComparison.OrdinalIgnoreCase))
					{
						wayfinderPinRecord = wayfinderPinRecord2;
						num = num3;
					}
				}
			}
			string text = (string.IsNullOrEmpty(portalTag) ? string.Empty : portalTag.Trim());
			string text2 = (string.IsNullOrEmpty(text) ? "Portal" : text);
			long num5 = WayfinderDynamicState.NowUnixSeconds();
			if (wayfinderPinRecord == null)
			{
				WayfinderPinRecord wayfinderPinRecord3 = new WayfinderPinRecord
				{
					id = Guid.NewGuid().ToString("N"),
					displayName = text2,
					subtype = subtype,
					iconKey = "wayfinder:portal",
					category = WayfinderPinCategory.Portal,
					source = WayfinderPinSource.InteractionDiscovery,
					Position = position,
					count = 1,
					isCluster = false,
					scale = 1f,
					portalTag = text,
					portalConnectionKnown = connectionKnown,
					portalConnected = (connectionKnown && connected),
					portalStatusSeenUnix = (connectionKnown ? num5 : 0),
					portalLive = true,
					portalVerified = true
				};
				wayfinderPinRecord3.members.Add(new WayfinderClusterMember(stableMemberKey, position));
				wayfinderPinRecord3.members.Add(new WayfinderClusterMember(positionMemberKey, position));
				_data.pins.Add(wayfinderPinRecord3);
				_dirty = true;
				Revision++;
				if (_config != null)
				{
					ScheduleSave();
				}
				action = "created";
				return wayfinderPinRecord3;
			}
			bool flag = false;
			bool flag2 = false;
			wayfinderPinRecord.portalLive = true;
			if (!wayfinderPinRecord.portalVerified)
			{
				wayfinderPinRecord.portalVerified = true;
				flag = true;
			}
			if (wayfinderPinRecord.category != WayfinderPinCategory.Portal)
			{
				wayfinderPinRecord.category = WayfinderPinCategory.Portal;
				flag = true;
			}
			if (wayfinderPinRecord.source != WayfinderPinSource.InteractionDiscovery)
			{
				wayfinderPinRecord.source = WayfinderPinSource.InteractionDiscovery;
				flag = true;
			}
			if (!string.Equals(wayfinderPinRecord.subtype, subtype, StringComparison.Ordinal))
			{
				wayfinderPinRecord.subtype = subtype;
				flag = true;
			}
			if (!string.Equals(wayfinderPinRecord.iconKey, "wayfinder:portal", StringComparison.OrdinalIgnoreCase))
			{
				wayfinderPinRecord.iconKey = "wayfinder:portal";
				flag = true;
			}
			if (!string.Equals(wayfinderPinRecord.portalTag ?? string.Empty, text, StringComparison.Ordinal))
			{
				wayfinderPinRecord.portalTag = text;
				flag = true;
			}
			if (syncDisplayName && !wayfinderPinRecord.portalNameOverride && !string.Equals(wayfinderPinRecord.displayName, text2, StringComparison.Ordinal))
			{
				wayfinderPinRecord.displayName = text2;
				flag = true;
			}
			else if (string.IsNullOrEmpty(wayfinderPinRecord.displayName))
			{
				wayfinderPinRecord.displayName = text2;
				flag = true;
			}
			if (connectionKnown)
			{
				if (!wayfinderPinRecord.portalConnectionKnown || wayfinderPinRecord.portalConnected != connected)
				{
					wayfinderPinRecord.portalConnectionKnown = true;
					wayfinderPinRecord.portalConnected = connected;
					flag = true;
				}
				if (num5 > 0 && (wayfinderPinRecord.portalStatusSeenUnix <= 0 || num5 - wayfinderPinRecord.portalStatusSeenUnix >= 5))
				{
					wayfinderPinRecord.portalStatusSeenUnix = num5;
					flag2 = true;
				}
			}
			else if (wayfinderPinRecord.portalConnectionKnown)
			{
				wayfinderPinRecord.portalConnectionKnown = false;
				flag = true;
			}
			if (HorizontalDistance(wayfinderPinRecord.Position, position) > 0.01f || Mathf.Abs(wayfinderPinRecord.y - position.y) > 0.01f)
			{
				wayfinderPinRecord.Position = position;
				flag = true;
			}
			if (wayfinderPinRecord.members == null)
			{
				wayfinderPinRecord.members = new List<WayfinderClusterMember>();
			}
			for (int num6 = wayfinderPinRecord.members.Count - 1; num6 >= 0; num6--)
			{
				WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[num6];
				if (wayfinderClusterMember == null || (!string.Equals(wayfinderClusterMember.key, stableMemberKey, StringComparison.Ordinal) && !string.Equals(wayfinderClusterMember.key, positionMemberKey, StringComparison.Ordinal)))
				{
					wayfinderPinRecord.members.RemoveAt(num6);
					flag = true;
				}
			}
			if (!RecordHasMemberKey(wayfinderPinRecord, stableMemberKey))
			{
				wayfinderPinRecord.members.Add(new WayfinderClusterMember(stableMemberKey, position));
				flag = true;
			}
			if (!RecordHasMemberKey(wayfinderPinRecord, positionMemberKey))
			{
				wayfinderPinRecord.members.Add(new WayfinderClusterMember(positionMemberKey, position));
				flag = true;
			}
			int num7 = MergePortalDuplicates(wayfinderPinRecord, stableMemberKey, positionMemberKey, position, subtype);
			if (num7 > 0)
			{
				flag = true;
			}
			if (flag || flag2)
			{
				_dirty = true;
				if (_config != null)
				{
					ScheduleSave();
				}
				if (flag)
				{
					Revision++;
				}
			}
			action = (flag ? "updated" : "matched") + ((num7 > 0) ? (";merged=" + num7) : string.Empty);
			return wayfinderPinRecord;
		}

		private static bool RecordHasMemberKey(WayfinderPinRecord record, string key)
		{
			if (record == null || record.members == null || string.IsNullOrEmpty(key))
			{
				return false;
			}
			for (int i = 0; i < record.members.Count; i++)
			{
				WayfinderClusterMember wayfinderClusterMember = record.members[i];
				if (wayfinderClusterMember != null && string.Equals(wayfinderClusterMember.key, key, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private int MergePortalDuplicates(WayfinderPinRecord keep, string stableKey, string positionKey, Vector3 position, string subtype)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord != keep && wayfinderPinRecord.category == WayfinderPinCategory.Portal)
				{
					float num3 = HorizontalDistance(wayfinderPinRecord.Position, position);
					bool num4 = (RecordHasMemberKey(wayfinderPinRecord, stableKey) && num3 <= 3f) || RecordHasMemberKey(wayfinderPinRecord, positionKey);
					bool flag = !wayfinderPinRecord.portalVerified && num3 <= 3f && string.Equals(wayfinderPinRecord.subtype ?? string.Empty, subtype, StringComparison.OrdinalIgnoreCase);
					if (num4 || flag)
					{
						MergePortalUserState(keep, wayfinderPinRecord);
						_data.pins.RemoveAt(num2);
						num++;
					}
				}
			}
			return num;
		}

		private static void MergePortalUserState(WayfinderPinRecord keep, WayfinderPinRecord duplicate)
		{
			if (duplicate.favorite)
			{
				keep.favorite = true;
			}
			if (duplicate.tracked && !keep.tracked)
			{
				keep.tracked = true;
				keep.trackSlot = duplicate.trackSlot;
			}
			if (duplicate.iconOverride && !keep.iconOverride)
			{
				keep.iconOverride = true;
				keep.iconOverrideKey = duplicate.iconOverrideKey;
			}
			if (duplicate.portalNameOverride && !keep.portalNameOverride)
			{
				keep.portalNameOverride = true;
				keep.displayName = duplicate.displayName;
			}
		}

		internal bool RemovePortalObservation(string stableMemberKey, string positionMemberKey, Vector3 position)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			for (int num = _data.pins.Count - 1; num >= 0; num--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Portal)
				{
					float num2 = HorizontalDistance(wayfinderPinRecord.Position, position);
					if ((RecordHasMemberKey(wayfinderPinRecord, stableMemberKey) && num2 <= 3f) || RecordHasMemberKey(wayfinderPinRecord, positionMemberKey) || (wayfinderPinRecord.portalVerified && num2 <= 1f))
					{
						_data.pins.RemoveAt(num);
						_dirty = true;
						Revision++;
						if (_config != null)
						{
							ScheduleSave();
						}
						return true;
					}
				}
			}
			return false;
		}

		internal bool RemovePointByMemberKey(WayfinderPinCategory category, string memberKey)
		{
			if (string.IsNullOrEmpty(memberKey))
			{
				return false;
			}
			for (int num = _data.pins.Count - 1; num >= 0; num--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == category && wayfinderPinRecord.members != null)
				{
					for (int i = 0; i < wayfinderPinRecord.members.Count; i++)
					{
						WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[i];
						if (wayfinderClusterMember != null && string.Equals(wayfinderClusterMember.key, memberKey, StringComparison.Ordinal))
						{
							_data.pins.RemoveAt(num);
							_dirty = true;
							Revision++;
							ScheduleSave();
							return true;
						}
					}
				}
			}
			return false;
		}

		internal void BeginDynamicObservationPass(WayfinderPinCategory category)
		{
			if (_data == null || _data.pins == null)
			{
				return;
			}
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == category && wayfinderPinRecord.source == WayfinderPinSource.Dynamic)
				{
					wayfinderPinRecord.dynamicLive = false;
				}
			}
		}

		internal bool HasDynamicPointByMemberKey(WayfinderPinCategory category, string memberKey)
		{
			if (string.IsNullOrEmpty(memberKey) || _data == null || _data.pins == null)
			{
				return false;
			}
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord == null || wayfinderPinRecord.category != category || wayfinderPinRecord.source != WayfinderPinSource.Dynamic || wayfinderPinRecord.members == null)
				{
					continue;
				}
				for (int j = 0; j < wayfinderPinRecord.members.Count; j++)
				{
					WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[j];
					if (wayfinderClusterMember != null && string.Equals(wayfinderClusterMember.key, memberKey, StringComparison.Ordinal))
					{
						return true;
					}
				}
			}
			return false;
		}

		internal int FinalizeDynamicObservationPass(WayfinderPinCategory category, HashSet<string> seenMemberKeys, float staleGraceSeconds)
		{
			if (_data == null || _data.pins == null)
			{
				return 0;
			}
			float unscaledTime = Time.unscaledTime;
			float num = Mathf.Max(0f, staleGraceSeconds);
			int num2 = 0;
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord == null || wayfinderPinRecord.category != category || wayfinderPinRecord.source != WayfinderPinSource.Dynamic)
				{
					continue;
				}
				bool flag = false;
				if (wayfinderPinRecord.members != null && seenMemberKeys != null)
				{
					for (int j = 0; j < wayfinderPinRecord.members.Count; j++)
					{
						WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[j];
						if (wayfinderClusterMember != null && seenMemberKeys.Contains(wayfinderClusterMember.key))
						{
							flag = true;
							break;
						}
					}
				}
				if (!flag && wayfinderPinRecord.dynamicLive)
				{
					float dynamicLastObservedRealtime = wayfinderPinRecord.dynamicLastObservedRealtime;
					if (!(dynamicLastObservedRealtime > 0f) || !(unscaledTime - dynamicLastObservedRealtime < num))
					{
						wayfinderPinRecord.dynamicLive = false;
						num2++;
					}
				}
			}
			return num2;
		}

		internal WayfinderPinRecord UpsertDynamicPoint(string subtype, string displayName, Vector3 position, string iconKey, WayfinderPinCategory category, string memberKey, float movementTolerance)
		{
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			//IL_0333: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(subtype) || string.IsNullOrEmpty(memberKey))
			{
				return null;
			}
			if (IsSuppressed(memberKey))
			{
				return null;
			}
			WayfinderPinRecord wayfinderPinRecord = null;
			WayfinderClusterMember wayfinderClusterMember = null;
			for (int i = 0; i < _data.pins.Count; i++)
			{
				if (wayfinderPinRecord != null)
				{
					break;
				}
				WayfinderPinRecord wayfinderPinRecord2 = _data.pins[i];
				if (wayfinderPinRecord2 == null || wayfinderPinRecord2.source != WayfinderPinSource.Dynamic || wayfinderPinRecord2.members == null)
				{
					continue;
				}
				for (int j = 0; j < wayfinderPinRecord2.members.Count; j++)
				{
					WayfinderClusterMember wayfinderClusterMember2 = wayfinderPinRecord2.members[j];
					if (wayfinderClusterMember2 != null && string.Equals(wayfinderClusterMember2.key, memberKey, StringComparison.Ordinal))
					{
						wayfinderPinRecord = wayfinderPinRecord2;
						wayfinderClusterMember = wayfinderClusterMember2;
						break;
					}
				}
			}
			if (wayfinderPinRecord == null)
			{
				WayfinderPinRecord wayfinderPinRecord3 = new WayfinderPinRecord
				{
					id = Guid.NewGuid().ToString("N"),
					displayName = (string.IsNullOrEmpty(displayName) ? subtype : displayName),
					subtype = subtype,
					iconKey = (iconKey ?? string.Empty),
					category = category,
					source = WayfinderPinSource.Dynamic,
					Position = position,
					count = 1,
					isCluster = false,
					scale = 1f,
					lastSeenUnix = WayfinderDynamicState.NowUnixSeconds(),
					vehicleNameOverride = false,
					dynamicLive = true,
					dynamicLastObservedRealtime = Time.unscaledTime
				};
				wayfinderPinRecord3.members.Add(new WayfinderClusterMember(memberKey, position));
				_data.pins.Add(wayfinderPinRecord3);
				_dirty = true;
				Revision++;
				ScheduleSave();
				return wayfinderPinRecord3;
			}
			bool flag = false;
			bool flag2 = false;
			bool flag3 = false;
			wayfinderPinRecord.dynamicLive = true;
			wayfinderPinRecord.dynamicLastObservedRealtime = Time.unscaledTime;
			long num = WayfinderDynamicState.NowUnixSeconds();
			if (num > 0 && (wayfinderPinRecord.lastSeenUnix <= 0 || num - wayfinderPinRecord.lastSeenUnix >= 5))
			{
				wayfinderPinRecord.lastSeenUnix = num;
				flag3 = true;
			}
			if (category == WayfinderPinCategory.Vehicle)
			{
				if (!wayfinderPinRecord.vehicleNameOverride && !string.IsNullOrEmpty(displayName) && !string.Equals(wayfinderPinRecord.displayName, displayName, StringComparison.Ordinal))
				{
					wayfinderPinRecord.displayName = displayName;
					flag = true;
				}
			}
			else if (string.IsNullOrEmpty(wayfinderPinRecord.displayName) && !string.IsNullOrEmpty(displayName))
			{
				wayfinderPinRecord.displayName = displayName;
				flag = true;
			}
			if (!string.Equals(wayfinderPinRecord.subtype, subtype, StringComparison.Ordinal))
			{
				wayfinderPinRecord.subtype = subtype;
				flag = true;
			}
			if (!string.IsNullOrEmpty(iconKey) && !string.Equals(wayfinderPinRecord.iconKey, iconKey, StringComparison.OrdinalIgnoreCase))
			{
				wayfinderPinRecord.iconKey = iconKey;
				flag = true;
			}
			if (wayfinderPinRecord.category != category)
			{
				wayfinderPinRecord.category = category;
				flag = true;
			}
			if (wayfinderPinRecord.source != WayfinderPinSource.Dynamic)
			{
				wayfinderPinRecord.source = WayfinderPinSource.Dynamic;
				flag = true;
			}
			float num2 = Mathf.Max(0f, movementTolerance);
			Vector3 position2 = wayfinderPinRecord.Position;
			float num3 = position2.x - position.x;
			float num4 = position2.y - position.y;
			float num5 = position2.z - position.z;
			if (num3 * num3 + num4 * num4 + num5 * num5 > num2 * num2)
			{
				wayfinderPinRecord.Position = position;
				if (wayfinderClusterMember == null)
				{
					wayfinderClusterMember = new WayfinderClusterMember(memberKey, position);
					wayfinderPinRecord.members.Add(wayfinderClusterMember);
					flag = true;
				}
				else
				{
					wayfinderClusterMember.x = position.x;
					wayfinderClusterMember.y = position.y;
					wayfinderClusterMember.z = position.z;
				}
				flag2 = true;
			}
			if (flag || flag2 || flag3)
			{
				_dirty = true;
				ScheduleSave();
				if (flag)
				{
					Revision++;
				}
			}
			return wayfinderPinRecord;
		}

		internal int CollapseDuplicateDynamicMemberKeys(WayfinderPinCategory category)
		{
			Dictionary<string, WayfinderPinRecord> dictionary = new Dictionary<string, WayfinderPinRecord>(StringComparer.Ordinal);
			int num = 0;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == category && wayfinderPinRecord.source == WayfinderPinSource.Dynamic && wayfinderPinRecord.members != null && wayfinderPinRecord.members.Count != 0)
				{
					string text = ((wayfinderPinRecord.members[0] == null) ? null : wayfinderPinRecord.members[0].key);
					if (!string.IsNullOrEmpty(text))
					{
						if (!dictionary.TryGetValue(text, out var value))
						{
							dictionary[text] = wayfinderPinRecord;
						}
						else
						{
							if (wayfinderPinRecord.tracked && !value.tracked)
							{
								value.tracked = true;
								value.trackSlot = wayfinderPinRecord.trackSlot;
							}
							if (wayfinderPinRecord.favorite)
							{
								value.favorite = true;
							}
							if (wayfinderPinRecord.iconOverride && !value.iconOverride)
							{
								value.iconOverride = true;
								value.iconOverrideKey = wayfinderPinRecord.iconOverrideKey ?? string.Empty;
							}
							if (wayfinderPinRecord.lastSeenUnix > value.lastSeenUnix)
							{
								value.lastSeenUnix = wayfinderPinRecord.lastSeenUnix;
							}
							if (wayfinderPinRecord.dynamicLive)
							{
								value.dynamicLive = true;
							}
							if (wayfinderPinRecord.dynamicLastObservedRealtime > value.dynamicLastObservedRealtime)
							{
								value.dynamicLastObservedRealtime = wayfinderPinRecord.dynamicLastObservedRealtime;
							}
							if (category == WayfinderPinCategory.Vehicle && wayfinderPinRecord.vehicleNameOverride && !value.vehicleNameOverride)
							{
								value.vehicleNameOverride = true;
								value.displayName = wayfinderPinRecord.displayName;
							}
							_data.pins.RemoveAt(num2);
							num++;
						}
					}
				}
			}
			if (num > 0)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal int RemoveStaleDynamicAliasesNear(WayfinderPinCategory category, Vector3 livePosition, float radius, string subtypePrefix, string currentMemberKey, HashSet<string> liveMemberKeys)
		{
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: 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_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Max(0f, radius);
			num *= num;
			int num2 = 0;
			for (int num3 = _data.pins.Count - 1; num3 >= 0; num3--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num3];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == category && wayfinderPinRecord.source == WayfinderPinSource.Dynamic && wayfinderPinRecord.members != null && wayfinderPinRecord.members.Count != 0 && (string.IsNullOrEmpty(subtypePrefix) || (wayfinderPinRecord.subtype != null && wayfinderPinRecord.subtype.StartsWith(subtypePrefix, StringComparison.OrdinalIgnoreCase))))
				{
					string text = ((wayfinderPinRecord.members[0] == null) ? null : wayfinderPinRecord.members[0].key);
					if (!string.IsNullOrEmpty(text) && !string.Equals(text, currentMemberKey, StringComparison.Ordinal) && (liveMemberKeys == null || !liveMemberKeys.Contains(text)))
					{
						float num4 = wayfinderPinRecord.Position.x - livePosition.x;
						float num5 = wayfinderPinRecord.Position.y - livePosition.y;
						float num6 = wayfinderPinRecord.Position.z - livePosition.z;
						if (!(num4 * num4 + num5 * num5 + num6 * num6 > num))
						{
							WayfinderPinRecord wayfinderPinRecord2 = null;
							for (int i = 0; i < _data.pins.Count; i++)
							{
								WayfinderPinRecord wayfinderPinRecord3 = _data.pins[i];
								if (wayfinderPinRecord3 == null || wayfinderPinRecord3.members == null)
								{
									continue;
								}
								for (int j = 0; j < wayfinderPinRecord3.members.Count; j++)
								{
									WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord3.members[j];
									if (wayfinderClusterMember != null && string.Equals(wayfinderClusterMember.key, currentMemberKey, StringComparison.Ordinal))
									{
										wayfinderPinRecord2 = wayfinderPinRecord3;
										break;
									}
								}
								if (wayfinderPinRecord2 != null)
								{
									break;
								}
							}
							if (wayfinderPinRecord2 != null)
							{
								if (wayfinderPinRecord.tracked && !wayfinderPinRecord2.tracked)
								{
									wayfinderPinRecord2.tracked = true;
									wayfinderPinRecord2.trackSlot = wayfinderPinRecord.trackSlot;
								}
								if (wayfinderPinRecord.favorite)
								{
									wayfinderPinRecord2.favorite = true;
								}
								if (wayfinderPinRecord.iconOverride && !wayfinderPinRecord2.iconOverride)
								{
									wayfinderPinRecord2.iconOverride = true;
									wayfinderPinRecord2.iconOverrideKey = wayfinderPinRecord.iconOverrideKey ?? string.Empty;
								}
								if (wayfinderPinRecord.lastSeenUnix > wayfinderPinRecord2.lastSeenUnix)
								{
									wayfinderPinRecord2.lastSeenUnix = wayfinderPinRecord.lastSeenUnix;
								}
								if (category == WayfinderPinCategory.Vehicle && wayfinderPinRecord.vehicleNameOverride && !wayfinderPinRecord2.vehicleNameOverride)
								{
									wayfinderPinRecord2.vehicleNameOverride = true;
									wayfinderPinRecord2.displayName = wayfinderPinRecord.displayName;
								}
							}
							_data.pins.RemoveAt(num3);
							num2++;
						}
					}
				}
			}
			if (num2 > 0)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num2;
		}

		internal bool RemoveDynamicPointByMemberKey(WayfinderPinCategory category, string memberKey)
		{
			if (string.IsNullOrEmpty(memberKey))
			{
				return false;
			}
			for (int num = _data.pins.Count - 1; num >= 0; num--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == category && wayfinderPinRecord.source == WayfinderPinSource.Dynamic && wayfinderPinRecord.members != null)
				{
					for (int i = 0; i < wayfinderPinRecord.members.Count; i++)
					{
						WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[i];
						if (wayfinderClusterMember != null && string.Equals(wayfinderClusterMember.key, memberKey, StringComparison.Ordinal))
						{
							_data.pins.RemoveAt(num);
							_dirty = true;
							Revision++;
							ScheduleSave();
							return true;
						}
					}
				}
			}
			return false;
		}

		internal bool RemoveNearestDynamicPoint(WayfinderPinCategory category, Vector3 position, float radius, string subtypePrefix)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: 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)
			float num = Mathf.Max(0f, radius);
			float num2 = num * num;
			int num3 = -1;
			float num4 = num2;
			for (int i = 0; i < _data.pins.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[i];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == category && wayfinderPinRecord.source == WayfinderPinSource.Dynamic && (string.IsNullOrEmpty(subtypePrefix) || (wayfinderPinRecord.subtype != null && wayfinderPinRecord.subtype.StartsWith(subtypePrefix, StringComparison.OrdinalIgnoreCase))))
				{
					float num5 = wayfinderPinRecord.Position.x - position.x;
					float num6 = wayfinderPinRecord.Position.y - position.y;
					float num7 = wayfinderPinRecord.Position.z - position.z;
					float num8 = num5 * num5 + num6 * num6 + num7 * num7;
					if (num8 <= num4)
					{
						num4 = num8;
						num3 = i;
					}
				}
			}
			if (num3 < 0)
			{
				return false;
			}
			_data.pins.RemoveAt(num3);
			_dirty = true;
			Revision++;
			ScheduleSave();
			return true;
		}

		internal int RemoveUnseenDynamicPoints(WayfinderPinCategory category, HashSet<string> seenMemberKeys)
		{
			int num = 0;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == category && wayfinderPinRecord.source == WayfinderPinSource.Dynamic)
				{
					bool flag = false;
					if (wayfinderPinRecord.members != null)
					{
						for (int i = 0; i < wayfinderPinRecord.members.Count; i++)
						{
							WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[i];
							if (wayfinderClusterMember != null && seenMemberKeys != null && seenMemberKeys.Contains(wayfinderClusterMember.key))
							{
								flag = true;
								break;
							}
						}
					}
					if (!flag && !wayfinderPinRecord.dynamicLive)
					{
						_data.pins.RemoveAt(num2);
						num++;
					}
				}
			}
			if (num > 0)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal void MarkMetadataChanged()
		{
			_dirty = true;
			ScheduleSave();
		}

		internal void AddOrUpdate(WayfinderPinRecord record)
		{
			if (record != null)
			{
				if (string.IsNullOrEmpty(record.id))
				{
					record.id = Guid.NewGuid().ToString("N");
				}
				int num = _data.pins.FindIndex((WayfinderPinRecord p) => p.id == record.id);
				if (num >= 0)
				{
					_data.pins[num] = record;
				}
				else
				{
					_data.pins.Add(record);
				}
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
		}

		internal bool Remove(string id)
		{
			return Remove(id, _config.SuppressRemovedAutoPins.Value);
		}

		internal bool Remove(string id, bool suppressAutoRediscovery)
		{
			WayfinderPinRecord wayfinderPinRecord = _data.pins.Find((WayfinderPinRecord p) => p != null && p.id == id);
			if (wayfinderPinRecord == null)
			{
				return false;
			}
			if (suppressAutoRediscovery && wayfinderPinRecord.source != WayfinderPinSource.Manual && wayfinderPinRecord.source != WayfinderPinSource.Vanilla && wayfinderPinRecord.source != WayfinderPinSource.Imported)
			{
				SuppressMembers(wayfinderPinRecord);
			}
			_data.pins.Remove(wayfinderPinRecord);
			_dirty = true;
			Revision++;
			ScheduleSave();
			return true;
		}

		internal int RemoveResourceMembers(Func<string, Vector3, bool> covered)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			if (covered == null)
			{
				return 0;
			}
			int num = 0;
			bool flag = false;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Resource)
				{
					if (wayfinderPinRecord.members == null || wayfinderPinRecord.members.Count == 0)
					{
						bool flag2 = false;
						try
						{
							flag2 = covered(wayfinderPinRecord.subtype, wayfinderPinRecord.Position);
						}
						catch
						{
						}
						if (flag2)
						{
							_data.pins.RemoveAt(num2);
							num++;
							flag = true;
						}
					}
					else
					{
						bool flag3 = false;
						for (int num3 = wayfinderPinRecord.members.Count - 1; num3 >= 0; num3--)
						{
							WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[num3];
							if (wayfinderClusterMember != null)
							{
								bool flag4 = false;
								try
								{
									flag4 = covered(wayfinderPinRecord.subtype, wayfinderClusterMember.Position);
								}
								catch
								{
								}
								if (flag4)
								{
									wayfinderPinRecord.members.RemoveAt(num3);
									num++;
									flag = true;
									flag3 = true;
								}
							}
						}
						if (wayfinderPinRecord.members.Count == 0)
						{
							_data.pins.RemoveAt(num2);
						}
						else if (flag3)
						{
							RecalculateCluster(wayfinderPinRecord);
						}
					}
				}
			}
			if (flag)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal int RemoveSightingMembers(Func<string, Vector3, bool> covered)
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			if (covered == null)
			{
				return 0;
			}
			int num = 0;
			bool flag = false;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Sighting)
				{
					if (wayfinderPinRecord.members == null || wayfinderPinRecord.members.Count == 0)
					{
						bool flag2 = false;
						try
						{
							flag2 = covered(wayfinderPinRecord.subtype, wayfinderPinRecord.Position);
						}
						catch
						{
						}
						if (flag2)
						{
							_data.pins.RemoveAt(num2);
							num++;
							flag = true;
						}
					}
					else
					{
						bool flag3 = false;
						for (int num3 = wayfinderPinRecord.members.Count - 1; num3 >= 0; num3--)
						{
							WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[num3];
							if (wayfinderClusterMember != null)
							{
								bool flag4 = false;
								try
								{
									flag4 = covered(wayfinderPinRecord.subtype, wayfinderClusterMember.Position);
								}
								catch
								{
								}
								if (flag4)
								{
									wayfinderPinRecord.members.RemoveAt(num3);
									num++;
									flag = true;
									flag3 = true;
								}
							}
						}
						if (wayfinderPinRecord.members.Count == 0)
						{
							_data.pins.RemoveAt(num2);
						}
						else if (flag3)
						{
							RecalculateCluster(wayfinderPinRecord);
						}
					}
				}
			}
			if (flag)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal int RemoveAutoResourceSubtype(string subtype)
		{
			if (string.IsNullOrEmpty(subtype))
			{
				return 0;
			}
			int num = 0;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Resource && string.Equals(wayfinderPinRecord.subtype, subtype, StringComparison.OrdinalIgnoreCase) && wayfinderPinRecord.source != WayfinderPinSource.Manual && wayfinderPinRecord.source != WayfinderPinSource.Imported)
				{
					_data.pins.RemoveAt(num2);
					num++;
				}
			}
			if (num > 0)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal int RemoveAutoResourceSubtypeBySource(string subtype, WayfinderPinSource source)
		{
			if (string.IsNullOrEmpty(subtype))
			{
				return 0;
			}
			int num = 0;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Resource && string.Equals(wayfinderPinRecord.subtype, subtype, StringComparison.OrdinalIgnoreCase) && wayfinderPinRecord.source == source)
				{
					_data.pins.RemoveAt(num2);
					num++;
				}
			}
			if (num > 0)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal int RemoveAutoPointSubtypesContaining(params string[] normalizedNeedles)
		{
			if (normalizedNeedles == null || normalizedNeedles.Length == 0)
			{
				return 0;
			}
			int num = 0;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && (wayfinderPinRecord.category == WayfinderPinCategory.PointOfInterest || wayfinderPinRecord.category == WayfinderPinCategory.Dungeon) && wayfinderPinRecord.source != WayfinderPinSource.Manual && wayfinderPinRecord.source != WayfinderPinSource.Imported && wayfinderPinRecord.source != WayfinderPinSource.Vanilla)
				{
					string text = NormalizeKey(wayfinderPinRecord.subtype);
					bool flag = false;
					for (int i = 0; i < normalizedNeedles.Length; i++)
					{
						string value = NormalizeKey(normalizedNeedles[i]);
						if (!string.IsNullOrEmpty(value) && text.Contains(value))
						{
							flag = true;
							break;
						}
					}
					if (flag)
					{
						_data.pins.RemoveAt(num2);
						num++;
					}
				}
			}
			if (num > 0)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal int RemoveAutoResourceSubtypeAboveY(string subtype, float yThreshold)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(subtype))
			{
				return 0;
			}
			int num = 0;
			bool flag = false;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Resource && string.Equals(wayfinderPinRecord.subtype, subtype, StringComparison.OrdinalIgnoreCase) && wayfinderPinRecord.source != WayfinderPinSource.Manual && wayfinderPinRecord.source != WayfinderPinSource.Imported)
				{
					if (wayfinderPinRecord.members == null || wayfinderPinRecord.members.Count == 0)
					{
						if (wayfinderPinRecord.Position.y > yThreshold)
						{
							_data.pins.RemoveAt(num2);
							num++;
							flag = true;
						}
					}
					else
					{
						bool flag2 = false;
						for (int num3 = wayfinderPinRecord.members.Count - 1; num3 >= 0; num3--)
						{
							WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[num3];
							if (wayfinderClusterMember != null && wayfinderClusterMember.Position.y > yThreshold)
							{
								wayfinderPinRecord.members.RemoveAt(num3);
								num++;
								flag2 = true;
								flag = true;
							}
						}
						if (wayfinderPinRecord.members.Count == 0)
						{
							_data.pins.RemoveAt(num2);
						}
						else if (flag2)
						{
							RecalculateCluster(wayfinderPinRecord);
						}
					}
				}
			}
			if (flag)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal int RemoveAutoDungeonResources(float interiorYThreshold)
		{
			//IL_006c: 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)
			int num = 0;
			bool flag = false;
			for (int num2 = _data.pins.Count - 1; num2 >= 0; num2--)
			{
				WayfinderPinRecord wayfinderPinRecord = _data.pins[num2];
				if (wayfinderPinRecord != null && wayfinderPinRecord.category == WayfinderPinCategory.Resource && wayfinderPinRecord.source != WayfinderPinSource.Manual && wayfinderPinRecord.source != WayfinderPinSource.Imported)
				{
					if (wayfinderPinRecord.members == null || wayfinderPinRecord.members.Count == 0)
					{
						if (wayfinderPinRecord.Position.y > interiorYThreshold)
						{
							_data.pins.RemoveAt(num2);
							num++;
							flag = true;
						}
					}
					else
					{
						bool flag2 = false;
						for (int num3 = wayfinderPinRecord.members.Count - 1; num3 >= 0; num3--)
						{
							WayfinderClusterMember wayfinderClusterMember = wayfinderPinRecord.members[num3];
							if (wayfinderClusterMember != null && wayfinderClusterMember.Position.y > interiorYThreshold)
							{
								wayfinderPinRecord.members.RemoveAt(num3);
								num++;
								flag2 = true;
								flag = true;
							}
						}
						if (wayfinderPinRecord.members.Count == 0)
						{
							_data.pins.RemoveAt(num2);
						}
						else if (flag2)
						{
							RecalculateCluster(wayfinderPinRecord);
						}
					}
				}
			}
			if (flag)
			{
				_dirty = true;
				Revision++;
				ScheduleSave();
			}
			return num;
		}

		internal bool IsSuppressed(string memberKey)
		{
			if (_config == null || _config.SuppressRemovedAutoPins == null || !_config.SuppressRemovedAutoPins.Value)
			{
				return false;
			}
			if (string.IsNullOrEmpty(memberKey))
			{
				return false;
			}
			return _suppressedKeyCache.Contains(memberKey);
		}

		private void RepairResourceMetadata(WayfinderPinRecord record, string displayName, string iconKey, WayfinderPinSource source)
		{
			if (record != null)