using System; using System.Collections.Generic; using System.Globalization; using UnityEngine; /// /// Shared helper for land-claim checks used by both the farming task and the consideration. /// /// Uses PersistentPlayerList.m_lpBlockMap — the game's own authoritative dictionary that maps /// every active keystone position to its owner. No chunk walking required; the map is kept /// current by PlaceLandProtectionBlock / RemoveLandProtectionBlock. /// /// Pass a non-null to restrict to a specific player's claims. /// public static class FarmLandClaimUtils { public static bool IsPlotInLandClaim( Vector3i plotPos, PersistentPlayerData ownerFilter = null) { int claimSize = GamePrefs.GetInt(EnumGamePrefs.LandClaimSize); if (claimSize <= 0) claimSize = 41; var persistentList = GameManager.Instance?.GetPersistentPlayerList(); if (persistentList?.m_lpBlockMap == null) return false; foreach (var entry in persistentList.m_lpBlockMap) { // Axis-aligned distance check — same logic as World.IsLandProtectedBlock. if (Math.Abs(entry.Key.x - plotPos.x) > claimSize) continue; if (Math.Abs(entry.Key.z - plotPos.z) > claimSize) continue; if (ownerFilter != null && entry.Value != ownerFilter) continue; return true; } return false; } } namespace UAI { /// /// Returns 0f when would return 1f. /// XML: <consideration class="NotFarmPlotInLandClaim, SCore" distance="50" /> /// public class UAIConsiderationNotFarmPlotInLandClaimV4 : UAIConsiderationFarmPlotInLandClaimV4 { public override float GetScore(Context _context, object target) => base.GetScore(_context, target) == 1f ? 0f : 1f; } /// /// Returns 1f if at least one unvisited farm plot within distance blocks is inside /// any active land claim. /// /// XML: /// <consideration class="FarmPlotInLandClaim, SCore" distance="50" /> /// /// General-purpose gate for behaviors that should only fire near settled land. /// The per-entity 2-second cache keeps evaluation cost negligible. /// public class UAIConsiderationFarmPlotInLandClaimV4 : UAIConsiderationBase { private int _distance = 50; private static readonly Dictionary _cache = new Dictionary(); private const float CacheTtl = 2f; public override void Init(Dictionary parameters) { base.Init(parameters); if (parameters.ContainsKey("distance")) StringParsers.TryParseSInt32(parameters["distance"], out _distance, 0, -1, NumberStyles.Integer); } public override float GetScore(Context _context, object target) { int id = _context.Self.entityId; float now = Time.time; if (_cache.TryGetValue(id, out var cached) && now - cached.time < CacheTtl) return cached.score; float score = EvaluateScore(_context); _cache[id] = (score, now); return score; } private float EvaluateScore(Context _context) { var npcPos = new Vector3i(_context.Self.position); if (FarmPlotManager.Instance == null) return 0f; foreach (var plot in FarmPlotManager.Instance.GetCloseEntry(npcPos, _distance)) { if (plot.Visited) continue; if (FarmLandClaimUtils.IsPlotInLandClaim(plot.GetBlockPos())) return 1f; } return 0f; } } }