Decompiled source of ValheimVillages v0.1.1

plugins/ValheimVillages/ValheimVillages.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Events;
using UnityEngine.UI;
using ValheimVillages.Abilities;
using ValheimVillages.Attributes;
using ValheimVillages.Behaviors;
using ValheimVillages.Behaviors.Combat;
using ValheimVillages.Behaviors.Crafting;
using ValheimVillages.Behaviors.Farming;
using ValheimVillages.Behaviors.Patrol;
using ValheimVillages.Behaviors.Repair;
using ValheimVillages.Behaviors.Tidy;
using ValheimVillages.Behaviors.Work;
using ValheimVillages.Diagnostics;
using ValheimVillages.Enums;
using ValheimVillages.Interfaces;
using ValheimVillages.Items;
using ValheimVillages.Items.Icons;
using ValheimVillages.Items.VirtualRecipes;
using ValheimVillages.Items.WorkOrders;
using ValheimVillages.Patches;
using ValheimVillages.Schemas;
using ValheimVillages.Tags;
using ValheimVillages.TaskQueue;
using ValheimVillages.TaskQueue.ActivityLog;
using ValheimVillages.TaskQueue.Handlers;
using ValheimVillages.Testing;
using ValheimVillages.UI.ContextMenus;
using ValheimVillages.UI.Core;
using ValheimVillages.UI.Interaction;
using ValheimVillages.UI.Panels;
using ValheimVillages.UI.Tabs;
using ValheimVillages.Villager;
using ValheimVillages.Villager.AI;
using ValheimVillages.Villager.AI.Memory;
using ValheimVillages.Villager.AI.Navigation;
using ValheimVillages.Villager.AI.Pathfinding;
using ValheimVillages.Villager.AI.Work;
using ValheimVillages.Villager.Records;
using ValheimVillages.Villager.Registry;
using ValheimVillages.Villager.Station;
using ValheimVillages.Villages;
using ValheimVillages.Villages.Entity;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Valheim Villages")]
[assembly: AssemblyDescription("A village-building and NPC management mod for Valheim")]
[assembly: AssemblyCompany("Myrcutio")]
[assembly: AssemblyProduct("ValheimVillages")]
[assembly: AssemblyCopyright("Copyright © Myrcutio 2026")]
[assembly: AssemblyFileVersion("0.1.1")]
[assembly: InternalsVisibleTo("ValheimVillages.Tests")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.1.1.0")]
namespace ValheimVillages
{
	internal static class DebugLog
	{
		private sealed class ThrottleState
		{
			public bool EverEmitted;

			public TimeSpan LastEmitted;

			public int Suppressed;
		}

		private static int _captureCounter;

		private static DebugCaptureBehaviour _captureBehaviour;

		private static readonly string LogPath = Path.Combine(Paths.ConfigPath, "vv_dumps", "legacy_debug.ndjson");

		private static int _cycleNumber;

		private static readonly Dictionary<string, ThrottleState> _throttle = new Dictionary<string, ThrottleState>();

		private static readonly TimeSpan DefaultThrottleWindow = TimeSpan.FromSeconds(10.0);

		private static readonly Stopwatch _sessionClock = Stopwatch.StartNew();

		private static string SidecarDir
		{
			get
			{
				string path;
				try
				{
					path = Paths.ConfigPath;
				}
				catch
				{
					path = ".";
				}
				return Path.Combine(path, "vv_dumps");
			}
		}

		public static void Capture(string trigger)
		{
			Capture(CaptureRequest.Default(trigger));
		}

		public static void Capture(CaptureRequest request)
		{
			try
			{
				Plugin instance = Plugin.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					if ((Object)(object)_captureBehaviour == (Object)null)
					{
						_captureBehaviour = ((Component)instance).gameObject.GetComponent<DebugCaptureBehaviour>() ?? ((Component)instance).gameObject.AddComponent<DebugCaptureBehaviour>();
					}
					_captureBehaviour.Enqueue(request);
				}
			}
			catch
			{
			}
		}

		internal static int NextCaptureCounter()
		{
			return Interlocked.Increment(ref _captureCounter);
		}

		public static string Vid(Guid id)
		{
			string text = id.ToString("N");
			if (text.Length < 8)
			{
				return text;
			}
			return text.Substring(0, 8);
		}

		public static string Vid(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				return "unknown";
			}
			if (Guid.TryParse(id, out var result))
			{
				return Vid(result);
			}
			string text = id.Replace("-", "");
			if (text.Length < 8)
			{
				return text;
			}
			return text.Substring(0, 8);
		}

		public static string Tid(string taskName, int instanceId)
		{
			return (taskName ?? "task") + "#" + instanceId;
		}

		public static void Append(string location, string message, Dictionary<string, object> data, string hypothesisId, string runId)
		{
			try
			{
				long num = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append('{');
				stringBuilder.AppendFormat("\"timestamp\":{0}", num);
				stringBuilder.AppendFormat(",\"location\":\"{0}\"", Esc(location));
				stringBuilder.AppendFormat(",\"message\":\"{0}\"", Esc(message));
				stringBuilder.AppendFormat(",\"hypothesisId\":\"{0}\"", Esc(hypothesisId));
				stringBuilder.AppendFormat(",\"runId\":\"{0}\"", Esc(runId));
				stringBuilder.Append(",\"data\":{");
				bool flag = true;
				foreach (KeyValuePair<string, object> datum in data)
				{
					if (!flag)
					{
						stringBuilder.Append(',');
					}
					flag = false;
					stringBuilder.AppendFormat("\"{0}\":", Esc(datum.Key));
					if (datum.Value is string v)
					{
						stringBuilder.AppendFormat("\"{0}\"", Esc(v));
					}
					else if (datum.Value is bool flag2)
					{
						stringBuilder.Append(flag2 ? "true" : "false");
					}
					else if (datum.Value is float num2)
					{
						stringBuilder.Append(num2.ToString(CultureInfo.InvariantCulture));
					}
					else if (datum.Value is double num3)
					{
						stringBuilder.Append(num3.ToString(CultureInfo.InvariantCulture));
					}
					else
					{
						stringBuilder.Append(datum.Value?.ToString() ?? "null");
					}
				}
				stringBuilder.Append("}}");
				File.AppendAllText(LogPath, stringBuilder?.ToString() + "\n");
			}
			catch
			{
			}
		}

		private static string Esc(string v)
		{
			return v?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? "";
		}

		public static void BeginCycle(bool isHotReload)
		{
			int num = Interlocked.Increment(ref _cycleNumber);
			string arg = (isHotReload ? "true" : "false");
			string arg2 = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
			Plugin.Log.LogInfo((object)$"===== VV CYCLE n={num} hot={arg} t={arg2} =====");
		}

		public static void Event(string component, string eventName, params (string key, object val)[] kv)
		{
			StringBuilder stringBuilder = new StringBuilder(64 + ((kv != null) ? kv.Length : 0) * 16);
			stringBuilder.Append('[').Append(component).Append("] ")
				.Append(eventName);
			stringBuilder.Append(' ').Append(T());
			if (kv != null)
			{
				for (int i = 0; i < kv.Length; i++)
				{
					stringBuilder.Append(' ').Append(kv[i].key).Append('=');
					AppendValue(stringBuilder, kv[i].val);
				}
			}
			Plugin.Log.LogInfo((object)stringBuilder.ToString());
		}

		private static void AppendValue(StringBuilder sb, object v)
		{
			if (v == null)
			{
				sb.Append("null");
				return;
			}
			string text = ((v is float num) ? num.ToString("0.###", CultureInfo.InvariantCulture) : ((v is double num2) ? num2.ToString("0.###", CultureInfo.InvariantCulture) : ((!(v is bool)) ? (v.ToString() ?? "") : (((bool)v) ? "true" : "false"))));
			bool flag = false;
			foreach (char c in text)
			{
				if (c == ' ' || c == '=' || c == '"')
				{
					flag = true;
					break;
				}
			}
			if (flag)
			{
				sb.Append('"').Append(text.Replace("\"", "\\\"")).Append('"');
			}
			else
			{
				sb.Append(text);
			}
		}

		public static void List(string component, string name, IEnumerable<object> items)
		{
			string[] array = items?.Select((object i) => i?.ToString() ?? "null").ToArray() ?? new string[0];
			string text = ShortSha(string.Join(",", array));
			string sidecarDir = SidecarDir;
			string text2 = Path.Combine(sidecarDir, name + "_" + text + ".json");
			try
			{
				Directory.CreateDirectory(sidecarDir);
				if (!File.Exists(text2))
				{
					StringBuilder stringBuilder = new StringBuilder();
					stringBuilder.Append('[');
					for (int num = 0; num < array.Length; num++)
					{
						if (num > 0)
						{
							stringBuilder.Append(',');
						}
						stringBuilder.Append('"').Append(JsonEscape(array[num])).Append('"');
					}
					stringBuilder.Append(']');
					File.WriteAllText(text2, stringBuilder.ToString());
				}
			}
			catch
			{
			}
			Event(component, name, ("count", array.Length), ("sha", text), ("path", text2));
		}

		private static string ShortSha(string s)
		{
			using SHA1 sHA = SHA1.Create();
			byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s ?? ""));
			StringBuilder stringBuilder = new StringBuilder(8);
			for (int i = 0; i < 4; i++)
			{
				stringBuilder.AppendFormat("{0:x2}", array[i]);
			}
			return stringBuilder.ToString();
		}

		private static string JsonEscape(string s)
		{
			return (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n")
				.Replace("\r", "\\r")
				.Replace("\t", "\\t");
		}

		public static void Throttled(string key, string component, string eventName, params (string key, object val)[] kv)
		{
			ThrottledWindow(key, DefaultThrottleWindow, component, eventName, kv);
		}

		public static void ThrottledWindow(string key, TimeSpan window, string component, string eventName, params (string key, object val)[] kv)
		{
			if (string.IsNullOrEmpty(key))
			{
				Event(component, eventName, kv);
				return;
			}
			ThrottleState value;
			lock (_throttle)
			{
				if (!_throttle.TryGetValue(key, out value))
				{
					value = new ThrottleState();
					_throttle[key] = value;
				}
			}
			lock (value)
			{
				TimeSpan timeSpan = Elapsed();
				bool num = !value.EverEmitted;
				bool flag = timeSpan - value.LastEmitted >= window;
				if (num || flag)
				{
					if (value.Suppressed > 0)
					{
						(string, object)[] array = new(string, object)[kv.Length + 2];
						Array.Copy(kv, array, kv.Length);
						array[kv.Length] = ("suppressed", value.Suppressed);
						array[kv.Length + 1] = ("window_s", window.TotalSeconds);
						Event(component, eventName, array);
					}
					else
					{
						Event(component, eventName, kv);
					}
					value.LastEmitted = timeSpan;
					value.EverEmitted = true;
					value.Suppressed = 0;
				}
				else
				{
					value.Suppressed++;
				}
			}
		}

		public static TimeSpan Elapsed()
		{
			return _sessionClock.Elapsed;
		}

		public static string T()
		{
			return "t=+" + _sessionClock.Elapsed.TotalSeconds.ToString("0.00", CultureInfo.InvariantCulture) + "s";
		}
	}
	internal readonly struct CaptureRequest
	{
		public readonly string Trigger;

		public readonly string OutputSubdir;

		public readonly string OutputBaseName;

		public readonly Vector3? AnchorOverride;

		public readonly float AnchorClearance;

		public readonly bool IncludeDiagnostics;

		public CaptureRequest(string trigger, string outputSubdir, string outputBaseName, Vector3? anchorOverride, float anchorClearance, bool includeDiagnostics)
		{
			Trigger = trigger ?? "unknown";
			OutputSubdir = outputSubdir ?? "";
			OutputBaseName = (string.IsNullOrEmpty(outputBaseName) ? "last_capture" : outputBaseName);
			AnchorOverride = anchorOverride;
			AnchorClearance = anchorClearance;
			IncludeDiagnostics = includeDiagnostics;
		}

		public static CaptureRequest Default(string trigger)
		{
			return new CaptureRequest(trigger, "", "last_capture", null, 45f, includeDiagnostics: true);
		}

		public static CaptureRequest ForIncident(string trigger, string incidentSubdir, string baseName, Vector3 anchor, float clearance)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			return new CaptureRequest(trigger, incidentSubdir, baseName, anchor, clearance, includeDiagnostics: false);
		}
	}
	internal class DebugCaptureBehaviour : MonoBehaviour
	{
		private readonly ConcurrentQueue<CaptureRequest> _pending = new ConcurrentQueue<CaptureRequest>();

		private bool _processing;

		private static readonly HashSet<string> OrchestratedTriggers = new HashSet<string> { "repartition", "manual" };

		private void Update()
		{
			if (!_processing && !_pending.IsEmpty)
			{
				((MonoBehaviour)this).StartCoroutine(ProcessQueue());
			}
		}

		public void Enqueue(CaptureRequest req)
		{
			_pending.Enqueue(req);
		}

		private IEnumerator ProcessQueue()
		{
			_processing = true;
			try
			{
				CaptureRequest result;
				while (_pending.TryDequeue(out result))
				{
					int counter = DebugLog.NextCaptureCounter();
					yield return CaptureRoutine(result, counter);
					yield return null;
				}
			}
			finally
			{
				_processing = false;
			}
		}

		private IEnumerator CaptureRoutine(CaptureRequest req, int counter)
		{
			yield return (object)new WaitForSecondsRealtime(0.6f);
			Camera cam = Camera.main;
			if ((Object)(object)cam == (Object)null)
			{
				yield break;
			}
			OrchestrationSession session = null;
			if (OrchestratedTriggers.Contains(req.Trigger) || req.AnchorOverride.HasValue)
			{
				session = OrchestrationSession.TryBegin(req);
				session?.LogStarted(req.Trigger, session.AnchorPos);
			}
			try
			{
				if (session != null)
				{
					yield return null;
				}
				session?.ApplyCameraPose();
				Vector3 pos = ((Component)cam).transform.position;
				Vector3 euler = ((Component)cam).transform.eulerAngles;
				float worldTime = -1f;
				try
				{
					if ((Object)(object)EnvMan.instance != (Object)null)
					{
						worldTime = EnvMan.instance.GetDayFraction();
					}
				}
				catch
				{
				}
				yield return (object)new WaitForEndOfFrame();
				WritePngAndSidecar(req, counter, pos, euler, worldTime);
			}
			finally
			{
				session?.Restore();
			}
		}

		private static void WritePngAndSidecar(CaptureRequest req, int counter, Vector3 pos, Vector3 euler, float worldTime)
		{
			string text;
			try
			{
				string path;
				try
				{
					path = Paths.ConfigPath;
				}
				catch
				{
					path = ".";
				}
				text = Path.Combine(path, "vv_dumps");
				if (!string.IsNullOrEmpty(req.OutputSubdir))
				{
					text = Path.Combine(text, req.OutputSubdir);
				}
				Directory.CreateDirectory(text);
			}
			catch
			{
				return;
			}
			string path2 = Path.Combine(text, req.OutputBaseName + ".png");
			string path3 = Path.Combine(text, req.OutputBaseName + ".json");
			Texture2D val = null;
			try
			{
				val = ScreenCapture.CaptureScreenshotAsTexture();
				byte[] bytes = ImageConversion.EncodeToPNG(val);
				File.WriteAllBytes(path2, bytes);
			}
			catch
			{
			}
			finally
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder(2048);
				stringBuilder.Append('{');
				stringBuilder.Append("\"counter\":").Append(counter.ToString(CultureInfo.InvariantCulture)).Append(',');
				stringBuilder.Append("\"trigger\":\"").Append(JsonEscape(req.Trigger)).Append("\",");
				stringBuilder.Append("\"timestampUtc\":\"").Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)).Append("\",");
				stringBuilder.Append("\"worldTimeOfDay\":").Append(worldTime.ToString("R", CultureInfo.InvariantCulture)).Append(',');
				stringBuilder.Append("\"cameraPos\":[").Append(pos.x.ToString("R", CultureInfo.InvariantCulture)).Append(',')
					.Append(pos.y.ToString("R", CultureInfo.InvariantCulture))
					.Append(',')
					.Append(pos.z.ToString("R", CultureInfo.InvariantCulture))
					.Append("],");
				stringBuilder.Append("\"cameraYaw\":").Append(euler.y.ToString("R", CultureInfo.InvariantCulture)).Append(',');
				stringBuilder.Append("\"cameraPitch\":").Append(euler.x.ToString("R", CultureInfo.InvariantCulture));
				if (req.IncludeDiagnostics)
				{
					AppendDiagnostics(stringBuilder);
				}
				stringBuilder.Append('}');
				File.WriteAllText(path3, stringBuilder.ToString());
			}
			catch
			{
			}
		}

		private static void AppendDiagnostics(StringBuilder sb)
		{
			sb.Append(",\"diagnostics\":{");
			sb.Append("\"lastRelaxation\":{\"available\":false},");
			sb.Append("\"villages\":[");
			bool flag = true;
			try
			{
				foreach (RegionGraph item in VillageRegistry.AllGraphs())
				{
					if (!flag)
					{
						sb.Append(',');
					}
					flag = false;
					sb.Append('{');
					sb.Append("\"key\":\"").Append(JsonEscape(item.RegisteredVillageKey ?? "")).Append("\",");
					sb.Append("\"regionCount\":").Append(item.RegionCount).Append(',');
					sb.Append("\"linkCount\":").Append(item.LinkCount).Append(',');
					sb.Append("\"bfsCells\":").Append(item.Diagnostics.LookupGridCellCount).Append(',');
					sb.Append("\"boundaryCells\":").Append(item.Diagnostics.BoundaryCellCount).Append(',');
					sb.Append("\"bfsBounds\":");
					AppendBounds(sb, item.Diagnostics.TryGetLookupGridBounds(out var minX, out var maxX, out var minZ, out var maxZ), minX, maxX, minZ, maxZ);
					sb.Append(",\"boundaryBounds\":");
					AppendBounds(sb, item.Diagnostics.TryGetBoundaryCellsBounds(out var minX2, out var maxX2, out var minZ2, out var maxZ2), minX2, maxX2, minZ2, maxZ2);
					sb.Append('}');
				}
			}
			catch
			{
			}
			sb.Append(']');
			sb.Append('}');
		}

		private static void AppendBounds(StringBuilder sb, bool ok, float minX, float maxX, float minZ, float maxZ)
		{
			if (!ok)
			{
				sb.Append("null");
			}
			else
			{
				sb.Append("{\"x\":[").Append(minX.ToString("R", CultureInfo.InvariantCulture)).Append(',')
					.Append(maxX.ToString("R", CultureInfo.InvariantCulture))
					.Append("],\"z\":[")
					.Append(minZ.ToString("R", CultureInfo.InvariantCulture))
					.Append(',')
					.Append(maxZ.ToString("R", CultureInfo.InvariantCulture))
					.Append("]}");
			}
		}

		private static string JsonFloat(float v)
		{
			if (float.IsNaN(v) || float.IsInfinity(v))
			{
				return "null";
			}
			return v.ToString("R", CultureInfo.InvariantCulture);
		}

		private static string JsonEscape(string s)
		{
			return (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n")
				.Replace("\r", "\\r")
				.Replace("\t", "\\t");
		}
	}
	internal class OrchestrationSession
	{
		private readonly Player m_player;

		private readonly Vector3 m_savedPos;

		private readonly Quaternion m_savedRot;

		private readonly Hud m_hud;

		private readonly bool m_savedHudVisible;

		private readonly Vector3 m_savedCamPos;

		private readonly Quaternion m_savedCamRot;

		private readonly GameCamera m_gameCamera;

		private readonly bool m_savedGameCameraEnabled;

		private readonly Vector3 m_anchorPos;

		private readonly Quaternion m_anchorRot;

		public Vector3 AnchorPos => m_anchorPos;

		private OrchestrationSession(Player player, Vector3 savedPos, Quaternion savedRot, Hud hud, bool savedHudVisible, Vector3 savedCamPos, Quaternion savedCamRot, GameCamera gameCamera, bool savedGameCameraEnabled, Vector3 anchorPos, Quaternion anchorRot)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: 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_0056: Unknown result type (might be due to invalid IL or missing references)
			m_player = player;
			m_savedPos = savedPos;
			m_savedRot = savedRot;
			m_hud = hud;
			m_savedHudVisible = savedHudVisible;
			m_savedCamPos = savedCamPos;
			m_savedCamRot = savedCamRot;
			m_gameCamera = gameCamera;
			m_savedGameCameraEnabled = savedGameCameraEnabled;
			m_anchorPos = anchorPos;
			m_anchorRot = anchorRot;
		}

		public void ApplyCameraPose()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				return;
			}
			try
			{
				((Component)main).transform.position = m_anchorPos;
				((Component)main).transform.rotation = m_anchorRot;
			}
			catch
			{
			}
		}

		public static OrchestrationSession TryBegin(CaptureRequest req)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0271: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: 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)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"[Capture] Orchestration skipped: no local player. Falling back to passive capture.");
				}
				return null;
			}
			CaptureAnchor.Result result = ((!req.AnchorOverride.HasValue) ? CaptureAnchor.Resolve(((Component)localPlayer).transform.position) : CaptureAnchor.ResolveAt(req.AnchorOverride.Value, req.AnchorClearance));
			if (!result.HasAnchor)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Capture] Orchestration skipped: " + result.Reason + ". Falling back to passive capture (no silent coordinate fabrication)."));
				}
				return null;
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			Quaternion rotation = ((Component)localPlayer).transform.rotation;
			Hud instance = Hud.instance;
			bool flag = true;
			try
			{
				if ((Object)(object)instance != (Object)null)
				{
					flag = ((Component)instance).gameObject.activeSelf;
				}
			}
			catch
			{
			}
			Camera main = Camera.main;
			Vector3 savedCamPos = (((Object)(object)main != (Object)null) ? ((Component)main).transform.position : Vector3.zero);
			Quaternion savedCamRot = (((Object)(object)main != (Object)null) ? ((Component)main).transform.rotation : Quaternion.identity);
			GameCamera instance2 = GameCamera.instance;
			bool flag2 = (Object)(object)instance2 != (Object)null && ((Behaviour)instance2).enabled;
			Quaternion val = Quaternion.Euler(result.Pitch, result.Yaw, 0f);
			try
			{
				((Component)localPlayer).transform.position = result.Pos;
				((Component)localPlayer).transform.rotation = val;
				if ((Object)(object)main != (Object)null)
				{
					((Component)main).transform.position = result.Pos;
					((Component)main).transform.rotation = val;
				}
				if ((Object)(object)instance2 != (Object)null)
				{
					((Behaviour)instance2).enabled = false;
				}
				if ((Object)(object)instance != (Object)null)
				{
					((Component)instance).gameObject.SetActive(false);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[Capture] Orchestration apply failed: " + ex.GetType().Name + ": " + ex.Message + ". Restoring state and falling back to passive capture."));
				}
				try
				{
					((Component)localPlayer).transform.position = position;
					((Component)localPlayer).transform.rotation = rotation;
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)instance2 != (Object)null)
					{
						((Behaviour)instance2).enabled = flag2;
					}
				}
				catch
				{
				}
				try
				{
					if ((Object)(object)instance != (Object)null)
					{
						((Component)instance).gameObject.SetActive(flag);
					}
				}
				catch
				{
				}
				return null;
			}
			return new OrchestrationSession(localPlayer, position, rotation, instance, flag, savedCamPos, savedCamRot, instance2, flag2, result.Pos, val);
		}

		public void LogStarted(string trigger, Vector3 anchorPos)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[Capture] Orchestrated trigger='{trigger}' anchor=({anchorPos.x:F1},{anchorPos.y:F1},{anchorPos.z:F1}) yaw=0 pitch=90");
			}
		}

		public void Restore()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)m_player != (Object)null)
				{
					((Component)m_player).transform.position = m_savedPos;
					((Component)m_player).transform.rotation = m_savedRot;
				}
			}
			catch
			{
			}
			try
			{
				if ((Object)(object)m_gameCamera != (Object)null)
				{
					((Behaviour)m_gameCamera).enabled = m_savedGameCameraEnabled;
				}
			}
			catch
			{
			}
			try
			{
				if ((Object)(object)m_hud != (Object)null)
				{
					((Component)m_hud).gameObject.SetActive(m_savedHudVisible);
				}
			}
			catch
			{
			}
		}
	}
	public static class HotReloadHelper
	{
		private const string ModPrefix = "VV_";

		private const string ModPrefabPrefix = "vv_";

		private const string VillagerStationPrefix = "$vv_";

		private static readonly Assembly CurrentAssembly = typeof(HotReloadHelper).Assembly;

		public static void FullCleanup()
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)"[HotReload] Starting full cleanup...");
			}
			ResetAllStaticState();
			int num = DestroyStaleComponents();
			int num2 = DestroyOrphanedModObjects();
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)("[HotReload] Cleanup complete: " + $"{num} stale component(s), " + $"{num2} orphaned GameObject(s) destroyed."));
			}
		}

		public static string PurgeStaleObjects()
		{
			string arg = ReportModInstances();
			int num = DestroyStaleComponents();
			return $"{arg}\n[HotReload] Purged {num} stale component(s).";
		}

		public static string ReportModInstances()
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			Dictionary<string, int> dictionary2 = new Dictionary<string, int>();
			MonoBehaviour[] array = Object.FindObjectsByType<MonoBehaviour>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			foreach (MonoBehaviour val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Type type = ((object)val).GetType();
					if (type.FullName != null && type.FullName.StartsWith("ValheimVillages."))
					{
						Dictionary<string, int> obj = ((type.Assembly == CurrentAssembly) ? dictionary : dictionary2);
						obj.TryGetValue(type.Name, out var value);
						obj[type.Name] = value + 1;
					}
				}
			}
			int num = 0;
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, int> item in dictionary2)
			{
				num += item.Value;
				list.Add($"  STALE {item.Key} x{item.Value}");
			}
			string text = $"[HotReload] Mod MonoBehaviours: {dictionary.Count} current type(s), " + $"{num} stale instance(s)";
			if (list.Count <= 0)
			{
				return text;
			}
			return text + "\n" + string.Join("\n", list);
		}

		private static void ResetAllStaticState()
		{
			AttributeScanner.InvokeAllCleanup(CurrentAssembly);
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogDebug((object)"[HotReload] All static registries cleared.");
			}
		}

		private static int DestroyStaleComponents()
		{
			int num = 0;
			MonoBehaviour[] array = Object.FindObjectsByType<MonoBehaviour>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			foreach (MonoBehaviour val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Type type = ((object)val).GetType();
				string fullName = type.FullName;
				if (fullName != null && fullName.StartsWith("ValheimVillages.") && !(type.Assembly == CurrentAssembly))
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogDebug((object)("[HotReload] Destroying stale component: " + fullName + " on \"" + ((Object)((Component)val).gameObject).name + "\""));
					}
					((Behaviour)val).enabled = false;
					Object.Destroy((Object)(object)val);
					num++;
				}
			}
			return num;
		}

		private static int DestroyOrphanedModObjects()
		{
			int num = 0;
			Transform[] array = Object.FindObjectsByType<Transform>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			List<GameObject> list = new List<GameObject>();
			Transform[] array2 = array;
			foreach (Transform val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					GameObject gameObject = ((Component)val).gameObject;
					string name = ((Object)gameObject).name;
					if ((AttributeScanner.GetModObjectNames(CurrentAssembly).Contains(name) || name.StartsWith("VV_") || name.StartsWith("vv_")) && !((Object)(object)gameObject.GetComponentInParent<ZNetView>(true) != (Object)null) && !HasCurrentAssemblyComponent(gameObject))
					{
						list.Add(gameObject);
					}
				}
			}
			foreach (GameObject item in list)
			{
				if (!((Object)(object)item == (Object)null))
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogDebug((object)("[HotReload] Destroying orphaned mod object: \"" + ((Object)item).name + "\""));
					}
					item.SetActive(false);
					Object.Destroy((Object)(object)item);
					num++;
				}
			}
			return num;
		}

		private static bool HasCurrentAssemblyComponent(GameObject go)
		{
			MonoBehaviour[] components = go.GetComponents<MonoBehaviour>();
			foreach (MonoBehaviour val in components)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Type type = ((object)val).GetType();
					if (type.FullName != null && type.FullName.StartsWith("ValheimVillages.") && type.Assembly == CurrentAssembly)
					{
						return true;
					}
				}
			}
			return false;
		}

		public static void FixupExistingNPCs()
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			ZNetView[] array = Object.FindObjectsOfType<ZNetView>();
			foreach (ZNetView val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ZDO zDO = val.GetZDO();
				if (zDO == null || zDO.GetPrefab() == RecordPrefabFactory.RecordPrefabHash || NativeNpcStripper.IsPlayerOwned(((Component)val).gameObject) || (string.IsNullOrEmpty(zDO.GetString("vv_record_id", "")) && string.IsNullOrEmpty(zDO.GetString("vv_villager_type", ""))))
				{
					continue;
				}
				if (zDO.GetVec3("vv_home_position", Vector3.zero) == Vector3.zero)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)($"[HotReload] NPC at {((Component)val).transform.position} " + "has no anchor position stored, skipping"));
					}
					continue;
				}
				RemoveOrphanedCraftingStations(((Component)val).gameObject);
				VillagerRestoration.Restore(((Component)val).gameObject, zDO);
				num++;
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogDebug((object)$"[HotReload] Fixed up NPC at {((Component)val).transform.position}");
				}
			}
			if (num > 0)
			{
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogInfo((object)$"[HotReload] Fixed up {num} existing NPC(s)");
				}
			}
		}

		public static void FixupExistingRegistries()
		{
			int stableHashCode = StringExtensionMethods.GetStableHashCode("vv_village_registry");
			int num = 0;
			ZNetView[] array = Object.FindObjectsOfType<ZNetView>();
			foreach (ZNetView val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					ZDO zDO = val.GetZDO();
					if (zDO != null && zDO.GetPrefab() == stableHashCode)
					{
						PieceFactory.ReapplyInteractionToInstance(((Component)val).gameObject);
						num++;
					}
				}
			}
			if (num > 0)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[HotReload] Re-applied interaction to {num} placed registry station(s)");
				}
			}
		}

		private static void RemoveOrphanedCraftingStations(GameObject go)
		{
			CraftingStation[] components = go.GetComponents<CraftingStation>();
			foreach (CraftingStation val in components)
			{
				if (!((Object)(object)val == (Object)null) && val.m_name != null && val.m_name.StartsWith("$vv_"))
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogDebug((object)("[HotReload] Removing orphaned CraftingStation '" + val.m_name + "' on \"" + ((Object)go).name + "\""));
					}
					((Behaviour)val).enabled = false;
					Object.Destroy((Object)(object)val);
				}
			}
		}
	}
	public static class PathTelemetry
	{
		[Serializable]
		private class HnaGraphData
		{
			public int regionCount;

			public int linkCount;

			public float minX;

			public float minZ;

			public float maxX;

			public float maxZ;

			public string regionCenters;

			public string linksSummary;
		}

		[Serializable]
		private class HnaPlayerDebugData
		{
			public float px;

			public float py;

			public float pz;

			public string regionId;

			public bool graphAvailable;

			public bool regionValid;

			public float solidHeightAtPosition;

			public float cellMinX;

			public float cellMaxX;

			public float cellMinZ;

			public float cellMaxZ;

			public float centerY;

			public float minY;

			public float maxY;

			public float verticalSpread;
		}

		private static readonly string LogPath = Path.Combine(Paths.ConfigPath, "vv_dumps", "path_telemetry.ndjson");

		private static readonly object Lock = new object();

		public static void LogRegionGraph(int regionCount, int linkCount, float minX, float minZ, float maxX, float maxZ, string regionCenters, string linksSummary)
		{
			HnaGraphData hnaGraphData = new HnaGraphData
			{
				regionCount = regionCount,
				linkCount = linkCount,
				minX = (float)Math.Round(minX, 2),
				minZ = (float)Math.Round(minZ, 2),
				maxX = (float)Math.Round(maxX, 2),
				maxZ = (float)Math.Round(maxZ, 2),
				regionCenters = (regionCenters ?? ""),
				linksSummary = (linksSummary ?? "")
			};
			Write("hna_graph", JsonUtility.ToJson((object)hnaGraphData), "hna");
		}

		public static void LogHnaPlayerDebug(Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			float px = (float)Math.Round(position.x, 2);
			float py = (float)Math.Round(position.y, 2);
			float pz = (float)Math.Round(position.z, 2);
			RegionGraph regionGraph = VillageRegistry.GraphAt(position);
			string text = regionGraph?.PointToRegionId(position);
			float originX;
			float originZ;
			bool graphAvailable = regionGraph?.GetOrigin(out originX, out originZ) ?? false;
			bool flag = regionGraph != null && !string.IsNullOrEmpty(text) && regionGraph.IsValidRegion(text);
			float num = 0f;
			if ((Object)(object)ZoneSystem.instance != (Object)null)
			{
				ZoneSystem.instance.GetSolidHeight(new Vector3(position.x, 0f, position.z), ref num, 500);
			}
			float cellMinX = 0f;
			float cellMaxX = 0f;
			float cellMinZ = 0f;
			float cellMaxZ = 0f;
			float centerY = 0f;
			float num2 = 0f;
			float num3 = 0f;
			float minX = 0f;
			float maxX = 0f;
			float minZ = 0f;
			float maxZ = 0f;
			if (flag && regionGraph.GetRegionBounds(text, out minX, out maxX, out minZ, out maxZ))
			{
				cellMinX = (float)Math.Round(minX, 2);
				cellMaxX = (float)Math.Round(maxX, 2);
				cellMinZ = (float)Math.Round(minZ, 2);
				cellMaxZ = (float)Math.Round(maxZ, 2);
			}
			float centerY2 = 0f;
			float minY = 0f;
			float maxY = 0f;
			if (flag && regionGraph.GetRegionSampleHeights(text, out centerY2, out minY, out maxY))
			{
				centerY = (float)Math.Round(centerY2, 2);
				num2 = (float)Math.Round(minY, 2);
				num3 = (float)Math.Round(maxY, 2);
			}
			HnaPlayerDebugData hnaPlayerDebugData = new HnaPlayerDebugData
			{
				px = px,
				py = py,
				pz = pz,
				regionId = (text ?? ""),
				graphAvailable = graphAvailable,
				regionValid = flag,
				solidHeightAtPosition = (float)Math.Round(num, 2),
				cellMinX = cellMinX,
				cellMaxX = cellMaxX,
				cellMinZ = cellMinZ,
				cellMaxZ = cellMaxZ,
				centerY = centerY,
				minY = num2,
				maxY = num3,
				verticalSpread = (float)Math.Round(num3 - num2, 2)
			};
			Write("hna_player_debug", JsonUtility.ToJson((object)hnaPlayerDebugData), "hna_debug");
		}

		private static void Write(string message, string dataJson, string runId)
		{
			try
			{
				long num = (long)(Time.time * 1000f);
				string text = "pt_" + num;
				string contents = "{\"id\":\"" + text + "\",\"timestamp\":" + num + ",\"location\":\"PathTelemetry\",\"message\":\"" + Escape(message) + "\",\"data\":" + dataJson + ",\"runId\":\"" + Escape(runId) + "\",\"hypothesisId\":\"path_compare\"}\n";
				lock (Lock)
				{
					File.AppendAllText(LogPath, contents);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[PathTelemetry] Write failed: " + ex.Message));
				}
			}
		}

		private static string Escape(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
		}
	}
	public static class PhysicsHelper
	{
		public static T GetFirstInRadius<T>(Vector3 center, float radius) where T : Component
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			Collider[] array = Physics.OverlapSphere(center, radius);
			foreach (Collider val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null))
				{
					T componentInParent = ((Component)val).gameObject.GetComponentInParent<T>();
					if ((Object)(object)componentInParent != (Object)null)
					{
						return componentInParent;
					}
				}
			}
			return default(T);
		}

		public static List<T> GetAllInRadius<T>(Vector3 center, float radius) where T : Component
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			List<T> list = new List<T>();
			Collider[] array = Physics.OverlapSphere(center, radius);
			foreach (Collider val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null))
				{
					T componentInParent = ((Component)val).gameObject.GetComponentInParent<T>();
					if ((Object)(object)componentInParent != (Object)null)
					{
						list.Add(componentInParent);
					}
				}
			}
			return list;
		}
	}
	[BepInPlugin("com.valheimvillages.mod", "Valheim Villages", "0.1.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGUID = "com.valheimvillages.mod";

		public const string PluginName = "Valheim Villages";

		public const string PluginVersion = "0.1.1";

		private static bool _recipeRefreshEnqueued;

		private static bool _regionPartitionEnqueued;

		private static bool _recordIndexEnqueued;

		private static bool _villageIndexEnqueued;

		private static float _hotReloadAt;

		private Harmony _harmony;

		public static readonly DateTime AssemblyLoadedAt = DateTime.UtcNow;

		public static ManualLogSource Log { get; private set; }

		public static Plugin Instance { get; private set; }

		public static bool LastLoadWasHotReload { get; private set; }

		private void Awake()
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			bool num = (LastLoadWasHotReload = (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items.Count > 0);
			DebugLog.BeginCycle(num);
			RegionGraphPersistence.LogAction = delegate(string msg)
			{
				Log.LogInfo((object)msg);
			};
			Log.LogInfo((object)"Valheim Villages v0.1.1 loading...");
			Harmony.UnpatchID("com.valheimvillages.mod");
			_harmony = new Harmony("com.valheimvillages.mod");
			_harmony.PatchAll();
			LocalizationPatch.RegisterTokens();
			if (num)
			{
				Log.LogInfo((object)"Hot reload detected — running full cleanup");
				HotReloadHelper.FullCleanup();
			}
			TaskHandlerRegistry.Clear();
			AttributeScanner.ScanAndRegister(typeof(Plugin).Assembly);
			if (num)
			{
				Log.LogInfo((object)"Hot reload — re-registering items in ObjectDB");
				ItemFactory.RegisterAll(ObjectDB.instance);
				VirtualRecipeLoader.RegisterAll(ObjectDB.instance);
				AttributeScanner.InvokeObjectDBRegistrations(typeof(Plugin).Assembly, ObjectDB.instance);
			}
			if (num && (Object)(object)ZNetScene.instance != (Object)null)
			{
				Log.LogInfo((object)"Hot reload — re-registering prefabs in ZNetScene");
				ItemFactory.RegisterAllInZNetScene(ZNetScene.instance);
				PieceFactory.RegisterAllInZNetScene(ZNetScene.instance);
				RecordPrefabFactory.RegisterInZNetScene(ZNetScene.instance);
				VillagePrefabFactory.RegisterInZNetScene(ZNetScene.instance);
				Log.LogInfo((object)"Hot reload — fixing up existing NPC components");
				HotReloadHelper.FixupExistingNPCs();
				Log.LogInfo((object)"Hot reload — fixing up placed registry stations");
				HotReloadHelper.FixupExistingRegistries();
				_hotReloadAt = Time.realtimeSinceStartup;
				_regionPartitionEnqueued = false;
				_recordIndexEnqueued = false;
				_villageIndexEnqueued = false;
				Log.LogInfo((object)"Hot reload — armed hna_partition (will fire 5s after settle)");
			}
			Log.LogInfo((object)"Valheim Villages loaded successfully!");
			IncidentRecorder.ClearOnLoad();
			FreezeTime.ApplyAutoFreeze();
			if (num && ModTestRunner.AutoRunEnabled)
			{
				Log.LogInfo((object)"Scheduling integration tests (will defer until fixtures are ready)...");
				GlobalTaskQueue.Enqueue(new VillagerTask
				{
					Name = "integration_tests",
					SourceId = "system",
					Priority = TaskPriority.Low,
					TimeoutSeconds = 120f,
					Attributes = new Dictionary<string, string>()
				});
			}
		}

		private void Update()
		{
			if (!_recipeRefreshEnqueued && (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null && Time.realtimeSinceStartup > 3f)
			{
				_recipeRefreshEnqueued = true;
				GlobalTaskQueue.Enqueue(new VillagerTask
				{
					Name = "recipe_discovery_refresh",
					SourceId = "system",
					Priority = TaskPriority.Low,
					TimeoutSeconds = 30f,
					Attributes = new Dictionary<string, string>()
				});
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogDebug((object)"[Valheim Villages] Enqueued recipe_discovery_refresh (post–world load)");
				}
			}
			if (!_recordIndexEnqueued && (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null && Time.realtimeSinceStartup > 3f)
			{
				_recordIndexEnqueued = true;
				GlobalTaskQueue.Enqueue(new VillagerTask
				{
					Name = "villager_record_index",
					SourceId = "system",
					Priority = TaskPriority.High,
					TimeoutSeconds = 30f,
					Attributes = new Dictionary<string, string>()
				});
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogDebug((object)"[Valheim Villages] Enqueued villager_record_index (post–world load)");
				}
			}
			if (!_villageIndexEnqueued && (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null && Time.realtimeSinceStartup > 3f)
			{
				_villageIndexEnqueued = true;
				GlobalTaskQueue.Enqueue(new VillagerTask
				{
					Name = "village_index",
					SourceId = "system",
					Priority = TaskPriority.High,
					TimeoutSeconds = 30f,
					Attributes = new Dictionary<string, string>()
				});
				ManualLogSource log3 = Log;
				if (log3 != null)
				{
					log3.LogDebug((object)"[Valheim Villages] Enqueued village_index (post–world load)");
				}
			}
			if (!_regionPartitionEnqueued && (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null && Time.realtimeSinceStartup > _hotReloadAt + 5f)
			{
				_regionPartitionEnqueued = true;
				GlobalTaskQueue.Enqueue(new VillagerTask
				{
					Name = "hna_partition",
					SourceId = "system",
					Priority = TaskPriority.Low,
					TimeoutSeconds = 30f,
					Attributes = new Dictionary<string, string>()
				});
				ManualLogSource log4 = Log;
				if (log4 != null)
				{
					log4.LogInfo((object)"[Valheim Villages] Enqueued hna_partition (low priority)");
				}
			}
			if (PieceChangePatch.IsDirty && Time.realtimeSinceStartup - PieceChangePatch.LastStructureChangeTime > 10f)
			{
				PieceChangePatch.IsDirty = false;
				_regionPartitionEnqueued = false;
				ManualLogSource log5 = Log;
				if (log5 != null)
				{
					log5.LogInfo((object)"[Valheim Villages] Structure change detected, will re-enqueue hna_partition");
				}
			}
			GlobalTaskQueue.ProcessBatch();
			PathDebugRenderer.AutoEnable();
			PlayerAvoidanceObstacle.Tick();
			foreach (VillagerAI value in VillagerAIManager.ActiveVillagers.Values)
			{
				if ((Object)(object)value != (Object)null)
				{
					((BaseAI)value).UpdateAI(Time.deltaTime);
				}
			}
		}
	}
	public static class VectorExtensions
	{
		public static float DistXZ(Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			float num = a.x - b.x;
			float num2 = a.z - b.z;
			return (float)Math.Sqrt(num * num + num2 * num2);
		}
	}
}
namespace ValheimVillages.Villages
{
	[HarmonyPatch(typeof(BaseAI), "RandomMovement")]
	public static class EnemyAvoidancePatch
	{
		private const float AvoidanceRadius = 20f;

		private const int MaxRerollAttempts = 3;

		private static void Prefix(BaseAI __instance)
		{
			//IL_001a: 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)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			if (VillageAreaManager.AreaCount == 0)
			{
				return;
			}
			Character component = ((Component)__instance).GetComponent<Character>();
			if ((Object)(object)component == (Object)null || (int)component.m_faction == 0 || component.IsTamed())
			{
				return;
			}
			FieldInfo fieldInfo = AccessTools.Field(typeof(BaseAI), "m_randomMoveTarget");
			if (fieldInfo == null)
			{
				return;
			}
			Vector3 val = (Vector3)fieldInfo.GetValue(__instance);
			if (!VillageAreaManager.IsNearAnyVillage(val, 20f))
			{
				return;
			}
			Vector3 position = ((Component)__instance).transform.position;
			float randomMoveRange = __instance.m_randomMoveRange;
			for (int i = 0; i < 3; i++)
			{
				float num = Random.Range(0f, 360f) * ((float)Math.PI / 180f);
				float num2 = Random.Range(randomMoveRange * 0.3f, randomMoveRange);
				Vector3 val2 = position + new Vector3(Mathf.Cos(num) * num2, 0f, Mathf.Sin(num) * num2);
				if (!VillageAreaManager.IsNearAnyVillage(val2, 20f))
				{
					fieldInfo.SetValue(__instance, val2);
					return;
				}
			}
			Vector3 val3 = position - val;
			Vector3 normalized = ((Vector3)(ref val3)).normalized;
			Vector3 val4 = position + normalized * randomMoveRange;
			fieldInfo.SetValue(__instance, val4);
		}
	}
	[HarmonyPatch(typeof(Piece), "SetCreator")]
	public static class StationBuildPatch
	{
		[HarmonyPostfix]
		public static void Postfix(Piece __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				VillageStationRegistry.RegisterStation(((Component)__instance).gameObject);
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				ZDO val = ((component != null) ? component.GetZDO() : null);
				if (val != null && val.GetPrefab() == StringExtensionMethods.GetStableHashCode("vv_village_registry"))
				{
					LinkRegistryToVillage(val);
				}
			}
		}

		private static void LinkRegistryToVillage(ZDO registryZdo)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			if (!string.IsNullOrEmpty(registryZdo.GetString("vv_village_id", "")))
			{
				return;
			}
			Vector3 position = registryZdo.GetPosition();
			Village village = VillageRegistry.GetVillageCovering(position) ?? VillageRegistry.GetOrCreateAt(position);
			if (village != null)
			{
				registryZdo.Set("vv_village_id", village.VillageId);
				registryZdo.Persistent = true;
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[StationBuildPatch] Registry at ({position.x:F1},{position.y:F1},{position.z:F1}) -> village {village.VillageId}");
				}
			}
		}
	}
	public class VillageArea
	{
		private readonly List<Vector2> m_polygon2D;

		private readonly List<Vector3> m_waypoints;

		public string VillageId { get; }

		public IReadOnlyList<Vector3> Waypoints => m_waypoints;

		public VillageArea(string villageId, List<Vector3> waypoints)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			VillageId = villageId;
			m_waypoints = new List<Vector3>(waypoints);
			m_polygon2D = new List<Vector2>(waypoints.Count);
			foreach (Vector3 waypoint in waypoints)
			{
				m_polygon2D.Add(new Vector2(waypoint.x, waypoint.z));
			}
		}

		public bool IsInsideArea(Vector3 position)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if (m_polygon2D.Count < 3)
			{
				return false;
			}
			return IsPointInPolygon(new Vector2(position.x, position.z));
		}

		public bool IsNearBoundary(Vector3 position, float radius)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if (m_polygon2D.Count < 3)
			{
				return false;
			}
			Vector2 point = default(Vector2);
			((Vector2)(ref point))..ctor(position.x, position.z);
			float num = radius * radius;
			for (int i = 0; i < m_polygon2D.Count; i++)
			{
				int index = (i + 1) % m_polygon2D.Count;
				if (PointToSegmentDistanceSq(point, m_polygon2D[i], m_polygon2D[index]) <= num)
				{
					return true;
				}
			}
			return false;
		}

		private bool IsPointInPolygon(Vector2 point)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: 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)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			int count = m_polygon2D.Count;
			int num = 0;
			int index = count - 1;
			while (num < count)
			{
				Vector2 val = m_polygon2D[num];
				Vector2 val2 = m_polygon2D[index];
				if (val.y > point.y != val2.y > point.y && point.x < (val2.x - val.x) * (point.y - val.y) / (val2.y - val.y) + val.x)
				{
					flag = !flag;
				}
				index = num++;
			}
			return flag;
		}

		private static float PointToSegmentDistanceSq(Vector2 point, Vector2 segA, Vector2 segB)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = segB - segA;
			Vector2 val2 = point - segA;
			float sqrMagnitude = ((Vector2)(ref val)).sqrMagnitude;
			if (sqrMagnitude < 0.0001f)
			{
				return ((Vector2)(ref val2)).sqrMagnitude;
			}
			float num = Mathf.Clamp01(Vector2.Dot(val2, val) / sqrMagnitude);
			Vector2 val3 = segA + val * num;
			Vector2 val4 = point - val3;
			return ((Vector2)(ref val4)).sqrMagnitude;
		}
	}
	public static class VillageAreaManager
	{
		private static readonly Dictionary<string, VillageArea> s_areas = new Dictionary<string, VillageArea>();

		public static int AreaCount => s_areas.Count;

		public static IEnumerable<VillageArea> AllAreas => s_areas.Values;

		public static void RegisterArea(VillageArea area)
		{
			if (area != null)
			{
				s_areas[area.VillageId] = area;
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[VillageArea] Registered area for {area.VillageId} with {area.Waypoints.Count} waypoints");
				}
				VillageStationRegistry.RefreshFor(area);
				VillagePoiRegistry.RefreshFor(area);
				VillageRoomCatalog.RefreshFor(area);
			}
		}

		public static void UnregisterArea(string villageId)
		{
			if (s_areas.Remove(villageId))
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("[VillageArea] Unregistered area for " + villageId));
				}
				VillageStationRegistry.RemoveFor(villageId);
				VillagePoiRegistry.RemoveFor(villageId);
				VillageRoomCatalog.RemoveFor(villageId);
			}
		}

		public static void RefreshFromVillage(Village village)
		{
			if (village == null || !village.HasGraph)
			{
				return;
			}
			string villageId = village.VillageId;
			List<Vector3> list = PatrolRouteBuilder.Build(village.Graph.GetBoundaryCells());
			if (list == null || list.Count < 3)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[VillageArea] Skipped registration for village={villageId}: insufficient boundary waypoints ({list?.Count ?? 0})");
				}
			}
			else
			{
				RegisterArea(new VillageArea(villageId, list));
			}
		}

		public static bool IsInsideAnyVillage(Vector3 position)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			foreach (VillageArea value in s_areas.Values)
			{
				if (value.IsInsideArea(position))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsNearAnyVillage(Vector3 position, float radius)
		{
			//IL_001b: 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)
			foreach (VillageArea value in s_areas.Values)
			{
				if (value.IsInsideArea(position) || value.IsNearBoundary(position, radius))
				{
					return true;
				}
			}
			return false;
		}

		public static bool TryGetCombinedBounds(out float minX, out float minZ, out float maxX, out float maxZ)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			minX = (minZ = float.MaxValue);
			maxX = (maxZ = float.MinValue);
			if (s_areas.Count == 0)
			{
				return false;
			}
			foreach (VillageArea value in s_areas.Values)
			{
				foreach (Vector3 waypoint in value.Waypoints)
				{
					if (waypoint.x < minX)
					{
						minX = waypoint.x;
					}
					if (waypoint.x > maxX)
					{
						maxX = waypoint.x;
					}
					if (waypoint.z < minZ)
					{
						minZ = waypoint.z;
					}
					if (waypoint.z > maxZ)
					{
						maxZ = waypoint.z;
					}
				}
			}
			if (minX <= maxX)
			{
				return minZ <= maxZ;
			}
			return false;
		}

		[RegisterCleanup]
		public static void Clear()
		{
			s_areas.Clear();
			VillageStationRegistry.Clear();
			VillagePoiRegistry.Clear();
			VillageRoomCatalog.Clear();
		}
	}
	public static class VillagePoiRegistry
	{
		private static readonly Dictionary<string, List<KnownLocation>> s_poisByVillage = new Dictionary<string, List<KnownLocation>>();

		public static void RefreshFor(VillageArea area)
		{
			//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)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: 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_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			if (area == null || area.Waypoints == null || area.Waypoints.Count < 3)
			{
				return;
			}
			float num = float.MaxValue;
			float num2 = float.MaxValue;
			float num3 = float.MinValue;
			float num4 = float.MinValue;
			float num5 = float.MaxValue;
			float num6 = float.MinValue;
			foreach (Vector3 waypoint in area.Waypoints)
			{
				if (waypoint.x < num)
				{
					num = waypoint.x;
				}
				if (waypoint.x > num3)
				{
					num3 = waypoint.x;
				}
				if (waypoint.z < num2)
				{
					num2 = waypoint.z;
				}
				if (waypoint.z > num4)
				{
					num4 = waypoint.z;
				}
				if (waypoint.y < num5)
				{
					num5 = waypoint.y;
				}
				if (waypoint.y > num6)
				{
					num6 = waypoint.y;
				}
			}
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor((num + num3) * 0.5f, (num5 + num6) * 0.5f, (num2 + num4) * 0.5f);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor((num3 - num) * 0.5f + 1f, (num6 - num5) * 0.5f + 20f, (num4 - num2) * 0.5f + 1f);
			HashSet<long> outsideCells = RubberBandPrune.ComputeOutsideCellsForBake(new Bounds(val, new Vector3(val2.x * 2f, val2.y * 2f, val2.z * 2f)));
			List<KnownLocation> list = new List<KnownLocation>();
			Collider[] array = Physics.OverlapBox(val, val2, Quaternion.identity);
			foreach (Collider val3 in array)
			{
				if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)null)
				{
					continue;
				}
				LocationType? locationType = ClassifyPoi(((Component)val3).gameObject);
				if (locationType.HasValue)
				{
					Vector3 position = ((Component)val3).gameObject.transform.position;
					if (!RubberBandPrune.IsOutsideCell(position, outsideCells))
					{
						bool hasShelter = VillagerBehaviorLogic.CheckShelter(position);
						float comfort = ComfortFor(locationType.Value, hasShelter);
						TryAddDeduped(list, position, locationType.Value, comfort, hasShelter);
					}
				}
			}
			s_poisByVillage[area.VillageId] = list;
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[VillagePoiRegistry] {area.VillageId}: cached {list.Count} PoI(s) inside hull");
			}
		}

		public static void RemoveFor(string villageKey)
		{
			s_poisByVillage.Remove(villageKey);
		}

		public static IReadOnlyList<KnownLocation> GetPois(Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			Village villageAt = VillageRegistry.GetVillageAt(position);
			if (villageAt == null)
			{
				return Array.Empty<KnownLocation>();
			}
			if (!s_poisByVillage.TryGetValue(villageAt.VillageId, out var value))
			{
				return Array.Empty<KnownLocation>();
			}
			return value;
		}

		public static IEnumerable<KnownLocation> GetPois(Vector3 position, LocationType type)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			foreach (KnownLocation poi in GetPois(position))
			{
				if (poi.Type == type)
				{
					yield return poi;
				}
			}
		}

		private static LocationType? ClassifyPoi(GameObject obj)
		{
			if ((Object)(object)obj == (Object)null)
			{
				return null;
			}
			if ((Object)(object)obj.GetComponent<Chair>() != (Object)null)
			{
				return LocationType.Chair;
			}
			if ((Object)(object)obj.GetComponent<Fireplace>() != (Object)null)
			{
				return LocationType.Fire;
			}
			string text = ((Object)obj).name.ToLowerInvariant();
			if (text.Contains("table") || text.Contains("bench"))
			{
				return LocationType.Table;
			}
			if (text.Contains("cultivat") || text.Contains("sapling") || text.Contains("plant_"))
			{
				return LocationType.Farm;
			}
			return null;
		}

		private static float ComfortFor(LocationType type, bool hasShelter)
		{
			if (type == LocationType.Fire)
			{
				return hasShelter ? 2f : 0.5f;
			}
			return 1f;
		}

		private static void TryAddDeduped(List<KnownLocation> list, Vector3 pos, LocationType type, float comfort, bool hasShelter)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			float minDistanceForType = KnownLocation.GetMinDistanceForType(type);
			int num = 0;
			foreach (KnownLocation item in list)
			{
				if (item.Type == type)
				{
					num++;
					if (Vector3.Distance(item.Position, pos) < minDistanceForType)
					{
						return;
					}
				}
			}
			if (num < KnownLocation.GetMaxLocationsForType(type))
			{
				list.Add(new KnownLocation
				{
					Position = pos,
					Type = type,
					ComfortValue = comfort,
					HasShelter = hasShelter
				});
			}
		}

		[DevCommand("List cached village PoIs (fire/table/chair/farm) for the village containing the player", Name = "vv_pois")]
		public static void DumpPois()
		{
			//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_0032: 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)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			StringBuilder stringBuilder = new StringBuilder();
			if ((Object)(object)localPlayer == (Object)null)
			{
				stringBuilder.AppendLine("[vv_pois] No local player.");
			}
			else
			{
				Vector3 position = ((Component)localPlayer).transform.position;
				Village villageAt = VillageRegistry.GetVillageAt(position);
				if (villageAt == null)
				{
					stringBuilder.AppendLine($"[vv_pois] player at ({position.x:F1},{position.z:F1}) is not inside any registered village.");
				}
				else
				{
					string villageId = villageAt.VillageId;
					List<KnownLocation> value;
					List<KnownLocation> list = (s_poisByVillage.TryGetValue(villageId, out value) ? value : null);
					stringBuilder.AppendLine($"[vv_pois] village {villageId}: {list?.Count ?? 0} PoI(s)");
					if (list != null)
					{
						foreach (KnownLocation item in list)
						{
							stringBuilder.AppendLine($"  [{item.Type}] @ ({item.Position.x:F1},{item.Position.y:F1},{item.Position.z:F1}) " + $"shelter={item.HasShelter} comfort={item.ComfortValue:F1} " + $"dist={Vector3.Distance(position, item.Position):F1}m");
						}
					}
				}
			}
			Console instance = Console.instance;
			if (instance != null)
			{
				instance.Print(stringBuilder.ToString());
			}
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)stringBuilder.ToString());
			}
		}

		[RegisterCleanup]
		public static void Clear()
		{
			s_poisByVillage.Clear();
		}
	}
	public static class VillageRoomCatalog
	{
		public enum Feature
		{
			Fire,
			Seat,
			Table,
			Bed,
			WorkStation,
			Storage,
			Farm
		}

		public sealed class RegionRoom
		{
			public string RegionId;

			public readonly Dictionary<Feature, int> Counts = new Dictionary<Feature, int>();

			public readonly List<string> Roles = new List<string>();

			public Vector3 Center;

			public int PieceCount;
		}

		private static readonly Dictionary<string, Dictionary<string, RegionRoom>> s_roomsByVillage = new Dictionary<string, Dictionary<string, RegionRoom>>();

		private const float ScanPadXZ = 12f;

		private static readonly Dictionary<Type, bool> s_sitTypeCache = new Dictionary<Type, bool>();

		public static void RefreshFor(VillageArea area)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			if (area == null || area.Waypoints == null || area.Waypoints.Count < 3)
			{
				return;
			}
			RegionGraph regionGraph = VillageRegistry.FindById(area.VillageId)?.Graph;
			if (regionGraph == null)
			{
				return;
			}
			float num = float.MaxValue;
			float num2 = float.MaxValue;
			float num3 = float.MaxValue;
			float num4 = float.MinValue;
			float num5 = float.MinValue;
			float num6 = float.MinValue;
			foreach (Vector3 waypoint in area.Waypoints)
			{
				if (waypoint.x < num)
				{
					num = waypoint.x;
				}
				if (waypoint.x > num4)
				{
					num4 = waypoint.x;
				}
				if (waypoint.z < num2)
				{
					num2 = waypoint.z;
				}
				if (waypoint.z > num5)
				{
					num5 = waypoint.z;
				}
				if (waypoint.y < num3)
				{
					num3 = waypoint.y;
				}
				if (waypoint.y > num6)
				{
					num6 = waypoint.y;
				}
			}
			Vector3 val = new Vector3((num + num4) * 0.5f, (num3 + num6) * 0.5f, (num2 + num5) * 0.5f);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor((num4 - num) * 0.5f + 12f, (num6 - num3) * 0.5f + 20f, (num5 - num2) * 0.5f + 12f);
			Dictionary<string, RegionRoom> dictionary = new Dictionary<string, RegionRoom>();
			HashSet<GameObject> hashSet = new HashSet<GameObject>();
			Collider[] array = Physics.OverlapBox(val, val2, Quaternion.identity);
			foreach (Collider val3 in array)
			{
				if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)null)
				{
					continue;
				}
				Piece componentInParent = ((Component)val3).GetComponentInParent<Piece>();
				GameObject val4 = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).gameObject : ((Component)val3).gameObject);
				if (!hashSet.Add(val4) || !TryClassifyFeature(val4, out var feature))
				{
					continue;
				}
				Vector3 position = val4.transform.position;
				string regionId = regionGraph.PointToRegionId(position);
				if (string.IsNullOrEmpty(regionId))
				{
					regionGraph.TryFindNearestLookupCell(position, null, out var _, out regionId, 3f);
				}
				if (!string.IsNullOrEmpty(regionId))
				{
					if (!dictionary.TryGetValue(regionId, out var value))
					{
						string key = regionId;
						RegionRoom obj = new RegionRoom
						{
							RegionId = regionId
						};
						value = obj;
						dictionary[key] = obj;
					}
					value.Counts.TryGetValue(feature, out var value2);
					value.Counts[feature] = value2 + 1;
					RegionRoom regionRoom = value;
					regionRoom.Center += position;
					value.PieceCount++;
				}
			}
			foreach (RegionRoom value3 in dictionary.Values)
			{
				if (value3.PieceCount > 0)
				{
					value3.Center /= (float)value3.PieceCount;
				}
				AssignRoles(value3);
			}
			s_roomsByVillage[area.VillageId] = dictionary;
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)$"[VillageRoomCatalog] {area.VillageId}: {dictionary.Count} furnished region(s)");
			}
		}

		public static void RemoveFor(string villageKey)
		{
			s_roomsByVillage.Remove(villageKey);
		}

		public static IReadOnlyCollection<RegionRoom> GetRooms(Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			Village villageAt = VillageRegistry.GetVillageAt(position);
			if (villageAt == null)
			{
				return (IReadOnlyCollection<RegionRoom>)(object)Array.Empty<RegionRoom>();
			}
			if (!s_roomsByVillage.TryGetValue(villageAt.VillageId, out var value))
			{
				return (IReadOnlyCollection<RegionRoom>)(object)Array.Empty<RegionRoom>();
			}
			return value.Values;
		}

		private static int Count(RegionRoom r, Feature f)
		{
			if (!r.Counts.TryGetValue(f, out var value))
			{
				return 0;
			}
			return value;
		}

		private static void AssignRoles(RegionRoom r)
		{
			if (Count(r, Feature.Bed) >= 1)
			{
				r.Roles.Add("Bedroom");
			}
			if (Count(r, Feature.Table) >= 1 && Count(r, Feature.Seat) >= 1)
			{
				r.Roles.Add((Count(r, Feature.Fire) >= 1) ? "Dining Hall" : "Dining Room");
			}
			if (Count(r, Feature.WorkStation) >= 1)
			{
				r.Roles.Add("Workshop");
			}
			if (Count(r, Feature.Farm) >= 1)
			{
				r.Roles.Add("Garden");
			}
			if (Count(r, Feature.Storage) >= 1)
			{
				r.Roles.Add("Storage");
			}
			if (r.Roles.Count == 0 && Count(r, Feature.Fire) >= 1)
			{
				r.Roles.Add("Hearth");
			}
		}

		private static bool TryClassifyFeature(GameObject go, out Feature feature)
		{
			feature = Feature.Fire;
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			if ((Object)(object)go.GetComponentInParent<Bed>() != (Object)null)
			{
				feature = Feature.Bed;
				return true;
			}
			if (VillageStationRegistry.TryClassifyStation(go, out var _))
			{
				feature = Feature.WorkStation;
				return true;
			}
			if (IsSittable(go))
			{
				feature = Feature.Seat;
				return true;
			}
			if ((Object)(object)go.GetComponentInParent<Fireplace>() != (Object)null)
			{
				feature = Feature.Fire;
				return true;
			}
			if ((Object)(object)go.GetComponentInParent<Container>() != (Object)null)
			{
				feature = Feature.Storage;
				return true;
			}
			string text = ((Object)go).name.ToLowerInvariant();
			if (text.Contains("table"))
			{
				feature = Feature.Table;
				return true;
			}
			if (text.Contains("cultivat") || text.Contains("sapling") || text.Contains("plant_"))
			{
				feature = Feature.Farm;
				return true;
			}
			return false;
		}

		private static bool HasSitField(Type t)
		{
			if (s_sitTypeCache.TryGetValue(t, out var value))
			{
				return value;
			}
			value = t.GetField("m_attachAnimation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null;
			s_sitTypeCache[t] = value;
			return value;
		}

		private static bool IsSittable(GameObject go)
		{
			Piece componentInParent = go.GetComponentInParent<Piece>();
			MonoBehaviour[] componentsInChildren = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).gameObject : go).GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					if (val is Chair)
					{
						return true;
					}
					if (HasSitField(((object)val).GetType()))
					{
						return true;
					}
				}
			}
			return false;
		}

		[DevCommand("List per-region room roles + furnishings for the village containing the player", Name = "vv_rooms")]
		public static void DumpRooms()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			StringBuilder stringBuilder = new StringBuilder();
			if ((Object)(object)localPlayer == (Object)null)
			{
				stringBuilder.AppendLine("[vv_rooms] No local player.");
			}
			else
			{
				Village villageAt = VillageRegistry.GetVillageAt(((Component)localPlayer).transform.position);
				Dictionary<string, RegionRoom> value;
				if (villageAt == null)
				{
					stringBuilder.AppendLine("[vv_rooms] player is not inside any registered village.");
				}
				else if (!s_roomsByVillage.TryGetValue(villageAt.VillageId, out value) || value.Count == 0)
				{
					stringBuilder.AppendLine("[vv_rooms] village " + villageAt.VillageId + ": no furnished regions cached.");
				}
				else
				{
					stringBuilder.AppendLine($"[vv_rooms] village {villageAt.VillageId}: {value.Count} furnished region(s)");
					foreach (RegionRoom value2 in value.Values)
					{
						string text = ((value2.Roles.Count > 0) ? string.Join(", ", value2.Roles) : "(unlabeled)");
						List<string> list = new List<string>();
						foreach (KeyValuePair<Feature, int> count in value2.Counts)
						{
							list.Add($"{count.Key}×{count.Value}");
						}
						stringBuilder.AppendLine($"  [{value2.RegionId}] {text} @ ({value2.Center.x:F1},{value2.Center.y:F1},{value2.Center.z:F1}) — " + string.Join(" ", list));
					}
				}
			}
			Console instance = Console.instance;
			if (instance != null)
			{
				instance.Print(stringBuilder.ToString());
			}
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)stringBuilder.ToString());
			}
		}

		[RegisterCleanup]
		public static void Clear()
		{
			s_roomsByVillage.Clear();
		}
	}
	public static class VillageStationRegistry
	{
		private static readonly Dictionary<string, List<Component>> s_stationsByVillage = new Dictionary<string, List<Component>>();

		private const float StationScanPadXZ = 12f;

		private const float StationVillageReachXZ = 3f;

		private static readonly Dictionary<Type, bool> s_conversionTypeCache = new Dictionary<Type, bool>();

		private const float MinApproachStandoffXZ = 1.5f;

		private const float PathSourceSnapMaxY = 3f;

		private const float ApproachMaxXzDist = 10f;

		private const float ApproachMaxYDelta = 3f;

		public static string LastApproachDiag = "(none)";

		private static readonly int s_losMask = LayerMask.GetMask(new string[4] { "Default", "static_solid", "piece", "terrain" });

		private static bool HasConversionField(Type t)
		{
			if (s_conversionTypeCache.TryGetValue(t, out var value))
			{
				return value;
			}
			value = t.GetField("m_conversion", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null;
			s_conversionTypeCache[t] = value;
			return value;
		}

		public static bool TryClassifyStation(GameObject go, out Component station)
		{
			station = null;
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			CraftingStation componentInParent = go.GetComponentInParent<CraftingStation>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				station = (Component)(object)componentInParent;
				return true;
			}
			Piece componentInParent2 = go.GetComponentInParent<Piece>();
			MonoBehaviour[] componentsInChildren = (((Object)(object)componentInParent2 != (Object)null) ? ((Component)componentInParent2).gameObject : go).GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && HasConversionField(((object)val).GetType()))
				{
					station = (Component)(object)val;
					return true;
				}
			}
			return false;
		}

		private static bool BelongsToVillage(RegionGraph graph, Vector3 pos)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (graph == null)
			{
				return false;
			}
			if (!string.IsNullOrEmpty(graph.PointToRegionId(pos)))
			{
				return true;
			}
			Vector3 worldPos;
			string regionId;
			return graph.TryFindNearestLookupCell(pos, null, out worldPos, out regionId, 3f);
		}

		public static void RegisterStation(GameObject go)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			if (!TryClassifyStation(go, out var station))
			{
				return;
			}
			Vector3 position = station.transform.position;
			foreach (Village item in VillageRegistry.EnumerateWithGraph())
			{
				if (!BelongsToVillage(item.Graph, position))
				{
					continue;
				}
				string villageId = item.VillageId;
				if (string.IsNullOrEmpty(villageId))
				{
					continue;
				}
				if (!s_stationsByVillage.TryGetValue(villageId, out var value))
				{
					value = (s_stationsByVillage[villageId] = new List<Component>());
				}
				if (!value.Contains(station))
				{
					value.Add(station);
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("[VillageStationRegistry] +" + ((object)station).GetType().Name + " " + ((Object)station.gameObject).name + " " + $"@ ({position.x:F1},{position.y:F1},{position.z:F1}) → {villageId} (on build)"));
					}
				}
				break;
			}
		}

		public static void RefreshFor(VillageArea area)
		{
			//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)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			if (area == null || area.Waypoints == null || area.Waypoints.Count < 3)
			{
				return;
			}
			float num = float.MaxValue;
			float num2 = float.MaxValue;
			float num3 = float.MinValue;
			float num4 = float.MinValue;
			float num5 = float.MaxValue;
			float num6 = float.MinValue;
			foreach (Vector3 waypoint in area.Waypoints)
			{
				if (waypoint.x < num)
				{
					num = waypoint.x;
				}
				if (waypoint.x > num3)
				{
					num3 = waypoint.x;
				}
				if (waypoint.z < num2)
				{
					num2 = waypoint.z;
				}
				if (waypoint.z > num4)
				{
					num4 = waypoint.z;
				}
				if (waypoint.y < num5)
				{
					num5 = waypoint.y;
				}
				if (waypoint.y > num6)
				{
					num6 = waypoint.y;
				}
			}
			Vector3 val = new Vector3((num + num3) * 0.5f, (num5 + num6) * 0.5f, (num2 + num4) * 0.5f);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor((num3 - num) * 0.5f + 12f, (num6 - num5) * 0.5f + 20f, (num4 - num2) * 0.5f + 12f);
			RegionGraph regionGraph = VillageRegistry.FindById(area.VillageId)?.Graph;
			List<Component> list = new List<Component>();
			HashSet<Component> hashSet = new HashSet<Component>();
			int num7 = 0;
			int num8 = 0;
			Collider[] array = Physics.OverlapBox(val, val2, Quaternion.identity);
			foreach (Collider val3 in array)
			{
				if (!((Object)(object)val3 == (Object)null) && !((Object)(object)((Component)val3).gameObject == (Object)null) && TryClassifyStation(((Component)val3).gameObject, out var station) && hashSet.Add(station))
				{
					num7++;
					if (regionGraph == null || BelongsToVillage(regionGraph, station.transform.position))
					{
						list.Add(station);
						num8++;
					}
				}
			}
			s_stationsByVillage[area.VillageId] = list;
			if (Plugin.Log == null)
			{
				return;
			}
			Plugin.Log.LogInfo((object)($"[VillageStationRegistry] {area.VillageId}: cached {list.Count} stations " + $"(candidates {num7} → kept {num8})"));
			foreach (Component item in list)
			{
				Vector3 position = item.transform.position;
				Plugin.Log.LogInfo((object)$"  [{((object)item).GetType().Name}] {((Object)item.gameObject).name} @ ({position.x:F1},{position.y:F1},{position.z:F1})");
			}
		}

		public static void RemoveFor(string villageKey)
		{
			s_stationsByVillage.Remove(villageKey);
		}

		public static bool HasVillageFor(Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			string villageId;
			return TryGetVillage(position, out villageId);
		}

		public static bool TryFindStation<T>(Vector3 position, Func<T, bool> filter, out Vector3 stationPos, out T component) where T : Component
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: 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_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			stationPos = Vector3.zero;
			component = default(T);
			if (!TryGetVillage(position, out var villageId))
			{
				return false;
			}
			if (!s_stationsByVillage.TryGetValue(villageId, out var value))
			{
				return false;
			}
			T val = default(T);
			float num = float.MaxValue;
			foreach (Component item in value)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				T val2 = (T)(object)((item is T) ? item : null);
				if (!((Object)(object)val2 == (Object)null) && (filter == null || filter(val2)))
				{
					Vector3 val3 = ((Component)val2).transform.position - position;
					float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
					if (sqrMagnitude < num)
					{
						num = sqrMagnitude;
						val = val2;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if (!TryResolveApproach(((Component)val).transform.position, position, out var approach))
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[VillageStationRegistry] " + villageId + ": no HNA-valid approach to " + ((Object)((Component)val).gameObject).name + " " + $"@ ({((Component)val).transform.position.x:F1},{((Component)val).transform.position.y:F1},{((Component)val).transform.position.z:F1})"));
				}
				stationPos = Vector3.zero;
				component = default(T);
				return false;
			}
			stationPos = approach;
			component = val;
			return true;
		}

		public static bool TryResolveApproach(Vector3 target, Vector3 pathSource, out Vector3 approach, Vector3? villageAnchor = null)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			approach = Vector3.zero;
			RegionGraph regionGraph = VillageRegistry.GetVillageAt(villageAnchor.GetValueOrDefault(pathSource))?.Graph;
			if (regionGraph == null)
			{
				LastApproachDiag = $"no graph for source ({pathSource.x:F1},{pathSource.y:F1},{pathSource.z:F1})";
				return false;
			}
			bool flag = true;
			if (!regionGraph.TryFindNearestLookupCell(pathSource, (Vector3 cell) => Mathf.Abs(cell.y - pathSource.y) <= 3f, out var worldPos, out var regionId))
			{
				flag = false;
				if (!regionGraph.TryFindNearestLookupCell(pathSource, null, out worldPos, out regionId))
				{
					worldPos = pathSource;
				}
			}
			float minStandoffSq = 2.25f;
			int considered = 0;
			int levelPass = 0;
			int standoffPass = 0;
			int losPass = 0;
			bool flag2 = regionGraph.TryFindNearestLookupCell(target, delegate(Vector3 candidate)
			{
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0040: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				//IL_0085: Unknown result type (might be due to invalid IL or missing references)
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				considered++;
				if (Mathf.Abs(candidate.y - target.y) > 3f)
				{
					return false;
				}
				levelPass++;
				float num = candidate.x - target.x;
				float num2 = candidate.z - target.z;
				if (num * num + num2 * num2 < minStandoffSq)
				{
					return false;
				}
				standoffPass++;
				if (!HasClearLineToStation(candidate, target))
				{
					return false;
				}
				losPass++;
				return true;
			}, out approach, out regionId, 10f);
			LastApproachDiag = string.Format("src=({0:F1},{1:F1},{2:F1}) snap={3} ", pathSource.x, pathSource.y, pathSource.z, flag ? "y" : "fallback") + $"pathStart=({worldPos.x:F1},{worldPos.y:F1},{worldPos.z:F1}) " + $"tgt=({target.x:F1},{target.y:F1},{target.z:F1}) " + $"considered={considered} levelPass={levelPass} standoffPass={standoffPass} losPass={losPass} " + "result=" + (flag2 ? $"({approach.x:F1},{approach.y:F1},{approach.z:F1})" : "FAIL");
			return flag2;
		}

		private static bool HasClearLineToStation(Vector3 approach, Vector3 station)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			/