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 AntTrails v1.2.2
plugins/AntTrails.dll
Decompiled 3 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("RAGEmedia")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Player footfall slowly wears dirt paths into the world, and heavy traffic cobbles them to stone.")] [assembly: AssemblyFileVersion("1.2.2.0")] [assembly: AssemblyInformationalVersion("1.2.2+baee46faa453df43d833c2ab0265d4600b3d2d7d")] [assembly: AssemblyProduct("AntTrails")] [assembly: AssemblyTitle("AntTrails")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AntTrails { internal static class AntTrailsConfig { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> StepsToPath; internal static ConfigEntry<float> FormationWindowDays; internal static ConfigEntry<float> RevertDays; internal static ConfigEntry<float> ResidualTrace; internal static ConfigEntry<float> StoneSteps; internal static ConfigEntry<float> StoneChance; internal static ConfigEntry<bool> RespectBuildPrivilege; internal static ConfigEntry<float> FlushIntervalSeconds; internal static ConfigEntry<float> DecaySweepSeconds; internal static ConfigEntry<float> SampleIntervalSeconds; internal static ConfigEntry<bool> VerboseLogging; internal static void Bind(ConfigFile cfg) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Expected O, but got Unknown //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Expected O, but got Unknown //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Expected O, but got Unknown //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; Enabled = cfg.Bind<bool>("General", "Enabled", true, new ConfigDescription("Master switch. When false, no trails form, decay, or upgrade. Existing painted terrain is left exactly as it is.", (AcceptableValueBase)null, new object[1] { val })); StepsToPath = cfg.Bind<float>("Formation", "StepsToPath", 25f, new ConfigDescription("Tile crossings needed to turn one 1x1m tile into a full dirt path. Counts from every player pooled together, not per player. The tile tints progressively on the way there; grass clears at the halfway mark.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 2000f), new object[1] { val })); FormationWindowDays = cfg.Bind<float>("Formation", "FormationWindowDays", 12f, new ConfigDescription("In-game days of no traffic for an unfinished tile to lose ALL its accumulated progress. Progress leaks at a flat StepsToPath/FormationWindowDays per day, so this is really a rate gate: a tile crossed less often than that never gains ground at all, however long you keep at it. Lower it and only frantic traffic leaves a mark; raise it and routes wear in from occasional use.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 200f), new object[1] { val })); RevertDays = cfg.Bind<float>("Decay", "RevertDays", 30f, new ConfigDescription("In-game days of no traffic for a finished dirt path to fade all the way down to ResidualTrace. Much slower than FormationWindowDays: paths are easy to lose before they exist and hard to lose afterwards.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2000f), new object[1] { val })); ResidualTrace = cfg.Bind<float>("Decay", "ResidualTrace", 0.3f, new ConfigDescription("Floor a finished path fades to, as a fraction of full dirt. Above 0 means abandoned routes stay visible forever as faint ghost trails. Below 0.5 the grass grows back over them. Set to 0 to let paths vanish entirely.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1] { val })); StoneSteps = cfg.Bind<float>("Stone", "StoneSteps", 400f, new ConfigDescription("Lifetime crossings on one tile before it becomes eligible to cobble over into stone. Unlike formation progress this total never decays, so it measures how heavily travelled the tile has been across the whole life of the world.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100000f), new object[1] { val })); StoneChance = cfg.Bind<float>("Stone", "StoneChance", 0.01f, new ConfigDescription("Chance per crossing, once eligible, that a tile turns to stone. Low on purpose: it should speckle stone through a well-worn road, not pave it uniformly. Stone is permanent -- it never decays and only a hoe or pickaxe removes it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[1] { val })); RespectBuildPrivilege = cfg.Bind<bool>("Eligibility", "RespectBuildPrivilege", true, new ConfigDescription("Skip tiles inside a workbench or ward radius, keeping bases pristine. Set false if you want trails to form around and through settlements.", (AcceptableValueBase)null, new object[1] { val })); FlushIntervalSeconds = cfg.Bind<float>("Performance", "FlushIntervalSeconds", 5f, new ConfigDescription("Seconds between pushing accumulated paint changes to the terrain. Every flush makes the affected zone re-serialize its entire terrain blob to all peers, so raising this trades responsiveness for bandwidth.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), new object[1] { val })); DecaySweepSeconds = cfg.Bind<float>("Performance", "DecaySweepSeconds", 60f, new ConfigDescription("Seconds between re-evaluating loaded tiles for decay. Tiles in unloaded zones cost nothing; they catch up from elapsed world time when the zone next loads.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 600f), new object[1] { val })); SampleIntervalSeconds = cfg.Bind<float>("Performance", "SampleIntervalSeconds", 0.25f, new ConfigDescription("How often this client samples its own position. Tiles between two samples are filled in, so this does not decide whether fast travel registers -- only how closely the traced line follows a curving route. Local setting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), Array.Empty<object>())); VerboseLogging = cfg.Bind<bool>("General", "VerboseLogging", false, "Log per-tile detail. Extremely noisy; for debugging only."); } } internal class AntTrailsRunner : MonoBehaviour { private void Update() { if (!((Object)(object)ZNet.instance == (Object)null)) { float deltaTime = Time.deltaTime; StepReporter.Tick(deltaTime); if (TrailEngine.Active) { TrailEngine.Tick(deltaTime); } } } } [BepInPlugin("com.ragemedia.anttrails", "AntTrails", "1.2.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class AntTrailsPlugin : BaseUnityPlugin { public const string PluginGuid = "com.ragemedia.anttrails"; public const string PluginName = "AntTrails"; public const string PluginVersion = "1.2.2"; internal static AntTrailsPlugin Instance; internal static ConfigEntry<bool> VerboseLogging; private AntTrailsRunner _runner; private readonly Harmony _harmony = new Harmony("com.ragemedia.anttrails"); private void Awake() { Instance = this; BindConfig(); _harmony.PatchAll(Assembly.GetExecutingAssembly()); VerifyPatches(); _runner = ((Component)this).gameObject.AddComponent<AntTrailsRunner>(); } private void OnDestroy() { if ((Object)(object)_runner != (Object)null) { Object.Destroy((Object)(object)_runner); } TrailEngine.End(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void BindConfig() { AntTrailsConfig.Bind(((BaseUnityPlugin)this).Config); VerboseLogging = AntTrailsConfig.VerboseLogging; } private void VerifyPatches() { List<MethodBase> list = _harmony.GetPatchedMethods().ToList(); LogInfo($"Harmony bound {list.Count} target(s):"); foreach (MethodBase item in list) { LogInfo(" bound " + item.DeclaringType?.FullName + "." + item.Name); } int num = 0; foreach (Type item2 in from t in Assembly.GetExecutingAssembly().GetTypes() where t.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Any() select t) { List<HarmonyMethod> list2 = (from HarmonyPatch a in item2.GetCustomAttributes(typeof(HarmonyPatch), inherit: true) select ((HarmonyAttribute)a).info).ToList(); HarmonyMethod val; try { val = HarmonyMethod.Merge(list2); } catch (Exception ex) { LogError(" UNRESOLVED " + item2.Name + ": could not merge patch attributes: " + ex.Message); num++; continue; } if (!(val?.declaringType == null) && !string.IsNullOrEmpty(val.methodName) && AccessTools.Method(val.declaringType, val.methodName, val.argumentTypes, (Type[])null) == null) { num++; LogError(" UNRESOLVED " + item2.Name + " -> " + val.declaringType.Name + "." + val.methodName + " does not exist. This patch will never run. The game version likely changed; re-check the method against the decompiled source."); } } if (num > 0) { LogError(string.Format("{0}: {1} patch target(s) failed to resolve. ", "AntTrails", num) + "This mod is loaded but not fully functional."); } else if (list.Count == 0) { LogWarning("AntTrails: loaded, but bound zero patches. Expected if this mod only adds content via Jotunn."); } else { LogInfo("AntTrails v1.2.2 ready."); } } internal static void LogInfo(string msg) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[AntTrails] " + msg)); } internal static void LogWarning(string msg) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)("[AntTrails] " + msg)); } internal static void LogError(string msg) { ((BaseUnityPlugin)Instance).Logger.LogError((object)("[AntTrails] " + msg)); } internal static void LogVerbose(string msg) { if (VerboseLogging != null && VerboseLogging.Value) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[AntTrails] " + msg)); } } } internal static class StepReporter { private const int MaxPendingTiles = 512; private const float SendIntervalSeconds = 2f; private const float MaxSegmentLength = 8f; private const int MaxSegmentTiles = 24; private static readonly HashSet<long> Pending = new HashSet<long>(); private static float _sampleTimer; private static float _sendTimer; private static bool _haveLastPos; private static Vector3 _lastPos; internal static void Reset() { Pending.Clear(); _sampleTimer = 0f; _sendTimer = 0f; _haveLastPos = false; } internal static void Tick(float dt) { if (AntTrailsConfig.Enabled.Value) { _sampleTimer += dt; if (_sampleTimer >= AntTrailsConfig.SampleIntervalSeconds.Value) { _sampleTimer = 0f; Sample(); } _sendTimer += dt; if (_sendTimer >= 2f) { _sendTimer = 0f; Send(); } } } private static void Sample() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_00b0: 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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead()) { _haveLastPos = false; return; } if (!((Character)localPlayer).IsOnGround() || ((Character)localPlayer).IsSwimming() || ((Character)localPlayer).InWater() || ((Character)localPlayer).InLiquid()) { _haveLastPos = false; return; } Collider lastGroundCollider = ((Character)localPlayer).GetLastGroundCollider(); if ((Object)(object)lastGroundCollider == (Object)null) { _haveLastPos = false; return; } Heightmap component = ((Component)lastGroundCollider).GetComponent<Heightmap>(); if ((Object)(object)component == (Object)null) { _haveLastPos = false; return; } Vector3 position = ((Component)localPlayer).transform.position; if (!_haveLastPos) { Credit(component, Mathf.FloorToInt(position.x), Mathf.FloorToInt(position.z), position.y); } else { TraceSegment(component, _lastPos, position); } _haveLastPos = true; _lastPos = position; } private static void TraceSegment(Heightmap hmap, Vector3 from, Vector3 to) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_0048: 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_006c: 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_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0126: 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) int num = Mathf.FloorToInt(from.x); int num2 = Mathf.FloorToInt(from.z); int num3 = Mathf.FloorToInt(to.x); int num4 = Mathf.FloorToInt(to.z); if (num == num3 && num2 == num4) { return; } float num5 = to.x - from.x; float num6 = to.z - from.z; if (num5 * num5 + num6 * num6 > 64f) { Credit(hmap, num3, num4, to.y); return; } int num7 = ((num5 > 0f) ? 1 : (-1)); int num8 = ((num6 > 0f) ? 1 : (-1)); float num9 = ((num5 != 0f) ? Mathf.Abs(1f / num5) : float.MaxValue); float num10 = ((num6 != 0f) ? Mathf.Abs(1f / num6) : float.MaxValue); float num11 = ((num5 > 0f) ? (((float)(num + 1) - from.x) / num5) : ((num5 < 0f) ? (((float)num - from.x) / num5) : float.MaxValue)); float num12 = ((num6 > 0f) ? (((float)(num2 + 1) - from.z) / num6) : ((num6 < 0f) ? (((float)num2 - from.z) / num6) : float.MaxValue)); for (int i = 0; i < 24; i++) { if (num11 < num12) { num += num7; num11 += num9; } else { num2 += num8; num12 += num10; } Credit(hmap, num, num2, to.y); if (num == num3 && num2 == num4) { break; } } } private static void Credit(Heightmap hmap, int tx, int tz, float y) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (Pending.Count < 512 && IsEligible(hmap, new Vector3((float)tx + 0.5f, y, (float)tz + 0.5f))) { Pending.Add(TrailStore.TileKey(tx, tz)); } } private static bool IsEligible(Heightmap hmap, Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (hmap.IsLava(pos, 0.6f)) { return false; } if (AntTrailsConfig.RespectBuildPrivilege.Value && InsideBuildPrivilege(pos)) { return false; } return true; } private static bool InsideBuildPrivilege(Vector3 pos) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) List<PrivateArea> allAreas = PrivateArea.m_allAreas; if (allAreas == null) { return false; } for (int i = 0; i < allAreas.Count; i++) { PrivateArea val = allAreas[i]; if ((Object)(object)val != (Object)null && val.IsEnabled() && val.IsInside(pos, 0f)) { return true; } } return false; } private static void Send() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (Pending.Count == 0 || ZRoutedRpc.instance == null) { return; } ZPackage val = new ZPackage(); val.Write(Pending.Count); foreach (long item in Pending) { TrailStore.SplitTile(item, out var x, out var z); val.Write(x); val.Write(z); } AntTrailsPlugin.LogVerbose($"Reporting {Pending.Count} tile crossing(s)."); Pending.Clear(); ZRoutedRpc.instance.InvokeRoutedRPC("AntTrails_Steps", new object[1] { val }); } } internal static class TerrainPainter { internal struct TilePaint { public int X; public int Z; public float R; public float PrevR; public bool Stone; } private struct PaintEntry { public int X; public int Y; public float R; public float PrevR; public bool Stone; } private const float PaintReadbackTolerance = 0.02f; private static readonly List<Heightmap> HmapBuffer = new List<Heightmap>(); private static readonly Dictionary<Heightmap, TerrainComp> CompCache = new Dictionary<Heightmap, TerrainComp>(); internal static HashSet<long> ApplyTiles(List<TilePaint> tiles) { //IL_0061: 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) HashSet<long> hashSet = new HashSet<long>(); CompCache.Clear(); if (tiles == null || Heightmap.s_heightmaps == null || Heightmap.s_heightmaps.Count == 0) { return hashSet; } Dictionary<TerrainComp, List<PaintEntry>> dictionary = new Dictionary<TerrainComp, List<PaintEntry>>(); int num = default(int); int num2 = default(int); foreach (TilePaint tile in tiles) { Vector3 val = new Vector3((float)tile.X + 0.5f, 0f, (float)tile.Z + 0.5f); HmapBuffer.Clear(); Heightmap.FindHeightmap(val, 1f, HmapBuffer); if (HmapBuffer.Count == 0) { continue; } bool flag = false; foreach (Heightmap item in HmapBuffer) { item.WorldToVertexMask(new Vector3((float)tile.X, 0f, (float)tile.Z), ref num, ref num2); int num3 = item.m_width + 1; if (num < 0 || num2 < 0 || num >= num3 || num2 >= num3) { continue; } TerrainComp val2 = ResolveComp(item); if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.m_nview == (Object)null) && val2.m_nview.IsValid() && val2.m_nview.IsOwner() && val2.m_initialized && !((Object)(object)val2.m_hmap == (Object)null)) { if (!dictionary.TryGetValue(val2, out var value)) { value = (dictionary[val2] = new List<PaintEntry>()); } value.Add(new PaintEntry { X = num, Y = num2, R = tile.R, PrevR = tile.PrevR, Stone = tile.Stone }); flag = true; } } if (flag) { hashSet.Add(TrailStore.TileKey(tile.X, tile.Z)); } } foreach (KeyValuePair<TerrainComp, List<PaintEntry>> item2 in dictionary) { ApplyBatch(item2.Key, item2.Value); } return hashSet; } private static TerrainComp ResolveComp(Heightmap hmap) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (CompCache.TryGetValue(hmap, out var value)) { return value; } Vector3 position = ((Component)hmap).transform.position; TerrainComp val = TerrainComp.FindTerrainCompiler(position); if ((Object)(object)val == (Object)null && (Object)(object)ZNetScene.instance != (Object)null && (Object)(object)ZoneSystem.instance != (Object)null && ZDOMan.instance != null && ZNetScene.instance.IsAreaReady(position)) { val = hmap.GetAndCreateTerrainCompiler(); } CompCache[hmap] = val; return val; } private static void ApplyBatch(TerrainComp comp, List<PaintEntry> entries) { //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_0296: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0143: 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_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_015e: 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_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) Heightmap hmap = comp.m_hmap; int num = comp.m_width + 1; int num2 = 0; float num3 = float.MaxValue; float num4 = float.MinValue; float num5 = float.MaxValue; float num6 = float.MinValue; foreach (PaintEntry entry in entries) { if (entry.X < 0 || entry.Y < 0 || entry.X >= num || entry.Y >= num) { continue; } int num7 = entry.Y * num + entry.X; Color paintMask = hmap.GetPaintMask(entry.X, entry.Y); if (paintMask.g > 0.5f || paintMask.b > 0.5f) { continue; } Color val = paintMask; if (entry.Stone) { val.r = 0f; val.g = 0f; val.b = 1f; } else { float num8 = ((paintMask.r > entry.PrevR + 0.02f) ? paintMask.r : 0f); val.r = Mathf.Max(num8, Mathf.Clamp01(entry.R)); } val.a = paintMask.a; if (!(Mathf.Abs(paintMask.r - val.r) < 0.001f) || !(Mathf.Abs(paintMask.g - val.g) < 0.001f) || !(Mathf.Abs(paintMask.b - val.b) < 0.001f)) { comp.m_modifiedPaint[num7] = true; comp.m_paintMask[num7] = val; num2++; float num9 = ((Component)hmap).transform.position.x + (float)(entry.X - num / 2); float num10 = ((Component)hmap).transform.position.z + (float)(entry.Y - num / 2); if (num9 < num3) { num3 = num9; } if (num9 > num4) { num4 = num9; } if (num10 < num5) { num5 = num10; } if (num10 > num6) { num6 = num10; } } } if (num2 != 0) { comp.Save(false); hmap.Poke(0, false); if ((Object)(object)ClutterSystem.instance != (Object)null) { Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor((num3 + num4) * 0.5f, 0f, (num5 + num6) * 0.5f); float num11 = Mathf.Max(num4 - num3, num6 - num5) * 0.5f + 2f; ClutterSystem.instance.ResetGrass(val2, num11); } AntTrailsPlugin.LogVerbose($"Painted {num2} vertex/vertices on terrain at {((Component)hmap).transform.position}."); } } } internal static class TrailEngine { private struct Viewer { public long PeerId; public int ZoneX; public int ZoneZ; public int NearZones; } private const float RepaintEpsilon = 0.06f; private const int MaxDirtyTiles = 8192; private const float PendingTimeoutSeconds = 15f; private const int DefaultNearZones = 3; private const long LocalViewerId = long.MinValue; private static readonly HashSet<long> Dirty = new HashSet<long>(); private static readonly Dictionary<long, float> Pending = new Dictionary<long, float>(); private static readonly Dictionary<long, float> Offered = new Dictionary<long, float>(); private static readonly List<Viewer> Viewers = new List<Viewer>(); private static readonly Dictionary<long, List<long>> ZoneBatches = new Dictionary<long, List<long>>(); private static readonly List<TerrainPainter.TilePaint> SendBuffer = new List<TerrainPainter.TilePaint>(); private static readonly List<long> SendKeys = new List<long>(); private static readonly List<TerrainPainter.TilePaint> Packet = new List<TerrainPainter.TilePaint>(); private static readonly List<long> ExpiredBuffer = new List<long>(); private static float _flushTimer; private static float _sweepTimer; internal static bool Active { get; private set; } private static double DayLengthSeconds { get { if (!((Object)(object)EnvMan.instance != (Object)null) || EnvMan.instance.m_dayLengthSec <= 0) { return 1800.0; } return EnvMan.instance.m_dayLengthSec; } } internal static double WorldTime { get { if (!((Object)(object)ZNet.instance != (Object)null)) { return 0.0; } return ZNet.instance.GetTimeSeconds(); } } internal static void Begin(string worldName) { TrailStore.Load(worldName); Dirty.Clear(); Pending.Clear(); Offered.Clear(); _flushTimer = 0f; _sweepTimer = 0f; Active = true; } internal static void End() { if (Active) { TrailStore.Save(force: true); TrailStore.Clear(); Dirty.Clear(); Pending.Clear(); Offered.Clear(); Active = false; } } private static void Advance(ref TileState t, double now) { double num = now - t.LastTouch; if (num <= 0.0) { t.LastTouch = now; return; } if (t.Stage == 2) { t.LastTouch = now; return; } float num2 = (float)(num / DayLengthSeconds); float num3 = Mathf.Max(1f, AntTrailsConfig.StepsToPath.Value); float num5; float num6; if (t.Stage == 1) { float num4 = Mathf.Clamp01(AntTrailsConfig.ResidualTrace.Value); num5 = num3 * num4; num6 = num3 * (1f - num4) / Mathf.Max(0.01f, AntTrailsConfig.RevertDays.Value); } else { num5 = 0f; num6 = num3 / Mathf.Max(0.01f, AntTrailsConfig.FormationWindowDays.Value); } t.Charge = Mathf.Max(num5, t.Charge - num2 * num6); t.LastTouch = now; } private static float DesiredR(in TileState t) { float num = Mathf.Max(1f, AntTrailsConfig.StepsToPath.Value); return Mathf.Clamp01(t.Charge / num); } private static void MarkIfChanged(long key, in TileState t) { if (Dirty.Count >= 8192 && !Dirty.Contains(key)) { return; } if (t.Stage == 2) { if (!t.PaintedStone) { Dirty.Add(key); } return; } float num = DesiredR(in t); if ((num >= 0.999f && t.PaintedR < 0.999f) || (num <= 0.001f && t.PaintedR > 0.001f) || Mathf.Abs(num - t.PaintedR) >= 0.06f) { Dirty.Add(key); } } internal static void RegisterSteps(int[] tiles) { if (!Active || !AntTrailsConfig.Enabled.Value || tiles == null) { return; } double worldTime = WorldTime; float num = Mathf.Max(1f, AntTrailsConfig.StepsToPath.Value); float value = AntTrailsConfig.StoneSteps.Value; float value2 = AntTrailsConfig.StoneChance.Value; for (int i = 0; i + 1 < tiles.Length; i += 2) { long key = TrailStore.TileKey(tiles[i], tiles[i + 1]); if (!TrailStore.TryGet(key, out var state)) { state = new TileState { LastTouch = worldTime }; } Advance(ref state, worldTime); if (state.Stage == 2) { state.TotalSteps += 1f; TrailStore.Set(key, in state); continue; } state.Charge = Mathf.Min(num, state.Charge + 1f); state.TotalSteps += 1f; if (state.Stage == 0 && state.Charge >= num) { state.Stage = 1; AntTrailsPlugin.LogVerbose($"Tile {tiles[i]},{tiles[i + 1]} became a dirt path."); } if (state.Stage == 1 && state.TotalSteps >= value && Random.value < value2) { state.Stage = 2; AntTrailsPlugin.LogVerbose($"Tile {tiles[i]},{tiles[i + 1]} cobbled over to stone."); } TrailStore.Set(key, in state); MarkIfChanged(key, in state); } } internal static void OnZoneLoaded(int zx, int zz) { if (!Active || !AntTrailsConfig.Enabled.Value) { return; } long key = TrailStore.ZoneKey(zx, zz); if (!TrailStore.Zones.TryGetValue(key, out var value) || value.Count == 0) { return; } double worldTime = WorldTime; foreach (long item in value) { if (TrailStore.TryGet(item, out var state)) { Advance(ref state, worldTime); TrailStore.Set(item, in state); MarkIfChanged(item, in state); } } AntTrailsPlugin.LogVerbose($"Reconciled {value.Count} tile(s) in zone {zx},{zz}."); } private static void Sweep() { CollectViewers(); if (Viewers.Count == 0) { return; } double worldTime = WorldTime; foreach (KeyValuePair<long, HashSet<long>> zone in TrailStore.Zones) { TrailStore.SplitZone(zone.Key, out var zx, out var zz); if (!AnyViewerNear(zx, zz)) { continue; } foreach (long item in zone.Value) { if (TrailStore.TryGet(item, out var state)) { if (state.Stage != 2) { Advance(ref state, worldTime); TrailStore.Set(item, in state); } MarkIfChanged(item, in state); } } } } private static void CollectViewers() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0068: 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_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) Viewers.Clear(); ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } if ((Object)(object)Player.m_localPlayer != (Object)null) { Vector2s zone = ZoneSystem.GetZone(((Component)Player.m_localPlayer).transform.position); Viewers.Add(new Viewer { PeerId = long.MinValue, ZoneX = zone.x, ZoneZ = zone.y, NearZones = 3 }); } foreach (ZNetPeer peer in instance.GetPeers()) { if (peer != null && peer.IsReady()) { int nearZones = Mathf.Max(1, ((SimulationDistance)(ref peer.m_simulationDistance)).NearSimulationDistance) + 1; Vector2s zone2 = ZoneSystem.GetZone(peer.GetRefPos()); Viewers.Add(new Viewer { PeerId = peer.m_uid, ZoneX = zone2.x, ZoneZ = zone2.y, NearZones = nearZones }); } } } private static bool AnyViewerNear(int zx, int zz) { for (int i = 0; i < Viewers.Count; i++) { if (ZoneInRange(Viewers[i], zx, zz)) { return true; } } return false; } private static bool ZoneInRange(Viewer viewer, int zx, int zz) { if (Mathf.Abs(viewer.ZoneX - zx) <= viewer.NearZones) { return Mathf.Abs(viewer.ZoneZ - zz) <= viewer.NearZones; } return false; } internal static void Tick(float dt) { if (Active && AntTrailsConfig.Enabled.Value) { _sweepTimer += dt; if (_sweepTimer >= AntTrailsConfig.DecaySweepSeconds.Value) { _sweepTimer = 0f; Sweep(); } _flushTimer += dt; if (_flushTimer >= AntTrailsConfig.FlushIntervalSeconds.Value) { _flushTimer = 0f; Flush(); } } } private static void Flush() { if (Dirty.Count == 0) { return; } CollectViewers(); if (Viewers.Count == 0) { return; } float time = Time.time; ExpirePending(time); ZoneBatches.Clear(); int num = 0; foreach (long item in Dirty) { if (!Pending.ContainsKey(item)) { TrailStore.SplitTile(item, out var x, out var z); long key = TrailStore.ZoneKeyForTile(x, z); if (!ZoneBatches.TryGetValue(key, out var value)) { value = new List<long>(); ZoneBatches[key] = value; } value.Add(item); num++; } } if (num == 0) { return; } int num2 = 0; foreach (KeyValuePair<long, List<long>> zoneBatch in ZoneBatches) { TrailStore.SplitZone(zoneBatch.Key, out var zx, out var zz); num2 += OfferZone(zx, zz, zoneBatch.Value, time); } if (num2 > 0) { AntTrailsPlugin.LogVerbose($"Offered {num2} tile(s); {Dirty.Count} dirty, {Pending.Count} awaiting ack."); } else { AntTrailsPlugin.LogVerbose($"{num} dirty tile(s) but no peer has their zone loaded; deferring."); } } private static int OfferZone(int zx, int zz, List<long> keys, float nowReal) { SendBuffer.Clear(); SendKeys.Clear(); foreach (long key in keys) { if (TrailStore.TryGet(key, out var state)) { TrailStore.SplitTile(key, out var x, out var z); bool flag = state.Stage == 2; float num = state.PaintedR; if (Offered.TryGetValue(key, out var value) && value > num) { num = value; } SendKeys.Add(key); SendBuffer.Add(new TerrainPainter.TilePaint { X = x, Z = z, R = (flag ? 1f : DesiredR(in state)), PrevR = num, Stone = flag }); } } if (SendBuffer.Count == 0) { return 0; } int num2 = 0; for (int i = 0; i < Viewers.Count; i++) { if (ZoneInRange(Viewers[i], zx, zz)) { num2++; } } if (num2 == 0) { return 0; } foreach (long key2 in keys) { Pending[key2] = nowReal; } for (int j = 0; j < SendKeys.Count; j++) { Offered[SendKeys[j]] = SendBuffer[j].R; } for (int k = 0; k < Viewers.Count; k++) { Viewer viewer = Viewers[k]; if (!ZoneInRange(viewer, zx, zz)) { continue; } for (int l = 0; l < SendBuffer.Count; l += 256) { int num3 = Mathf.Min(256, SendBuffer.Count - l); Packet.Clear(); for (int m = 0; m < num3; m++) { Packet.Add(SendBuffer[l + m]); } if (viewer.PeerId == long.MinValue) { ConfirmPainted(TerrainPainter.ApplyTiles(Packet)); } else { TrailNetwork.SendPaint(viewer.PeerId, Packet); } } } return SendBuffer.Count; } internal static void ConfirmPainted(ICollection<long> keys) { if (!Active || keys == null) { return; } foreach (long key in keys) { Pending.Remove(key); Dirty.Remove(key); if (TrailStore.TryGet(key, out var state)) { if (state.Stage == 2) { state.PaintedStone = true; state.PaintedR = 1f; } else { state.PaintedR = DesiredR(in state); } if (Offered.TryGetValue(key, out var value) && state.PaintedR >= value - 0.001f) { Offered.Remove(key); } TrailStore.Set(key, in state); } } } private static void ExpirePending(float nowReal) { if (Pending.Count == 0) { return; } ExpiredBuffer.Clear(); foreach (KeyValuePair<long, float> item in Pending) { if (nowReal - item.Value >= 15f) { ExpiredBuffer.Add(item.Key); } } foreach (long item2 in ExpiredBuffer) { Pending.Remove(item2); } } } internal static class TrailNetwork { internal const string StepsRpc = "AntTrails_Steps"; internal const string PaintRpc = "AntTrails_Paint"; internal const string PaintAckRpc = "AntTrails_PaintAck"; internal const int MaxTilesPerPacket = 256; private static bool _registered; internal static void Register() { if (!_registered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<ZPackage>("AntTrails_Steps", (Action<long, ZPackage>)RPC_Steps); ZRoutedRpc.instance.Register<ZPackage>("AntTrails_Paint", (Action<long, ZPackage>)RPC_Paint); ZRoutedRpc.instance.Register<ZPackage>("AntTrails_PaintAck", (Action<long, ZPackage>)RPC_PaintAck); _registered = true; AntTrailsPlugin.LogInfo("Registered step, paint, and paint-ack RPCs."); } } internal static void Reset() { _registered = false; } private static void RPC_Steps(long sender, ZPackage pkg) { if (!TrailEngine.Active || pkg == null) { return; } int num = pkg.ReadInt(); if (num > 0 && num <= 4096) { int[] array = new int[num * 2]; for (int i = 0; i < num; i++) { array[i * 2] = pkg.ReadInt(); array[i * 2 + 1] = pkg.ReadInt(); } TrailEngine.RegisterSteps(array); } } private static void RPC_Paint(long sender, ZPackage pkg) { if (pkg == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } int num = pkg.ReadInt(); if (num > 0 && num <= 256) { List<TerrainPainter.TilePaint> list = new List<TerrainPainter.TilePaint>(num); for (int i = 0; i < num; i++) { list.Add(new TerrainPainter.TilePaint { X = pkg.ReadInt(), Z = pkg.ReadInt(), R = pkg.ReadSingle(), PrevR = pkg.ReadSingle(), Stone = pkg.ReadBool() }); } HashSet<long> hashSet = TerrainPainter.ApplyTiles(list); AntTrailsPlugin.LogVerbose($"Paint batch: {num} tile(s) offered, {hashSet.Count} applied here."); SendPaintAck(sender, hashSet); } } private static void RPC_PaintAck(long sender, ZPackage pkg) { if (!TrailEngine.Active || pkg == null) { return; } int num = pkg.ReadInt(); if (num > 0 && num <= 256) { List<long> list = new List<long>(num); for (int i = 0; i < num; i++) { int x = pkg.ReadInt(); int z = pkg.ReadInt(); list.Add(TrailStore.TileKey(x, z)); } TrailEngine.ConfirmPainted(list); } } internal static void SendPaint(long targetPeerId, List<TerrainPainter.TilePaint> tiles) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (ZRoutedRpc.instance == null || tiles.Count == 0) { return; } ZPackage val = new ZPackage(); val.Write(tiles.Count); foreach (TerrainPainter.TilePaint tile in tiles) { val.Write(tile.X); val.Write(tile.Z); val.Write(tile.R); val.Write(tile.PrevR); val.Write(tile.Stone); } ZRoutedRpc.instance.InvokeRoutedRPC(targetPeerId, "AntTrails_Paint", new object[1] { val }); } private static void SendPaintAck(long targetPeerId, HashSet<long> handled) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown if (ZRoutedRpc.instance == null || handled.Count == 0) { return; } ZPackage val = new ZPackage(); val.Write(handled.Count); foreach (long item in handled) { TrailStore.SplitTile(item, out var x, out var z); val.Write(x); val.Write(z); } ZRoutedRpc.instance.InvokeRoutedRPC(targetPeerId, "AntTrails_PaintAck", new object[1] { val }); } } internal struct TileState { public float Charge; public float TotalSteps; public double LastTouch; public byte Stage; public float PaintedR; public bool PaintedStone; } internal static class TrailStore { private const int FileVersion = 1; private static readonly Dictionary<long, TileState> Tiles = new Dictionary<long, TileState>(); private static readonly Dictionary<long, HashSet<long>> ByZone = new Dictionary<long, HashSet<long>>(); private static string _path; private static bool _dirty; internal static int Count => Tiles.Count; internal static Dictionary<long, HashSet<long>> Zones => ByZone; internal static long TileKey(int x, int z) { return ((long)x << 32) | (uint)z; } internal static void SplitTile(long key, out int x, out int z) { x = (int)(key >> 32); z = (int)(key & 0xFFFFFFFFu); } internal static long ZoneKey(int zx, int zz) { return ((long)zx << 32) | (uint)zz; } internal static void SplitZone(long key, out int zx, out int zz) { zx = (int)(key >> 32); zz = (int)(key & 0xFFFFFFFFu); } internal static long ZoneKeyForTile(int x, int z) { int zx = (int)Math.Floor(((double)x + 32.0) / 64.0); int zz = (int)Math.Floor(((double)z + 32.0) / 64.0); return ZoneKey(zx, zz); } internal static bool TryGet(long key, out TileState state) { return Tiles.TryGetValue(key, out state); } internal static void Set(long key, in TileState state) { if (!Tiles.ContainsKey(key)) { long key2 = ZoneKeyForTile((int)(key >> 32), (int)(key & 0xFFFFFFFFu)); if (!ByZone.TryGetValue(key2, out var value)) { value = new HashSet<long>(); ByZone[key2] = value; } value.Add(key); } Tiles[key] = state; _dirty = true; } internal static void Clear() { Tiles.Clear(); ByZone.Clear(); _path = null; _dirty = false; } private static string PathFor(string worldName) { string text = Path.Combine(Paths.ConfigPath, "AntTrails"); Directory.CreateDirectory(text); char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { worldName = worldName.Replace(oldChar, '_'); } return Path.Combine(text, worldName + ".trails"); } internal static void Load(string worldName) { Clear(); _path = PathFor(worldName); if (!File.Exists(_path)) { AntTrailsPlugin.LogInfo("No trail data for world '" + worldName + "'; starting fresh."); return; } try { using (FileStream input = File.OpenRead(_path)) { using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num != 1) { AntTrailsPlugin.LogWarning($"Trail data for '{worldName}' is version {num}, expected {1}. " + "Ignoring it; wear counters restart but painted terrain is unaffected."); return; } int num2 = binaryReader.ReadInt32(); for (int i = 0; i < num2; i++) { int x = binaryReader.ReadInt32(); int z = binaryReader.ReadInt32(); TileState state = new TileState { Charge = binaryReader.ReadSingle(), TotalSteps = binaryReader.ReadSingle(), LastTouch = binaryReader.ReadDouble(), Stage = binaryReader.ReadByte(), PaintedR = binaryReader.ReadSingle(), PaintedStone = binaryReader.ReadBoolean() }; Set(TileKey(x, z), in state); } } _dirty = false; AntTrailsPlugin.LogInfo($"Loaded {Tiles.Count} worn tile(s) for world '{worldName}'."); } catch (Exception ex) { AntTrailsPlugin.LogError("Could not read trail data at " + _path + ": " + ex.Message + ". Wear counters restart; painted terrain is unaffected."); Clear(); _path = PathFor(worldName); } } internal static void Save(bool force = false) { if (_path == null || (!_dirty && !force)) { return; } try { string text = _path + ".tmp"; using (FileStream output = File.Create(text)) { using BinaryWriter binaryWriter = new BinaryWriter(output); binaryWriter.Write(1); binaryWriter.Write(Tiles.Count); foreach (KeyValuePair<long, TileState> tile in Tiles) { SplitTile(tile.Key, out var x, out var z); binaryWriter.Write(x); binaryWriter.Write(z); binaryWriter.Write(tile.Value.Charge); binaryWriter.Write(tile.Value.TotalSteps); binaryWriter.Write(tile.Value.LastTouch); binaryWriter.Write(tile.Value.Stage); binaryWriter.Write(tile.Value.PaintedR); binaryWriter.Write(tile.Value.PaintedStone); } } if (File.Exists(_path)) { File.Delete(_path); } File.Move(text, _path); _dirty = false; AntTrailsPlugin.LogVerbose($"Saved {Tiles.Count} worn tile(s)."); } catch (Exception ex) { AntTrailsPlugin.LogError("Could not write trail data to " + _path + ": " + ex.Message); } } } } namespace AntTrails.Patches { [HarmonyPatch(typeof(Game), "Start")] internal static class GameStartPatch { private static void Postfix() { TrailNetwork.Register(); StepReporter.Reset(); if ((Object)(object)ZNet.instance == (Object)null) { return; } if (!ZNet.instance.IsServer()) { AntTrailsPlugin.LogInfo("Client session: reporting steps to the host."); return; } string text = ((ZNet.World != null) ? ZNet.World.m_worldName : ZNet.instance.GetWorldName()); if (string.IsNullOrEmpty(text)) { text = "unknown"; } TrailEngine.Begin(text); AntTrailsPlugin.LogInfo("Host session for '" + text + "': tracking trail wear."); } } [HarmonyPatch(typeof(ZNet), "SaveWorld")] internal static class ZNetSaveWorldPatch { private static void Postfix() { TrailStore.Save(); } } [HarmonyPatch(typeof(ZNet), "Shutdown")] internal static class ZNetShutdownPatch { private static void Prefix(bool save) { if (save) { TrailStore.Save(force: true); } TrailEngine.End(); TrailNetwork.Reset(); StepReporter.Reset(); } } [HarmonyPatch(typeof(ZNet), "ShutdownWithoutSave")] internal static class ZNetShutdownWithoutSavePatch { private static void Prefix() { TrailEngine.End(); TrailNetwork.Reset(); StepReporter.Reset(); } } [HarmonyPatch(typeof(TerrainComp), "Awake")] internal static class TerrainCompAwakePatch { private static void Postfix(TerrainComp __instance) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_002d: Unknown result type (might be due to invalid IL or missing references) if (TrailEngine.Active && !((Object)(object)__instance.m_hmap == (Object)null)) { Vector2s zone = ZoneSystem.GetZone(((Component)__instance).transform.position); TrailEngine.OnZoneLoaded(zone.x, zone.y); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }