Decompiled source of FehuRoads v420.0.7

plugins/FehuRoads/FehuRoads.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using Forteca.FehuRoads.Commands;
using Forteca.FehuRoads.Core;
using Forteca.FehuRoads.Detection;
using Forteca.FehuRoads.Integration;
using Forteca.FehuRoads.Networking;
using Forteca.FehuRoads.Plugin;
using Forteca.FehuRoads.Rendering;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyTitle("FehuRoads")]
[assembly: AssemblyFileVersion("0.1.7.0")]
[assembly: AssemblyVersion("0.1.7.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 Forteca.FehuRoads.Rendering
{
	internal static class FcwTacticalMapBridge
	{
		internal static void BeforeClanMapDrawings(Rect __0, Vector2 __1, float __2, float __3)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			FehuRoadsPlugin instance = FehuRoadsPlugin.Instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.Overlay != (Object)null)
			{
				instance.Overlay.DrawClanMapLayer(__0, __1, __2, __3);
			}
		}
	}
	internal sealed class RoadMapOverlay : MonoBehaviour
	{
		private const float ClanMapWorldRadius = 10500f;

		private readonly List<KeyValuePair<long, RoadType>> _visible = new List<KeyValuePair<long, RoadType>>();

		private readonly List<KeyValuePair<long, RoadType>> _clanMapRoads = new List<KeyValuePair<long, RoadType>>();

		private Rect _lastUv;

		private int _lastMode = -1;

		private int _lastClanMapCount = -1;

		private int _clanMapLastFrame = -100;

		private int _lastVanillaDirt = -1;

		private int _lastVanillaPaved = -1;

		private int _lastVanillaRendered = -1;

		private int _lastClanDirt = -1;

		private int _lastClanPaved = -1;

		private int _lastClanRendered = -1;

		private bool _dirty = true;

		private bool _clanMapDirty = true;

		private bool _clanMapOpen;

		private static readonly FieldInfo Explored = typeof(Minimap).GetField("m_explored", BindingFlags.Instance | BindingFlags.NonPublic);

		private void Awake()
		{
			FehuRoadsPlugin instance = FehuRoadsPlugin.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				instance.RoadDataChanged += delegate
				{
					Invalidate();
				};
				instance.Roads.ChunkChanged += delegate
				{
					Invalidate();
				};
				instance.LogInfo("[FehuRoads] Minimap hook installed");
				instance.LogInfo("[FehuRoads] Large map overlay initialized");
			}
		}

		internal void Invalidate()
		{
			_dirty = true;
			_clanMapDirty = true;
		}

		private void Update()
		{
			if (_clanMapOpen && Time.frameCount - _clanMapLastFrame > 2)
			{
				_clanMapOpen = false;
				_lastClanMapCount = -1;
			}
		}

		private void Rebuild(Minimap minimap, RawImage image)
		{
			//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_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between I4 and Unknown
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0042: Expected I4, but got Unknown
			//IL_0043: 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)
			Rect uvRect = image.uvRect;
			if (_dirty || _lastMode != (int)minimap.m_mode || !(uvRect == _lastUv))
			{
				_lastMode = (int)minimap.m_mode;
				_lastUv = uvRect;
				_dirty = false;
				float num = (float)minimap.m_textureSize * minimap.m_pixelSize;
				FehuRoadsPlugin.Instance.Roads.Query(Mathf.FloorToInt((((Rect)(ref uvRect)).xMin - 0.5f) * num), Mathf.FloorToInt((((Rect)(ref uvRect)).yMin - 0.5f) * num), Mathf.CeilToInt((((Rect)(ref uvRect)).xMax - 0.5f) * num), Mathf.CeilToInt((((Rect)(ref uvRect)).yMax - 0.5f) * num), _visible);
			}
		}

		private void OnGUI()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Invalid comparison between Unknown and I4
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Invalid comparison between Unknown and I4
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			FehuRoadsPlugin instance = FehuRoadsPlugin.Instance;
			Minimap instance2 = Minimap.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && (int)Event.current.type == 7)
			{
				bool flag = (int)instance2.m_mode == 2;
				bool flag2 = (int)instance2.m_mode == 1;
				if ((!flag || instance.LargeMap.Value) && (!flag2 || instance.MiniMap.Value) && (flag || flag2))
				{
					DrawVanillaMap(instance2, flag ? instance2.m_mapImageLarge : instance2.m_mapImageSmall);
				}
			}
		}

		private void DrawVanillaMap(Minimap minimap, RawImage image)
		{
			//IL_0089: 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_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0309: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Invalid comparison between Unknown and I4
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)image == (Object)null)
			{
				return;
			}
			Rebuild(minimap, image);
			Vector3[] array = (Vector3[])(object)new Vector3[4];
			((Graphic)image).rectTransform.GetWorldCorners(array);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(array[0].x, (float)Screen.height - array[2].y, array[2].x - array[0].x, array[2].y - array[0].y);
			Rect uvRect = image.uvRect;
			float num = (float)minimap.m_textureSize * minimap.m_pixelSize;
			float num2 = ((Rect)(ref val)).width / (num * ((Rect)(ref uvRect)).width);
			float num3 = ((Rect)(ref val)).height / (num * ((Rect)(ref uvRect)).height);
			FehuRoadsPlugin instance = FehuRoadsPlugin.Instance;
			GUI.BeginGroup(val);
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			foreach (KeyValuePair<long, RoadType> item in _visible)
			{
				RoadDatabase.Decode(item.Key, out var x, out var z);
				RoadType value = item.Value;
				if (!TypeEnabled(instance, value))
				{
					continue;
				}
				float num7 = ((float)x / minimap.m_pixelSize + (float)minimap.m_textureSize / 2f) / (float)minimap.m_textureSize;
				float num8 = ((float)z / minimap.m_pixelSize + (float)minimap.m_textureSize / 2f) / (float)minimap.m_textureSize;
				if (IsExplored(minimap, num7, num8, instance.RespectFog.Value))
				{
					float num9 = (num7 - ((Rect)(ref uvRect)).x) / ((Rect)(ref uvRect)).width * ((Rect)(ref val)).width;
					float num10 = (1f - (num8 - ((Rect)(ref uvRect)).y) / ((Rect)(ref uvRect)).height) * ((Rect)(ref val)).height;
					float num11 = ((value == RoadType.Paved) ? instance.PavedWidth.Value : instance.DirtWidth.Value);
					float num12 = Mathf.Max(num11, num2 + 0.35f);
					float num13 = Mathf.Max(num11, num3 + 0.35f);
					GUI.color = ((value == RoadType.Paved) ? instance.PavedColor : instance.DirtColor);
					GUI.DrawTexture(new Rect(num9 - num12 * 0.5f, num10 - num13 * 0.5f, num12, num13), (Texture)(object)Texture2D.whiteTexture);
					switch (value)
					{
					case RoadType.Dirt:
						num4++;
						break;
					case RoadType.Paved:
						num5++;
						break;
					}
					num6++;
				}
			}
			GUI.EndGroup();
			GUI.color = Color.white;
			if (num4 != _lastVanillaDirt || num5 != _lastVanillaPaved || num6 != _lastVanillaRendered)
			{
				_lastVanillaDirt = num4;
				_lastVanillaPaved = num5;
				_lastVanillaRendered = num6;
				instance.LogInfo("[FehuRoads] render surface=" + (((int)minimap.m_mode == 2) ? "large-map" : "mini-map") + " dirt=" + num4 + " paved=" + num5 + " total=" + num6);
			}
		}

		internal void DrawClanMapLayer(Rect local, Vector2 center, float baseDiameter, float zoom)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Invalid comparison between Unknown and I4
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: 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_01f2: 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_0217: Unknown result type (might be due to invalid IL or missing references)
			FehuRoadsPlugin instance = FehuRoadsPlugin.Instance;
			if ((Object)(object)instance == (Object)null || !instance.KingdomsMap.Value || (int)Event.current.type != 7)
			{
				return;
			}
			_clanMapLastFrame = Time.frameCount;
			if (!_clanMapOpen)
			{
				_clanMapOpen = true;
				_clanMapDirty = true;
				instance.LogInfo("[FehuRoads] FCW tactical UI OPEN");
				instance.LogInfo("[FehuRoads] FCW tactical root path=IMGUI/FortecaClanWarfare.UI.ClanWorldMapPanel/DrawCanvas");
				instance.LogInfo("[FehuRoads] FCW tactical parent path=GUI.BeginGroup(canvas) instance=static");
				instance.LogInfo("[FehuRoads] FCW tactical layer attached after=DrawPolygons before=ClanMapDrawingUi.Render");
			}
			if (_clanMapDirty)
			{
				instance.Roads.Query(-10500, -10500, 10500, 10500, _clanMapRoads);
				_clanMapDirty = false;
			}
			float num = baseDiameter * 0.5f * zoom;
			float num2 = baseDiameter * zoom / 21000f;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			foreach (KeyValuePair<long, RoadType> clanMapRoad in _clanMapRoads)
			{
				RoadDatabase.Decode(clanMapRoad.Key, out var x, out var z);
				RoadType value = clanMapRoad.Value;
				if (!TypeEnabled(instance, value) || (double)x * (double)x + (double)z * (double)z > 110250000.0)
				{
					continue;
				}
				float num6 = center.x + (float)x / 10500f * num;
				float num7 = center.y - (float)z / 10500f * num;
				if (!(num6 < 0f) && !(num7 < 0f) && !(num6 > ((Rect)(ref local)).width) && !(num7 > ((Rect)(ref local)).height))
				{
					float num8 = ((value == RoadType.Paved) ? instance.PavedWidth.Value : instance.DirtWidth.Value);
					float num9 = Mathf.Max(num8, num2 + 0.35f);
					GUI.color = ((value == RoadType.Paved) ? instance.PavedColor : instance.DirtColor);
					GUI.DrawTexture(new Rect(num6 - num9 * 0.5f, num7 - num9 * 0.5f, num9, num9), (Texture)(object)Texture2D.whiteTexture);
					switch (value)
					{
					case RoadType.Dirt:
						num3++;
						break;
					case RoadType.Paved:
						num4++;
						break;
					}
					num5++;
				}
			}
			GUI.color = Color.white;
			if (num3 != _lastClanDirt || num4 != _lastClanPaved || num5 != _lastClanRendered)
			{
				_lastClanMapCount = num5;
				_lastClanDirt = num3;
				_lastClanPaved = num4;
				_lastClanRendered = num5;
				instance.LogInfo("[FehuRoads] render surface=kingdom-map dirt=" + num3 + " paved=" + num4 + " total=" + num5 + " canvas=" + ((Rect)(ref local)).width.ToString("F0") + "x" + ((Rect)(ref local)).height.ToString("F0") + " zoom=" + zoom.ToString("F2"));
			}
		}

		private static bool TypeEnabled(FehuRoadsPlugin plugin, RoadType type)
		{
			return type switch
			{
				RoadType.Dirt => plugin.DirtEnabled.Value, 
				RoadType.Paved => plugin.PavedEnabled.Value, 
				_ => false, 
			};
		}

		private static bool IsExplored(Minimap minimap, float mapX, float mapY, bool required)
		{
			if (!required)
			{
				return true;
			}
			bool[] array = ((Explored == null) ? null : (Explored.GetValue(minimap) as bool[]));
			int num = Mathf.RoundToInt(mapX * (float)minimap.m_textureSize);
			int num2 = Mathf.RoundToInt(mapY * (float)minimap.m_textureSize);
			return array != null && num >= 0 && num2 >= 0 && num < minimap.m_textureSize && num2 < minimap.m_textureSize && array[num2 * minimap.m_textureSize + num];
		}
	}
}
namespace Forteca.FehuRoads.Plugin
{
	[BepInPlugin("forteca.fehuroads", "Fehu Roads", "0.1.7")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class FehuRoadsPlugin : BaseUnityPlugin, IRoadDataProvider
	{
		internal static FehuRoadsPlugin Instance;

		private RoadDatabase _roads;

		private ClientRoadNetwork _network;

		private RoadMapOverlay _overlay;

		private Harmony _harmony;

		private long _worldUid;

		private bool _snapshotComplete;

		internal ConfigEntry<bool> Enabled;

		internal ConfigEntry<bool> LargeMap;

		internal ConfigEntry<bool> MiniMap;

		internal ConfigEntry<bool> KingdomsMap;

		internal ConfigEntry<bool> RespectFog;

		internal ConfigEntry<bool> DirtEnabled;

		internal ConfigEntry<bool> PavedEnabled;

		internal ConfigEntry<bool> NetworkEnabled;

		internal ConfigEntry<bool> DebugLogging;

		internal ConfigEntry<string> DirtColorHex;

		internal ConfigEntry<string> PavedColorHex;

		internal ConfigEntry<float> DirtWidth;

		internal ConfigEntry<float> PavedWidth;

		internal RoadDatabase Roads => _roads;

		internal long WorldUid => _worldUid;

		internal bool SnapshotComplete => _snapshotComplete;

		internal RoadMapOverlay Overlay => _overlay;

		internal bool FcwTacticalAttached { get; private set; }

		internal Color DirtColor
		{
			get
			{
				//IL_0030: 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_0031: 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)
				Color val = default(Color);
				return (Color)(ColorUtility.TryParseHtmlString(DirtColorHex.Value, ref val) ? val : new Color(0.94f, 0.64f, 0.24f, 1f));
			}
		}

		internal Color PavedColor
		{
			get
			{
				//IL_0030: 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_0031: 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)
				Color val = default(Color);
				return (Color)(ColorUtility.TryParseHtmlString(PavedColorHex.Value, ref val) ? val : new Color(0.7f, 0.71f, 0.73f, 0.82f));
			}
		}

		public event Action<RoadCell, RoadType> RoadDataChanged;

		private void Awake()
		{
			Instance = this;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable road display.");
			LargeMap = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowOnLargeMap", true, "Show roads on the M map.");
			MiniMap = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowOnMinimap", true, "Show roads on the HUD minimap.");
			KingdomsMap = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowOnKingdomsMap", true, "Show roads with the Clan Warfare map.");
			RespectFog = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "RespectFogOfWar", true, "Do not draw in unexplored map areas.");
			DirtEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("DirtRoad", "Enabled", true, "Show dirt roads.");
			PavedEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("PavedRoad", "Enabled", true, "Show paved roads.");
			DirtColorHex = ((BaseUnityPlugin)this).Config.Bind<string>("DirtRoad", "Color", "#F0A43CFF", "High-contrast packed-earth color and opacity.");
			PavedColorHex = ((BaseUnityPlugin)this).Config.Bind<string>("PavedRoad", "Color", "#B2B6BBD0", "Cool stone color and opacity.");
			DirtWidth = ((BaseUnityPlugin)this).Config.Bind<float>("DirtRoad", "WidthMultiplier", 3f, "Minimum display pixels.");
			PavedWidth = ((BaseUnityPlugin)this).Config.Bind<float>("PavedRoad", "WidthMultiplier", 2f, "Minimum display pixels.");
			NetworkEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Network", "EnableSynchronization", true, "Receive authoritative server road chunks.");
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableDebugLogging", false, "Enable diagnostic logging.");
			_roads = new RoadDatabase();
			_roads.Changed += ForwardChange;
			_network = new ClientRoadNetwork(this);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[FehuRoads] Client loaded version=0.1.7");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[FehuRoads] RPC/network initialized");
			InstallTerrainHook();
			if (!Application.isBatchMode)
			{
				_overlay = ((Component)this).gameObject.AddComponent<RoadMapOverlay>();
				RegisterConsoleCommandsSafe();
			}
			InstallFcwTacticalBridge();
		}

		private void RegisterConsoleCommandsSafe()
		{
			try
			{
				FehuRoadsCommands.Register(this);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[FehuRoads] Console commands registered");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[FehuRoads] Console command registration unavailable; continuing without optional commands: " + ex));
			}
		}

		private void InstallFcwTacticalBridge()
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			if (!Chainloader.PluginInfos.TryGetValue("com.forteca.clanwarfare", out var value) || value == null || (Object)(object)value.Instance == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[FehuRoads] FCW map bridge not present; vanilla maps remain enabled");
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[FehuRoads] FCW map bridge detected");
			try
			{
				Type type = ((object)value.Instance).GetType().Assembly.GetType("FortecaClanWarfare.UI.ClanMapDrawingUi", throwOnError: false);
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.DeclaredMethod(type, "Render", (Type[])null, (Type[])null));
				MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(FcwTacticalMapBridge), "BeforeClanMapDrawings", (Type[])null, (Type[])null);
				if (methodInfo == null || methodInfo2 == null)
				{
					throw new MissingMethodException("FortecaClanWarfare.UI.ClanMapDrawingUi.Render");
				}
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				FcwTacticalAttached = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[FehuRoads] FCW ClanMap bridge installed; waiting for actual ClanWorldMapPanel canvas");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[FehuRoads] FCW tactical road layer unavailable; vanilla maps remain active: " + ex.Message));
			}
		}

		private void InstallTerrainHook()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected O, but got Unknown
			_harmony = new Harmony("forteca.fehuroads.client");
			try
			{
				MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(TerrainOp), "Awake", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(TerrainPaintClient), "TerrainOpPostfix", (Type[])null, (Type[])null);
				if (methodInfo == null || methodInfo2 == null)
				{
					throw new MissingMethodException("TerrainOp.Awake()");
				}
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[FehuRoads] Terrain change hint hook installed: TerrainOp.Awake()");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("[FehuRoads] Terrain change hook failed; client networking and rendering remain active: " + ex));
			}
		}

		private void Update()
		{
			if (Enabled.Value)
			{
				_network.Update();
			}
		}

		private void ForwardChange(RoadCell cell, RoadType type)
		{
			this.RoadDataChanged?.Invoke(cell, type);
		}

		internal void BeginWorld(long uid)
		{
			if (_worldUid != uid)
			{
				_worldUid = uid;
				_snapshotComplete = false;
				_roads.Clear();
				InvalidateRender();
			}
		}

		internal void EndWorld()
		{
			if (_worldUid != 0)
			{
				_worldUid = 0L;
				_snapshotComplete = false;
				_roads.Clear();
				InvalidateRender();
			}
		}

		internal void BeginSnapshot()
		{
			_snapshotComplete = false;
			_roads.Clear();
			InvalidateRender();
		}

		internal void ApplyChunk(RoadChunkData chunk)
		{
			_roads.ReplaceChunk(chunk);
		}

		internal void CompleteSnapshot()
		{
			_snapshotComplete = true;
			InvalidateRender();
		}

		public RoadType GetRoadType(Vector3 p)
		{
			//IL_0007: 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)
			return _roads.Get(Mathf.RoundToInt(p.x), Mathf.RoundToInt(p.z));
		}

		internal string Stats()
		{
			int num = 0;
			int num2 = 0;
			foreach (KeyValuePair<long, RoadType> entry in _roads.Entries)
			{
				if (entry.Value == RoadType.Dirt)
				{
					num++;
				}
				else if (entry.Value == RoadType.Paved)
				{
					num2++;
				}
			}
			return "FehuRoads client protocol=" + 1 + " world=" + _worldUid.ToString("X") + " snapshot=" + _snapshotComplete + " chunks=" + _roads.ChunkCount + " dirt=" + num + " paved=" + num2;
		}

		internal void InvalidateRender()
		{
			if ((Object)(object)_overlay != (Object)null)
			{
				_overlay.Invalidate();
			}
		}

		internal void SendRoadUpdate(IList<KeyValuePair<long, RoadType>> cells)
		{
			_network.SendRoadUpdate(cells);
		}

		internal void LogInfo(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
		}

		internal void LogWarning(string message)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)message);
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
}
namespace Forteca.FehuRoads.Networking
{
	internal sealed class ClientRoadNetwork
	{
		private readonly FehuRoadsPlugin _plugin;

		private bool _registered;

		private bool _snapshotRequested;

		private long _requestedWorld;

		private ZRoutedRpc _routedRpc;

		internal ClientRoadNetwork(FehuRoadsPlugin plugin)
		{
			_plugin = plugin;
		}

		internal void Update()
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Invalid comparison between Unknown and I4
			if (!_plugin.NetworkEnabled.Value)
			{
				ResetSession();
				return;
			}
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null)
			{
				ResetSession();
				return;
			}
			if (_routedRpc != instance2)
			{
				_routedRpc = instance2;
				_registered = false;
				ResetWorld();
			}
			if (instance.IsServer() || (int)ZNet.GetConnectionStatus() != 2)
			{
				ResetWorld();
				return;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer == null || ZNet.World == null)
			{
				ResetWorld();
				return;
			}
			if (!_registered)
			{
				instance2.Register<ZPackage>("FehuRoads_Begin", (Action<long, ZPackage>)OnBegin);
				instance2.Register<ZPackage>("FehuRoads_Chunk", (Action<long, ZPackage>)OnChunk);
				instance2.Register<ZPackage>("FehuRoads_End", (Action<long, ZPackage>)OnEnd);
				_registered = true;
				_plugin.LogInfo("[FehuRoads] Protocol registered version=" + 1);
			}
			long worldUID = instance.GetWorldUID();
			if (worldUID != 0)
			{
				if (worldUID != _requestedWorld)
				{
					_requestedWorld = worldUID;
					_snapshotRequested = false;
					_plugin.BeginWorld(worldUID);
				}
				if (!_plugin.SnapshotComplete && !_snapshotRequested)
				{
					_snapshotRequested = true;
					instance2.InvokeRoutedRPC("FehuRoads_Request", new object[1] { RoadProtocol.Header(worldUID) });
					_plugin.LogInfo("[FehuRoads] Snapshot requested world=" + worldUID.ToString("X"));
				}
			}
		}

		private void ResetWorld()
		{
			if (_requestedWorld != 0)
			{
				_plugin.EndWorld();
			}
			_requestedWorld = 0L;
			_snapshotRequested = false;
		}

		private void ResetSession()
		{
			ResetWorld();
			_registered = false;
			_routedRpc = null;
		}

		internal void SendRoadUpdate(IList<KeyValuePair<long, RoadType>> cells)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Invalid comparison between Unknown and I4
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!_registered || cells == null || cells.Count == 0 || _plugin.WorldUid == 0L || (Object)(object)instance == (Object)null || instance2 == null || ZNet.World == null || instance.GetServerPeer() == null || (int)ZNet.GetConnectionStatus() != 2)
			{
				return;
			}
			instance2.InvokeRoutedRPC("FehuRoads_Update", new object[1] { RoadProtocol.EncodeUpdate(_plugin.WorldUid, cells) });
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			foreach (KeyValuePair<long, RoadType> cell in cells)
			{
				if (cell.Value == RoadType.Dirt)
				{
					num++;
				}
				else if (cell.Value == RoadType.Paved)
				{
					num2++;
				}
				else if (cell.Value == RoadType.None)
				{
					num3++;
				}
			}
			_plugin.LogInfo("[FehuRoads] road update type=DIRT cells=" + num + " type=PAVED cells=" + num2 + " removed=" + num3);
		}

		private bool IsServer(long sender)
		{
			ZNetPeer val = (((Object)(object)ZNet.instance == (Object)null) ? null : ZNet.instance.GetServerPeer());
			return val != null && val.m_uid == sender;
		}

		private void OnBegin(long sender, ZPackage package)
		{
			if (IsServer(sender) && RoadProtocol.ReadAndValidateHeader(package, _plugin.WorldUid))
			{
				_plugin.BeginSnapshot();
			}
		}

		private void OnChunk(long sender, ZPackage package)
		{
			if (!IsServer(sender) || !RoadProtocol.TryDecodeChunk(package, _plugin.WorldUid, out var chunk))
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			RoadChunkData chunk2 = _plugin.Roads.GetChunk(chunk.Coord);
			HashSet<long> hashSet = new HashSet<long>();
			KeyValuePair<long, RoadType>[] cells = chunk.Cells;
			for (int i = 0; i < cells.Length; i++)
			{
				KeyValuePair<long, RoadType> keyValuePair = cells[i];
				hashSet.Add(keyValuePair.Key);
				if (keyValuePair.Value == RoadType.Dirt)
				{
					num++;
				}
				else if (keyValuePair.Value == RoadType.Paved)
				{
					num2++;
				}
			}
			int num3 = 0;
			KeyValuePair<long, RoadType>[] cells2 = chunk2.Cells;
			foreach (KeyValuePair<long, RoadType> keyValuePair2 in cells2)
			{
				if (!hashSet.Contains(keyValuePair2.Key))
				{
					num3++;
				}
			}
			_plugin.ApplyChunk(chunk);
			_plugin.LogInfo("[FehuRoads] delta dirt=" + num + " paved=" + num2 + " removed=" + num3);
		}

		private void OnEnd(long sender, ZPackage package)
		{
			if (!IsServer(sender) || !RoadProtocol.ReadAndValidateHeader(package, _plugin.WorldUid))
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			foreach (KeyValuePair<long, RoadType> entry in _plugin.Roads.Entries)
			{
				if (entry.Value == RoadType.Dirt)
				{
					num++;
				}
				else if (entry.Value == RoadType.Paved)
				{
					num2++;
				}
			}
			_plugin.CompleteSnapshot();
			_plugin.LogInfo("[FehuRoads] snapshot dirt=" + num + " paved=" + num2);
		}
	}
}
namespace Forteca.FehuRoads.Integration
{
	public interface IRoadDataProvider
	{
		event Action<RoadCell, RoadType> RoadDataChanged;

		RoadType GetRoadType(Vector3 worldPosition);
	}
}
namespace Forteca.FehuRoads.Detection
{
	internal static class TerrainPaintClient
	{
		internal static void TerrainOpPostfix(TerrainOp __instance)
		{
			//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_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: 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_0087: 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_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected I4, but got Unknown
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: 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_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			Vector3 position = ((Component)__instance).transform.position;
			Settings settings = __instance.m_settings;
			FehuRoadsPlugin instance = FehuRoadsPlugin.Instance;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)instance == (Object)null || (Object)(object)localPlayer == (Object)null || settings == null || !settings.m_paintCleared || Vector3.Distance(((Component)localPlayer).transform.position, position) > 32f)
			{
				return;
			}
			PaintType paintType = settings.m_paintType;
			PaintType val = paintType;
			RoadType value;
			switch ((int)val)
			{
			default:
				return;
			case 0:
				value = RoadType.Dirt;
				break;
			case 2:
				value = RoadType.Paved;
				break;
			case 1:
			case 3:
				value = RoadType.None;
				break;
			}
			float num = Mathf.Clamp(settings.m_paintRadius, 0.5f, 12f);
			int num2 = Mathf.FloorToInt(position.x - num);
			int num3 = Mathf.CeilToInt(position.x + num);
			int num4 = Mathf.FloorToInt(position.z - num);
			int num5 = Mathf.CeilToInt(position.z + num);
			List<KeyValuePair<long, RoadType>> list = new List<KeyValuePair<long, RoadType>>();
			float num6 = num * num;
			for (int i = num4; i <= num5; i++)
			{
				for (int j = num2; j <= num3; j++)
				{
					Vector2 val2 = new Vector2((float)j - position.x, (float)i - position.z);
					if (((Vector2)(ref val2)).sqrMagnitude <= num6)
					{
						list.Add(new KeyValuePair<long, RoadType>(RoadDatabase.Key(j, i), value));
					}
				}
			}
			instance.LogInfo("[FehuRoads] detected type=" + value.ToString().ToUpperInvariant() + " cells=" + list.Count);
			instance.SendRoadUpdate(list);
		}
	}
}
namespace Forteca.FehuRoads.Core
{
	public struct RoadCell
	{
		public readonly int X;

		public readonly int Z;

		public RoadCell(int x, int z)
		{
			X = x;
			Z = z;
		}
	}
	public struct RoadChunkCoord : IEquatable<RoadChunkCoord>
	{
		public readonly int X;

		public readonly int Z;

		public RoadChunkCoord(int x, int z)
		{
			X = x;
			Z = z;
		}

		public bool Equals(RoadChunkCoord o)
		{
			return X == o.X && Z == o.Z;
		}

		public override int GetHashCode()
		{
			return (X * 486187739) ^ Z;
		}
	}
	public sealed class RoadChunkData
	{
		public readonly RoadChunkCoord Coord;

		public readonly uint Revision;

		public readonly KeyValuePair<long, RoadType>[] Cells;

		public RoadChunkData(RoadChunkCoord c, uint r, KeyValuePair<long, RoadType>[] a)
		{
			Coord = c;
			Revision = r;
			Cells = a;
		}
	}
	public sealed class RoadDatabase
	{
		public const int ChunkSize = 32;

		private readonly Dictionary<long, RoadType> _cells = new Dictionary<long, RoadType>();

		private readonly Dictionary<RoadChunkCoord, HashSet<long>> _chunks = new Dictionary<RoadChunkCoord, HashSet<long>>();

		private readonly Dictionary<RoadChunkCoord, uint> _revisions = new Dictionary<RoadChunkCoord, uint>();

		public int Count => _cells.Count;

		public int ChunkCount => _chunks.Count;

		public IEnumerable<KeyValuePair<long, RoadType>> Entries => _cells;

		public IEnumerable<RoadChunkData> Chunks
		{
			get
			{
				foreach (RoadChunkCoord c in _chunks.Keys)
				{
					yield return GetChunk(c);
				}
			}
		}

		public event Action<RoadCell, RoadType> Changed;

		public event Action<RoadChunkCoord, uint> ChunkChanged;

		public static long Key(int x, int z)
		{
			return ((long)x << 32) ^ (uint)z;
		}

		public static void Decode(long k, out int x, out int z)
		{
			x = (int)(k >> 32);
			z = (int)k;
		}

		private static int FloorDiv(int n, int d)
		{
			int num = n / d;
			int num2 = n % d;
			return (num2 < 0) ? (num - 1) : num;
		}

		public static RoadChunkCoord ChunkOf(int x, int z)
		{
			return new RoadChunkCoord(FloorDiv(x, 32), FloorDiv(z, 32));
		}

		public RoadType Get(int x, int z)
		{
			RoadType value;
			return _cells.TryGetValue(Key(x, z), out value) ? value : RoadType.None;
		}

		public bool Set(int x, int z, RoadType t)
		{
			long num = Key(x, z);
			RoadType roadType = Get(x, z);
			if (roadType == t)
			{
				return false;
			}
			RoadChunkCoord roadChunkCoord = ChunkOf(x, z);
			if (t == RoadType.None)
			{
				_cells.Remove(num);
				if (_chunks.TryGetValue(roadChunkCoord, out var value))
				{
					value.Remove(num);
					if (value.Count == 0)
					{
						_chunks.Remove(roadChunkCoord);
					}
				}
			}
			else
			{
				_cells[num] = t;
				if (!_chunks.TryGetValue(roadChunkCoord, out var value2))
				{
					value2 = new HashSet<long>();
					_chunks[roadChunkCoord] = value2;
				}
				value2.Add(num);
			}
			Touch(roadChunkCoord);
			this.Changed?.Invoke(new RoadCell(x, z), t);
			return true;
		}

		private void Touch(RoadChunkCoord c)
		{
			_revisions.TryGetValue(c, out var value);
			value++;
			_revisions[c] = value;
			this.ChunkChanged?.Invoke(c, value);
		}

		public void Query(int minX, int minZ, int maxX, int maxZ, List<KeyValuePair<long, RoadType>> dst)
		{
			dst.Clear();
			RoadChunkCoord roadChunkCoord = ChunkOf(minX, minZ);
			RoadChunkCoord roadChunkCoord2 = ChunkOf(maxX, maxZ);
			for (int i = roadChunkCoord.Z; i <= roadChunkCoord2.Z; i++)
			{
				for (int j = roadChunkCoord.X; j <= roadChunkCoord2.X; j++)
				{
					if (!_chunks.TryGetValue(new RoadChunkCoord(j, i), out var value))
					{
						continue;
					}
					foreach (long item in value)
					{
						Decode(item, out var x, out var z);
						if (x >= minX && x <= maxX && z >= minZ && z <= maxZ)
						{
							dst.Add(new KeyValuePair<long, RoadType>(item, _cells[item]));
						}
					}
				}
			}
		}

		public RoadChunkData GetChunk(RoadChunkCoord c)
		{
			_revisions.TryGetValue(c, out var value);
			if (!_chunks.TryGetValue(c, out var value2))
			{
				return new RoadChunkData(c, value, new KeyValuePair<long, RoadType>[0]);
			}
			KeyValuePair<long, RoadType>[] array = new KeyValuePair<long, RoadType>[value2.Count];
			int num = 0;
			foreach (long item in value2)
			{
				array[num++] = new KeyValuePair<long, RoadType>(item, _cells[item]);
			}
			return new RoadChunkData(c, value, array);
		}

		public bool ReplaceChunk(RoadChunkData d)
		{
			_revisions.TryGetValue(d.Coord, out var value);
			if (d.Revision <= value)
			{
				return false;
			}
			if (_chunks.TryGetValue(d.Coord, out var value2))
			{
				foreach (long item in value2)
				{
					_cells.Remove(item);
				}
				_chunks.Remove(d.Coord);
			}
			if (d.Cells.Length != 0)
			{
				HashSet<long> hashSet = new HashSet<long>();
				KeyValuePair<long, RoadType>[] cells = d.Cells;
				for (int i = 0; i < cells.Length; i++)
				{
					KeyValuePair<long, RoadType> keyValuePair = cells[i];
					if (keyValuePair.Value == RoadType.Dirt || keyValuePair.Value == RoadType.Paved)
					{
						_cells[keyValuePair.Key] = keyValuePair.Value;
						hashSet.Add(keyValuePair.Key);
					}
				}
				if (hashSet.Count > 0)
				{
					_chunks[d.Coord] = hashSet;
				}
			}
			_revisions[d.Coord] = d.Revision;
			this.ChunkChanged?.Invoke(d.Coord, d.Revision);
			return true;
		}

		public void Clear()
		{
			_cells.Clear();
			_chunks.Clear();
			_revisions.Clear();
		}

		public void Write(BinaryWriter w)
		{
			w.Write(_cells.Count);
			foreach (KeyValuePair<long, RoadType> cell in _cells)
			{
				w.Write(cell.Key);
				w.Write((byte)cell.Value);
			}
		}

		public void Read(BinaryReader r)
		{
			Clear();
			int num = r.ReadInt32();
			if (num < 0 || num > 20000000)
			{
				throw new InvalidDataException("Invalid road-cell count");
			}
			for (int i = 0; i < num; i++)
			{
				long k = r.ReadInt64();
				RoadType roadType = (RoadType)r.ReadByte();
				if (roadType == RoadType.Dirt || roadType == RoadType.Paved)
				{
					Decode(k, out var x, out var z);
					Set(x, z, roadType);
				}
			}
		}
	}
	public static class RoadProtocol
	{
		public const int Version = 1;

		public const int MaxCellsPerChunk = 2048;

		public const string RequestRpc = "FehuRoads_Request";

		public const string UpdateRpc = "FehuRoads_Update";

		public const string BeginRpc = "FehuRoads_Begin";

		public const string ChunkRpc = "FehuRoads_Chunk";

		public const string EndRpc = "FehuRoads_End";

		public const int MaxUpdateCells = 512;

		public static ZPackage Header(long worldUid)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(worldUid);
			return val;
		}

		public static bool ReadAndValidateHeader(ZPackage package, long expectedWorldUid)
		{
			try
			{
				return package.ReadInt() == 1 && package.ReadLong() == expectedWorldUid;
			}
			catch
			{
				return false;
			}
		}

		public static ZPackage EncodeUpdate(long worldUid, IList<KeyValuePair<long, RoadType>> cells)
		{
			ZPackage val = Header(worldUid);
			val.Write(cells.Count);
			for (int i = 0; i < cells.Count; i++)
			{
				val.Write(cells[i].Key);
				val.Write((byte)cells[i].Value);
			}
			return val;
		}

		public static bool TryDecodeUpdate(ZPackage package, long expectedWorldUid, out KeyValuePair<long, RoadType>[] cells)
		{
			cells = null;
			if (!ReadAndValidateHeader(package, expectedWorldUid))
			{
				return false;
			}
			try
			{
				int num = package.ReadInt();
				if (num < 1 || num > 512)
				{
					return false;
				}
				cells = new KeyValuePair<long, RoadType>[num];
				for (int i = 0; i < num; i++)
				{
					long key = package.ReadLong();
					RoadType roadType = (RoadType)package.ReadByte();
					if (roadType != RoadType.None && roadType != RoadType.Dirt && roadType != RoadType.Paved)
					{
						return false;
					}
					cells[i] = new KeyValuePair<long, RoadType>(key, roadType);
				}
				return true;
			}
			catch
			{
				cells = null;
				return false;
			}
		}

		public static ZPackage EncodeChunk(long worldUid, RoadChunkData chunk)
		{
			ZPackage val = Header(worldUid);
			val.Write(chunk.Coord.X);
			val.Write(chunk.Coord.Z);
			val.Write(chunk.Revision);
			val.Write(chunk.Cells.Length);
			KeyValuePair<long, RoadType>[] cells = chunk.Cells;
			for (int i = 0; i < cells.Length; i++)
			{
				KeyValuePair<long, RoadType> keyValuePair = cells[i];
				val.Write(keyValuePair.Key);
				val.Write((byte)keyValuePair.Value);
			}
			return val;
		}

		public static bool TryDecodeChunk(ZPackage package, long expectedWorldUid, out RoadChunkData chunk)
		{
			chunk = null;
			if (!ReadAndValidateHeader(package, expectedWorldUid))
			{
				return false;
			}
			try
			{
				int x = package.ReadInt();
				int z = package.ReadInt();
				uint r = package.ReadUInt();
				int num = package.ReadInt();
				if (num < 0 || num > 2048)
				{
					return false;
				}
				KeyValuePair<long, RoadType>[] array = new KeyValuePair<long, RoadType>[num];
				for (int i = 0; i < num; i++)
				{
					long key = package.ReadLong();
					RoadType roadType = (RoadType)package.ReadByte();
					if (roadType != RoadType.Dirt && roadType != RoadType.Paved)
					{
						return false;
					}
					array[i] = new KeyValuePair<long, RoadType>(key, roadType);
				}
				chunk = new RoadChunkData(new RoadChunkCoord(x, z), r, array);
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	public enum RoadType : byte
	{
		None,
		Dirt,
		Paved
	}
}
namespace Forteca.FehuRoads.Commands
{
	internal static class FehuRoadsCommands
	{
		internal static void Register(FehuRoadsPlugin plugin)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			new ConsoleCommand("fehuroads", "fehuroads stats | rebuild-render", (ConsoleEvent)delegate(ConsoleEventArgs args)
			{
				if (args.Length < 2)
				{
					args.Context.AddString("fehuroads stats | rebuild-render");
				}
				else if (args[1] == "stats")
				{
					args.Context.AddString(plugin.Stats());
				}
				else if (args[1] == "rebuild-render")
				{
					plugin.InvalidateRender();
					args.Context.AddString("FehuRoads renderer invalidated.");
				}
			}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}
	}
}