Decompiled source of Cairn v0.12.0

plugins/Cairn.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RavenIron.Cairn.Config;
using RavenIron.Cairn.Core;
using RavenIron.Cairn.Net;
using RavenIron.Cairn.Systems;
using RavenIron.Cairn.Visuals;
using RavenIron.Cairn.Voice;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Raven Iron")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.12.0.0")]
[assembly: AssemblyInformationalVersion("0.12.0+e3e2c35caac9aacde0f0875e526b29e40159e9ba")]
[assembly: AssemblyProduct("Cairn")]
[assembly: AssemblyTitle("Cairn")]
[assembly: AssemblyVersion("0.12.0.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 RavenIron.Cairn
{
	[BepInPlugin("com.raveniron.cairn", "Cairn", "0.12.0")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class Cairn : BaseUnityPlugin
	{
		public const string PluginId = "com.raveniron.cairn";

		public const string PluginName = "Cairn";

		public const string PluginVersion = "0.12.0";

		private Harmony _harmony;

		public static Cairn Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static bool HasRenderer => (int)SystemInfo.graphicsDeviceType != 4;

		public static bool IsSimulationAuthority()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			return instance.IsServer();
		}

		public static bool IsDedicated()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.IsDedicated();
			}
			return false;
		}

		private void Awake()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigMigration.Begin(((BaseUnityPlugin)this).Config);
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			ConfigMigration.Finish(((BaseUnityPlugin)this).Config, ModConfig.ConfigVersion);
			_harmony = new Harmony("com.raveniron.cairn");
			_harmony.PatchAll();
			((Component)this).gameObject.AddComponent<CairnTick>();
			RegisterSystems();
			if (HasRenderer)
			{
				((Component)this).gameObject.AddComponent<Beacon>();
				((Component)this).gameObject.AddComponent<HuginVoice>();
			}
			Log.LogInfo((object)(string.Format("{0} v{1} loaded — renderer={2}, ", "Cairn", "0.12.0", HasRenderer) + $"systems={CairnTick.SystemCount}, " + "client pieces=" + (HasRenderer ? "beacons + raven" : "none (headless)") + "."));
		}

		private static void RegisterSystems()
		{
			CairnTick.Register(new LandmarkSystem());
			CairnTick.Register(new TombstoneSystem());
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			Instance = null;
		}
	}
	internal static class BuildVersion
	{
		public const string Value = "0.12.0";
	}
}
namespace RavenIron.Cairn.Voice
{
	public class HuginVoice : MonoBehaviour
	{
		private Type _ravenType;

		private Type _textType;

		private FieldInfo _tempTextsField;

		private FieldInfo _instanceField;

		private GameObject _guidePost;

		private object _ourText;

		private LandmarkKey _spokenFor;

		private bool _holding;

		private float _sinceCheck;

		private bool _failed;

		private bool _warnedNoBird;

		public static HuginVoice Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
		}

		public bool RavenExists()
		{
			if (_instanceField == null)
			{
				return false;
			}
			object? value = _instanceField.GetValue(null);
			Object val = (Object)((value is Object) ? value : null);
			if (val != null)
			{
				return val != (Object)null;
			}
			return false;
		}

		public List<string> Describe()
		{
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0291: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_034a: Unknown result type (might be due to invalid IL or missing references)
			//IL_035b: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			if (_failed)
			{
				list.Add("  voice DISABLED after an error — see the log");
				return list;
			}
			if (!ModConfig.EnableRavenVoice.Value)
			{
				list.Add("  disabled by config");
				return list;
			}
			bool flag = Resolve();
			list.Add("  reflection : " + (flag ? "resolved" : "NOT resolved — Valheim's API moved"));
			list.Add($"  raven in scene : {RavenExists()}");
			if (!RavenExists())
			{
				list.Add("    ^ vanilla spawns Hugin from a GuidePoint (the start temple has one). Until one loads, there is no bird to carry a line and the voice is silent BY DESIGN.");
			}
			int num = -1;
			try
			{
				if (flag && _tempTextsField.GetValue(null) is IList list2)
				{
					num = list2.Count;
				}
			}
			catch
			{
			}
			list.Add("  raven temp queue : " + ((num < 0) ? "unreadable" : num.ToString()) + " vanilla entry(s) — we no longer queue there, see Offer");
			list.Add("  our guide point : " + (_holding ? "planted" : "none"));
			if (flag && AccessTools.Field(_ravenType, "m_tutorialsEnabled")?.GetValue(null) is bool flag2)
			{
				list.Add("  tutorials : " + (flag2 ? "on" : "OFF in the game settings — the raven will not land to say a name while they are off"));
			}
			object obj2 = RavenInstance();
			if (obj2 != null)
			{
				object obj3 = AccessTools.Field(_ravenType, "m_isMunin")?.GetValue(obj2);
				bool flag3 = default(bool);
				int num2;
				if (obj3 is bool)
				{
					flag3 = (bool)obj3;
					num2 = 1;
				}
				else
				{
					num2 = 0;
				}
				list.Add("  this raven is : " + ((((uint)num2 & (flag3 ? 1u : 0u)) != 0) ? "MUNIN" : "Hugin") + " (our text must match, or it is filtered out before anything else)");
				list.Add("  registered : " + (OurTextRegistered() ? "yes — vanilla holds our static text" : "NO — RegisterStaticText never took"));
				object obj4 = Invoke(obj2, "GetBestText");
				string text = ((obj4 == null) ? "nothing — the bird has no reason to come" : ((obj4 == _ourText) ? "OURS — vanilla has chosen our line" : "a VANILLA text, not ours"));
				list.Add("  GetBestText : " + text);
				object obj5 = Invoke(obj2, "IsAway");
				bool flag4 = default(bool);
				int num3;
				if (obj5 is bool)
				{
					flag4 = (bool)obj5;
					num3 = 1;
				}
				else
				{
					num3 = 0;
				}
				list.Add("  bird state : " + ((((uint)num3 & (flag4 ? 1u : 0u)) != 0) ? "away (free to arrive)" : "already perched somewhere"));
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				list.Add("  nearest named : no local player");
				return list;
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			float value = ModConfig.RavenNameMeters.Value;
			if (NearestNamed(position, float.MaxValue, out var key, out var name))
			{
				float num4 = (float)key.X - position.x;
				float num5 = (float)key.Z - position.z;
				float num6 = Mathf.Sqrt(num4 * num4 + num5 * num5);
				list.Add($"  nearest named : \"{name}\" at {key} — {num6:F0}m away" + ((num6 <= value) ? $", inside the {value:F0}m reach" : $" — WALK {num6 - value:F0}m CLOSER to hear it"));
			}
			else
			{
				list.Add("  nearest named : none anywhere in this world. Unnamed cairns never trigger the voice — put a sign within 6m of one and name it.");
			}
			list.Add($"  your altitude : {position.y:F0}m " + ((position.y > 30f) ? "(above the raven's 30m floor)" : "(BELOW the 30m floor — it cannot land)"));
			return list;
		}

		private void Update()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			if (_failed || !ModConfig.EnableRavenVoice.Value)
			{
				Release();
				return;
			}
			_sinceCheck += Time.deltaTime;
			if (_sinceCheck < 1f)
			{
				return;
			}
			_sinceCheck = 0f;
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					Release();
					return;
				}
				if (!Resolve())
				{
					Release();
					return;
				}
				Vector3 position = ((Component)localPlayer).transform.position;
				float value = ModConfig.RavenNameMeters.Value;
				if (!NearestNamed(position, value, out var key, out var name))
				{
					Release();
				}
				else if (!_holding || !(key == _spokenFor))
				{
					if (!RavenExists() && !_warnedNoBird)
					{
						_warnedNoBird = true;
						Cairn.Log.LogInfo((object)"HuginVoice: a named landmark is in reach, but there is no Raven in this scene to carry the line. Vanilla spawns Hugin from a GuidePoint — the start temple has one — so this world has simply never loaded one. The voice stays silent by design; `cairn raven` reports this too.");
					}
					Release();
					Offer(key, name);
				}
			}
			catch (Exception ex)
			{
				_failed = true;
				Release();
				Cairn.Log.LogWarning((object)("HuginVoice disabled after an error: " + ex.Message));
			}
		}

		private static bool NearestNamed(Vector3 here, float reach, out LandmarkKey key, out string name)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			key = default(LandmarkKey);
			name = null;
			float num = reach * reach;
			bool result = false;
			foreach (LandmarkSync.Beacon item in LandmarkSync.Current())
			{
				if (!string.IsNullOrEmpty(item.Name))
				{
					float num2 = (float)item.Key.X - here.x;
					float num3 = (float)item.Key.Z - here.z;
					float num4 = num2 * num2 + num3 * num3;
					if (!(num4 > num))
					{
						num = num4;
						key = item.Key;
						name = item.Name;
						result = true;
					}
				}
			}
			return result;
		}

		private bool Resolve()
		{
			if (_ravenType == null)
			{
				_ravenType = AccessTools.TypeByName("Raven");
			}
			if (_ravenType == null)
			{
				return false;
			}
			if (_textType == null)
			{
				_textType = AccessTools.Inner(_ravenType, "RavenText");
			}
			if (_tempTextsField == null)
			{
				_tempTextsField = AccessTools.Field(_ravenType, "m_tempTexts");
			}
			if (_instanceField == null)
			{
				_instanceField = AccessTools.Field(_ravenType, "m_instance");
			}
			if (_textType != null)
			{
				return _tempTextsField != null;
			}
			return false;
		}

		private void Offer(LandmarkKey key, string storedName)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			if (!RavenExists())
			{
				return;
			}
			Type type = AccessTools.TypeByName("GuidePoint");
			if (type == null)
			{
				return;
			}
			string text = DisplayText.ForSpeech(storedName, ModConfig.RavenNameMaxLength.Value);
			if (text.Length != 0)
			{
				GameObject val = new GameObject("cairn_guidepoint");
				val.SetActive(false);
				val.transform.position = new Vector3((float)key.X, (float)key.Y, (float)key.Z);
				Component target = val.AddComponent(type);
				object obj = Activator.CreateInstance(_textType);
				SetField(obj, "m_text", text);
				SetField(obj, "m_topic", "");
				SetField(obj, "m_key", "");
				SetField(obj, "m_munin", RavenIsMunin());
				SetField(obj, "m_priority", 0);
				SetField(obj, "m_alwaysSpawn", true);
				SetField(target, "m_text", obj);
				val.SetActive(true);
				_guidePost = val;
				_ourText = obj;
				_spokenFor = key;
				_holding = true;
				if (ModConfig.VerboseLogging.Value)
				{
					Cairn.Log.LogInfo((object)$"HuginVoice: guide point for \"{text}\" at {key}.");
				}
			}
		}

		private void Release()
		{
			if (!_holding)
			{
				return;
			}
			_holding = false;
			try
			{
				if ((Object)(object)_guidePost != (Object)null)
				{
					Object.Destroy((Object)(object)_guidePost);
				}
			}
			catch
			{
			}
			_guidePost = null;
			_ourText = null;
		}

		private object RavenInstance()
		{
			if (_instanceField == null)
			{
				return null;
			}
			object value = _instanceField.GetValue(null);
			Object val = (Object)((value is Object) ? value : null);
			if (val != null && val == (Object)null)
			{
				return null;
			}
			return value;
		}

		private bool OurTextRegistered()
		{
			try
			{
				if (_ourText == null)
				{
					return false;
				}
				if (!(AccessTools.Field(_ravenType, "m_staticTexts")?.GetValue(null) is IList list))
				{
					return false;
				}
				foreach (object item in list)
				{
					if (item == _ourText)
					{
						return true;
					}
				}
				return false;
			}
			catch
			{
				return false;
			}
		}

		private bool RavenIsMunin()
		{
			object obj = RavenInstance();
			if (obj == null)
			{
				return false;
			}
			object obj2 = AccessTools.Field(_ravenType, "m_isMunin")?.GetValue(obj);
			bool flag = default(bool);
			int num;
			if (obj2 is bool)
			{
				flag = (bool)obj2;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private object Invoke(object target, string method)
		{
			try
			{
				return AccessTools.Method(target.GetType(), method, Type.EmptyTypes, (Type[])null)?.Invoke(target, null);
			}
			catch
			{
				return null;
			}
		}

		private static void SetField(object target, string name, object value)
		{
			AccessTools.Field(target.GetType(), name)?.SetValue(target, value);
		}

		private void OnDestroy()
		{
			Release();
		}
	}
}
namespace RavenIron.Cairn.Visuals
{
	public class Beacon : MonoBehaviour
	{
		private sealed class Lit
		{
			public GameObject Go;

			public Renderer Renderer;

			public Material Material;

			public Vector3 Position;

			public float Visibility;

			public bool Blocked;

			public bool Tomb;

			public bool HasSign;

			public Color Sign;
		}

		private struct LightKey : IEquatable<LightKey>
		{
			public bool Tomb;

			public LandmarkKey Place;

			public long Owner;

			public long Died;

			public static LightKey ForCairn(LandmarkKey k)
			{
				return new LightKey
				{
					Place = k
				};
			}

			public static LightKey ForTomb(TombstoneLights.Grave g)
			{
				//IL_0040: Unknown result type (might be due to invalid IL or missing references)
				return new LightKey
				{
					Tomb = true,
					Owner = g.Owner,
					Died = g.TimeOfDeath,
					Place = ((g.TimeOfDeath == 0L) ? LandmarkKey.FromPosition(g.Position) : default(LandmarkKey))
				};
			}

			public bool Equals(LightKey o)
			{
				if (Tomb == o.Tomb && Place == o.Place && Owner == o.Owner)
				{
					return Died == o.Died;
				}
				return false;
			}

			public override bool Equals(object obj)
			{
				if (obj is LightKey o)
				{
					return Equals(o);
				}
				return false;
			}

			public override int GetHashCode()
			{
				int hashCode = Place.GetHashCode();
				hashCode = hashCode * 31 + Owner.GetHashCode();
				hashCode = hashCode * 31 + Died.GetHashCode();
				if (!Tomb)
				{
					return hashCode;
				}
				return ~hashCode;
			}

			public override string ToString()
			{
				if (!Tomb)
				{
					return Place.ToString();
				}
				return $"TOMBSTONE (died {Died})";
			}
		}

		private sealed class ColourLog
		{
			public Color? Logged;

			public Color? Pending;

			public float ChangedAt;
		}

		private static readonly string[] CandidateShaders = new string[5] { "Particles/Standard Unlit", "Legacy Shaders/Particles/Additive", "Sprites/Default", "UI/Default", "Legacy Shaders/Particles/Alpha Blended" };

		private readonly Dictionary<LightKey, Lit> _lit = new Dictionary<LightKey, Lit>(16);

		private readonly List<LightKey> _scratch = new List<LightKey>(16);

		private Material _material;

		private Mesh _quad;

		private bool _failed;

		private int _seenRevision = -1;

		private float _sinceRebuild;

		private int _occlusionCursor;

		private int _occlusionMask = -1;

		private readonly ColourLog _beaconLog = new ColourLog();

		private readonly ColourLog _tombLog = new ColourLog();

		private int _seenTombRevision = -1;

		public static Beacon Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
		}

		public List<string> Describe()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			if (_failed)
			{
				list.Add("  renderer DISABLED after an error — see the log");
				return list;
			}
			if ((Object)(object)_material == (Object)null)
			{
				list.Add("  (no material built yet — nothing has been drawn)");
			}
			Camera main = Camera.main;
			Vector3 val = (((Object)(object)main != (Object)null) ? ((Component)main).transform.position : Vector3.zero);
			float value = ModConfig.BeaconMaxDistanceMeters.Value;
			foreach (KeyValuePair<LightKey, Lit> item in _lit)
			{
				Lit value2 = item.Value;
				float num = (((Object)(object)main != (Object)null) ? Vector3.Distance(val, value2.Position) : (-1f));
				string text = (value2.Blocked ? "HIDDEN (terrain in the way)" : ((num > value) ? $"OUT OF RANGE (>{value:F0}m)" : ((value2.Visibility > 0.01f) ? "lit" : "fading")));
				string text2 = (value2.HasSign ? (ModConfig.UseSignColours.Value ? ("  sign colour #" + ColorUtility.ToHtmlStringRGBA(value2.Sign)) : "  sign colour ignored (UseSignColours = false)") : "");
				list.Add($"  {item.Key}  {num:F0}m  vis={value2.Visibility:F2}  {text}{text2}");
			}
			if (_lit.Count == 0)
			{
				list.Add("  no beacons known — nothing has been synced to this client");
			}
			return list;
		}

		private void Update()
		{
			if (_failed)
			{
				return;
			}
			if (!ModConfig.EnableBeacons.Value && !ModConfig.ShowTombstoneLight.Value)
			{
				if (_lit.Count > 0)
				{
					ClearAll();
				}
				return;
			}
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				return;
			}
			try
			{
				_sinceRebuild += Time.deltaTime;
				if (_seenRevision != LandmarkSync.Revision || _seenTombRevision != TombstoneSync.Revision || _sinceRebuild >= 5f)
				{
					_seenRevision = LandmarkSync.Revision;
					_seenTombRevision = TombstoneSync.Revision;
					_sinceRebuild = 0f;
					Rebuild();
				}
				if (_lit.Count != 0)
				{
					StepOcclusion(main);
					Draw(main);
				}
			}
			catch (Exception arg)
			{
				_failed = true;
				ClearAll();
				Cairn.Log.LogError((object)$"Beacon disabled after an error: {arg}");
			}
		}

		private void Rebuild()
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			HashSet<LightKey> hashSet = new HashSet<LightKey>();
			float value = ModConfig.BeaconHeightMeters.Value;
			if (ModConfig.EnableBeacons.Value)
			{
				foreach (LandmarkSync.Beacon item in LandmarkSync.Current())
				{
					LightKey key = LightKey.ForCairn(item.Key);
					Lit lit = Place(key, item.Light + Vector3.up * value, tomb: false, hashSet);
					if (lit == null)
					{
						return;
					}
					lit.HasSign = SignColour.TryRead(item.Name, out var r, out var g, out var b, out var a);
					if (lit.HasSign)
					{
						lit.Sign = new Color(r, g, b, a);
					}
				}
			}
			if (ModConfig.ShowTombstoneLight.Value)
			{
				foreach (TombstoneLights.Grave item2 in TombstoneSync.Mine())
				{
					LightKey key2 = LightKey.ForTomb(item2);
					if (Place(key2, item2.Position + Vector3.up * value, tomb: true, hashSet) == null)
					{
						return;
					}
				}
			}
			_scratch.Clear();
			foreach (KeyValuePair<LightKey, Lit> item3 in _lit)
			{
				if (!hashSet.Contains(item3.Key))
				{
					_scratch.Add(item3.Key);
				}
			}
			foreach (LightKey item4 in _scratch)
			{
				Discard(_lit[item4]);
				_lit.Remove(item4);
			}
		}

		private Lit Place(LightKey key, Vector3 position, bool tomb, HashSet<LightKey> wanted)
		{
			//IL_003a: 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)
			wanted.Add(key);
			if (!_lit.TryGetValue(key, out var value))
			{
				value = Create();
				if (value == null)
				{
					return null;
				}
				value.Tomb = tomb;
				_lit[key] = value;
			}
			value.Position = position;
			return value;
		}

		private Lit Create()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			if ((Object)(object)_material == (Object)null)
			{
				_material = BuildMaterial();
				if ((Object)(object)_material == (Object)null)
				{
					_failed = true;
					return null;
				}
			}
			if ((Object)(object)_quad == (Object)null)
			{
				_quad = BuildQuad();
			}
			GameObject val = new GameObject("cairn_beacon");
			val.transform.SetParent(((Component)this).transform, true);
			val.AddComponent<MeshFilter>().sharedMesh = _quad;
			MeshRenderer val2 = val.AddComponent<MeshRenderer>();
			((Renderer)val2).sharedMaterial = _material;
			((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)val2).receiveShadows = false;
			Material material = (((Renderer)val2).material = new Material(_material));
			return new Lit
			{
				Go = val,
				Renderer = (Renderer)(object)val2,
				Material = material,
				Visibility = 0f
			};
		}

		public void ClearForWorldChange()
		{
			ClearAll();
			_seenRevision = -1;
			_seenTombRevision = -1;
			_occlusionCursor = 0;
		}

		private void ClearAll()
		{
			foreach (KeyValuePair<LightKey, Lit> item in _lit)
			{
				Discard(item.Value);
			}
			_lit.Clear();
		}

		private static void Discard(Lit lit)
		{
			if ((Object)(object)lit.Go != (Object)null)
			{
				Object.Destroy((Object)(object)lit.Go);
			}
			if ((Object)(object)lit.Material != (Object)null)
			{
				Object.Destroy((Object)(object)lit.Material);
			}
		}

		private void Draw(Camera cam)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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_00cc: 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_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)cam).transform.position;
			float value = ModConfig.BeaconMaxDistanceMeters.Value;
			float value2 = ModConfig.BeaconAngularSize.Value;
			float value3 = ModConfig.BeaconMinSizeMeters.Value;
			float value4 = ModConfig.BeaconMaxSizeMeters.Value;
			float num = Mathf.Clamp01(Time.deltaTime * 4f);
			Color value5 = ModConfig.BeaconColour.Value;
			Color value6 = ModConfig.TombstoneColour.Value;
			bool value7 = ModConfig.UseSignColours.Value;
			LogColourChange("BeaconColour", value5, _beaconLog);
			LogColourChange("TombstoneColour", value6, _tombLog);
			foreach (KeyValuePair<LightKey, Lit> item in _lit)
			{
				Lit value8 = item.Value;
				if ((Object)(object)value8.Go == (Object)null)
				{
					continue;
				}
				float num2 = Vector3.Distance(position, value8.Position);
				float num3 = ((num2 > value) ? 0f : 1f);
				if (value8.Blocked)
				{
					num3 = 0f;
				}
				if (num3 > 0f && num2 > value * 0.8f)
				{
					num3 = Mathf.InverseLerp(value, value * 0.8f, num2);
				}
				value8.Visibility = Mathf.Lerp(value8.Visibility, num3, num);
				if (value8.Visibility <= 0.01f)
				{
					if (value8.Go.activeSelf)
					{
						value8.Go.SetActive(false);
					}
					continue;
				}
				if (!value8.Go.activeSelf)
				{
					value8.Go.SetActive(true);
				}
				float num4 = Mathf.Clamp(num2 * value2, value3, value4);
				Transform transform = value8.Go.transform;
				transform.position = value8.Position;
				transform.rotation = Quaternion.LookRotation(value8.Position - position);
				transform.localScale = new Vector3(num4, num4, 1f);
				Color val = (value8.Tomb ? value6 : ((value7 && value8.HasSign) ? value8.Sign : value5));
				val.a = value8.Visibility * val.a;
				value8.Material.color = val;
			}
		}

		private static void LogColourChange(string setting, Color now, ColourLog log)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!log.Logged.HasValue)
			{
				log.Logged = now;
				log.Pending = now;
				Cairn.Log.LogInfo((object)("Beacon: " + setting + " is #" + ColorUtility.ToHtmlStringRGBA(now) + "."));
				return;
			}
			Color? pending = log.Pending;
			Color val = now;
			if (!pending.HasValue || pending.GetValueOrDefault() != val)
			{
				log.Pending = now;
				log.ChangedAt = realtimeSinceStartup;
				return;
			}
			pending = log.Logged;
			val = now;
			if ((!pending.HasValue || pending.GetValueOrDefault() != val) && realtimeSinceStartup - log.ChangedAt >= 1f)
			{
				log.Logged = now;
				Cairn.Log.LogInfo((object)("Beacon: " + setting + " is now #" + ColorUtility.ToHtmlStringRGBA(now) + "."));
			}
		}

		private void StepOcclusion(Camera cam)
		{
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.BeaconOcclusion.Value)
			{
				foreach (KeyValuePair<LightKey, Lit> item in _lit)
				{
					item.Value.Blocked = false;
				}
				return;
			}
			if (_occlusionMask == -1)
			{
				_occlusionMask = LayerMask.GetMask(new string[3] { "terrain", "static_solid", "Default" });
				if (_occlusionMask == 0)
				{
					Cairn.Log.LogWarning((object)"Beacon: no occlusion layers resolved, so beacons will shine through terrain. Set BeaconOcclusion = false to accept that deliberately.");
				}
				else
				{
					Cairn.Log.LogInfo((object)$"Beacon: occlusion mask resolved ({_occlusionMask}).");
				}
			}
			if (_occlusionMask == 0 || _lit.Count == 0)
			{
				return;
			}
			_scratch.Clear();
			foreach (KeyValuePair<LightKey, Lit> item2 in _lit)
			{
				_scratch.Add(item2.Key);
			}
			if (_occlusionCursor >= _scratch.Count)
			{
				_occlusionCursor = 0;
			}
			LightKey key = _scratch[_occlusionCursor];
			_occlusionCursor++;
			if (_lit.TryGetValue(key, out var value))
			{
				Vector3 position = ((Component)cam).transform.position;
				Vector3 val = value.Position - position;
				float num = ((Vector3)(ref val)).magnitude - 1.5f;
				if (num <= 0f)
				{
					value.Blocked = false;
				}
				else
				{
					value.Blocked = Physics.Raycast(position, ((Vector3)(ref val)).normalized, num, _occlusionMask, (QueryTriggerInteraction)1);
				}
			}
		}

		private static Mesh BuildQuad()
		{
			//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_0011: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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_007a: 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_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: 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)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			Mesh val = new Mesh
			{
				name = "cairn_beacon_quad"
			};
			val.vertices = (Vector3[])(object)new Vector3[4]
			{
				new Vector3(-0.5f, -0.5f, 0f),
				new Vector3(0.5f, -0.5f, 0f),
				new Vector3(-0.5f, 0.5f, 0f),
				new Vector3(0.5f, 0.5f, 0f)
			};
			val.uv = (Vector2[])(object)new Vector2[4]
			{
				new Vector2(0f, 0f),
				new Vector2(1f, 0f),
				new Vector2(0f, 1f),
				new Vector2(1f, 1f)
			};
			val.triangles = new int[6] { 0, 2, 1, 2, 3, 1 };
			val.RecalculateBounds();
			return val;
		}

		private static Material BuildMaterial()
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Expected O, but got Unknown
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			Shader val = null;
			string[] candidateShaders = CandidateShaders;
			foreach (string text in candidateShaders)
			{
				val = Shader.Find(text);
				if ((Object)(object)val != (Object)null)
				{
					Cairn.Log.LogInfo((object)("Beacon: using shader '" + text + "'."));
					break;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				Cairn.Log.LogError((object)"Beacon: no usable shader found — Valheim's build has stripped every candidate. Beacons cannot draw; the ledger is unaffected.");
				return null;
			}
			Texture2D val2 = new Texture2D(64, 64, (TextureFormat)4, false);
			float num = 31.5f;
			for (int j = 0; j < 64; j++)
			{
				for (int k = 0; k < 64; k++)
				{
					float num2 = ((float)k - num) / num;
					float num3 = ((float)j - num) / num;
					float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3);
					float num5 = Mathf.Clamp01(1f - num4);
					val2.SetPixel(k, j, new Color(1f, 1f, 1f, num5 * num5 * (0.55f + 0.45f * num5)));
				}
			}
			val2.Apply();
			return new Material(val)
			{
				mainTexture = (Texture)(object)val2
			};
		}

		private void OnDestroy()
		{
			ClearAll();
		}
	}
}
namespace RavenIron.Cairn.Systems
{
	public class LandmarkSystem : IWorldSystem
	{
		private struct Target
		{
			public string Prefab;

			public bool IsSign;
		}

		private readonly List<Target> _targets = new List<Target>(8);

		private int _cursor;

		private readonly Dictionary<int, string> _watchedHashes = new Dictionary<int, string>(8);

		private readonly List<ZDO> _found = new List<ZDO>(64);

		private int _sweepIndex;

		private readonly List<Vector3> _stones = new List<Vector3>(256);

		private readonly List<Vector3> _signPositions = new List<Vector3>(32);

		private readonly List<string> _signNames = new List<string>(32);

		private readonly List<string> _signAuthors = new List<string>(32);

		private readonly Dictionary<string, int> _foundCounts = new Dictionary<string, int>(8);

		private readonly Dictionary<string, int> _keptCounts = new Dictionary<string, int>(8);

		private bool _rotationClean = true;

		public const float QuickRadiusMeters = 24f;

		private readonly List<Vector3> _quickAreas = new List<Vector3>(8);

		public string Name => "LandmarkSystem";

		public bool Enabled => ModConfig.EnableLandmarks.Value;

		public float IntervalSeconds => Mathf.Max(0.5f, ModConfig.LandmarkRotationSeconds.Value / (float)Mathf.Max(1, _targets.Count));

		public static LandmarkSystem Instance { get; private set; }

		public bool IsWatched(int prefabHash, out string prefab)
		{
			return _watchedHashes.TryGetValue(prefabHash, out prefab);
		}

		public void Initialise()
		{
			_cursor = 0;
			_found.Clear();
			_sweepIndex = 0;
			_stones.Clear();
			_signPositions.Clear();
			_signNames.Clear();
			_signAuthors.Clear();
			_foundCounts.Clear();
			_keptCounts.Clear();
			_rotationClean = true;
			_quickAreas.Clear();
			_targets.Clear();
			AddTargets(ModConfig.SignPrefabs.Value, isSign: true);
			AddTargets(ModConfig.StonePrefabs.Value, isSign: false);
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			_watchedHashes.Clear();
			foreach (Target target in _targets)
			{
				(target.IsSign ? list : list2).Add(target.Prefab);
				_watchedHashes[StringExtensionMethods.GetStableHashCode(target.Prefab)] = target.Prefab;
			}
			Cairn.Log.LogInfo((object)($"[{Name}] sweeping every {IntervalSeconds:F1}s per prefab, {ModConfig.LandmarkRotationSeconds.Value:F0}s per rotation — " + "signs: " + ((list.Count > 0) ? string.Join(", ", list.ToArray()) : "none") + "; stone: " + ((list2.Count > 0) ? string.Join(", ", list2.ToArray()) : "none") + ". " + $"A pile is {ModConfig.PileMinPieces.Value}-{ModConfig.PileMaxPieces.Value} pieces " + $"within {ModConfig.PileMaxExtentMeters.Value:F1}m of footprint; a sign names one " + $"from {ModConfig.LandmarkPairMeters.Value:F0}m."));
		}

		private void AddTargets(string csv, bool isSign)
		{
			string[] array = (csv ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					_targets.Add(new Target
					{
						Prefab = text,
						IsSign = isSign
					});
				}
			}
		}

		public void Tick(float deltaSeconds)
		{
			//IL_0178: 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 (_targets.Count == 0)
			{
				return;
			}
			Game instance = Game.instance;
			if ((Object)(object)instance != (Object)null && instance.IsShuttingDown())
			{
				return;
			}
			ZDOMan instance2 = ZDOMan.instance;
			if (instance2 == null)
			{
				return;
			}
			Target target = _targets[_cursor];
			try
			{
				bool flag = false;
				while (!flag)
				{
					flag = instance2.GetAllZDOsWithPrefabIterative(target.Prefab, _found, ref _sweepIndex);
				}
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("[" + Name + "] sweep failed on '" + target.Prefab + "': " + ex.Message));
				_found.Clear();
				_sweepIndex = 0;
				_rotationClean = false;
				return;
			}
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < _found.Count; i++)
			{
				ZDO val = _found[i];
				if (val == null || !val.IsValid())
				{
					continue;
				}
				num++;
				if (target.IsSign)
				{
					string rawText = val.GetString(ZDOVars.s_text, "");
					string rawAuthor = val.GetString(ZDOVars.s_author, "");
					if (SignReading.TryRead(rawText, rawAuthor, out var name, out var author))
					{
						_signPositions.Add(val.GetPosition());
						_signNames.Add(name);
						_signAuthors.Add(author);
						num2++;
					}
				}
				else
				{
					_stones.Add(val.GetPosition());
					num2++;
				}
			}
			_foundCounts[target.Prefab] = num;
			_keptCounts[target.Prefab] = num2;
			_found.Clear();
			_sweepIndex = 0;
			_cursor++;
			if (_cursor >= _targets.Count)
			{
				_cursor = 0;
				CompleteRotation();
			}
		}

		private void CompleteRotation()
		{
			long ticks = DateTime.UtcNow.Ticks;
			List<Vector3> fresh = new List<Vector3>(_quickAreas);
			LedgerUpdate.Result result = LedgerUpdate.Apply(_stones, _signPositions, _signNames, _signAuthors, (Vector3 p) => !NearAny(p, fresh, 24f), _rotationClean, ticks);
			if (result.Changed > 0 || result.Pruned > 0)
			{
				LandmarkSync.RequestBroadcast();
			}
			LogRotation(result.Piles + result.Skipped, result.Skipped, result.Changed, result.Pruned, result.Carried);
			_stones.Clear();
			_signPositions.Clear();
			_signNames.Clear();
			_signAuthors.Clear();
			_foundCounts.Clear();
			_keptCounts.Clear();
			_quickAreas.Clear();
			_rotationClean = true;
		}

		public LandmarkSystem()
		{
			Instance = this;
		}

		private static bool NearAny(Vector3 p, List<Vector3> spots, float radius)
		{
			//IL_0004: 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)
			for (int i = 0; i < spots.Count; i++)
			{
				if (LedgerUpdate.DistanceXZ(p, spots[i]) <= radius)
				{
					return true;
				}
			}
			return false;
		}

		public bool QuickCheck(Vector3 spot)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			if (_targets.Count == 0)
			{
				return false;
			}
			Game instance = Game.instance;
			if ((Object)(object)instance != (Object)null && instance.IsShuttingDown())
			{
				return false;
			}
			ZDOMan instance2 = ZDOMan.instance;
			if (instance2 == null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				return false;
			}
			HashSet<int> hashSet = new HashSet<int>();
			HashSet<int> hashSet2 = new HashSet<int>();
			foreach (Target target in _targets)
			{
				(target.IsSign ? hashSet2 : hashSet).Add(StringExtensionMethods.GetStableHashCode(target.Prefab));
			}
			List<ZDO> list = new List<ZDO>(256);
			try
			{
				instance2.FindSectorObjects(ZoneSystem.GetZone(spot), new SimulationDistance(1, 0, true), list, (List<ZDO>)null);
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)$"[{Name}] quick check failed near {spot}: {ex.Message}");
				return false;
			}
			List<Vector3> list2 = new List<Vector3>(64);
			List<Vector3> list3 = new List<Vector3>(8);
			List<string> list4 = new List<string>(8);
			List<string> list5 = new List<string>(8);
			foreach (ZDO item in list)
			{
				if (item != null && item.IsValid())
				{
					int prefab = item.GetPrefab();
					string name;
					string author;
					if (hashSet.Contains(prefab))
					{
						list2.Add(item.GetPosition());
					}
					else if (hashSet2.Contains(prefab) && SignReading.TryRead(item.GetString(ZDOVars.s_text, ""), item.GetString(ZDOVars.s_author, ""), out name, out author))
					{
						list3.Add(item.GetPosition());
						list4.Add(name);
						list5.Add(author);
					}
				}
			}
			LedgerUpdate.Result result = LedgerUpdate.Apply(list2, list3, list4, list5, (Vector3 p) => LedgerUpdate.DistanceXZ(p, spot) <= 24f, allowPrune: true, DateTime.UtcNow.Ticks);
			_quickAreas.Add(spot);
			bool flag = result.Changed > 0 || result.Pruned > 0;
			if (flag || ModConfig.VerboseLogging.Value)
			{
				Cairn.Log.LogInfo((object)($"[{Name}] quick check at ({spot.x:F0}, {spot.z:F0}): {list2.Count} stone(s), " + $"{list3.Count} named sign(s) in {list.Count} nearby object(s) — " + $"{result.Piles} pile(s) here, {result.Changed} changed, {result.Pruned} pruned [{DateTime.Now:HH:mm:ss.fff}]"));
			}
			return flag;
		}

		private void LogRotation(int pileCount, int leftToQuick, int changed, int pruned, int carried)
		{
			List<string> list = new List<string>(_foundCounts.Count);
			int num = 0;
			int num2 = 0;
			foreach (Target target in _targets)
			{
				_foundCounts.TryGetValue(target.Prefab, out var value);
				_keptCounts.TryGetValue(target.Prefab, out var value2);
				list.Add($"{target.Prefab} found={value} kept={value2}");
				if (target.IsSign)
				{
					num += value;
				}
				else
				{
					num2 += value;
				}
			}
			string text = "";
			if (num == 0 && num2 == 0)
			{
				text = " — NOTHING FOUND AT ALL: either this world is bare, or every prefab name is wrong. Check with `cairn prefabs sign` and `cairn prefabs stone`.";
			}
			else if (num2 > 0 && pileCount == 0)
			{
				text = " — stone exists but none of it is shaped like a cairn: too few pieces, too many, or too wide a footprint.";
			}
			Cairn.Log.LogInfo((object)("[" + Name + "] sweep complete (" + string.Join(", ", list.ToArray()) + ") — " + $"{pileCount} pile(s), {LandmarkStore.Count} landmark(s), {changed} changed, {pruned} pruned" + ((leftToQuick > 0) ? $" ({leftToQuick} pile(s) left as a quick check just set them)" : "") + ((carried > 0) ? $", {carried} history carried across a drift" : "") + (_rotationClean ? "" : ", PRUNING SKIPPED (a prefab failed this rotation)") + text + (ModConfig.VerboseLogging.Value ? $" [{DateTime.Now:HH:mm:ss.fff}]" : "")));
		}
	}
	public class TombstoneSystem : IWorldSystem
	{
		private const string FallbackPrefab = "Player_tombstone";

		private string _prefab;

		private readonly List<ZDO> _found = new List<ZDO>(16);

		private int _sweepIndex;

		public string Name => "TombstoneSystem";

		public bool Enabled => ModConfig.EnableTombstoneLights.Value;

		public float IntervalSeconds => 5f;

		public static List<TombstoneLights.Grave> Latest { get; private set; } = new List<TombstoneLights.Grave>();

		public static float LatestAt { get; private set; } = -1f;

		public void Initialise()
		{
			_prefab = null;
			_found.Clear();
			_sweepIndex = 0;
			Latest = new List<TombstoneLights.Grave>();
			LatestAt = -1f;
		}

		public static void ResetForWorldChange()
		{
			Latest = new List<TombstoneLights.Grave>();
			LatestAt = -1f;
		}

		public void Tick(float deltaSeconds)
		{
			//IL_0159: 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)
			Game instance = Game.instance;
			if ((Object)(object)instance != (Object)null && instance.IsShuttingDown())
			{
				return;
			}
			ZDOMan instance2 = ZDOMan.instance;
			if (instance2 == null)
			{
				return;
			}
			if (_prefab == null)
			{
				_prefab = ResolvePrefab(out var how);
				if (_prefab == null)
				{
					return;
				}
				Cairn.Log.LogInfo((object)("[" + Name + "] tombstone lights ON — looking for '" + _prefab + "' (" + how + ")."));
			}
			try
			{
				bool flag = false;
				while (!flag)
				{
					flag = instance2.GetAllZDOsWithPrefabIterative(_prefab, _found, ref _sweepIndex);
				}
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("[" + Name + "] sweep failed: " + ex.Message));
				_found.Clear();
				_sweepIndex = 0;
				return;
			}
			List<TombstoneLights.Grave> list = new List<TombstoneLights.Grave>(_found.Count);
			for (int i = 0; i < _found.Count; i++)
			{
				ZDO val = _found[i];
				if (val != null && val.IsValid())
				{
					list.Add(new TombstoneLights.Grave
					{
						Owner = val.GetLong(ZDOVars.s_owner, 0L),
						TimeOfDeath = val.GetLong(ZDOVars.s_timeOfDeath, 0L),
						Position = val.GetPosition()
					});
				}
			}
			_found.Clear();
			_sweepIndex = 0;
			if (list.Count != Latest.Count && ModConfig.VerboseLogging.Value)
			{
				Cairn.Log.LogInfo((object)$"[{Name}] {list.Count} tombstone(s) in the world (was {Latest.Count}).");
			}
			Latest = list;
			LatestAt = Time.realtimeSinceStartup;
		}

		private static string ResolvePrefab(out string how)
		{
			how = "";
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			try
			{
				GameObject prefab = instance.GetPrefab("Player");
				Player val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Player>() : null);
				if ((Object)(object)val != (Object)null && (Object)(object)val.m_tombstone != (Object)null)
				{
					how = "read from the Player prefab";
					return ((Object)val.m_tombstone).name;
				}
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("[TombstoneSystem] could not read Player.m_tombstone: " + ex.Message));
			}
			how = "FALLBACK name: the Player prefab did not say";
			return "Player_tombstone";
		}
	}
}
namespace RavenIron.Cairn.Patches
{
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	public static class Patch_PieceCost
	{
		private const string PilePrefab = "stone_pile";

		private const string StoneItem = "Stone";

		private static bool _applied;

		private static void Postfix(ZNetScene __instance)
		{
			if (_applied)
			{
				return;
			}
			int value = ModConfig.StonePileStoneCost.Value;
			if (value <= 0)
			{
				return;
			}
			try
			{
				_applied = Rewrite(__instance, value);
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("stone cost override failed: " + ex.Message));
			}
		}

		private static bool Rewrite(ZNetScene scene, int target)
		{
			GameObject prefab = scene.GetPrefab("stone_pile");
			if ((Object)(object)prefab == (Object)null)
			{
				Cairn.Log.LogWarning((object)"stone cost override: no prefab named 'stone_pile'. Vanilla left alone.");
				return false;
			}
			Component component = prefab.GetComponent("Piece");
			if ((Object)(object)component == (Object)null)
			{
				Cairn.Log.LogWarning((object)"stone cost override: 'stone_pile' has no Piece component. Vanilla left alone.");
				return false;
			}
			if (!(AccessTools.Field(((object)component).GetType(), "m_resources")?.GetValue(component) is IEnumerable enumerable))
			{
				Cairn.Log.LogWarning((object)"stone cost override: could not read Piece.m_resources — Valheim's API moved.");
				return false;
			}
			foreach (object item in enumerable)
			{
				if (item == null)
				{
					continue;
				}
				FieldInfo fieldInfo = AccessTools.Field(item.GetType(), "m_resItem");
				FieldInfo fieldInfo2 = AccessTools.Field(item.GetType(), "m_amount");
				if (fieldInfo == null || fieldInfo2 == null)
				{
					continue;
				}
				object? value = fieldInfo.GetValue(item);
				Component val = (Component)((value is Component) ? value : null);
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.gameObject == (Object)null) && string.Equals(((Object)val.gameObject).name, "Stone", StringComparison.OrdinalIgnoreCase))
				{
					int num = (int)fieldInfo2.GetValue(item);
					if (num == target)
					{
						Cairn.Log.LogInfo((object)string.Format("stone cost override: '{0}' already costs {1} stone.", "stone_pile", target));
						return true;
					}
					fieldInfo2.SetValue(item, target);
					Cairn.Log.LogWarning((object)(string.Format("stone cost override ACTIVE: '{0}' now costs {1} stone, was {2}. ", "stone_pile", target, num) + "This edits a VANILLA recipe — set StonePileStoneCost = 0 to leave the game alone."));
					return true;
				}
			}
			Cairn.Log.LogWarning((object)"stone cost override: 'stone_pile' has no 'Stone' requirement to change.");
			return false;
		}
	}
	public static class Patch_QuickNotice
	{
		[HarmonyPatch(typeof(Player), "PlacePiece")]
		public static class Placed
		{
			private static void Postfix(Player __instance, Piece piece, Vector3 pos)
			{
				//IL_0048: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					if (!((Object)(object)piece == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
					{
						string name = ((Object)((Component)piece).gameObject).name;
						if (StoneStacking.IsListed(ModConfig.StonePrefabs.Value, name) || StoneStacking.IsListed(ModConfig.SignPrefabs.Value, name))
						{
							CairnNotice.Notify(pos, "placed " + name);
						}
					}
				}
				catch (Exception ex)
				{
					Cairn.Log.LogWarning((object)("QuickNotice: placement notice failed: " + ex.Message));
				}
			}
		}

		[HarmonyPatch(typeof(Sign), "SetText")]
		public static class Written
		{
			private static void Postfix(Sign __instance)
			{
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					if ((Object)(object)__instance != (Object)null)
					{
						CairnNotice.Notify(((Component)__instance).transform.position, "sign written");
					}
				}
				catch (Exception ex)
				{
					Cairn.Log.LogWarning((object)("QuickNotice: sign notice failed: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")]
	public static class Patch_RoutedRpcGuard
	{
		private static int _beaconHash;

		private static int _tombstoneHash;

		private static bool _hashed;

		private static float _lastWarn = -999f;

		private static int _dropped;

		[HarmonyPriority(200)]
		private static bool Prefix(ZPackage pkg, bool __runOriginal)
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (!__runOriginal)
			{
				return false;
			}
			if (pkg == null)
			{
				return true;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return true;
			}
			if (!_hashed)
			{
				_beaconHash = StringExtensionMethods.GetStableHashCode("com.raveniron.cairn.beacons1");
				_tombstoneHash = StringExtensionMethods.GetStableHashCode("com.raveniron.cairn.tombstones1");
				_hashed = true;
			}
			int pos = pkg.GetPos();
			try
			{
				pkg.ReadLong();
				long num = pkg.ReadLong();
				pkg.ReadLong();
				pkg.ReadZDOID();
				int num2 = pkg.ReadInt();
				if (num2 != _beaconHash && num2 != _tombstoneHash)
				{
					return true;
				}
				_dropped++;
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup - _lastWarn >= 60f)
				{
					_lastWarn = realtimeSinceStartup;
					Cairn.Log.LogWarning((object)($"Dropped a beacon push sent by a client (claimed sender {num}); " + $"only the server sends beacons. {_dropped} dropped so far."));
				}
				return false;
			}
			catch (Exception)
			{
				return true;
			}
			finally
			{
				pkg.SetPos(pos);
			}
		}
	}
	public static class Patch_StoneStacking
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		public static class Flag
		{
			private static bool _capturedVanilla;

			private static bool _vanillaSupports;

			private static bool _announced;

			private static void Postfix(ZNetScene __instance)
			{
				try
				{
					GameObject prefab = __instance.GetPrefab("Placeable_Stone");
					WearNTear val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<WearNTear>() : null);
					if ((Object)(object)val == (Object)null)
					{
						Cairn.Log.LogWarning((object)"stone stacking: no 'Placeable_Stone' with a WearNTear in this game. Stones stack exactly as vanilla allows, which is not at all.");
						return;
					}
					if (!_capturedVanilla)
					{
						_vanillaSupports = val.m_supports;
						_capturedVanilla = true;
					}
					bool supports = ModConfig.StackableStones.Value || _vanillaSupports;
					val.m_supports = supports;
					if (!_announced)
					{
						_announced = true;
						if (ModConfig.StackableStones.Value)
						{
							Cairn.Log.LogWarning((object)("stone stacking ON: Placeable_Stone now holds up what is stacked on it (vanilla: supports=" + _vanillaSupports.ToString().ToLowerInvariant() + "), and only a stone or a sign may be placed on a stone. This changes a VANILLA piece — set StackableStones = false to leave it alone."));
						}
						else
						{
							Cairn.Log.LogInfo((object)"stone stacking off: Placeable_Stone is left exactly as vanilla made it.");
						}
					}
				}
				catch (Exception ex)
				{
					Cairn.Log.LogWarning((object)("stone stacking could not be applied: " + ex.Message));
				}
			}
		}

		[HarmonyPatch(typeof(Player), "UpdatePlacementGhost")]
		public static class Fence
		{
			private delegate bool PieceRayTestFn(Player self, out Vector3 point, out Vector3 normal, out Piece piece, out Heightmap heightmap, out Collider waterSurface, bool water);

			private static PieceRayTestFn _rayTest;

			private static bool _rayMissingLogged;

			private static bool _failed;

			private static PieceRayTestFn RayTest()
			{
				if (_rayTest != null)
				{
					return _rayTest;
				}
				try
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(Player), "PieceRayTest", (Type[])null, (Type[])null);
					if (methodInfo != null)
					{
						_rayTest = AccessTools.MethodDelegate<PieceRayTestFn>(methodInfo, (object)null, true);
					}
				}
				catch (Exception ex)
				{
					if (!_rayMissingLogged)
					{
						Cairn.Log.LogError((object)("stone stacking: could not bind Player.PieceRayTest (" + ex.Message + ") — Valheim's API moved."));
					}
					_rayMissingLogged = true;
				}
				if (_rayTest == null && !_rayMissingLogged)
				{
					_rayMissingLogged = true;
					Cairn.Log.LogError((object)"stone stacking: Player.PieceRayTest not found — Valheim's API moved. Stones still stack, but any piece can be placed on one.");
				}
				return _rayTest;
			}

			private static void Postfix(Player __instance, GameObject ___m_placementGhost, ref PlacementStatus ___m_placementStatus)
			{
				if (_failed || !ModConfig.StackableStones.Value || ___m_placementStatus || (Object)(object)___m_placementGhost == (Object)null || StoneStacking.IsStone(((Object)___m_placementGhost).name))
				{
					return;
				}
				try
				{
					PieceRayTestFn pieceRayTestFn = RayTest();
					if (pieceRayTestFn == null)
					{
						return;
					}
					Piece component = ___m_placementGhost.GetComponent<Piece>();
					bool water = (Object)(object)component != (Object)null && (component.m_waterPiece || component.m_noInWater);
					if (pieceRayTestFn(__instance, out var _, out var _, out var piece, out var _, out var _, water) && !((Object)(object)piece == (Object)null) && StoneStacking.Refuse(stackingEnabled: true, ((Object)___m_placementGhost).name, ((Object)((Component)piece).gameObject).name, ModConfig.SignPrefabs.Value))
					{
						___m_placementStatus = (PlacementStatus)1;
						if ((Object)(object)component != (Object)null)
						{
							component.SetInvalidPlacementHeightlight(true);
						}
					}
				}
				catch (Exception arg)
				{
					_failed = true;
					Cairn.Log.LogError((object)$"stone stacking fence disabled after an error: {arg}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	public static class Patch_Terminal_Cairn
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;
		}

		private static bool _confirmed;

		private static FieldInfo _commandsField;

		private static void Postfix()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			try
			{
				object obj = <>O.<0>__Run;
				if (obj == null)
				{
					ConsoleEvent val = Run;
					<>O.<0>__Run = val;
					obj = (object)val;
				}
				new ConsoleCommand("cairn", "Cairn: status | landmarks | prefabs | pieces | beacons | raven | save", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
				if (!_confirmed)
				{
					_confirmed = true;
					IDictionary dictionary = ReadCommandMap();
					if (dictionary == null)
					{
						Cairn.Log.LogWarning((object)"cairn console: registered, but Terminal's command map could not be read back, so registration is UNCONFIRMED. The command may still work. If Valheim renamed the field, this instrument needs updating.");
					}
					else if (dictionary.Contains("cairn"))
					{
						Cairn.Log.LogInfo((object)("cairn console registered — confirmed present in Terminal's command map " + $"({dictionary.Count} command(s) total)."));
					}
					else
					{
						Cairn.Log.LogError((object)"cairn console did NOT appear in Terminal's command map after registration. The console is the only instrument this build has; treat this as fatal.");
					}
				}
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("cairn console: register failed: " + ex.Message));
			}
		}

		private static IDictionary ReadCommandMap()
		{
			try
			{
				if (_commandsField == null)
				{
					_commandsField = AccessTools.Field(typeof(Terminal), "commands");
				}
				if (_commandsField == null)
				{
					Cairn.Log.LogWarning((object)"cairn console: Terminal has no field named 'commands' — Valheim's API moved.");
					return null;
				}
				return _commandsField.GetValue(null) as IDictionary;
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("cairn console: reading Terminal's command map threw: " + ex.Message));
				return null;
			}
		}

		private static void Say(ConsoleEventArgs args, string line)
		{
			Terminal context = args.Context;
			if (context != null)
			{
				context.AddString(line);
			}
			Cairn.Log.LogInfo((object)("cairn> " + line));
		}

		private static void Run(ConsoleEventArgs args)
		{
			try
			{
				switch ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "help")
				{
				case "status":
					Status(args);
					break;
				case "landmarks":
					Landmarks(args);
					break;
				case "prefabs":
					Prefabs(args);
					break;
				case "pieces":
					Pieces(args);
					break;
				case "beacons":
					Beacons(args);
					break;
				case "raven":
					RavenVoice(args);
					break;
				case "save":
					SaveNow(args);
					break;
				default:
					Help(args);
					break;
				}
			}
			catch (Exception ex)
			{
				Say(args, "cairn: " + ex.Message);
				Cairn.Log.LogWarning((object)$"cairn console threw: {ex}");
			}
		}

		private static void Help(ConsoleEventArgs args)
		{
			Say(args, "cairn status         — what this process is, and what is running");
			Say(args, "cairn landmarks      — every landmark in the ledger");
			Say(args, "cairn prefabs <text> — every prefab name containing <text>");
			Say(args, "cairn pieces <text>  — only BUILDABLE pieces, with tool and cost");
			Say(args, "cairn beacons        — every light this client knows, and why it is dark");
			Say(args, "cairn raven          — why Hugin is or is not saying anything");
			Say(args, "cairn save           — flush the ledger now (authority only)");
		}

		private static void Prefabs(ConsoleEventArgs args)
		{
			string text = ((args.Args.Length > 2) ? args.Args[2] : "sign").ToLowerInvariant();
			try
			{
				Type type = AccessTools.TypeByName("ZNetScene");
				if (type == null)
				{
					Say(args, "cairn: no ZNetScene type.");
					return;
				}
				object obj = ReadSingleton(type);
				if (obj == null)
				{
					Say(args, "cairn: ZNetScene is not loaded — join or start a world first.");
					return;
				}
				FieldInfo fieldInfo = AccessTools.Field(type, "m_prefabs");
				if (fieldInfo == null)
				{
					Say(args, "cairn: ZNetScene has no m_prefabs — Valheim's API moved.");
					return;
				}
				if (!(fieldInfo.GetValue(obj) is IEnumerable enumerable))
				{
					Say(args, "cairn: m_prefabs was not enumerable.");
					return;
				}
				List<string> list = new List<string>();
				int num = 0;
				foreach (object item in enumerable)
				{
					Object val = (Object)((item is Object) ? item : null);
					if (!(val == (Object)null))
					{
						num++;
						string name = val.name;
						if (name != null && name.ToLowerInvariant().Contains(text))
						{
							list.Add(name);
						}
					}
				}
				list.Sort(StringComparer.OrdinalIgnoreCase);
				Say(args, $"cairn: {list.Count} of {num} prefab(s) contain \"{text}\"");
				foreach (string item2 in list)
				{
					Say(args, "  " + item2);
				}
			}
			catch (Exception ex)
			{
				Say(args, "cairn: prefab listing failed: " + ex.Message);
			}
		}

		private static void Pieces(ConsoleEventArgs args)
		{
			string text = ((args.Args.Length > 2) ? args.Args[2] : "stone").ToLowerInvariant();
			try
			{
				Type type = AccessTools.TypeByName("ZNetScene");
				object obj = ((type != null) ? ReadSingleton(type) : null);
				if (obj == null)
				{
					Say(args, "cairn: ZNetScene is not loaded — join or start a world first.");
					return;
				}
				if (!(AccessTools.Field(type, "m_prefabs")?.GetValue(obj) is IEnumerable enumerable))
				{
					Say(args, "cairn: could not read ZNetScene.m_prefabs — Valheim's API moved.");
					return;
				}
				List<GameObject> list = new List<GameObject>();
				foreach (object item in enumerable)
				{
					GameObject val = (GameObject)((item is GameObject) ? item : null);
					if (val != null)
					{
						list.Add(val);
					}
				}
				Dictionary<GameObject, List<string>> dictionary = MapPiecesToTools(list);
				List<string> list2 = new List<string>();
				Dictionary<string, string> dictionary2 = new Dictionary<string, string>();
				foreach (GameObject item2 in list)
				{
					if (!((Object)(object)item2 == (Object)null) && ((Object)item2).name != null && ((Object)item2).name.ToLowerInvariant().IndexOf(text, StringComparison.Ordinal) >= 0)
					{
						Component component = item2.GetComponent("Piece");
						if (!((Object)(object)component == (Object)null))
						{
							List<string> value;
							string text2 = ((dictionary.TryGetValue(item2, out value) && value.Count > 0) ? string.Join("/", value.ToArray()) : "no table");
							string text3 = "  " + ((Object)item2).name + "  [" + text2 + "]  " + DescribeCost(component) + "  " + DescribePlacement(component);
							list2.Add(text3);
							dictionary2[text3] = "      stack: " + DescribeStacking(item2, component);
						}
					}
				}
				list2.Sort(StringComparer.OrdinalIgnoreCase);
				Say(args, $"cairn: {list2.Count} BUILDABLE piece(s) containing \"{text}\"");
				foreach (string item3 in list2)
				{
					Say(args, item3);
					if (dictionary2.TryGetValue(item3, out var value2))
					{
						Say(args, value2);
					}
				}
			}
			catch (Exception ex)
			{
				Say(args, "cairn: piece listing failed: " + ex.Message);
			}
		}

		private static Dictionary<GameObject, List<string>> MapPiecesToTools(List<GameObject> prefabs)
		{
			Dictionary<GameObject, List<string>> dictionary = new Dictionary<GameObject, List<string>>();
			foreach (GameObject prefab in prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				try
				{
					Component component = prefab.GetComponent("ItemDrop");
					if ((Object)(object)component == (Object)null)
					{
						continue;
					}
					object obj = AccessTools.Field(((object)component).GetType(), "m_itemData")?.GetValue(component);
					object obj2 = ((obj == null) ? null : AccessTools.Field(obj.GetType(), "m_shared")?.GetValue(obj));
					object obj3 = ((obj2 == null) ? null : AccessTools.Field(obj2.GetType(), "m_buildPieces")?.GetValue(obj2));
					if (obj3 == null || !(AccessTools.Field(obj3.GetType(), "m_pieces")?.GetValue(obj3) is IEnumerable enumerable))
					{
						continue;
					}
					foreach (object item in enumerable)
					{
						GameObject val = (GameObject)((item is GameObject) ? item : null);
						if (!((Object)(object)val == (Object)null))
						{
							if (!dictionary.TryGetValue(val, out var value))
							{
								value = (dictionary[val] = new List<string>(2));
							}
							if (!value.Contains(((Object)prefab).name))
							{
								value.Add(((Object)prefab).name);
							}
						}
					}
				}
				catch
				{
				}
			}
			return dictionary;
		}

		private static string DescribePlacement(Component piece)
		{
			try
			{
				Type type = ((object)piece).GetType();
				List<string> list = new List<string>(4);
				FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (fieldInfo.FieldType != typeof(bool))
					{
						continue;
					}
					object value = fieldInfo.GetValue(piece);
					if (value is bool && (bool)value)
					{
						switch (fieldInfo.Name)
						{
						case "m_groundPiece":
							list.Add("SNAPS-TO-GROUND");
							break;
						case "m_groundOnly":
							list.Add("GROUND-ONLY");
							break;
						case "m_noClipping":
							list.Add("NO-CLIPPING");
							break;
						default:
							list.Add(fieldInfo.Name.StartsWith("m_") ? fieldInfo.Name.Substring(2) : fieldInfo.Name);
							break;
						case "m_enabled":
						case "m_canRotate":
						case "m_canBeRemoved":
							break;
						}
					}
				}
				return (list.Count > 0) ? string.Join(" ", list.ToArray()) : "free-placement";
			}
			catch
			{
				return "";
			}
		}

		private static string DescribeStacking(GameObject go, Component piece)
		{
			try
			{
				List<string> list = new List<string>(3);
				List<string> list2 = new List<string>(6);
				Rigidbody[] componentsInChildren = go.GetComponentsInChildren<Rigidbody>(true);
				if (componentsInChildren.Length != 0)
				{
					list2.Add("rigidbody=YES(" + (componentsInChildren[0].isKinematic ? "kinematic" : "dynamic") + ")");
					list.Add("rigidbody — the placement ray ignores it, ghost vanishes");
				}
				else
				{
					list2.Add("rigidbody=no");
				}
				int mask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid", "terrain", "vehicle" });
				Collider[] componentsInChildren2 = go.GetComponentsInChildren<Collider>(true);
				List<string> list3 = new List<string>(2);
				int num = 0;
				bool flag = false;
				Collider[] array = componentsInChildren2;
				foreach (Collider val in array)
				{
					if (!((Object)(object)val == (Object)null) && !val.isTrigger)
					{
						num++;
						string item = LayerMask.LayerToName(((Component)val).gameObject.layer);
						if (!list3.Contains(item))
						{
							list3.Add(item);
						}
						if (((1 << ((Component)val).gameObject.layer) & mask) == 0)
						{
							flag = true;
						}
					}
				}
				list2.Add(string.Format("colliders={0}({1})", num, string.Join(",", list3.ToArray())));
				if (num == 0)
				{
					list.Add("no solid collider — nothing for the ray to hit");
				}
				else if (flag)
				{
					list.Add("collider layer outside the placement mask — ray passes through");
				}
				Component component = go.GetComponent("WearNTear");
				if ((Object)(object)component == (Object)null)
				{
					list2.Add("wearntear=none");
				}
				else
				{
					Type type = ((object)component).GetType();
					object obj = AccessTools.Field(type, "m_supports")?.GetValue(component);
					object arg = AccessTools.Field(type, "m_materialType")?.GetValue(component);
					bool flag2 = default(bool);
					int num2;
					if (obj is bool)
					{
						flag2 = (bool)obj;
						num2 = 1;
					}
					else
					{
						num2 = 0;
					}
					bool flag3 = (byte)((uint)num2 & (flag2 ? 1u : 0u)) != 0;
					list2.Add($"wearntear=yes supports={flag3.ToString().ToLowerInvariant()} material={arg}");
					if (!flag3)
					{
						list.Add("supports=false — anything placed on it is Invalid");
					}
				}
				if (Flag(piece, ((object)piece).GetType(), "m_noClipping"))
				{
					list2.Add("RISK: noClipping — rounded stones penetrate >0.2m and go red");
				}
				try
				{
					List<Transform> list4 = new List<Transform>();
					AccessTools.Method(((object)piece).GetType(), "GetSnapPoints", new Type[1] { typeof(List<Transform>) }, (Type[])null)?.Invoke(piece, new object[1] { list4 });
					list2.Add($"snaps={list4.Count}");
				}
				catch
				{
				}
				return ((list.Count == 0) ? "STACKABLE by every rule the engine has" : ("BLOCKED: " + string.Join("; ", list.ToArray()))) + "  [" + string.Join("  ", list2.ToArray()) + "]";
			}
			catch (Exception ex)
			{
				return "could not read the prefab: " + ex.Message;
			}
		}

		private static bool Flag(object obj, Type t, string field)
		{
			object obj2 = AccessTools.Field(t, field)?.GetValue(obj);
			bool flag = default(bool);
			int num;
			if (obj2 is bool)
			{
				flag = (bool)obj2;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static string DescribeCost(Component piece)
		{
			try
			{
				if (!(AccessTools.Field(((object)piece).GetType(), "m_resources")?.GetValue(piece) is IEnumerable enumerable))
				{
					return "";
				}
				List<string> list = new List<string>(3);
				foreach (object item in enumerable)
				{
					if (item != null)
					{
						object? obj = AccessTools.Field(item.GetType(), "m_resItem")?.GetValue(item);
						Component val = (Component)((obj is Component) ? obj : null);
						object obj2 = AccessTools.Field(item.GetType(), "m_amount")?.GetValue(item);
						if (!((Object)(object)val == (Object)null) && !((Object)(object)val.gameObject == (Object)null) && obj2 != null)
						{
							list.Add($"{((Object)val.gameObject).name} x{obj2}");
						}
					}
				}
				return (list.Count > 0) ? string.Join(", ", list.ToArray()) : "free";
			}
			catch
			{
				return "";
			}
		}

		private static void Beacons(ConsoleEventArgs args)
		{
			if (!Cairn.HasRenderer)
			{
				Say(args, "cairn: no renderer on this process — beacons are drawn client-side.");
				return;
			}
			Beacon instance = Beacon.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Say(args, "cairn: the beacon renderer was never created. Is EnableBeacons off?");
				return;
			}
			List<string> list = instance.Describe();
			Say(args, $"cairn: {list.Count} beacon line(s), occlusion={ModConfig.BeaconOcclusion.Value}");
			foreach (string item in list)
			{
				Say(args, item);
			}
		}

		private static void RavenVoice(ConsoleEventArgs args)
		{
			if (!Cairn.HasRenderer)
			{
				Say(args, "cairn: no renderer here — the raven is client-side.");
				return;
			}
			HuginVoice instance = HuginVoice.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Say(args, "cairn: the voice component was never created. Is EnableRavenVoice off?");
				return;
			}
			Say(args, "cairn: the raven");
			foreach (string item in instance.Describe())
			{
				Say(args, item);
			}
		}

		private static object ReadSingleton(Type t)
		{
			object obj = (AccessTools.Field(t, "m_instance") ?? AccessTools.Field(t, "s_instance") ?? AccessTools.Field(t, "instance"))?.GetValue(null);
			if (obj == null)
			{
				obj = AccessTools.Property(t, "instance")?.GetValue(null, null);
			}
			Object val = (Object)((obj is Object) ? obj : null);
			if (val != null && val == (Object)null)
			{
				return null;
			}
			return obj;
		}

		private static void Landmarks(ConsoleEventArgs args)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: 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_015c: Unknown result type (might be due to invalid IL or missing references)
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			if (!Persistence.IsLoaded)
			{
				List<LandmarkSync.Beacon> list = LandmarkSync.Current();
				List<string> list2 = new List<string>(list.Count);
				List<Vector3> list3 = new List<Vector3>(list.Count);
				foreach (LandmarkSync.Beacon item in list)
				{
					list2.Add(item.Name);
					list3.Add(item.Light);
				}
				Player localPlayer = Player.m_localPlayer;
				Vector3 val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).transform.position : Vector3.zero);
				List<int> list4 = NamedListing.NamedByDistance(list2, list3, val);
				if (list4.Count == 0)
				{
					Say(args, (list.Count == 0) ? "cairn: no named cairns yet. The server has not sent any lit cairns to this game." : ("cairn: none of the " + list.Count.ToString(invariantCulture) + " lit cairn(s) near you has a name. Write on a sign within " + ModConfig.LandmarkPairMeters.Value.ToString("0.#", invariantCulture) + " m of a cairn to name it."));
					return;
				}
				Say(args, "cairn: " + list4.Count.ToString(invariantCulture) + " named cairn(s) near you, nearest first (the server sends the lit cairns nearest its players)");
				{
					foreach (int item2 in list4)
					{
						float num = NamedListing.DistanceXZ(list3[item2], val);
						Say(args, string.Format("  \"{0}\"  {1} m  at {2}", list2[item2], num.ToString("0", invariantCulture), list[item2].Key));
					}
					return;
				}
			}
			List<Landmark> list5 = LandmarkStore.Snapshot();
			if (list5.Count == 0)
			{
				Say(args, $"cairn: no landmarks (store loaded={Persistence.IsLoaded}, " + $"authority={Cairn.IsSimulationAuthority()})");
				return;
			}
			Say(args, "cairn: " + list5.Count.ToString(invariantCulture) + " landmark(s)");
			foreach (Landmark item3 in list5)
			{
				Say(args, $"  {item3.Key}  \"{item3.Name}\"  by {item3.Author}  first seen " + new DateTime(item3.FirstSeenUtcTicks, DateTimeKind.Utc).ToString("u", invariantCulture));
			}
		}

		private static void SaveNow(ConsoleEventArgs args)
		{
			if (!Persistence.IsLoaded)
			{
				Say(args, "cairn: no ledger on this process — the store lives on the server. Type this at the server's own console, or on a listen host.");
				return;
			}
			Persistence.Save(force: true);
			Say(args, "cairn: saved " + LandmarkStore.Count.ToString(CultureInfo.InvariantCulture) + " landmark(s).");
		}

		private static void Status(ConsoleEventArgs args)
		{
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			Say(args, "Cairn v0.12.0");
			Say(args, $"  role       : {CairnTick.Role()} (authority={Cairn.IsSimulationAuthority()}, " + $"dedicated={Cairn.IsDedicated()})");
			Say(args, $"  renderer   : {Cairn.HasRenderer}");
			Say(args, "  tick       : " + (CairnTick.WorldSeen ? "running" : "not yet — no world") + ", " + CairnTick.SystemCount.ToString(invariantCulture) + " system(s), budget " + ModConfig.TickBudgetMs.Value.ToString("0.##", invariantCulture) + "ms/frame");
			Say(args, $"  enabled    : {ModConfig.Enabled.Value} (verbose={ModConfig.VerboseLogging.Value})");
			Say(args, "  config     : layout " + ModConfig.ConfigVersion.Value.ToString(invariantCulture) + " — " + ConfigMigration.StatusLine());
			Say(args, "  ledger     : " + (Persistence.IsLoaded ? "loaded" : "not on this process") + ", " + LandmarkStore.Count.ToString(invariantCulture) + " landmark(s)" + (LandmarkStore.IsDirty ? ", unsaved changes" : ""));
			int value = ModConfig.BeaconMaxCount.Value;
			if (!Persistence.IsLoaded)
			{
				Say(args, "  broadcast  : the server's — on a client, `cairn beacons` shows what arrived");
			}
			else if (LandmarkSync.LastLit < 0)
			{
				Say(args, "  broadcast  : none sent yet (cap " + value.ToString(invariantCulture) + ")");
			}
			else
			{
				Say(args, "  broadcast  : " + LandmarkSync.LastLit.ToString(invariantCulture) + " lit, " + LandmarkSync.LastSent.ToString(invariantCulture) + " sent, cap " + value.ToString(invariantCulture) + ((LandmarkSync.LastLit > LandmarkSync.LastSent) ? " — OVER THE CAP: only the cairns nearest a player reach clients" : ""));
			}
			if (Persistence.IsLoaded)
			{
				Say(args, ModConfig.QuickChecks.Value ? ("  quick      : ON — " + CairnNotice.ChecksRun.ToString(invariantCulture) + " check(s) this session, " + CairnNotice.ChecksThatChanged.ToString(invariantCulture) + " changed a cairn") : "  quick      : OFF (QuickChecks = false) — new cairns wait for the full search");
			}
			int count = TombstoneSync.Mine().Count;
			string text = (ModConfig.ShowTombstoneLight.Value ? "shown" : "HIDDEN in this game");
			if (Persistence.IsLoaded)
			{
				string text2 = ((TombstoneSystem.LatestAt < 0f) ? "not swept yet" : (TombstoneSystem.Latest.Count.ToString(invariantCulture) + " in the world"));
				Say(args, ModConfig.EnableTombstoneLights.Value ? ("  tombstones : lights ON — " + text2 + ", " + count.ToString(invariantCulture) + " yours (" + text + ")") : "  tombstones : lights OFF (EnableTombstoneLights = false here)");
			}
			else
			{
				int lastReceivedCount = TombstoneSync.LastReceivedCount;
				Say(args, (lastReceivedCount < 0) ? "  tombstones : nothing received — the server has tombstone lights off, or is older than 0.11.0" : ("  tombstones : last push " + lastReceivedCount.ToString(invariantCulture) + " (all players), " + count.ToString(invariantCulture) + " yours (" + text + "), you are " + TombstoneSync.LocalPlayerId().ToString(invariantCulture)));
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	public static class Patch_WorldLifecycle
	{
		private static void Prefix(ZNet __instance)
		{
			try
			{
				CairnTick.OnZNetDestroyed(__instance);
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("world-end reset failed: " + ex.Message));
			}
		}
	}
}
namespace RavenIron.Cairn.Net
{
	public static class CairnNotice
	{
		private struct Pending
		{
			public Vector3 Spot;

			public float Due;

			public bool Retried;
		}

		public const string RpcName = "com.raveniron.cairn.notice1";

		private const float WaitSeconds = 1f;

		private const float RetrySeconds = 3f;

		private const float MergeMeters = 8f;

		private const int MaxPending = 64;

		private const float SenderWindowSeconds = 1f;

		private const int MaxPerSenderPerWindow = 4;

		private const float SendGapSeconds = 0.25f;

		private static Vector3 _lastSentSpot;

		private static float _lastSentAt = float.NegativeInfinity;

		private static readonly List<Pending> _pending = new List<Pending>(16);

		private static readonly Dictionary<long, float> _windowStart = new Dictionary<long, float>();

		private static readonly Dictionary<long, int> _windowCount = new Dictionary<long, int>();

		private static ZRoutedRpc _registeredOn;

		private static ZDOMan _watchingOn;

		public static int ChecksRun { get; private set; }

		public static int ChecksThatChanged { get; private set; }

		public static void EnsureRegistered()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || instance == _registeredOn)
			{
				return;
			}
			try
			{
				instance.Register<Vector3>("com.raveniron.cairn.notice1", (Action<long, Vector3>)RPC_Notice);
				_registeredOn = instance;
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("CairnNotice: register failed: " + ex.Message));
				_registeredOn = instance;
			}
		}

		public static void Notify(Vector3 spot, string why = "change")
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: 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)
			try
			{
				if (!ModConfig.Enabled.Value)
				{
					return;
				}
				if (Cairn.IsSimulationAuthority())
				{
					if (ModConfig.VerboseLogging.Value)
					{
						Cairn.Log.LogInfo((object)($"CairnNotice: {why} at ({spot.x:F0}, {spot.z:F0}) queued here " + $"[{DateTime.Now:HH:mm:ss.fff}]"));
					}
					Enqueue(spot, Time.realtimeSinceStartup);
					return;
				}
				ZRoutedRpc instance = ZRoutedRpc.instance;
				ZNet instance2 = ZNet.instance;
				ZNetPeer val = (((Object)(object)instance2 != (Object)null) ? instance2.GetServerPeer() : null);
				if (instance == null || val == null)
				{
					return;
				}
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup - _lastSentAt < 0.25f && LedgerUpdate.DistanceXZ(spot, _lastSentSpot) <= 8f)
				{
					if (ModConfig.VerboseLogging.Value)
					{
						Cairn.Log.LogInfo((object)($"CairnNotice: {why} at ({spot.x:F0}, {spot.z:F0}) held back (sent one here " + $"{realtimeSinceStartup - _lastSentAt:F2}s ago) [{DateTime.Now:HH:mm:ss.fff}]"));
					}
					return;
				}
				_lastSentSpot = spot;
				_lastSentAt = realtimeSinceStartup;
				if (ModConfig.VerboseLogging.Value)
				{
					Cairn.Log.LogInfo((object)($"CairnNotice: {why} at ({spot.x:F0}, {spot.z:F0}) sent to the server " + $"[{DateTime.Now:HH:mm:ss.fff}]"));
				}
				instance.InvokeRoutedRPC(val.m_uid, "com.raveniron.cairn.notice1", new object[1] { spot });
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("CairnNotice: send failed: " + ex.Message));
			}
		}

		private static void RPC_Notice(long sender, Vector3 spot)
		{
			//IL_0113: 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_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			if (!Cairn.IsSimulationAuthority() || !ModConfig.QuickChecks.Value)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!_windowStart.TryGetValue(sender, out var value) || realtimeSinceStartup - value > 1f)
			{
				_windowStart[sender] = realtimeSinceStartup;
				_windowCount[sender] = 0;
			}
			if (++_windowCount[sender] > 4)
			{
				if (ModConfig.VerboseLogging.Value)
				{
					Cairn.Log.LogInfo((object)($"CairnNotice: notice from {sender} at ({spot.x:F0}, {spot.z:F0}) DROPPED by the " + $"per-sender limit [{DateTime.Now:HH:mm:ss.fff}]"));
				}
				return;
			}
			if (ModConfig.VerboseLogging.Value)
			{
				Cairn.Log.LogInfo((object)($"CairnNotice: notice from {sender} at ({spot.x:F0}, {spot.z:F0}) received " + $"[{DateTime.Now:HH:mm:ss.fff}]"));
			}
			Enqueue(spot, realtimeSinceStartup);
		}

		public static void EnsureWatching()
		{
			ZDOMan instance = ZDOMan.instance;
			if (instance != null && instance != _watchingOn)
			{
				StopWatching();
				instance.m_onZDODestroyed = (Action<ZDO>)Delegate.Combine(instance.m_onZDODestroyed, new Action<ZDO>(OnZdoDestroyed));
				_watchingOn = instance;
			}
		}

		private static void StopWatching()
		{
			if (_watchingOn != null)
			{
				try
				{
					_watchingOn.m_onZDODestroyed = (Action<ZDO>)Delegate.Remove(_watchingOn.m_onZDODestroyed, new Action<ZDO>(OnZdoDestroyed));
				}
				catch (Exception)
				{
				}
				_watchingOn = null;
			}
		}

		private static void OnZdoDestroyed(ZDO zdo)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (zdo == null || !ModConfig.Enabled.Value || !ModConfig.QuickChecks.Value)
				{
					return;
				}
				LandmarkSystem instance = LandmarkSystem.Instance;
				if (instance != null && instance.IsWatched(zdo.GetPrefab(), out var prefab))
				{
					Vector3 position = zdo.GetPosition();
					if (ModConfig.VerboseLogging.Value)
					{
						Cairn.Log.LogInfo((object)($"CairnNotice: {prefab} destroyed at ({position.x:F0}, {position.z:F0}) " + $"[{DateTime.Now:HH:mm:ss.fff}]"));
					}
					Enqueue(position, Time.realtimeSinceStartup, mayRetry: false);
				}
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("CairnNotice: removal watch failed: " + ex.Message));
			}
		}

		private static void Enqueue(Vector3 spot, float now, bool mayRetry = true)
		{
			//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_003e: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.QuickChecks.Value)
			{
				return;
			}
			for (int i = 0; i < _pending.Count; i++)
			{
				if (!(LedgerUpdate.DistanceXZ(_pending[i].Spot, spot) > 8f))
				{
					_pending[i] = new Pending
					{
						Spot = spot,
						Due = now + 1f,
						Retried = (!mayRetry && _pending[i].Retried)
					};
					return;
				}
			}
			if (_pending.Count < 64)
			{
				_pending.Add(new Pending
				{
					Spot = spot,
					Due = now + 1f,
					Retried = !mayRetry
				});
			}
		}

		public static void Process(float now)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			if (_pending.Count == 0)
			{
				return;
			}
			int num = -1;
			for (int i = 0; i < _pending.Count; i++)
			{
				if (_pending[i].Due <= now)
				{
					num = i;
					break;
				}
			}
			if (num < 0)
			{
				return;
			}
			Pending pending = _pending[num];
			_pending.RemoveAt(num);
			LandmarkSystem instance = LandmarkSystem.Instance;
			if (instance != null && instance.Enabled)
			{
				bool flag;
				try
				{
					flag = instance.QuickCheck(pending.Spot);
				}
				catch (Exception ex)
				{
					Cairn.Log.LogWarning((object)("CairnNotice: quick check threw: " + ex.Message));
					return;
				}
				ChecksRun++;
				if (flag)
				{
					ChecksThatChanged++;
					LandmarkSync.RequestBroadcast();
				}
				else if (!pending.Retried && _pending.Count < 64)
				{
					_pending.Add(new Pending
					{
						Spot = pending.Spot,
						Due = now + 3f,
						Retried = true
					});
				}
			}
		}

		public static void ResetForWorldChange()
		{
			StopWatching();
			_pending.Clear();
			_windowStart.Clear();
			_windowCount.Clear();
			_lastSentAt = float.NegativeInfinity;
			ChecksRun = 0;
			ChecksThatChanged = 0;
		}
	}
	public static class LandmarkSync
	{
		public struct Beacon
		{
			public LandmarkKey Key;

			public Vector3 Light;

			public string Name;
		}

		public const string RpcName = "com.raveniron.cairn.beacons1";

		private static readonly List<Beacon> _cache = new List<Beacon>(32);

		private static ZRoutedRpc _registeredOn;

		private static bool _broadcastRequested;

		private static bool _warnedCap;

		private const int MaxBeaconsPerPush = 500;

		private static float _lastRejectWarn = -999f;

		public static int Revision { get; private set; }

		public static bool BroadcastRequested => _broadcastRequested;

		public static int LastLit { get; private set; } = -1;

		public static int LastSent { get; private set; } = -1;

		public static List<Beacon> Current()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (!Persistence.IsLoaded)
			{
				return _cache;
			}
			List<Beacon> list = new List<Beacon>(16);
			foreach (Landmark item in LandmarkStore.Snapshot())
			{
				if (item.HasPile)
				{
					list.Add(new Beacon
					{
						Key = item.Key,
						Light = item.Light,
						Name = item.Name
					});
				}
			}
			return list;
		}

		public static void ResetForWorldChange()
		{
			_cache.Clear();
			Revision++;
			LastLit = -1;
			LastSent = -1;
			_warnedCap = false;
		}

		public static void EnsureRegistered()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || instance == _registeredOn)
			{
				return;
			}
			try
			{
				instance.Register<ZPackage>("com.raveniron.cairn.beacons1", (Action<long, ZPackage>)RPC_Beacons);
				_registeredOn = instance;
				_cache.Clear();
				Revision++;
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("LandmarkSync: register failed: " + ex.Message));
				_registeredOn = instance;
			}
		}

		public static void RequestBroadcast()
		{
			_broadcastRequested = true;
			if (Persistence.IsLoaded)
			{
				Revision++;
			}
		}

		public static void ClearBroadcastRequest()
		{
			_broadcastRequested = false;
		}

		public static void Broadcast(int maxBeacons)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				return;
			}
			try
			{
				ZPackage val = new ZPackage();
				List<Landmark> list = new List<Landmark>(16);
				foreach (Landmark item in LandmarkStore.Snapshot())
				{
					if (item.HasPile)
					{
						list.Add(item);
					}
				}
				List<Landmark> list2 = ((list.Count > maxBeacons) ? BeaconSelection.Choose(list, PlayerPositions(), maxBeacons) : list);
				LastLit = list.Count;
				LastSent = list2.Count;
				if (list.Count > maxBeacons && !_warnedCap)
				{
					_warnedCap = true;
					Cairn.Log.LogWarning((object)($"LandmarkSync: {list.Count} cairns are lit and BeaconMaxCount is {maxBeacons} — " + $"each broadcast carries the {maxBeacons} nearest a player. Cairns further " + "out stay unlit for clients until someone comes near. Raise BeaconMaxCount (up to 500, on the server) to send more. `cairn status` shows the numbers."));
				}
				val.Write(list2.Count);
				foreach (Landmark item2 in list2)
				{
					val.Write(item2.Key.X);
					val.Write(item2.Key.Y);
					val.Write(item2.Key.Z);
					val.Write(item2.Light.x);
					val.Write(item2.Light.y);
					val.Write(item2.Light.z);
					val.Write(item2.Name ?? "");
				}
				instance.InvokeRoutedRPC(0L, "com.raveniron.cairn.beacons1", new object[1] { val });
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("LandmarkSync: broadcast failed: " + ex.Message));
			}
		}

		private static List<Vector3> PlayerPositions()
		{
			//IL_003b: 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)
			List<Vector3> list = new List<Vector3>(8);
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return list;
			}
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer != null && peer.IsReady())
				{
					list.Add(peer.m_refPos);
				}
			}
			if (!instance.IsDedicated())
			{
				list.Add(instance.GetReferencePosition());
			}
			return list;
		}

		private static void RPC_Beacons(long sender, ZPackage pkg)
		{
			//IL_00ec: 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)
			if (Persistence.IsLoaded)
			{
				return;
			}
			if (!FromServer(sender))
			{
				WarnRejected($"sender {sender} is not the server");
				return;
			}
			try
			{
				List<Beacon> list = new List<Beacon>(16);
				int num = pkg.ReadInt();
				if (num < 0 || num > 500)
				{
					WarnRejected($"{num} beacons in one push (the most a server sends is {500})");
					return;
				}
				for (int i = 0; i < num; i++)
				{
					int x = pkg.ReadInt();
					int y = pkg.ReadInt();
					int z = pkg.ReadInt();
					float num2 = pkg.ReadSingle();
					float num3 = pkg.ReadSingle();
					float num4 = pkg.ReadSingle();
					string text = pkg.ReadString() ?? "";
					if (text.Length > 128)
					{
						text = text.Substring(0, 128);
					}
					list.Add(new Beacon
					{
						Key = new LandmarkKey(x, y, z),
						Light = new Vector3(num2, num3, num4),
						Name = text
					});
				}
				_cache.Clear();
				_cache.AddRange(list);
				Revision++;
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("LandmarkSync: receive failed: " + ex.Message));
			}
		}

		private static bool FromServer(long sender)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer != null)
			{
				return serverPeer.m_uid == sender;
			}
			return false;
		}

		private static void WarnRejected(string why)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!(realtimeSinceStartup - _lastRejectWarn < 60f))
			{
				_lastRejectWarn = realtimeSinceStartup;
				Cairn.Log.LogWarning((object)("LandmarkSync: ignored a beacon push — " + why + "."));
			}
		}
	}
	public static class TombstoneSync
	{
		public const string RpcName = "com.raveniron.cairn.tombstones1";

		private static readonly List<TombstoneLights.Grave> _cache = new List<TombstoneLights.Grave>(8);

		private static float _received = -1f;

		private static ZRoutedRpc _registeredOn;

		public static int Revision { get; private set; }

		public static int LastReceivedCount { get; private set; } = -1;

		public static int LastSent { get; private set; } = -1;

		public static void EnsureRegistered()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || instance == _registeredOn)
			{
				return;
			}
			try
			{
				instance.Register<ZPackage>("com.raveniron.cairn.tombstones1", (Action<long, ZPackage>)RPC_Tombstones);
				_registeredOn = instance;
				_cache.Clear();
				_received = -1f;
				Revision++;
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("TombstoneSync: register failed: " + ex.Message));
				_registeredOn = instance;
			}
		}

		public static List<TombstoneLights.Grave> Mine()
		{
			long me = LocalPlayerId();
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (Cairn.IsSimulationAuthority())
			{
				if (!ModConfig.EnableTombstoneLights.Value)
				{
					return new List<TombstoneLights.Grave>();
				}
				return TombstoneLights.Mine(TombstoneSystem.Latest, me);
			}
			if (TombstoneLights.Stale(_received, realtimeSinceStartup, ModConfig.BeaconSyncSeconds.Value))
			{
				return new List<TombstoneLights.Grave>();
			}
			return TombstoneLights.Mine(_cache, me);
		}

		public static long LocalPlayerId()
		{
			try
			{
				Game instance = Game.instance;
				PlayerProfile val = (((Object)(object)instance != (Object)null) ? instance.GetPlayerProfile() : null);
				return (val != null) ? val.GetPlayerID() : 0;
			}
			catch (Exception)
			{
				return 0L;
			}
		}

		public static void Broadcast(List<TombstoneLights.Grave> graves)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_0056: 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_007a: Unknown result type (might be due to invalid IL or missing references)
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || graves == null)
			{
				return;
			}
			try
			{
				int num = Math.Min(graves.Count, 256);
				ZPackage val = new ZPackage();
				val.Write(num);
				for (int i = 0; i < num; i++)
				{
					TombstoneLights.Grave grave = graves[i];
					val.Write(grave.Owner);
					val.Write(grave.TimeOfDeath);
					val.Write(grave.Position.x);
					val.Write(grave.Position.y);
					val.Write(grave.Position.z);
				}
				LastSent = num;
				instance.InvokeRoutedRPC(0L, "com.raveniron.cairn.tombstones1", new object[1] { val });
			}
			catch (Exception ex)
			{
				Cairn.Log.LogWarning((object)("TombstoneSync: broadcast failed: " + ex.Message));
			}
		}

		private static void RPC_Tombstones(long sender, ZPackage pkg)
		{
			//IL_0098: 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)
			if (Cairn.IsSimulationAuthority() || !FromServer(sender))
			{
				return;
			}
			try
			{
				int num = pkg.ReadInt();
				if (num < 0 || num > 256)
				{
					Cairn.Log.LogWarning((object)$"TombstoneSync: ignored a push of {num} tombstones.");
					return;
				}
				List<TombstoneLights.Grave> list = new List<TombstoneLights.Grave>(num);
				for (int i = 0; i < num; i++)
				{
					long owner = pkg.ReadLong