Decompiled source of AntTrails v0.1.2

plugins/AntTrails.dll

Decompiled 9 hours ago
using 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("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2+dfbd84d6540ff099aae56d941403062ce14f6d7e")]
[assembly: AssemblyProduct("AntTrails")]
[assembly: AssemblyTitle("AntTrails")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.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", 60f, 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", 4f, new ConfigDescription("In-game days of no traffic for an unfinished tile to lose ALL its accumulated progress. Progress leaks continuously, so a route walked once a week never becomes a path while one walked daily does.", (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", 1200f, 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.004f, 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 checks which tile it is standing on. Local setting; it affects only how finely your own movement is traced.", (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", "0.1.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 = "0.1.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 v0.1.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 static readonly HashSet<long> Pending = new HashSet<long>();

		private static float _sampleTimer;

		private static float _sendTimer;

		private static bool _haveLastTile;

		private static int _lastX;

		private static int _lastZ;

		internal static void Reset()
		{
			Pending.Clear();
			_sampleTimer = 0f;
			_sendTimer = 0f;
			_haveLastTile = 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_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead())
			{
				_haveLastTile = false;
				return;
			}
			if (!((Character)localPlayer).IsOnGround() || ((Character)localPlayer).IsSwimming() || ((Character)localPlayer).InWater() || ((Character)localPlayer).InLiquid())
			{
				_haveLastTile = false;
				return;
			}
			Collider lastGroundCollider = ((Character)localPlayer).GetLastGroundCollider();
			if ((Object)(object)lastGroundCollider == (Object)null)
			{
				_haveLastTile = false;
				return;
			}
			Heightmap component = ((Component)lastGroundCollider).GetComponent<Heightmap>();
			if ((Object)(object)component == (Object)null)
			{
				_haveLastTile = false;
				return;
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			int num = Mathf.FloorToInt(position.x);
			int num2 = Mathf.FloorToInt(position.z);
			if (!_haveLastTile || num != _lastX || num2 != _lastZ)
			{
				_haveLastTile = true;
				_lastX = num;
				_lastZ = num2;
				if (IsEligible(component, position) && Pending.Count < 512)
				{
					Pending.Add(TrailStore.TileKey(num, num2));
				}
			}
		}

		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
	{
		private struct PaintEntry
		{
			public int X;

			public int Y;

			public float R;

			public bool Stone;
		}

		internal const string PaintRpc = "AntTrails_Paint";

		private static readonly List<Heightmap> HmapBuffer = new List<Heightmap>();

		internal static HashSet<long> Apply(HashSet<long> dirty)
		{
			//IL_0063: 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)
			HashSet<long> hashSet = new HashSet<long>();
			if (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 (long item in dirty)
			{
				TrailStore.SplitTile(item, out var x, out var z);
				if (!TrailStore.TryGet(item, out var state))
				{
					continue;
				}
				Vector3 val = new Vector3((float)x + 0.5f, 0f, (float)z + 0.5f);
				HmapBuffer.Clear();
				Heightmap.FindHeightmap(val, 1f, HmapBuffer);
				if (HmapBuffer.Count == 0)
				{
					continue;
				}
				bool flag = state.Stage == 2;
				float r = (flag ? 1f : Mathf.Clamp01(state.Charge / Mathf.Max(1f, AntTrailsConfig.StepsToPath.Value)));
				bool flag2 = false;
				foreach (Heightmap item2 in HmapBuffer)
				{
					item2.WorldToVertexMask(new Vector3((float)x, 0f, (float)z), ref num, ref num2);
					int num3 = item2.m_width + 1;
					if (num < 0 || num2 < 0 || num >= num3 || num2 >= num3)
					{
						continue;
					}
					TerrainComp andCreateTerrainCompiler = item2.GetAndCreateTerrainCompiler();
					if (!((Object)(object)andCreateTerrainCompiler == (Object)null))
					{
						if (!dictionary.TryGetValue(andCreateTerrainCompiler, out var value))
						{
							value = (dictionary[andCreateTerrainCompiler] = new List<PaintEntry>());
						}
						value.Add(new PaintEntry
						{
							X = num,
							Y = num2,
							R = r,
							Stone = flag
						});
						flag2 = true;
					}
				}
				if (flag2)
				{
					hashSet.Add(item);
				}
			}
			foreach (KeyValuePair<TerrainComp, List<PaintEntry>> item3 in dictionary)
			{
				Send(item3.Key, item3.Value);
			}
			return hashSet;
		}

		private static void Send(TerrainComp comp, List<PaintEntry> entries)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			ZNetView nview = comp.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return;
			}
			ZPackage val = new ZPackage();
			val.Write(entries.Count);
			foreach (PaintEntry entry in entries)
			{
				val.Write(entry.X);
				val.Write(entry.Y);
				val.Write(entry.R);
				val.Write(entry.Stone);
			}
			nview.InvokeRPC("AntTrails_Paint", new object[1] { val });
		}

		internal static void HandlePaintRpc(TerrainComp comp, long sender, ZPackage pkg)
		{
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: 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_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: 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)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)comp == (Object)null || (Object)(object)comp.m_nview == (Object)null || !comp.m_nview.IsOwner())
			{
				return;
			}
			Heightmap hmap = comp.m_hmap;
			if ((Object)(object)hmap == (Object)null || !comp.m_initialized)
			{
				return;
			}
			int num = pkg.ReadInt();
			int num2 = comp.m_width + 1;
			int num3 = 0;
			float num4 = float.MaxValue;
			float num5 = float.MinValue;
			float num6 = float.MaxValue;
			float num7 = float.MinValue;
			for (int i = 0; i < num; i++)
			{
				int num8 = pkg.ReadInt();
				int num9 = pkg.ReadInt();
				float num10 = pkg.ReadSingle();
				bool flag = pkg.ReadBool();
				if (num8 < 0 || num9 < 0 || num8 >= num2 || num9 >= num2)
				{
					continue;
				}
				int num11 = num9 * num2 + num8;
				Color paintMask = hmap.GetPaintMask(num8, num9);
				if (paintMask.g > 0.5f || paintMask.b > 0.5f)
				{
					continue;
				}
				Color val = paintMask;
				if (flag)
				{
					val.r = 0f;
					val.g = 0f;
					val.b = 1f;
				}
				else
				{
					val.r = Mathf.Clamp01(num10);
				}
				val.a = paintMask.a;
				if (!comp.m_modifiedPaint[num11] || !(Mathf.Abs(comp.m_paintMask[num11].r - val.r) < 0.001f) || !(Mathf.Abs(comp.m_paintMask[num11].b - val.b) < 0.001f))
				{
					comp.m_modifiedPaint[num11] = true;
					comp.m_paintMask[num11] = val;
					num3++;
					float num12 = ((Component)hmap).transform.position.x + (float)(num8 - num2 / 2);
					float num13 = ((Component)hmap).transform.position.z + (float)(num9 - num2 / 2);
					if (num12 < num4)
					{
						num4 = num12;
					}
					if (num12 > num5)
					{
						num5 = num12;
					}
					if (num13 < num6)
					{
						num6 = num13;
					}
					if (num13 > num7)
					{
						num7 = num13;
					}
				}
			}
			if (num3 != 0)
			{
				comp.Save();
				hmap.Poke(false);
				if ((Object)(object)ClutterSystem.instance != (Object)null)
				{
					Vector3 val2 = default(Vector3);
					((Vector3)(ref val2))..ctor((num4 + num5) * 0.5f, 0f, (num6 + num7) * 0.5f);
					float num14 = Mathf.Max(num5 - num4, num7 - num6) * 0.5f + 2f;
					ClutterSystem.instance.ResetGrass(val2, num14);
				}
				AntTrailsPlugin.LogVerbose($"Painted {num3} vertex/vertices on terrain at {((Component)hmap).transform.position}.");
			}
		}
	}
	internal static class TrailEngine
	{
		private const float RepaintEpsilon = 0.06f;

		private static readonly HashSet<long> Dirty = new HashSet<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 1200.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();
			_flushTimer = 0f;
			_sweepTimer = 0f;
			Active = true;
		}

		internal static void End()
		{
			if (Active)
			{
				TrailStore.Save(force: true);
				TrailStore.Clear();
				Dirty.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 (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()
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			double worldTime = WorldTime;
			foreach (KeyValuePair<long, HashSet<long>> zone in TrailStore.Zones)
			{
				TrailStore.SplitZone(zone.Key, out var zx, out var zz);
				if (!instance.IsZoneLoaded(new Vector2i(zx, zz)))
				{
					continue;
				}
				foreach (long item in zone.Value)
				{
					if (TrailStore.TryGet(item, out var state) && state.Stage != 2)
					{
						Advance(ref state, worldTime);
						TrailStore.Set(item, in state);
						MarkIfChanged(item, in state);
					}
				}
			}
		}

		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;
			}
			HashSet<long> hashSet = TerrainPainter.Apply(Dirty);
			foreach (long item in hashSet)
			{
				if (TrailStore.TryGet(item, out var state))
				{
					if (state.Stage == 2)
					{
						state.PaintedStone = true;
						state.PaintedR = 1f;
					}
					else
					{
						state.PaintedR = DesiredR(in state);
					}
					TrailStore.Set(item, in state);
					Dirty.Remove(item);
				}
			}
			if (hashSet.Count > 0)
			{
				AntTrailsPlugin.LogVerbose($"Flushed {hashSet.Count} tile(s); {Dirty.Count} deferred.");
			}
		}
	}
	internal static class TrailNetwork
	{
		internal const string StepsRpc = "AntTrails_Steps";

		private static bool _registered;

		internal static void Register()
		{
			if (!_registered && ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<ZPackage>("AntTrails_Steps", (Action<long, ZPackage>)RPC_Steps);
				_registered = true;
				AntTrailsPlugin.LogInfo("Registered step-reporting RPC.");
			}
		}

		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);
			}
		}
	}
	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_fileName : 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: 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_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			ZNetView nview = __instance.m_nview;
			if (!((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				nview.Register<ZPackage>("AntTrails_Paint", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
				{
					TerrainPainter.HandlePaintRpc(__instance, sender, pkg);
				});
				if (!((Object)(object)__instance.m_hmap == (Object)null))
				{
					Vector2i 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)
		{
		}
	}
}