Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Cairn v0.12.0
plugins/Cairn.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
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