Decompiled source of Joeys Wayfinder v1.0.0

JoeyBadManners.Wayfinder.dll

Decompiled 3 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
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 TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace JoeyBadManners.Wayfinder
{
	[BepInPlugin("com.joeybadmanners.wayfinder", "Joey's Wayfinder", "1.0.0")]
	public sealed class WayfinderPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.joeybadmanners.wayfinder";

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

		public const string PluginVersion = "1.0.0";

		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;

		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.0.0 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 (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 (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 == 0)
			{
				CloseWorldSessionIfNeeded();
			}
			else if ((num != _loadedWorldUid || Pins.WorldUid != num) && Pins.LoadWorld(num, ZNet.instance.GetWorldName()))
			{
				_loadedWorldUid = num;
				LiveDiscoveryRegistry.ResetForWorld(num);
				PhysicalSpawnerIndex.Reset();
				LiveDiscoveryRegistry.SyncFromLoadedLocations();
				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 != 0) && 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 (Map != null && (Object)(object)Minimap.instance != (Object)null)
			{
				Map.EnforceNativeShipMarkerVisibility(Minimap.instance);
				Map.EnforceVanillaPersistentPinUiVisibility(Minimap.instance);
			}
		}

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

		private void OnDestroy()
		{
			try
			{
				if (TrackingBeacons != null)
				{
					TrackingBeacons.Clear();
				}
				if (Pins != null)
				{
					Pins.Save();
				}
				if (Harmony != null)
				{
					Harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
		}
	}
}
namespace JoeyBadManners.Wayfinder.Core
{
	internal sealed class WayfinderConfig
	{
		internal readonly ConfigEntry<bool> Enabled;

		internal readonly ConfigEntry<bool> DebugLogging;

		internal readonly ConfigEntry<bool> VerboseRuntimeLogging;

		internal readonly ConfigEntry<bool> SupersedeVanillaPinVisuals;

		internal readonly ConfigEntry<float> BossPinScale;

		internal readonly ConfigEntry<bool> RightClickRemovesWayfinderPins;

		internal readonly ConfigEntry<bool> SuppressRemovedAutoPins;

		internal readonly ConfigEntry<string> HiddenCategories;

		internal readonly ConfigEntry<KeyboardShortcut> PinManagerToggleKey;

		internal readonly ConfigEntry<bool> ShowPinManagerMapButton;

		internal readonly ConfigEntry<bool> ReplaceVanillaPersistentPinControls;

		internal readonly ConfigEntry<float> NearbyDiscoveryListRadius;

		internal readonly ConfigEntry<bool> OverrideVanillaBossNames;

		internal readonly ConfigEntry<string> BossNameOverrides;

		internal readonly ConfigEntry<bool> ResourceClustering;

		internal readonly ConfigEntry<float> ResourceClusterLinkDistance;

		internal readonly ConfigEntry<bool> ShowClusterCounts;

		internal readonly ConfigEntry<bool> InteractionDiscovery;

		internal readonly ConfigEntry<bool> AreaScanning;

		internal readonly ConfigEntry<float> AreaScanRadius;

		internal readonly ConfigEntry<bool> MatchAreaScanToMapRevealRadius;

		internal readonly ConfigEntry<bool> ScanUnexploredAreas;

		internal readonly ConfigEntry<bool> EnemySightings;

		internal readonly ConfigEntry<float> EnemySightingRadius;

		internal readonly ConfigEntry<float> EnemySightingClusterDistance;

		internal readonly ConfigEntry<bool> ScanDungeonContents;

		internal readonly ConfigEntry<bool> AutoPinStone;

		internal readonly ConfigEntry<bool> AutoPinBranches;

		internal readonly ConfigEntry<bool> AutoPinFlint;

		internal readonly ConfigEntry<bool> AutoPinDandelions;

		internal readonly ConfigEntry<bool> RememberGeneratedStructures;

		internal readonly ConfigEntry<float> StructureDiscoveryRadius;

		internal readonly ConfigEntry<bool> RememberPhysicalSpawners;

		internal readonly ConfigEntry<bool> AutoPinSurtlingSpawners;

		internal readonly ConfigEntry<float> SpawnerDiscoveryRadius;

		internal readonly ConfigEntry<bool> SuppressSightingsInsideSpawnerRadius;

		internal readonly ConfigEntry<float> SpawnerSightingSuppressionPadding;

		internal readonly ConfigEntry<float> SpawnerSightingExclusionRadius;

		internal readonly ConfigEntry<bool> RememberPortals;

		internal readonly ConfigEntry<float> PortalDiscoveryRadius;

		internal readonly ConfigEntry<float> PortalSyncInterval;

		internal readonly ConfigEntry<bool> PortalNameFromTag;

		internal readonly ConfigEntry<bool> ShowPortalNamesOnLargeMap;

		internal readonly ConfigEntry<bool> RemoveDestroyedPortals;

		internal readonly ConfigEntry<bool> CompassEnabled;

		internal readonly ConfigEntry<bool> CompassShowDistance;

		internal readonly ConfigEntry<bool> CompassShowNames;

		internal readonly ConfigEntry<bool> CompassShowCardinals;

		internal readonly ConfigEntry<bool> CompassClampOffscreenTracked;

		internal readonly ConfigEntry<float> CompassMaxDistance;

		internal readonly ConfigEntry<float> CompassWidth;

		internal readonly ConfigEntry<float> CompassTopOffset;

		internal readonly ConfigEntry<float> CompassArcDegrees;

		internal readonly ConfigEntry<float> CompassOpacity;

		internal readonly ConfigEntry<int> MaxTrackedPins;

		internal readonly ConfigEntry<bool> WaypointEnabled;

		internal readonly ConfigEntry<bool> WaypointShowVerticalDifference;

		internal readonly ConfigEntry<bool> BeaconEnabled;

		internal readonly ConfigEntry<float> BeaconOpacity;

		internal readonly ConfigEntry<float> BeaconHeight;

		internal readonly ConfigEntry<float> BeaconWidth;

		internal readonly ConfigEntry<bool> TrackBoats;

		internal readonly ConfigEntry<bool> TrackCarts;

		internal readonly ConfigEntry<bool> KeepLastKnownVehiclePosition;

		internal readonly ConfigEntry<bool> RemoveDestroyedVehicles;

		internal readonly ConfigEntry<float> VehicleDiscoveryRadius;

		internal readonly ConfigEntry<float> VehicleUpdateInterval;

		internal readonly ConfigEntry<float> LastKnownVehicleOpacity;

		internal readonly ConfigEntry<bool> ShowLastKnownVehicleBadge;

		internal readonly ConfigEntry<bool> ShowVehicleNamesOnLargeMap;

		internal readonly ConfigEntry<bool> ExpandedExploration;

		internal readonly ConfigEntry<float> WalkingExploreMultiplier;

		internal readonly ConfigEntry<float> SailingExploreMultiplier;

		internal readonly ConfigEntry<float> AreaScanInterval;

		internal readonly ConfigEntry<float> EnemySightingInterval;

		internal readonly ConfigEntry<float> StaticDiscoveryInterval;

		internal readonly ConfigEntry<float> SpawnerIndexCacheSeconds;

		internal readonly ConfigEntry<float> PersistenceFlushInterval;

		internal readonly ConfigEntry<float> MapVisualRefreshInterval;

		internal WayfinderConfig(ConfigFile config)
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			Enabled = config.Bind<bool>("00 - General", "Enabled", true, "Enable Joey's Wayfinder.");
			DebugLogging = config.Bind<bool>("00 - General", "DebugLogging", false, "Write additional diagnostic information to the BepInEx log.");
			VerboseRuntimeLogging = config.Bind<bool>("00 - General", "VerboseRuntimeLogging", false, "With DebugLogging enabled, also write high-frequency scan/sync/save/input diagnostics. Off by default to keep normal debug logs readable.");
			SupersedeVanillaPinVisuals = config.Bind<bool>("10 - Map", "SupersedeVanillaPinVisuals", true, "Keep vanilla pin data intact but let Wayfinder replace supported pin visuals.");
			BossPinScale = config.Bind<float>("10 - Map", "BossPinScale", 1.35f, "Visual scale used for Vegvisir-discovered boss pins when Wayfinder trophy styling is active.");
			RightClickRemovesWayfinderPins = config.Bind<bool>("10 - Map", "RightClickRemovesWayfinderPins", true, "Allow normal map right-click removal to delete Wayfinder pins too. Managed portal pins are protected from accidental map removal; delete them deliberately from the Pins panel instead.");
			SuppressRemovedAutoPins = config.Bind<bool>("10 - Map", "SuppressRemovedAutoPins", true, "When you manually remove an automatically discovered Wayfinder pin, remember that choice so the same scanned objects do not immediately reappear.");
			HiddenCategories = config.Bind<string>("11 - Pin Manager", "HiddenCategories", string.Empty, "Comma-separated Wayfinder categories hidden from map rendering. The in-map pin manager edits this automatically.");
			PinManagerToggleKey = config.Bind<KeyboardShortcut>("11 - Pin Manager", "ToggleKey", new KeyboardShortcut((KeyCode)288, (KeyCode[])(object)new KeyCode[0]), "Fallback shortcut for opening or closing the Wayfinder Pin Manager while the large map is open.");
			ShowPinManagerMapButton = config.Bind<bool>("11 - Pin Manager", "ShowMapButton", true, "Show Joey's Wayfinder compact controls on the large map.");
			ReplaceVanillaPersistentPinControls = config.Bind<bool>("11 - Pin Manager", "ReplaceVanillaPersistentPinControls", true, "Hide Valheim's old persistent pin-type strip and old Add/Cross-off/Remove pin hints while the large map is open. Wayfinder's compact controls replace that workflow. Vanilla middle-mouse ping and Visible to other players are intentionally preserved.");
			NearbyDiscoveryListRadius = config.Bind<float>("11 - Pin Manager", "NearbyDiscoveryListRadius", 100f, "In the compact Pins view, automatically discovered Resources/Sightings/Habitats farther than this are hidden from the LIST only when search is empty. Their map markers remain visible. Searching by name shows matching results at any remembered distance.");
			OverrideVanillaBossNames = config.Bind<bool>("11 - Pin Manager", "OverrideVanillaBossNames", true, "Let Wayfinder control the visible labels of native/Vegvisir boss pins without changing the underlying vanilla pin data.");
			BossNameOverrides = config.Bind<string>("11 - Pin Manager", "BossNameOverrides", "Eikthyr=Eikthyr;TheElder=The Elder;Bonemass=Bonemass;Moder=Moder;Yagluth=Yagluth;Queen=The Queen;Fader=Fader", "Wayfinder display names for native boss pins. The in-map manager edits this automatically.");
			ResourceClustering = config.Bind<bool>("20 - Discovery", "ResourceClustering", true, "Merge nearby observations of the SAME resource into connected clusters.");
			ResourceClusterLinkDistance = config.Bind<float>("20 - Discovery", "ResourceClusterLinkDistance", 6f, "A resource joins a cluster when it is within this many meters of ANY member of that same-resource cluster. Chained members stay one cluster.");
			ShowClusterCounts = config.Bind<bool>("20 - Discovery", "ShowClusterCounts", true, "Show counts such as Raspberries x5 or Copper x2.");
			InteractionDiscovery = config.Bind<bool>("20 - Discovery", "InteractionDiscovery", true, "Remember resources/locations after legitimate interaction or discovery.");
			AreaScanning = config.Bind<bool>("20 - Discovery", "AreaScanning", false, "Optionally scan the surrounding area for configured map targets. Off by default. Buried Silver is always interaction-only and is never revealed by area scanning.");
			AreaScanRadius = config.Bind<float>("20 - Discovery", "AreaScanRadius", 100f, "Manual area scan radius in meters. Used only when MatchAreaScanToMapRevealRadius is disabled.");
			MatchAreaScanToMapRevealRadius = config.Bind<bool>("20 - Discovery", "MatchAreaScanToMapRevealRadius", true, "Match Wayfinder's area scan radius to the player's current map reveal/exploration radius. On by default so Wayfinder only remembers visible things within the same area the map is revealing.");
			ScanUnexploredAreas = config.Bind<bool>("20 - Discovery", "ScanUnexploredAreas", false, "Allow automatic discovery to create pins in unexplored map fog. Off by default.");
			EnemySightings = config.Bind<bool>("20 - Discovery", "EnemySightings", false, "Track ordinary enemy sightings/hotspots. Off by default to avoid clutter.");
			EnemySightingRadius = config.Bind<float>("20 - Discovery", "EnemySightingRadius", 80f, "Maximum range in meters for optional enemy sighting discovery.");
			EnemySightingClusterDistance = config.Bind<float>("20 - Discovery", "EnemySightingClusterDistance", 20f, "Same-enemy sightings within this connected distance merge into one activity hotspot.");
			ScanDungeonContents = config.Bind<bool>("20 - Discovery", "ScanDungeonContents", false, "Allow resource discovery/pinning inside generated dungeon interiors. Off by default because the dungeon entrance pin is usually enough.");
			AutoPinStone = config.Bind<bool>("21 - Resource Filters", "Stone", false, "Automatically pin loose Stone sources. Off by default to avoid map clutter.");
			AutoPinBranches = config.Bind<bool>("21 - Resource Filters", "Branches", false, "Automatically pin branch/basic Wood pickups. Off by default to avoid map clutter.");
			AutoPinFlint = config.Bind<bool>("21 - Resource Filters", "Flint", false, "Automatically pin Flint pickups. Off by default to avoid map clutter.");
			AutoPinDandelions = config.Bind<bool>("21 - Resource Filters", "Dandelions", false, "Automatically pin Dandelions. Off by default to avoid map clutter.");
			RememberGeneratedStructures = config.Bind<bool>("22 - Structures", "RememberGeneratedStructures", true, "Remember generated overworld structures/locations as Wayfinder POIs after they are actually loaded/discovered.");
			StructureDiscoveryRadius = config.Bind<float>("22 - Structures", "DiscoveryRadius", 80f, "Maximum distance in meters for remembering loaded generated structures around the player.");
			RememberPhysicalSpawners = config.Bind<bool>("23 - Spawners", "RememberPhysicalSpawners", true, "Remember persistent physical world spawners after they are legitimately loaded/discovered, using the dedicated Wayfinder spawner icon.");
			AutoPinSurtlingSpawners = config.Bind<bool>("23 - Spawners", "SurtlingSpawners", true, "Automatically remember Surtling fire geyser/spawner locations using the dedicated Wayfinder spawner marker.");
			SpawnerDiscoveryRadius = config.Bind<float>("23 - Spawners", "DiscoveryRadius", 80f, "Maximum distance in meters for remembering loaded physical spawners around the player.");
			SuppressSightingsInsideSpawnerRadius = config.Bind<bool>("23 - Spawners", "SuppressSightingsInsideSpawnerRadius", true, "Suppress ordinary enemy-sighting/hotspot pins for creatures currently inside the radius of a physical spawner that can actually spawn that creature.");
			SpawnerSightingSuppressionPadding = config.Bind<float>("23 - Spawners", "SightingSuppressionPadding", 0f, "Optional extra meters added to the physical spawner radius when suppressing matching spawned-enemy sightings.");
			SpawnerSightingExclusionRadius = config.Bind<float>("23 - Spawners", "SightingExclusionRadius", 25f, "Blanket no-sighting bubble around every known physical spawner. Any enemy sighting inside this many meters is suppressed so the spawner marker remains readable.");
			RememberPortals = config.Bind<bool>("24 - Portals", "RememberPortals", true, "Remember loaded player/world portals as dedicated Wayfinder Portal pins.");
			PortalDiscoveryRadius = config.Bind<float>("24 - Portals", "DiscoveryRadius", 120f, "Maximum distance in meters for remembering and live-syncing loaded portals around the player.");
			PortalSyncInterval = config.Bind<float>("24 - Portals", "SyncInterval", 1f, "Seconds between portal tag/connection-state sync passes. Portal renames normally update within this interval.");
			PortalNameFromTag = config.Bind<bool>("24 - Portals", "NamePinsFromTag", true, "Automatically keep discovered portal pin names synchronized to the portal's actual in-game tag. Untagged portals are named Portal unless that individual portal has a manual Wayfinder name override.");
			ShowPortalNamesOnLargeMap = config.Bind<bool>("24 - Portals", "ShowNamesOnLargeMap", true, "Show remembered portal names/tags beside their Wayfinder icons on the large map. Small minimap labels stay hidden to avoid clutter.");
			RemoveDestroyedPortals = config.Bind<bool>("24 - Portals", "RemoveDestroyedPortals", true, "Remove the remembered Wayfinder portal record when the client actually observes that portal being destroyed.");
			CompassEnabled = config.Bind<bool>("30 - Compass", "Enabled", true, "Enable the Wayfinder horizontal compass HUD.");
			CompassShowDistance = config.Bind<bool>("30 - Compass", "ShowDistance", true, "Show live meter distance beside tracked compass markers.");
			CompassShowNames = config.Bind<bool>("30 - Compass", "ShowNames", false, "Show tracked pin names on the compass. Off by default to keep the HUD compact.");
			CompassShowCardinals = config.Bind<bool>("30 - Compass", "ShowCardinals", true, "Show N/E/S/W and bearing ticks on the compass band.");
			CompassClampOffscreenTracked = config.Bind<bool>("30 - Compass", "ClampOffscreenTracked", true, "Keep tracked targets outside the visible compass arc pinned to the left/right edge so you still know which way to turn.");
			CompassMaxDistance = config.Bind<float>("30 - Compass", "MaxDistance", 0f, "Legacy compatibility setting. Explicitly tracked Wayfinder targets are always shown at any practical world distance; this value no longer culls tracked compass markers.");
			CompassWidth = config.Bind<float>("30 - Compass", "Width", 720f, "Compass width in screen pixels.");
			CompassTopOffset = config.Bind<float>("30 - Compass", "TopOffset", 18f, "Distance in screen pixels from the top edge.");
			CompassArcDegrees = config.Bind<float>("30 - Compass", "VisibleArcDegrees", 180f, "Horizontal bearing arc represented across the compass width.");
			CompassOpacity = config.Bind<float>("30 - Compass", "Opacity", 0.72f, "Opacity of the compass background/band. Marker colors remain more vivid.");
			MaxTrackedPins = config.Bind<int>("30 - Compass", "MaxTrackedPins", 5, "Maximum number of Wayfinder pins that may be explicitly tracked at once. Tracking is separate from deletion and vanilla map pings.");
			WaypointEnabled = config.Bind<bool>("40 - Waypoint", "Enabled", true, "Enable Wayfinder tracked-target navigation features.");
			WaypointShowVerticalDifference = config.Bind<bool>("40 - Waypoint", "ShowVerticalDifference", true, "Show target elevation difference for tracked targets when supported by the HUD.");
			BeaconEnabled = config.Bind<bool>("40 - Waypoint", "BeaconEnabled", true, "Show subtle vertical beams for explicitly tracked Wayfinder pins.");
			BeaconOpacity = config.Bind<float>("40 - Waypoint", "BeaconOpacity", 1f, "Opacity of tracked-target beacon beams.");
			BeaconHeight = config.Bind<float>("40 - Waypoint", "BeaconHeight", 100f, "Height in meters of each tracked-target beacon beam.");
			BeaconWidth = config.Bind<float>("40 - Waypoint", "BeaconWidth", 1f, "Width in meters of each tracked-target beacon beam.");
			TrackBoats = config.Bind<bool>("50 - Vehicles", "TrackBoats", true, "Remember legitimately encountered loaded boats as dynamic Wayfinder vehicle markers. They can then be explicitly tracked as T1-T5 targets.");
			TrackCarts = config.Bind<bool>("50 - Vehicles", "TrackCarts", true, "Remember legitimately encountered loaded carts as dynamic Wayfinder vehicle markers. They can then be explicitly tracked as T1-T5 targets.");
			KeepLastKnownVehiclePosition = config.Bind<bool>("50 - Vehicles", "KeepLastKnownPosition", true, "Keep the most recently known map position when a remembered boat/cart leaves loaded range.");
			RemoveDestroyedVehicles = config.Bind<bool>("50 - Vehicles", "RemoveDestroyedVehicles", true, "Remove a remembered dynamic vehicle marker when Wayfinder observes that boat/cart being actually destroyed.");
			VehicleDiscoveryRadius = config.Bind<float>("50 - Vehicles", "DiscoveryRadius", 160f, "Maximum horizontal distance in meters for first remembering a loaded boat/cart. Once a specific vehicle is remembered, Wayfinder keeps updating it anywhere that same stable vehicle remains loaded.");
			VehicleUpdateInterval = config.Bind<float>("50 - Vehicles", "UpdateInterval", 0.5f, "Seconds between dynamic boat/cart position updates. Lower values are smoother but scan more often.");
			LastKnownVehicleOpacity = config.Bind<float>("50 - Vehicles", "LastKnownOpacity", 0.58f, "Opacity multiplier for remembered boat/cart markers that are no longer currently observed. Also dims their tracked compass icon/beam so last-known positions are not mistaken for live positions.");
			ShowLastKnownVehicleBadge = config.Bind<bool>("50 - Vehicles", "ShowLastKnownBadge", true, "Show a small amber clock-ring badge on map markers that represent a boat/cart's last known position rather than a currently observed live vehicle.");
			ShowVehicleNamesOnLargeMap = config.Bind<bool>("50 - Vehicles", "ShowNamesOnLargeMap", true, "Show remembered boat/cart names beside their Wayfinder icons on the large map. Small minimap labels stay hidden to avoid clutter.");
			ExpandedExploration = config.Bind<bool>("60 - Exploration", "Enabled", false, "Enable custom map exploration radii. Off by default.");
			WalkingExploreMultiplier = config.Bind<float>("60 - Exploration", "WalkingMultiplier", 1f, "Map exploration multiplier while travelling on land.");
			SailingExploreMultiplier = config.Bind<float>("60 - Exploration", "SailingMultiplier", 1f, "Map exploration multiplier while sailing.");
			AreaScanInterval = config.Bind<float>("90 - Performance", "AreaScanInterval", 4f, "Seconds between optional area scans. Higher values reduce scan spikes while preserving discovery behavior.");
			EnemySightingInterval = config.Bind<float>("90 - Performance", "EnemySightingInterval", 4f, "Seconds between optional enemy-sighting scans. Higher values reduce CPU cost.");
			StaticDiscoveryInterval = config.Bind<float>("90 - Performance", "StaticDiscoveryInterval", 5f, "Seconds between structure/spawner discovery passes.");
			SpawnerIndexCacheSeconds = config.Bind<float>("90 - Performance", "SpawnerIndexCacheSeconds", 10f, "How long physical-spawner footprint data is cached before being rebuilt.");
			PersistenceFlushInterval = config.Bind<float>("90 - Performance", "PersistenceFlushInterval", 4f, "Batch automatic Wayfinder disk writes for this many seconds. Logout/quit still forces an immediate save.");
			MapVisualRefreshInterval = config.Bind<float>("90 - Performance", "MapVisualRefreshInterval", 0.25f, "Minimum seconds between full Wayfinder map-visual refresh passes.");
		}
	}
	internal static class WayfinderDiscoveryRadius
	{
		internal static float GetEffectiveAreaScanRadius(WayfinderConfig config)
		{
			if (config == null)
			{
				return 100f;
			}
			if (!config.MatchAreaScanToMapRevealRadius.Value)
			{
				return Mathf.Max(1f, config.AreaScanRadius.Value);
			}
			Minimap instance = Minimap.instance;
			float num = (((Object)(object)instance != (Object)null) ? instance.m_exploreRadius : config.AreaScanRadius.Value);
			if (config.ExpandedExploration.Value && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				float num2 = (((Character)Player.m_localPlayer).IsAttachedToShip() ? config.SailingExploreMultiplier.Value : config.WalkingExploreMultiplier.Value);
				num *= Mathf.Max(0f, num2);
			}
			return Mathf.Max(1f, num);
		}
	}
}
namespace JoeyBadManners.Wayfinder.Discovery
{
	internal sealed class AreaScanner
	{
		private sealed class ResourceComponentCacheEntry
		{
			internal Collider Collider;

			internal Pickable Pickable;

			internal MineRock MineRock;

			internal MineRock5 MineRock5;

			internal DropOnDestroyed DropOnDestroyed;

			internal float ExpiresAt;
		}

		private sealed class VisualOreCacheEntry
		{
			internal Collider Collider;

			internal GameObject Source;

			internal string ResourcePrefab;

			internal float ExpiresAt;
		}

		private static readonly MethodInfo IsExploredMethod = AccessTools.Method(typeof(Minimap), "IsExplored", new Type[1] { typeof(Vector3) }, (Type[])null);

		private readonly ManualLogSource _log;

		private readonly WayfinderConfig _config;

		private readonly ResourceDiscovery _resources;

		private float _nextScanTime;

		private float _nextColliderCachePrune;

		private readonly Collider[] _colliderBuffer = (Collider[])(object)new Collider[4096];

		private readonly HashSet<string> _visualOreObjects = new HashSet<string>();

		private readonly HashSet<int> _scanPickables = new HashSet<int>();

		private readonly HashSet<int> _scanMineRocks = new HashSet<int>();

		private readonly HashSet<int> _scanMineRocks5 = new HashSet<int>();

		private readonly HashSet<int> _scanDestroyedDrops = new HashSet<int>();

		private readonly Dictionary<int, ResourceComponentCacheEntry> _resourceColliderCache = new Dictionary<int, ResourceComponentCacheEntry>();

		private readonly Dictionary<int, VisualOreCacheEntry> _visualOreCache = new Dictionary<int, VisualOreCacheEntry>();

		private bool _scanInProgress;

		private int _scanCursor;

		private int _nearbyColliderCount;

		private Vector3 _scanCenter;

		private float _scanRadius;

		private float _scanRadiusSq;

		private int _scanObserved;

		private int _scanPickableCount;

		private int _scanMineRockCount;

		private int _scanMineRock5Count;

		private int _scanDestroyedDropCount;

		private int _scanVisualOreCount;

		private int _lastProcessedThisSlice;

		private double _lastQueryMs;

		internal AreaScanner(ManualLogSource log, WayfinderConfig config, ResourceDiscovery resources)
		{
			_log = log;
			_config = config;
			_resources = resources;
		}

		internal void Tick()
		{
			TickBudgeted(1000.0);
		}

		internal bool TickBudgeted(double budgetMs)
		{
			if (!_config.Enabled.Value || !_config.AreaScanning.Value || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				ResetActiveScan();
				return false;
			}
			if (!_config.ScanDungeonContents.Value && ((Character)Player.m_localPlayer).InInterior())
			{
				ResetActiveScan();
				return false;
			}
			long startTimestamp = DiscoveryWorkBudget.Start();
			if (!_scanInProgress)
			{
				if (Time.unscaledTime < _nextScanTime)
				{
					return false;
				}
				BeginScan();
				if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs))
				{
					return true;
				}
			}
			_lastProcessedThisSlice = 0;
			while (_scanCursor < _nearbyColliderCount)
			{
				Collider collider = _colliderBuffer[_scanCursor++];
				_lastProcessedThisSlice++;
				ProcessCollider(collider);
				if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs))
				{
					return true;
				}
			}
			if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs))
			{
				return true;
			}
			FinishScan();
			return false;
		}

		internal string GetPerfDetail()
		{
			if (_scanInProgress)
			{
				return " query=" + _lastQueryMs.ToString("0.00") + "ms processed=" + _scanCursor + "/" + _nearbyColliderCount + " yielded=true";
			}
			return " query=" + _lastQueryMs.ToString("0.00") + "ms processed=" + _nearbyColliderCount + "/" + _nearbyColliderCount + " yielded=false";
		}

		private void BeginScan()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			_nextScanTime = Time.unscaledTime + Mathf.Max(1f, _config.AreaScanInterval.Value);
			_scanCenter = ((Component)Player.m_localPlayer).transform.position;
			_scanRadius = WayfinderDiscoveryRadius.GetEffectiveAreaScanRadius(_config);
			_scanRadiusSq = _scanRadius * _scanRadius;
			_scanObserved = 0;
			_scanPickableCount = 0;
			_scanMineRockCount = 0;
			_scanMineRock5Count = 0;
			_scanDestroyedDropCount = 0;
			_scanVisualOreCount = 0;
			_scanPickables.Clear();
			_scanMineRocks.Clear();
			_scanMineRocks5.Clear();
			_scanDestroyedDrops.Clear();
			_visualOreObjects.Clear();
			long startTimestamp = DiscoveryWorkBudget.Start();
			_nearbyColliderCount = Physics.OverlapSphereNonAlloc(_scanCenter, _scanRadius, _colliderBuffer, -1, (QueryTriggerInteraction)2);
			_lastQueryMs = DiscoveryWorkBudget.ElapsedMilliseconds(startTimestamp);
			_scanCursor = 0;
			_scanInProgress = true;
		}

		private void ProcessCollider(Collider collider)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: 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_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: 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_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)collider == (Object)null)
			{
				return;
			}
			ResourceComponentCacheEntry cachedResourceComponents = GetCachedResourceComponents(collider);
			bool flag = false;
			if (cachedResourceComponents != null)
			{
				Pickable pickable = cachedResourceComponents.Pickable;
				if ((Object)(object)pickable != (Object)null)
				{
					flag = true;
					int instanceID = ((Object)pickable).GetInstanceID();
					Vector3 position = ((Component)pickable).transform.position;
					if (_scanPickables.Add(instanceID) && WithinHorizontalRadius(_scanCenter, position, _scanRadiusSq) && MayReveal(position))
					{
						_resources.ObservePickableFromScan(pickable);
						_scanObserved++;
						_scanPickableCount++;
					}
				}
				MineRock mineRock = cachedResourceComponents.MineRock;
				if ((Object)(object)mineRock != (Object)null)
				{
					flag = true;
					int instanceID2 = ((Object)mineRock).GetInstanceID();
					Vector3 position2 = ((Component)mineRock).transform.position;
					if (_scanMineRocks.Add(instanceID2) && WithinHorizontalRadius(_scanCenter, position2, _scanRadiusSq) && MayReveal(position2))
					{
						_resources.ObserveMineRockFromScan(mineRock);
						_scanObserved++;
						_scanMineRockCount++;
					}
				}
				MineRock5 mineRock2 = cachedResourceComponents.MineRock5;
				if ((Object)(object)mineRock2 != (Object)null)
				{
					flag = true;
					int instanceID3 = ((Object)mineRock2).GetInstanceID();
					Vector3 position3 = ((Component)mineRock2).transform.position;
					if (_scanMineRocks5.Add(instanceID3) && WithinHorizontalRadius(_scanCenter, position3, _scanRadiusSq) && MayReveal(position3))
					{
						_resources.ObserveMineRock5FromScan(mineRock2);
						_scanObserved++;
						_scanMineRock5Count++;
					}
				}
				DropOnDestroyed dropOnDestroyed = cachedResourceComponents.DropOnDestroyed;
				if ((Object)(object)dropOnDestroyed != (Object)null)
				{
					flag = true;
					int instanceID4 = ((Object)dropOnDestroyed).GetInstanceID();
					Vector3 position4 = ((Component)dropOnDestroyed).transform.position;
					if (_scanDestroyedDrops.Add(instanceID4) && WithinHorizontalRadius(_scanCenter, position4, _scanRadiusSq) && MayReveal(position4) && _resources.ObserveDropOnDestroyedFromScan(dropOnDestroyed))
					{
						_scanObserved++;
						_scanDestroyedDropCount++;
					}
				}
			}
			if (flag || !TryGetCachedVisibleMineable(collider, out var source, out var resourcePrefab) || (Object)(object)source == (Object)null || string.IsNullOrEmpty(resourcePrefab))
			{
				return;
			}
			string item = ((Object)source).GetInstanceID() + ":" + resourcePrefab;
			if (_visualOreObjects.Add(item))
			{
				Vector3 position5 = source.transform.position;
				if (WithinHorizontalRadius(_scanCenter, position5, _scanRadiusSq) && MayReveal(position5) && _resources.ObserveVisibleMineableFromScan(source, resourcePrefab))
				{
					_scanObserved++;
					_scanVisualOreCount++;
				}
			}
		}

		private void FinishScan()
		{
			_scanInProgress = false;
			if (Time.unscaledTime >= _nextColliderCachePrune)
			{
				_nextColliderCachePrune = Time.unscaledTime + 60f;
				PruneColliderCaches();
			}
			if (_config.DebugLogging.Value && _config.VerboseRuntimeLogging.Value)
			{
				_log.LogInfo((object)("Area scan checked " + _scanObserved + " nearby resource sources inside " + _scanRadius.ToString("0") + "m" + (_config.MatchAreaScanToMapRevealRadius.Value ? " [map-matched]" : " [manual]") + " (Pickable=" + _scanPickableCount + ", MineRock=" + _scanMineRockCount + ", MineRock5=" + _scanMineRock5Count + ", VisualOre=" + _scanVisualOreCount + ", DropOnDestroyed=" + _scanDestroyedDropCount + "; local collider scan)."));
				if (_nearbyColliderCount >= _colliderBuffer.Length)
				{
					_log.LogWarning((object)("Area scan collider buffer filled (" + _colliderBuffer.Length + "). Nearby discovery may be incomplete in this unusually dense area."));
				}
			}
		}

		private void ResetActiveScan()
		{
			_scanInProgress = false;
			_scanCursor = 0;
			_nearbyColliderCount = 0;
		}

		private ResourceComponentCacheEntry GetCachedResourceComponents(Collider collider)
		{
			if ((Object)(object)collider == (Object)null)
			{
				return null;
			}
			int instanceID = ((Object)collider).GetInstanceID();
			if (_resourceColliderCache.TryGetValue(instanceID, out var value) && value != null && object.ReferenceEquals(value.Collider, collider) && Time.unscaledTime < value.ExpiresAt)
			{
				return value;
			}
			value = new ResourceComponentCacheEntry();
			value.Collider = collider;
			value.ExpiresAt = Time.unscaledTime + 45f;
			try
			{
				value.Pickable = ((Component)collider).GetComponentInParent<Pickable>();
				if ((Object)(object)value.Pickable == (Object)null)
				{
					value.MineRock5 = ((Component)collider).GetComponentInParent<MineRock5>();
					if ((Object)(object)value.MineRock5 == (Object)null)
					{
						value.MineRock = ((Component)collider).GetComponentInParent<MineRock>();
						if ((Object)(object)value.MineRock == (Object)null)
						{
							value.DropOnDestroyed = ((Component)collider).GetComponentInParent<DropOnDestroyed>();
						}
					}
				}
			}
			catch
			{
			}
			_resourceColliderCache[instanceID] = value;
			return value;
		}

		private void PruneColliderCaches()
		{
			List<int> list = new List<int>();
			float unscaledTime = Time.unscaledTime;
			foreach (KeyValuePair<int, ResourceComponentCacheEntry> item in _resourceColliderCache)
			{
				ResourceComponentCacheEntry value = item.Value;
				if (value == null || (Object)(object)value.Collider == (Object)null || unscaledTime >= value.ExpiresAt)
				{
					list.Add(item.Key);
				}
			}
			for (int i = 0; i < list.Count; i++)
			{
				_resourceColliderCache.Remove(list[i]);
			}
			list.Clear();
			foreach (KeyValuePair<int, VisualOreCacheEntry> item2 in _visualOreCache)
			{
				VisualOreCacheEntry value2 = item2.Value;
				if (value2 == null || (Object)(object)value2.Collider == (Object)null || unscaledTime >= value2.ExpiresAt)
				{
					list.Add(item2.Key);
				}
			}
			for (int j = 0; j < list.Count; j++)
			{
				_visualOreCache.Remove(list[j]);
			}
		}

		private bool TryGetCachedVisibleMineable(Collider collider, out GameObject source, out string resourcePrefab)
		{
			source = null;
			resourcePrefab = string.Empty;
			if ((Object)(object)collider == (Object)null)
			{
				return false;
			}
			int instanceID = ((Object)collider).GetInstanceID();
			if (_visualOreCache.TryGetValue(instanceID, out var value) && value != null && object.ReferenceEquals(value.Collider, collider) && Time.unscaledTime < value.ExpiresAt)
			{
				source = value.Source;
				resourcePrefab = value.ResourcePrefab ?? string.Empty;
				if ((Object)(object)source != (Object)null)
				{
					return !string.IsNullOrEmpty(resourcePrefab);
				}
				return false;
			}
			GameObject source2;
			string resourcePrefab2;
			bool flag = TryFindVisibleMineable(collider, out source2, out resourcePrefab2);
			VisualOreCacheEntry visualOreCacheEntry = new VisualOreCacheEntry();
			visualOreCacheEntry.Collider = collider;
			visualOreCacheEntry.Source = (flag ? source2 : null);
			visualOreCacheEntry.ResourcePrefab = (flag ? resourcePrefab2 : string.Empty);
			visualOreCacheEntry.ExpiresAt = Time.unscaledTime + (flag ? 60f : 30f);
			value = visualOreCacheEntry;
			_visualOreCache[instanceID] = value;
			source = value.Source;
			resourcePrefab = value.ResourcePrefab;
			if (flag && (Object)(object)source != (Object)null)
			{
				return !string.IsNullOrEmpty(resourcePrefab);
			}
			return false;
		}

		private static bool TryFindVisibleMineable(Collider collider, out GameObject source, out string resourcePrefab)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			source = null;
			resourcePrefab = string.Empty;
			if ((Object)(object)collider == (Object)null)
			{
				return false;
			}
			Transform val = ((Component)collider).transform;
			GameObject val2 = null;
			string text = string.Empty;
			int num = 0;
			while ((Object)(object)val != (Object)null && num < 7)
			{
				GameObject gameObject = ((Component)val).gameObject;
				if (!((Object)(object)gameObject == (Object)null))
				{
					Scene scene = gameObject.scene;
					if (((Scene)(ref scene)).IsValid())
					{
						if (IdentifyVisibleSurfaceMineable(gameObject, out var resourcePrefab2))
						{
							if (IsHiddenWishboneResource(resourcePrefab2, ((Object)gameObject).name))
							{
								return false;
							}
							source = gameObject;
							resourcePrefab = resourcePrefab2;
							return true;
						}
						if (TryIdentifyMineableFromMaterials(gameObject, out var resourcePrefab3))
						{
							if (IsHiddenWishboneResource(resourcePrefab3, ((Object)gameObject).name))
							{
								return false;
							}
							if ((Object)(object)val2 == (Object)null)
							{
								val2 = gameObject;
								text = resourcePrefab3;
							}
						}
					}
				}
				num++;
				val = val.parent;
			}
			if ((Object)(object)val2 != (Object)null && !string.IsNullOrEmpty(text))
			{
				source = val2;
				resourcePrefab = text;
				return true;
			}
			return false;
		}

		private static bool IdentifyVisibleSurfaceMineable(GameObject go, out string resourcePrefab)
		{
			resourcePrefab = string.Empty;
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			string text = NormalizeOreText(((Object)go).name ?? string.Empty);
			if (ContainsIronIdentity(text) || text.Contains("silver"))
			{
				return false;
			}
			MineRock5 component = go.GetComponent<MineRock5>();
			if ((Object)(object)component != (Object)null)
			{
				text += NormalizeOreText(SafeMineRock5Name(component));
			}
			MineRock component2 = go.GetComponent<MineRock>();
			if ((Object)(object)component2 != (Object)null)
			{
				text += NormalizeOreText(SafeMineRockName(component2));
			}
			DropOnDestroyed component3 = go.GetComponent<DropOnDestroyed>();
			bool flag = (Object)(object)component != (Object)null || (Object)(object)component2 != (Object)null || (Object)(object)component3 != (Object)null;
			if (text.Contains("copper") && (flag || LooksLikeDepositName(text)))
			{
				resourcePrefab = "CopperOre";
				return true;
			}
			if (text.Contains("tin") && (flag || LooksLikeDepositName(text)))
			{
				resourcePrefab = "TinOre";
				return true;
			}
			if (text.Contains("obsidian") && (flag || LooksLikeDepositName(text)))
			{
				resourcePrefab = "Obsidian";
				return true;
			}
			if (text.Contains("flametal") && (flag || LooksLikeDepositName(text) || text.Contains("meteor")))
			{
				resourcePrefab = "FlametalOreNew";
				return true;
			}
			if (text.Contains("blackmarble") && flag)
			{
				resourcePrefab = "BlackMarble";
				return true;
			}
			if (text.Contains("softtissue") && flag)
			{
				resourcePrefab = "SoftTissue";
				return true;
			}
			if (text.Contains("crystal") && flag)
			{
				resourcePrefab = "Crystal";
				return true;
			}
			return false;
		}

		private static bool TryIdentifyMineableFromMaterials(GameObject go, out string resourcePrefab)
		{
			resourcePrefab = string.Empty;
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			Renderer[] componentsInChildren;
			try
			{
				componentsInChildren = go.GetComponentsInChildren<Renderer>(true);
			}
			catch
			{
				return false;
			}
			foreach (Renderer val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null || val.sharedMaterials == null)
				{
					continue;
				}
				Material[] sharedMaterials = val.sharedMaterials;
				foreach (Material val2 in sharedMaterials)
				{
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					string text = NormalizeOreText(((Object)val2).name ?? string.Empty);
					if (!text.Contains("silver") && !ContainsIronIdentity(text))
					{
						bool flag = text.Contains("rock") || text.Contains("ore") || text.Contains("deposit") || text.Contains("vein") || text.Contains("meteor");
						if (text.Contains("copper") && flag)
						{
							resourcePrefab = "CopperOre";
							return true;
						}
						if (text.Contains("tin") && flag)
						{
							resourcePrefab = "TinOre";
							return true;
						}
						if (text.Contains("obsidian") && flag)
						{
							resourcePrefab = "Obsidian";
							return true;
						}
						if (text.Contains("flametal") && flag)
						{
							resourcePrefab = "FlametalOreNew";
							return true;
						}
						if (text.Contains("blackmarble") && flag)
						{
							resourcePrefab = "BlackMarble";
							return true;
						}
						if (text.Contains("softtissue") && (flag || text.Contains("tissue")))
						{
							resourcePrefab = "SoftTissue";
							return true;
						}
						if (text.Contains("crystal") && flag)
						{
							resourcePrefab = "Crystal";
							return true;
						}
					}
				}
			}
			return false;
		}

		private static bool LooksLikeDepositName(string normalizedName)
		{
			if (!normalizedName.Contains("minerock") && !normalizedName.Contains("deposit") && !normalizedName.Contains("ore") && !normalizedName.Contains("vein") && !normalizedName.Contains("meteor"))
			{
				return normalizedName.Contains("rock");
			}
			return true;
		}

		private static bool IsHiddenWishboneResource(string resourcePrefab, string objectName)
		{
			string text = NormalizeOreText((resourcePrefab ?? string.Empty) + " " + (objectName ?? string.Empty));
			if (!text.Contains("silver"))
			{
				return ContainsIronIdentity(text);
			}
			return true;
		}

		private static bool ContainsIronIdentity(string normalized)
		{
			if (string.IsNullOrEmpty(normalized))
			{
				return false;
			}
			if (!normalized.Contains("ironscrap") && !normalized.Contains("scrapiron") && !normalized.Contains("muddyscrap") && !normalized.Contains("muddypile") && !normalized.Contains("muddy"))
			{
				return normalized.Contains("buriediron");
			}
			return true;
		}

		private static string SafeMineRock5Name(MineRock5 rock)
		{
			if ((Object)(object)rock == (Object)null)
			{
				return string.Empty;
			}
			try
			{
				return rock.m_name ?? string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string SafeMineRockName(MineRock rock)
		{
			if ((Object)(object)rock == (Object)null)
			{
				return string.Empty;
			}
			try
			{
				return rock.m_name ?? string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string NormalizeOreText(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			char[] array = new char[value.Length];
			int length = 0;
			for (int i = 0; i < value.Length; i++)
			{
				char c = char.ToLowerInvariant(value[i]);
				if (char.IsLetterOrDigit(c))
				{
					array[length++] = c;
				}
			}
			return new string(array, 0, length);
		}

		private static bool IsLiveSceneObject(Component component)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)component == (Object)null || (Object)(object)component.gameObject == (Object)null)
			{
				return false;
			}
			try
			{
				Scene scene = component.gameObject.scene;
				return ((Scene)(ref scene)).IsValid();
			}
			catch
			{
				return false;
			}
		}

		private bool MayReveal(Vector3 position)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (_config.ScanUnexploredAreas.Value)
			{
				return true;
			}
			Minimap instance = Minimap.instance;
			if ((Object)(object)instance == (Object)null || IsExploredMethod == null)
			{
				return false;
			}
			try
			{
				object obj = IsExploredMethod.Invoke(instance, new object[1] { position });
				return obj is bool && (bool)obj;
			}
			catch
			{
				return false;
			}
		}

		private static bool WithinHorizontalRadius(Vector3 a, Vector3 b, float radiusSq)
		{
			float num = a.x - b.x;
			float num2 = a.z - b.z;
			return num * num + num2 * num2 <= radiusSq;
		}
	}
	internal sealed class DiscoveryArtConsistency
	{
		private readonly ManualLogSource _log;

		private readonly WayfinderConfig _config;

		private readonly RuntimeIconRegistry _icons;

		private readonly WayfinderPinDatabase _database;

		private long _processedWorldUid = long.MinValue;

		internal DiscoveryArtConsistency(ManualLogSource log, WayfinderConfig config, RuntimeIconRegistry icons, WayfinderPinDatabase database)
		{
			_log = log;
			_config = config;
			_icons = icons;
			_database = database;
		}

		internal void ResetSession()
		{
			_processedWorldUid = long.MinValue;
		}

		internal void Tick()
		{
			if (!_config.Enabled.Value || _database.WorldUid == 0 || _processedWorldUid == _database.WorldUid || _icons == null || !_icons.IsBuilt)
			{
				return;
			}
			_processedWorldUid = _database.WorldUid;
			IReadOnlyList<WayfinderPinRecord> records = _database.Records;
			if (records == null || records.Count == 0)
			{
				return;
			}
			List<WayfinderPinRecord> list = new List<WayfinderPinRecord>();
			List<string> list2 = new List<string>();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			for (int i = 0; i < records.Count; i++)
			{
				WayfinderPinRecord wayfinderPinRecord = records[i];
				if (wayfinderPinRecord == null || wayfinderPinRecord.source == WayfinderPinSource.Manual || wayfinderPinRecord.source == WayfinderPinSource.Imported || wayfinderPinRecord.source == WayfinderPinSource.Vanilla)
				{
					continue;
				}
				string name = (wayfinderPinRecord.subtype ?? string.Empty) + " " + (wayfinderPinRecord.displayName ?? string.Empty);
				if (wayfinderPinRecord.category != WayfinderPinCategory.Boss && WayfinderIconPolicy.IsBossGuidanceRunestoneName(name))
				{
					if (!string.IsNullOrEmpty(wayfinderPinRecord.id))
					{
						list2.Add(wayfinderPinRecord.id);
					}
					continue;
				}
				bool flag = false;
				if (wayfinderPinRecord.category == WayfinderPinCategory.Sighting && (wayfinderPinRecord.iconOverride || string.IsNullOrEmpty(wayfinderPinRecord.iconKey) || string.Equals(wayfinderPinRecord.iconKey, "wayfinder:sighting", StringComparison.OrdinalIgnoreCase)))
				{
					WayfinderIconEntry wayfinderIconEntry = _icons.FindBestTrophy(wayfinderPinRecord.subtype);
					if (wayfinderIconEntry == null)
					{
						wayfinderIconEntry = _icons.FindBestTrophy(wayfinderPinRecord.displayName);
					}
					if (wayfinderIconEntry != null && !string.IsNullOrEmpty(wayfinderIconEntry.Key) && !string.Equals(wayfinderPinRecord.iconKey, wayfinderIconEntry.Key, StringComparison.OrdinalIgnoreCase))
					{
						wayfinderPinRecord.iconKey = wayfinderIconEntry.Key;
						flag = true;
						num2++;
					}
				}
				if (WayfinderIconPolicy.IsRunestoneName(name) && !string.Equals(wayfinderPinRecord.iconKey, "wayfinder:runestone", StringComparison.OrdinalIgnoreCase))
				{
					wayfinderPinRecord.iconKey = "wayfinder:runestone";
					flag = true;
					num++;
				}
				string defaultIconKey = WayfinderIconPolicy.GetDefaultIconKey(wayfinderPinRecord);
				if (!string.IsNullOrEmpty(defaultIconKey) && !string.Equals(wayfinderPinRecord.iconKey, defaultIconKey, StringComparison.OrdinalIgnoreCase))
				{
					wayfinderPinRecord.iconKey = defaultIconKey;
					flag = true;
					num3++;
				}
				if (flag)
				{
					list.Add(wayfinderPinRecord);
				}
			}
			for (int j = 0; j < list2.Count; j++)
			{
				_database.Remove(list2[j], suppressAutoRediscovery: false);
			}
			for (int k = 0; k < list.Count; k++)
			{
				_database.AddOrUpdate(list[k]);
			}
			if (_config.DebugLogging.Value)
			{
				_log.LogInfo((object)("Art/discovery consistency pass: removed " + list2.Count + " obsolete boss-guidance marker(s), normalized " + num + " runestone icon record(s), restored " + num2 + " enemy sighting trophy icon(s), repaired " + num3 + " other automatic semantic icon key(s). Manual/imported/vanilla records were untouched."));
			}
		}
	}
	internal static class DiscoveryWorkBudget
	{
		internal static long Start()
		{
			return Stopwatch.GetTimestamp();
		}

		internal static bool Expired(long startTimestamp, double budgetMs)
		{
			if (budgetMs <= 0.0)
			{
				return true;
			}
			return ElapsedMilliseconds(startTimestamp) >= budgetMs;
		}

		internal static double ElapsedMilliseconds(long startTimestamp)
		{
			return (double)(Stopwatch.GetTimestamp() - startTimestamp) * 1000.0 / (double)Stopwatch.Frequency;
		}
	}
	internal sealed class EnemySightingDiscovery
	{
		private const float DungeonInteriorY = 3000f;

		private static readonly MethodInfo IsPlayerMethod = AccessTools.Method(typeof(Character), "IsPlayer", Type.EmptyTypes, (Type[])null);

		private static readonly MethodInfo IsTamedMethod = AccessTools.Method(typeof(Character), "IsTamed", Type.EmptyTypes, (Type[])null);

		private static readonly MethodInfo IsBossMethod = AccessTools.Method(typeof(Character), "IsBoss", Type.EmptyTypes, (Type[])null);

		private static readonly MethodInfo GetHoverNameMethod = AccessTools.Method(typeof(Character), "GetHoverName", Type.EmptyTypes, (Type[])null);

		private static readonly MethodInfo IsEnemyMethod = FindIsEnemyMethod();

		private static readonly MethodInfo IsExploredMethod = AccessTools.Method(typeof(Minimap), "IsExplored", new Type[1] { typeof(Vector3) }, (Type[])null);

		private readonly ManualLogSource _log;

		private readonly WayfinderConfig _config;

		private readonly RuntimeIconRegistry _icons;

		private readonly WayfinderPinDatabase _database;

		private float _nextScanTime;

		private long _seenWorldUid = long.MinValue;

		private readonly HashSet<int> _seenLiveInstances = new HashSet<int>();

		private float _lastSpawnerCleanupTime;

		private bool _scanInProgress;

		private List<Character> _scanCharacters = new List<Character>();

		private int _scanCursor;

		private Player _scanPlayer;

		private Vector3 _scanCenter;

		private float _scanRadiusSq;

		private List<PhysicalSpawnerFootprint> _scanSpawnerFootprints;

		private int _scanRemembered;

		private bool _cleanupAfterScan;

		internal EnemySightingDiscovery(ManualLogSource log, WayfinderConfig config, RuntimeIconRegistry icons, WayfinderPinDatabase database)
		{
			_log = log;
			_config = config;
			_icons = icons;
			_database = database;
		}

		internal void Tick()
		{
			TickBudgeted(1000.0);
		}

		internal bool TickBudgeted(double budgetMs)
		{
			if (!_config.Enabled.Value || !_config.EnemySightings.Value || _database.WorldUid == 0 || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				ResetActiveScan();
				return false;
			}
			if (_seenWorldUid != _database.WorldUid)
			{
				_seenWorldUid = _database.WorldUid;
				_seenLiveInstances.Clear();
				_lastSpawnerCleanupTime = 0f;
				ResetActiveScan();
			}
			long startTimestamp = DiscoveryWorkBudget.Start();
			if (!_scanInProgress)
			{
				if (Time.unscaledTime < _nextScanTime)
				{
					return false;
				}
				BeginScan();
				if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs))
				{
					return true;
				}
			}
			int num = 0;
			while (_scanCursor < _scanCharacters.Count)
			{
				Character character = _scanCharacters[_scanCursor++];
				num++;
				ProcessCharacter(character);
				if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs))
				{
					return true;
				}
			}
			if (_cleanupAfterScan)
			{
				if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs))
				{
					return true;
				}
				RunSpawnerCleanup();
				_cleanupAfterScan = false;
			}
			if (_config.DebugLogging.Value && _config.VerboseRuntimeLogging.Value && _scanRemembered > 0)
			{
				_log.LogInfo((object)("Enemy sightings remembered/confirmed: " + _scanRemembered));
			}
			_scanInProgress = false;
			_scanCharacters.Clear();
			return false;
		}

		private void BeginScan()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			_nextScanTime = Time.unscaledTime + Mathf.Max(1f, _config.EnemySightingInterval.Value);
			_scanPlayer = Player.m_localPlayer;
			_scanCenter = ((Component)_scanPlayer).transform.position;
			float num = Mathf.Max(5f, _config.EnemySightingRadius.Value);
			_scanRadiusSq = num * num;
			_scanRemembered = 0;
			_scanCursor = 0;
			_scanSpawnerFootprints = null;
			if (_config.SuppressSightingsInsideSpawnerRadius.Value)
			{
				_scanSpawnerFootprints = PhysicalSpawnerIndex.GetCached(_config.SpawnerSightingSuppressionPadding.Value, _config.SpawnerIndexCacheSeconds.Value);
			}
			_cleanupAfterScan = _config.SuppressSightingsInsideSpawnerRadius.Value && Time.unscaledTime - _lastSpawnerCleanupTime >= Mathf.Max(2f, _config.SpawnerIndexCacheSeconds.Value);
			List<Character> allCharacters = Character.GetAllCharacters();
			_scanCharacters = ((allCharacters != null) ? new List<Character>(allCharacters) : new List<Character>());
			_scanInProgress = true;
		}

		private void ProcessCharacter(Character character)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)character == (Object)null || (Object)(object)((Component)character).gameObject == (Object)null || object.ReferenceEquals(character, _scanPlayer))
			{
				return;
			}
			Vector3 position = ((Component)character).transform.position;
			if ((position.y > 3000f && !_config.ScanDungeonContents.Value) || !WithinHorizontalRadius(_scanCenter, position, _scanRadiusSq) || !MayReveal(position))
			{
				return;
			}
			int instanceID = ((Object)character).GetInstanceID();
			if (_seenLiveInstances.Contains(instanceID) || InvokeBool(character, IsPlayerMethod, fallback: false) || InvokeBool(character, IsTamedMethod, fallback: false) || InvokeBool(character, IsBossMethod, fallback: false) || !IsHostileOrMonster(character, _scanPlayer))
			{
				return;
			}
			string prefabName = GetPrefabName(((Component)character).gameObject);
			if (string.IsNullOrEmpty(prefabName))
			{
				return;
			}
			if (_config.SuppressSightingsInsideSpawnerRadius.Value)
			{
				float exclusionRadius = Mathf.Max(0f, _config.SpawnerSightingExclusionRadius.Value);
				if (PhysicalSpawnerIndex.WithinAny(_scanSpawnerFootprints, position, exclusionRadius) || PhysicalSpawnerIndex.Covers(_scanSpawnerFootprints, position, prefabName))
				{
					return;
				}
			}
			string displayName = GetDisplayName(character, prefabName);
			string iconKey = string.Empty;
			if (_icons != null && _icons.IsBuilt)
			{
				WayfinderIconEntry wayfinderIconEntry = _icons.FindBestTrophy(prefabName);
				if (wayfinderIconEntry == null)
				{
					wayfinderIconEntry = _icons.FindBestTrophy(displayName);
				}
				if (wayfinderIconEntry != null)
				{
					iconKey = wayfinderIconEntry.Key;
				}
			}
			WayfinderPinRecord wayfinderPinRecord = _database.AddSightingObservation(prefabName, displayName, position, iconKey, "sighting:" + prefabName + ":" + instanceID, Mathf.Max(2f, _config.EnemySightingClusterDistance.Value));
			if (wayfinderPinRecord != null)
			{
				_seenLiveInstances.Add(instanceID);
				_scanRemembered++;
			}
		}

		private void RunSpawnerCleanup()
		{
			_lastSpawnerCleanupTime = Time.unscaledTime;
			float exclusionRadius = Mathf.Max(0f, _config.SpawnerSightingExclusionRadius.Value);
			int num = _database.RemoveSightingMembers((string subtype, Vector3 position) => PhysicalSpawnerIndex.WithinAny(_scanSpawnerFootprints, position, exclusionRadius) || PhysicalSpawnerIndex.Covers(_scanSpawnerFootprints, position, subtype));
			if (_config.DebugLogging.Value && num > 0)
			{
				_log.LogInfo((object)("Spawner-aware sightings: removed " + num + " redundant sighting member(s) inside matching physical spawner radii."));
			}
		}

		private void ResetActiveScan()
		{
			_scanInProgress = false;
			_scanCharacters.Clear();
			_scanCursor = 0;
			_scanPlayer = null;
			_scanSpawnerFootprints = null;
			_cleanupAfterScan = false;
		}

		private static MethodInfo FindIsEnemyMethod()
		{
			try
			{
				MethodInfo[] methods = typeof(BaseAI).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (string.Equals(methodInfo.Name, "IsEnemy", StringComparison.Ordinal))
					{
						ParameterInfo[] parameters = methodInfo.GetParameters();
						if (methodInfo.IsStatic && parameters.Length == 2 && typeof(Character).IsAssignableFrom(parameters[0].ParameterType) && typeof(Character).IsAssignableFrom(parameters[1].ParameterType))
						{
							return methodInfo;
						}
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static bool IsHostileOrMonster(Character character, Player player)
		{
			if ((Object)(object)character == (Object)null || (Object)(object)player == (Object)null || IsEnemyMethod == null)
			{
				return false;
			}
			try
			{
				object obj = IsEnemyMethod.Invoke(null, new object[2] { player, character });
				return obj is bool && (bool)obj;
			}
			catch
			{
				return false;
			}
		}

		private static bool InvokeBool(object instance, MethodInfo method, bool fallback)
		{
			if (instance == null || method == null)
			{
				return fallback;
			}
			try
			{
				object obj = method.Invoke(instance, null);
				return (obj is bool) ? ((bool)obj) : fallback;
			}
			catch
			{
				return fallback;
			}
		}

		private static string GetDisplayName(Character character, string fallback)
		{
			string text = fallback;
			if ((Object)(object)character != (Object)null && GetHoverNameMethod != null)
			{
				try
				{
					object obj = GetHoverNameMethod.Invoke(character, null);
					string text2 = obj as string;
					if (!string.IsNullOrEmpty(text2))
					{
						text = text2;
					}
				}
				catch
				{
				}
			}
			try
			{
				if (Localization.instance != null)
				{
					text = Localization.instance.Localize(text);
				}
			}
			catch
			{
			}
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return fallback;
		}

		private static string GetPrefabName(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return string.Empty;
			}
			try
			{
				string prefabName = Utils.GetPrefabName(gameObject);
				if (!string.IsNullOrEmpty(prefabName))
				{
					return prefabName.Replace("(Clone)", string.Empty).Trim();
				}
			}
			catch
			{
			}
			return (((Object)gameObject).name ?? string.Empty).Replace("(Clone)", string.Empty).Trim();
		}

		private bool MayReveal(Vector3 position)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if (_config.ScanUnexploredAreas.Value)
			{
				return true;
			}
			Minimap instance = Minimap.instance;
			if ((Object)(object)instance == (Object)null || IsExploredMethod == null)
			{
				return true;
			}
			try
			{
				object obj = IsExploredMethod.Invoke(instance, new object[1] { position });
				return !(obj is bool) || (bool)obj;
			}
			catch
			{
				return true;
			}
		}

		private static bool WithinHorizontalRadius(Vector3 a, Vector3 b, float radiusSq)
		{
			float num = a.x - b.x;
			float num2 = a.z - b.z;
			return num * num + num2 * num2 <= radiusSq;
		}
	}
	internal sealed class GravestoneIntegration
	{
		private sealed class GraveState
		{
			internal string Key;

			internal Vector3 Position;

			internal PinData Pin;
		}

		private static readonly FieldInfo PinsField = AccessTools.Field(typeof(Minimap), "m_pins");

		private static readonly Type ZNetViewType = AccessTools.TypeByName("ZNetView");

		private static readonly MethodInfo GetZdoIdMethod = FindOptionalInstanceMethod(ZNetViewType, "GetZDOID");

		private static readonly MethodInfo GetZdoMethod = FindOptionalInstanceMethod(ZNetViewType, "GetZDO");

		private readonly ManualLogSource _log;

		private readonly WayfinderConfig _config;

		private readonly RuntimeIconRegistry _icons;

		private readonly Dictionary<string, GraveState> _graves = new Dictionary<string, GraveState>(StringComparer.Ordinal);

		private readonly Dictionary<PinData, string> _graveKeyByPin = new Dictionary<PinData, string>();

		private float _nextBindTime;

		internal GravestoneIntegration(ManualLogSource log, WayfinderConfig config, RuntimeIconRegistry icons)
		{
			_log = log;
			_config = config;
			_icons = icons;
		}

		internal void ResetSession()
		{
			_graves.Clear();
			_graveKeyByPin.Clear();
			_nextBindTime = 0f;
		}

		internal void ClearSession()
		{
			ResetSession();
		}

		internal void RegisterLoadedGrave(TombStone tombstone)
		{
			//IL_007a: 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)
			if ((Object)(object)tombstone == (Object)null)
			{
				return;
			}
			Component val = null;
			try
			{
				if (ZNetViewType != null)
				{
					val = ((Component)tombstone).GetComponent(ZNetViewType);
				}
			}
			catch
			{
			}
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			string stableGraveKey = GetStableGraveKey(val);
			if (!string.IsNullOrEmpty(stableGraveKey))
			{
				if (!_graves.TryGetValue(stableGraveKey, out var value) || value == null)
				{
					GraveState graveState = new GraveState();
					graveState.Key = stableGraveKey;
					value = graveState;
					_graves[stableGraveKey] = value;
				}
				try
				{
					value.Position = ((Component)tombstone).transform.position;
				}
				catch
				{
				}
				TryBindStateToNativeDeathPin(value, Minimap.instance);
			}
		}

		internal void BeforePermanentNetworkDestroy(GameObject gameObject)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)gameObject == (Object)null)
			{
				return;
			}
			TombStone val = null;
			try
			{
				val = gameObject.GetComponent<TombStone>();
			}
			catch
			{
			}
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Component val2 = null;
			try
			{
				if (ZNetViewType != null)
				{
					val2 = gameObject.GetComponent(ZNetViewType);
				}
			}
			catch
			{
			}
			if ((Object)(object)val2 == (Object)null)
			{
				return;
			}
			string stableGraveKey = GetStableGraveKey(val2);
			Vector3 position = Vector3.zero;
			try
			{
				position = ((Component)val).transform.position;
			}
			catch
			{
			}
			GraveState value = null;
			if (!string.IsNullOrEmpty(stableGraveKey))
			{
				_graves.TryGetValue(stableGraveKey, out value);
			}
			if (value == null)
			{
				GraveState graveState = new GraveState();
				graveState.Key = stableGraveKey ?? string.Empty;
				graveState.Position = position;
				value = graveState;
			}
			else
			{
				value.Position = position;
			}
			Minimap instance = Minimap.instance;
			if (value.Pin == null)
			{
				TryBindStateToNativeDeathPin(value, instance);
			}
			PinData pin = value.Pin;
			if (pin != null && (Object)(object)instance != (Object)null)
			{
				try
				{
					instance.RemovePin(pin);
					if (_config != null && _config.DebugLogging.Value && _config.VerboseRuntimeLogging.Value)
					{
						_log.LogInfo((object)("Gravestone permanently disappeared; removed its associated native death marker (" + (string.IsNullOrEmpty(stableGraveKey) ? "unkeyed" : stableGraveKey) + ")."));
					}
				}
				catch (Exception ex)
				{
					if (_config != null && _config.DebugLogging.Value)
					{
						_log.LogWarning((object)("Could not remove cleaned-up gravestone marker: " + ex.Message));
					}
				}
			}
			if (pin != null)
			{
				_graveKeyByPin.Remove(pin);
			}
			if (!string.IsNullOrEmpty(stableGraveKey))
			{
				_graves.Remove(stableGraveKey);
			}
		}

		internal void Tick()
		{
			if (_config == null || !_config.Enabled.Value || Time.unscaledTime < _nextBindTime)
			{
				return;
			}
			_nextBindTime = Time.unscaledTime + 0.25f;
			Minimap instance = Minimap.instance;
			if ((Object)(object)instance == (Object)null || _graves.Count == 0)
			{
				return;
			}
			List<PinData> pins = GetPins(instance);
			if (pins == null)
			{
				return;
			}
			List<string> list = null;
			foreach (KeyValuePair<string, GraveState> grafe in _graves)
			{
				GraveState value = grafe.Value;
				if (value != null && value.Pin != null && !pins.Contains(value.Pin))
				{
					_graveKeyByPin.Remove(value.Pin);
					value.Pin = null;
					if (list == null)
					{
						list = new List<string>();
					}
					list.Add(grafe.Key);
				}
			}
			foreach (KeyValuePair<string, GraveState> grafe2 in _graves)
			{
				GraveState value2 = grafe2.Value;
				if (value2 != null && value2.Pin == null)
				{
					TryBindStateToNativeDeathPin(value2, instance);
				}
			}
		}

		internal bool TryGetGravestoneSprite(out Sprite sprite)
		{
			sprite = null;
			if (_icons != null && _icons.TryGet("wayfinder:gravestone", out sprite))
			{
				return (Object)(object)sprite != (Object)null;
			}
			return false;
		}

		internal static bool IsNativeDeathPin(PinData pin)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (pin == null)
			{
				return false;
			}
			string text = string.Empty;
			try
			{
				text = ((object)pin.m_type).ToString();
			}
			catch
			{
			}
			if (!string.IsNullOrEmpty(text) && text.IndexOf("death", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			string a = pin.m_name ?? string.Empty;
			if (!string.Equals(a, "$msg_mapmarker_death", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "$map_death", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(a, "Death", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private void TryBindStateToNativeDeathPin(GraveState state, Minimap map)
		{
			if (state == null || state.Pin != null || (Object)(object)map == (Object)null)
			{
				return;
			}
			List<PinData> pins = GetPins(map);
			if (pins == null)
			{
				return;
			}
			PinData val = null;
			float num = 144f;
			for (int i = 0; i < pins.Count; i++)
			{
				PinData val2 = pins[i];
				if (IsNativeDeathPin(val2) && !_graveKeyByPin.ContainsKey(val2))
				{
					float num2 = val2.m_pos.x - state.Position.x;
					float num3 = val2.m_pos.z - state.Position.z;
					float num4 = num2 * num2 + num3 * num3;
					if (num4 <= num)
					{
						num = num4;
						val = val2;
					}
				}
			}
			if (val != null)
			{
				state.Pin = val;
				_graveKeyByPin[val] = state.Key ?? string.Empty;
			}
		}

		private static List<PinData> GetPins(Minimap map)
		{
			if ((Object)(object)map == (Object)null || PinsField == null)
			{
				return null;
			}
			try
			{
				return PinsField.GetValue(map) as List<PinData>;
			}
			catch
			{
				return null;
			}
		}

		private static string GetStableGraveKey(Component view)
		{
			if ((Object)(object)view == (Object)null)
			{
				return string.Empty;
			}
			object obj = null;
			if (GetZdoIdMethod != null)
			{
				try
				{
					obj = GetZdoIdMethod.Invoke(view, null);
				}
				catch
				{
					obj = null;
				}
			}
			if (obj == null && GetZdoMethod != null)
			{
				try
				{
					object obj3 = GetZdoMethod.Invoke(view, null);
					if (obj3 != null)
					{
						FieldInfo field = obj3.GetType().GetField("m_uid", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						if (field != null)
						{
							obj = field.GetValue(obj3);
						}
					}
				}
				catch
				{
					obj = null;
				}
			}
			string text = StableIdToString(obj);
			if (!string.IsNullOrEmpty(text))
			{
				switch (text)
				{
				case "0":
				case "0:0":
				case "0_0":
					break;
				default:
					return "grave:" + text;
				}
			}
			return string.Empty;
		}

		private static MethodInfo FindOptionalInstanceMethod(Type type, string name)
		{
			if (type == null || string.IsNullOrEmpty(name))
			{
				return null;
			}
			try
			{
				return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			}
			catch
			{
				return null;
			}
		}

		private static string StableIdToString(object id)
		{
			if (id == null)
			{
				return string.Empty;
			}
			try
			{
				Type type = id.GetType();
				BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
				object obj = ReadSilentMember(type, id, flags, "m_userID", "userID", "UserID");
				object obj2 = ReadSilentMember(type, id, flags, "m_id", "id", "ID");
				if (obj != null && obj2 != null)
				{
					string text = Convert.ToString(obj);
					string text2 = Convert.ToString(obj2);
					if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2))
					{
						return text + ":" + text2;
					}
				}
			}
			catch
			{
			}
			try
			{
				return id.ToString();
			}
			catch
			{
				return string.Empty;
			}
		}

		private static object ReadSilentMember(Type type, object instance, BindingFlags flags, params string[] names)
		{
			if (type == null || instance == null || names == null)
			{
				return null;
			}
			foreach (string text in names)
			{
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				try
				{
					FieldInfo field = type.GetField(text, flags);
					if (field != null)
					{
						return field.GetValue(instance);
					}
				}
				catch
				{
				}
				try
				{
					PropertyInfo property = type.GetProperty(text, flags);
					if (property != null && property.GetIndexParameters().Length == 0)
					{
						return property.GetValue(instance, null);
					}
				}
				catch
				{
				}
			}
			return null;
		}
	}
	internal static class LiveDiscoveryRegistry
	{
		private static readonly FieldInfo AllLocationsField = AccessTools.Field(typeof(Location), "s_allLocations");

		private static readonly Dictionary<int, Location> Locations = new Dictionary<int, Location>();

		private static readonly Dictionary<int, SpawnArea> SpawnAreas = new Dictionary<int, SpawnArea>();

		private static readonly Dictionary<int, RuneStone> Runestones = new Dictionary<int, RuneStone>();

		private static readonly Dictionary<int, Vegvisir> Vegvisirs = new Dictionary<int, Vegvisir>();

		private static readonly Dictionary<int, Trader> Traders = new Dictionary<int, Trader>();

		private static long _worldUid = long.MinValue;

		private static int _revision;

		internal static int Revision => _revision;

		internal static void ResetForWorld(long worldUid)
		{
			if (_worldUid != worldUid)
			{
				_worldUid = worldUid;
				Locations.Clear();
				SpawnAreas.Clear();
				Runestones.Clear();
				Vegvisirs.Clear();
				Traders.Clear();
				_revision++;
			}
		}

		internal static void RegisterLocation(Location location)
		{
			if (!AddLive<Location>(Locations, location))
			{
				return;
			}
			try
			{
				RegisterMany(((Component)location).GetComponentsInChildren<SpawnArea>(true), RegisterSpawnArea);
				RegisterMany(((Component)location).GetComponentsInChildren<RuneStone>(true), RegisterRunestone);
				RegisterMany(((Component)location).GetComponentsInChildren<Vegvisir>(true), RegisterVegvisir);
				RegisterMany(((Component)location).GetComponentsInChildren<Trader>(true), RegisterTrader);
			}
			catch
			{
			}
		}

		internal static void UnregisterLocation(Location location)
		{
			RemoveLive<Location>(Locations, location);
		}

		internal static void RegisterSpawnArea(SpawnArea area)
		{
			AddLive<SpawnArea>(SpawnAreas, area);
		}

		internal static void RegisterRunestone(RuneStone stone)
		{
			AddLive<RuneStone>(Runestones, stone);
		}

		internal static void RegisterVegvisir(Vegvisir vegvisir)
		{
			AddLive<Vegvisir>(Vegvisirs, vegvisir);
		}

		internal static void RegisterTrader(Trader trader)
		{
			AddLive<Trader>(Traders, trader);
		}

		internal static void SyncFromLoadedLocations()
		{
			if (AllLocationsField == null)
			{
				PruneDead();
				return;
			}
			try
			{
				if (AllLocationsField.GetValue(null) is IEnumerable enumerable)
				{
					foreach (object item in enumerable)
					{
						Location val = (Location)((item is Location) ? item : null);
						if ((Object)(object)val != (Object)null)
						{
							RegisterLocation(val);
						}
					}
				}
			}
			catch
			{
			}
			PruneDead();
		}

		internal static Location[] SnapshotLocations()
		{
			SyncFromLoadedLocations();
			return Snapshot<Location>(Locations);
		}

		internal static SpawnArea[] SnapshotSpawnAreas()
		{
			SyncFromLoadedLocations();
			return Snapshot<SpawnArea>(SpawnAreas);
		}

		internal static RuneStone[] SnapshotRunestones()
		{
			SyncFromLoadedLocations();
			return Snapshot<RuneStone>(Runestones);
		}

		internal static Vegvisir[] SnapshotVegvisirs()
		{
			SyncFromLoadedLocations();
			return Snapshot<Vegvisir>(Vegvisirs);
		}

		internal static Trader[] SnapshotTraders()
		{
			SyncFromLoadedLocations();
			return Snapshot<Trader>(Traders);
		}

		private static bool AddLive<T>(Dictionary<int, T> registry, T value) where T : Object
		{
			if (registry == null || (Object)(object)value == (Object)null)
			{
				return false;
			}
			int instanceID;
			try
			{
				instanceID = ((Object)value/*cast due to .constrained prefix*/).GetInstanceID();
			}
			catch
			{
				return false;
			}
			if (registry.TryGetValue(instanceID, out var value2) && (Object)(object)value2 != (Object)null)
			{
				return false;
			}
			registry[instanceID] = value;
			_revision++;
			return true;
		}

		private static void RemoveLive<T>(Dictionary<int, T> registry, T value) where T : Object
		{
			if (registry == null || object.ReferenceEquals(value, null))
			{
				return;
			}
			try
			{
				if (registry.Remove(((Object)value/*cast due to .constrained prefix*/).GetInstanceID()))
				{
					_revision++;
				}
			}
			catch
			{
			}
		}

		private static void RegisterMany<T>(T[] values, Action<T> register)
		{
			if (values != null && register != null)
			{
				for (int i = 0; i < values.Length; i++)
				{
					register(values[i]);
				}
			}
		}

		private static T[] Snapshot<T>(Dictionary<int, T> registry) where T : Object
		{
			if (registry == null || registry.Count == 0)
			{
				return new T[0];
			}
			List<T> list = new List<T>(registry.Count);
			List<int> list2 = null;
			foreach (KeyValuePair<int, T> item in registry)
			{
				T value = item.Value;
				if ((Object)(object)value == (Object)null)
				{
					if (list2 == null)
					{
						list2 = new List<int>();
					}
					list2.Add(item.Key);
				}
				else
				{
					list.Add(value);
				}
			}
			if (list2 != null)
			{
				for (int i = 0; i < list2.Count; i++)
				{
					registry.Remove(list2[i]);
				}
				_revision++;
			}
			return list.ToArray();
		}

		private static void PruneDead()
		{
			PruneDead<Location>(Locations);
			PruneDead<SpawnArea>(SpawnAreas);
			PruneDead<RuneStone>(Runestones);
			PruneDead<Vegvisir>(Vegvisirs);
			PruneDead<Trader>(Traders);
		}

		private static void PruneDead<T>(Dictionary<int, T> registry) where T : Object
		{
			if (registry == null || registry.Count == 0)
			{
				return;
			}
			List<int> list = null;
			foreach (KeyValuePair<int, T> item in registry)
			{
				if (!((Object)(object)item.Value != (Object)null))
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(item.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					registry.Remove(list[i]);
				}
				_revision++;
			}
		}
	}
	internal sealed class OreDiagnosticScanner
	{
		private readonly ManualLogSource _log;

		private readonly WayfinderConfig _config;

		private readonly HashSet<int> _loggedCandidates = new HashSet<int>();

		private float _nextScanTime;

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

		internal void Tick()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			if (!_config.DebugLogging.Value || !_config.VerboseRuntimeLogging.Value || (Object)(object)Player.m_localPlayer == (Object)null || Time.unscaledTime < _nextScanTime)
			{
				return;
			}
			_nextScanTime = Time.unscaledTime + 4f;
			Vector3 position = ((Component)Player.m_localPlayer).transform.position;
			Collider[] array = Physics.OverlapSphere(position, 22f, -1, (QueryTriggerInteraction)2);
			HashSet<int> hashSet = new HashSet<int>();
			int num = 0;
			foreach (Collider val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Transform val2 = ((Component)val).transform;
				int num2 = 0;
				while ((Object)(object)val2 != (Object)null && num2 < 5)
				{
					GameObject gameObject = ((Component)val2).gameObject;
					if (!((Object)(object)gameObject == (Object)null))
					{
						Scene scene = gameObject.scene;
						if (((Scene)(ref scene)).IsValid())
						{
							int instanceID = ((Object)gameObject).GetInstanceID();
							if (hashSet.Add(instanceID) && !_loggedCandidates.Contains(instanceID) && LooksOreLike(gameObject, out var reason))
							{
								_loggedCandidates.Add(instanceID);
								LogCandidate(gameObject, "nearby:" + reason);
								num++;
								if (num >= 12)
								{
									break;
								}
							}
						}
					}
					num2++;
					val2 = val2.parent;
				}
				if (num >= 12)
				{
					break;
				}
			}
			if (num > 0)
			{
				_log.LogInfo((object)("ORE-DIAG logged " + num + " new nearby ore/deposit candidate(s)."));
			}
		}

		internal void LogHit(GameObject gameObject, HitData hit, string hook)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (!_config.DebugLogging.Value || !_config.VerboseRuntimeLogging.Value || (Object)(object)gameObject == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			try
			{
				Vector3 position = gameObject.transform.position;
				Vector3 position2 = ((Component)Player.m_localPlayer).transform.position;
				float num = position.x - position2.x;
				float num2 = position.z - position2.z;
				if (num * num + num2 * num2 > 1225f)
				{
					return;
				}
			}
			catch
			{
			}
			LogCandidate(gameObject, "HIT:" + (hook ?? "unknown"));
		}

		private void LogCandidate(GameObject go, string reason)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)go == (Object)null)
			{
				return;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder(1024);
				StringBuilder stringBuilder2 = stringBuilder.Append("ORE-DIAG ").Append(reason).Append(" | prefab='")
					.Append(GetPrefabName(go))
					.Append("'")
					.Append(" object='")
					.Append(((Object)go).name ?? string.Empty)
					.Append("'")
					.Append(" hierarchy='")
					.Append(GetHierarchy(go.transform))
					.Append("'")
					.Append(" pos=");
				float x = go.transform.position.x;
				StringBuilder stringBuilder3 = stringBuilder2.Append(x.ToString("0.0")).Append(",");
				float y = go.transform.position.y;
				StringBuilder stringBuilder4 = stringBuilder3.Append(y.ToString("0.0")).Append(",");
				float z = go.transform.position.z;
				stringBuilder4.Append(z.ToString("0.0")).Append(" activeSelf=").Append(go.activeSelf)
					.Append(" activeHierarchy=")
					.Append(go.activeInHierarchy)
					.Append(" components=[")
					.Append(GetComponentNames(go))
					.Append("]");
				string value = TryGetHoverName(go);
				if (!string.IsNullOrEmpty(value))
				{
					stringBuilder.Append(" hover='").Append(value).Append("'");
				}
				string visualHints = GetVisualHints(go);
				if (!string.IsNullOrEmpty(visualHints))
				{
					stringBuilder.Append(" visuals=[").Append(visualHints).Append("]");
				}
				string interestingComponentFields = GetInterestingComponentFields(go);
				if (!string.IsNullOrEmpty(interestingComponentFields))
				{
					stringBuilder.Append(" fields=[").Append(interestingComponentFields).Append("]");
				}
				_log.LogInfo((object)stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("ORE-DIAG failed to describe candidate: " + ex.Message));
			}
		}

		private static bool LooksOreLike(GameObject go, out string reason)
		{
			reason = string.Empty;
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			string normalized = Normalize((((Object)go).name ?? string.Empty) + " " + GetHierarchy(go.transform));
			if (ContainsOreToken(normalized))
			{
				reason = "name";
				return true;
			}
			Component[] components = go.GetComponents<Component>();
			foreach (Component val in components)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string text = Normalize(((object)val).GetType().Name);
					if (text.Contains("minerock"))
					{
						reason = "component:" + ((object)val).GetType().Name;
						return true;
					}
				}
			}
			Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val2 in componentsInChildren)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				Material[] sharedMaterials = val2.sharedMaterials;
				if (sharedMaterials == null)
				{
					continue;
				}
				foreach (Material val3 in sharedMaterials)
				{
					if (!((Object)(object)val3 == (Object)null))
					{
						string normalized2 = Normalize(((Object)val3).name ?? string.Empty);
						if (ContainsOreToken(normalized2))
						{
							reason = "material:" + ((Object)val3).name;
							return true;
						}
					}
				}
			}
			MeshFilter[] componentsInChildren2 = go.GetComponentsInChildren<MeshFilter>(true);
			foreach (MeshFilter val4 in componentsInChildren2)
			{
				if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4.sharedMesh == (Object)null))
				{
					string normalized3 = Normalize(((Object)val4.sharedMesh).name ?? string.Empty);
					if (ContainsOreToken(normalized3))
					{
						reason = "mesh:" + ((Object)val4.sharedMesh).name;
						return true;
					}
				}
			}
			return false;
		}

		private static bool ContainsOreToken(string normalized)
		{
			if (string.IsNullOrEmpty(normalized))
			{
				return false;
			}
			if (!normalized.Contains("copper") && !normalized.Contains("tin") && !normalized.Contains("silver") && !normalized.Contains("obsidian") && !normalized.Contains("flametal") && !normalized.Contains("deposit") && !normalized.Contains("ore"))
			{
				return normalized.Contains("minerock");
			}
			return true;
		}

		private static string GetComponentNames(GameObject go)
		{
			Component[] components = go.GetComponents<Component>();
			StringBuilder stringBuilder = new StringBuilder();
			foreach (Component val in components)
			{
				if (!((Object)(object)val == (Object)null))
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(",");
					}
					stringBuilder.Append(((object)val).GetType().Name);
				}
			}
			return stringBuilder.ToString();
		}

		private static string GetInterestingComponentFields(GameObject go)
		{
			Component[] components = go.GetComponents<Component>();
			StringBuilder stringBuilder = new StringBuilder();
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			foreach (Component val in components)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				string name = ((object)val).GetType().Name;
				string text = Normalize(name);
				if (!text.Contains("mine") && !text.Contains("drop") && !text.Contains("destruct") && !text.Contains("pickable") && !text.Contains("wear") && !text.Contains("health"))
				{
					continue;
				}
				FieldInfo[] fields;
				try
				{
					fields = ((object)val).GetType().GetFields(bindingAttr);
				}
				catch
				{
					continue;
				}
				int num = 0;
				for (int j = 0; j < fields.Length; j++)
				{
					if (num >= 8)
					{
						break;
					}
					FieldInfo fieldInfo = fields[j];
					string text2 = Normalize(fieldInfo.Name);
					if (!text2.Contains("name") && !text2.Contains("drop") && !text2.Contains("item") && !text2.Contains("prefab") && !text2.Contains("health") && !text2.Contains("destroy"))
					{
						continue;
					}
					object value = null;
					try
					{
						value = fieldInfo.GetValue(val);
					}
					catch
					{
					}
					string value2 = RenderValue(value);
					if (!string.IsNullOrEmpty(value2))
					{
						if (stringBuilder.Length > 0)
						{
							stringBuilder.Append("; ");
						}
						stringBuilder.Append(name).Append(".").Append(fieldInfo.Name)
							.Append("=")
							.Append(value2);
						num++;
					}
				}
			}
			return stringBuilder.ToString();
		}

		private static string RenderValue(object value)
		{
			if (value == null)
			{
				return string.Empty;
			}
			if (value is string result)
			{
				return result;
			}
			GameObject val = (GameObject)((value is GameObject) ? value : null);
			if ((Object)(object)val != (Object)null)
			{
				return "GameObject(" + GetPrefabName(val) + ")";
			}
			Object val2 = (Object)((value is Object) ? value : null);
			if (val2 != (Object)null)
			{
				return ((object)val2).GetType().Name + "(" + (val2.name ?? string.Empty) + ")";
			}
			Type type = value.GetType();
			string name = type.Name;
			if (name.IndexOf("DropTable", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				string text = TryDescribeDropContainer(value);
				if (!string.IsNullOrEmpty(text))
				{
					return name + "(" + text + ")";
				}
				return name;
			}
			if (value is IEnumerable enumerable && !(value is string))
			{
				string text2 = TryDescribeEnumerable(enumerable);
				if (!string.IsNullOrEmpty(text2))
				{
					return name + "(" + text2 + ")";
				}
			}
			if (type.IsPrimitive || value is decimal)
			{
				return Convert.ToString(value, CultureInfo.InvariantCulture);
			}
			return name;
		}

		private static string TryDescribeDropContainer(object container)
		{
			if (container == null)
			{
				return string.Empty;
			}
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			FieldInfo[] fields;
			try
			{
				fields = container.GetType().GetFields(bindingAttr);
			}
			catch
			{
				return string.Empty;
			}
			foreach (FieldInfo fieldInfo in fields)
			{
				string text = Normalize(fieldInfo.Name);
				if (!text.Contains("drop") && !text.Contains("item"))
				{
					continue;
				}
				object obj2 = null;
				try
				{
					obj2 = fieldInfo.GetValue(container);
				}
				catch
				{
				}
				if (obj2 is IEnumerable enumerable && !(obj2 is string))
				{
					string text2 = TryDescribeEnumerable(enumerable);
					if (!string.IsNullOrEmpty(text2))
					{
						return text2;
					}
				}
			}
			return string.Empty;
		}

		private static string TryDescribeEnumerable(IEnumerable enumerable)
		{
			if (enumerable == null)
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			foreach (object item in enumerable)
			{
				if (item == null || num >= 8)
				{
					continue;
				}
				GameObject val = (GameObject)((item is GameObject) ? item : null);
				if ((Object)(object)val != (Object)null)
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(",");
					}
					stringBuilder.Append(GetPrefabName(val));
					num++;
					continue;
				}
				BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
				FieldInfo[] fields;
				try
				{
					fields = item.GetType().GetFields(bindingAttr);
				}
				catch
				{
					continue;
				}
				for (int i = 0; i < fields.Length; i++)
				{
					if (!typeof(GameObject).IsAssignableFrom(fields[i].FieldType))
					{
						continue;
					}
					string text = Normalize(fields[i].Name);
					if (!text.Contains("item") && !text.Contains("prefab") && !text.Contains("drop"))
					{
						continue;
					}
					GameObject val2 = null;