Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValheimVillages v0.1.1
plugins/ValheimVillages/ValheimVillages.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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) /