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 Wispbone v0.3.0
plugins/Wispbone.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("Wispbone")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.3.0.0")] [assembly: AssemblyInformationalVersion("0.3.0")] [assembly: AssemblyProduct("Wispbone")] [assembly: AssemblyTitle("Wispbone")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.0.0")] [module: UnverifiableCode] [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 Wispbone { internal sealed class ConfigurationManagerAttributes { public int? Order; } internal static class ModLog { public static void Info(string message) { long start = Timings.Start(); try { Plugin.Log.LogInfo((object)message); } finally { Timings.Stop(Timings.LogWrite, start, message.Length, 0L, 0L); } } public static void Warning(string message) { long start = Timings.Start(); try { Plugin.Log.LogWarning((object)message); } finally { Timings.Stop(Timings.LogWrite, start, message.Length, 0L, 0L); } } } internal sealed class NameMatcher { internal sealed class Entry { public readonly string Text; private readonly Regex _wildcard; public bool MatchesEverything => Text.Trim(new char[1] { '*' }).Length == 0; public Entry(string text) { Text = text; if (text.Contains("*")) { _wildcard = new Regex("^" + Regex.Escape(text).Replace("\\*", ".*") + "$", RegexOptions.IgnoreCase); } } public bool IsMatch(string id, string family) { if (_wildcard == null) { if (!string.Equals(Text, id, StringComparison.OrdinalIgnoreCase)) { return string.Equals(Text, family, StringComparison.OrdinalIgnoreCase); } return true; } return _wildcard.IsMatch(id); } } public readonly List<Entry> Entries = (from s in (list ?? "").Split(new char[1] { ',' }) select s.Trim() into s where s.Length > 0 select new Entry(s)).ToList(); public NameMatcher(string list) { } public bool IsMatch(string id) { return IsMatch(id, Family(id)); } public bool IsMatch(string id, string family) { foreach (Entry entry in Entries) { if (entry.IsMatch(id, family)) { return true; } } return false; } public static string Family(string id) { int num = id.Length; while (num > 0 && char.IsDigit(id[num - 1])) { num--; } if (num == id.Length) { return id; } while (num > 0 && (id[num - 1] == '_' || id[num - 1] == '-')) { num--; } if (num != 0) { return id.Substring(0, num); } return id; } public static string DescribeFamilies(IEnumerable<string> ids) { IEnumerable<string> source = ids.Distinct<string>(StringComparer.OrdinalIgnoreCase).GroupBy<string, string>(Family, StringComparer.OrdinalIgnoreCase).OrderBy<IGrouping<string, string>, string>((IGrouping<string, string> g) => g.Key, StringComparer.OrdinalIgnoreCase) .Select(delegate(IGrouping<string, string> g) { List<string> list = g.OrderBy<string, string>((string s) => s, StringComparer.OrdinalIgnoreCase).ToList(); return (list.Count != 1 || !list[0].Equals(g.Key, StringComparison.OrdinalIgnoreCase)) ? (g.Key + " (" + string.Join(", ", list.ToArray()) + ")") : list[0]; }); return string.Join(", ", source.ToArray()); } } [BepInPlugin("com.adapperbadger.wispbone", "Wispbone", "0.3.0")] [BepInProcess("valheim.exe")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<string> Tracked; internal static ConfigEntry<bool> FindDungeons; internal static ConfigEntry<string> IgnoredSources; internal static ConfigEntry<float> Range; internal static ConfigEntry<float> HoverDistance; internal static ConfigEntry<bool> Ping; internal static ConfigEntry<float> PingSilentWithin; internal static ConfigEntry<bool> LogTimings; internal static ConfigEntry<bool> LogDungeons; private const string GeneralSection = "1 - General"; private const string TrackingSection = "2 - Tracking"; private const string WispSection = "3 - Wisp"; private const string PingSection = "4 - Ping"; private const string DebugSection = "5 - Debug"; private readonly Harmony _harmony = new Harmony("com.adapperbadger.wispbone"); private void Awake() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, "Turn the wisp's attraction and the ping on or off."); Tracked = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Tracking", "Tracked", "Softtissue", new ConfigDescription("Comma-separated item IDs (the names the spawn command uses), object IDs and location IDs. Items: the wisp is drawn to them on the ground, in treasure chests and pickups, to anything that drops one, and to the portal of a dungeon holding one. Objects, such as Spawner_GreydwarfNest: to each one, but a chest only while it holds something and a pickable only while there's something to pick. Locations: to each one, a dungeon by its portal. A name without its number covers every variant: Crypt covers Crypt2, Crypt3 and Crypt4. * is a wildcard. BepInEx/LogOutput.log lists every location ID when you load a world. Keep it to rare things: Wood would make every tree a target.", (AcceptableValueBase)null, new object[1] { Order(3) })); FindDungeons = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Tracking", "FindDungeons", false, new ConfigDescription("The wisp is drawn to the portal of every dungeon, whatever is inside, as if every dungeon type were in Tracked.", (AcceptableValueBase)null, new object[1] { Order(2) })); IgnoredSources = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Tracking", "IgnoredSources", "", new ConfigDescription("Comma-separated IDs of sources, objects, chests or locations to skip, matched the same way as Tracked: Dverger* skips every kind of dvergr, an item's own ID skips it on the ground (chests and pickups holding it still count), SunkenCrypt stops sunken crypts drawing the wisp to their portals.", (AcceptableValueBase)null, new object[1] { Order(1) })); Range = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Wisp", "Range", 40f, new ConfigDescription("How close a target (an item source, an object, a location or a dungeon portal) must be, in meters, before the wisp is drawn to it. The nearest one wins.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 100f), new object[1] { Order(2) })); HoverDistance = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Wisp", "HoverDistance", 8f, new ConfigDescription("Within this distance the wisp flies over and hovers above the target, clearing the mist around it. 0 = never hover.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), new object[1] { Order(1) })); Ping = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Ping", "Enabled", true, "Play the Wishbone's ping sound (no visual), faster the closer you get."); PingSilentWithin = ((BaseUnityPlugin)this).Config.Bind<float>("4 - Ping", "SilentWithin", 4f, new ConfigDescription("The ping stops once you're this close (meters) to the target; the wisp keeps hovering. 0 = never go quiet.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>())); LogTimings = ((BaseUnityPlugin)this).Config.Bind<bool>("5 - Debug", "LogTimings", false, "Every 30 seconds, log how long the mod's work took, one line per part. For checking performance; leave off otherwise."); LogDungeons = ((BaseUnityPlugin)this).Config.Bind<bool>("5 - Debug", "LogDungeons", false, "Log what the mod can see inside each dungeon that loads near you: the tracked things, chests, pickups and pickables, with their IDs. At most one dungeon a second. Each report is a long line that takes a few milliseconds to write, so leave off otherwise."); Tracked.SettingChanged += delegate { SourceTable.MarkDirty(); }; FindDungeons.SettingChanged += delegate { SourceTable.MarkDirty(); }; IgnoredSources.SettingChanged += delegate { SourceTable.MarkDirty(); }; _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Wispbone 0.3.0 loaded"); } private void Update() { Sites.Tick(); } private void OnDestroy() { _harmony.UnpatchSelf(); } private static ConfigurationManagerAttributes Order(int order) { return new ConfigurationManagerAttributes { Order = order }; } } internal static class PrefabIndex { internal enum Kind { Chest, Pickup, Pickable } internal enum ChestType { World, Buildable, Tombstone, SpillCrate } internal sealed class DropList { public string Kind; public HashSet<string> Items; } internal sealed class Dropper { public string Name; public List<DropList> Lists; } internal sealed class Swap { public string Intact; public string[] Becomes; } internal sealed class Interior { public Kind Kind; public string Name; public ChestType ChestType; } public static readonly List<string> Items = new List<string>(); public static readonly List<string> Objects = new List<string>(); public static readonly List<Dropper> Droppers = new List<Dropper>(); public static readonly List<Swap> Swaps = new List<Swap>(); public static readonly Dictionary<int, Interior> Interiors = new Dictionary<int, Interior>(); public static readonly Dictionary<int, string> Locations = new Dictionary<int, string>(); public static bool Built { get; private set; } public static void Build() { Items.Clear(); Objects.Clear(); Droppers.Clear(); Swaps.Clear(); Interiors.Clear(); Locations.Clear(); Built = false; if ((Object)(object)ZNetScene.instance == (Object)null) { return; } long start = Timings.Start(); List<GameObject> list = ZNetScene.instance.m_prefabs.Where((GameObject p) => (Object)(object)p != (Object)null).ToList(); try { HashSet<string> spillCrates = new HashSet<string>(); foreach (GameObject item in list) { IndexPrefab(item, spillCrates); } ClassifyChests(spillCrates); IndexLocations(); Built = true; } finally { Timings.Stop(Timings.Index, start, list.Count, 0L, 0L); } } private static void IndexPrefab(GameObject prefab, HashSet<string> spillCrates) { if ((Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null) { Items.Add(((Object)prefab).name); } else { Objects.Add(((Object)prefab).name); } List<DropList> list = (from l in GetDrops(prefab) where l.Items.Count > 0 select l).ToList(); if (list.Count > 0) { Droppers.Add(new Dropper { Name = ((Object)prefab).name, Lists = list }); } Destructible component = prefab.GetComponent<Destructible>(); if ((Object)(object)component != (Object)null) { string[] array = (from b in (IEnumerable<GameObject>)(object)new GameObject[2] { component.m_spawnWhenDamaged, component.m_spawnWhenDestroyed } where (Object)(object)b != (Object)null select ((Object)b).name).ToArray(); if (array.Length != 0) { Swaps.Add(new Swap { Intact = ((Object)prefab).name, Becomes = array }); } } Container component2 = prefab.GetComponent<Container>(); Piece component3 = prefab.GetComponent<Piece>(); if ((Object)(object)component2 != (Object)null && (Object)(object)component2.m_destroyedLootPrefab != (Object)null) { spillCrates.Add(((Object)component2.m_destroyedLootPrefab).name); } if ((Object)(object)component3 != (Object)null) { if ((Object)(object)component3.m_destroyedLootPrefab != (Object)null) { spillCrates.Add(((Object)component3.m_destroyedLootPrefab).name); } Container[] componentsInChildren = prefab.GetComponentsInChildren<Container>(true); foreach (Container val in componentsInChildren) { if ((Object)(object)val.m_destroyedLootPrefab != (Object)null) { spillCrates.Add(((Object)val.m_destroyedLootPrefab).name); } } } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); if ((Object)(object)component2 != (Object)null) { Interiors[stableHashCode] = new Interior { Kind = Kind.Chest, Name = ((Object)prefab).name, ChestType = (((Object)(object)prefab.GetComponent<TombStone>() != (Object)null) ? ChestType.Tombstone : ChestType.World) }; } else if ((Object)(object)prefab.GetComponent<PickableItem>() != (Object)null) { Interiors[stableHashCode] = new Interior { Kind = Kind.Pickup, Name = ((Object)prefab).name }; } else if ((Object)(object)prefab.GetComponent<Pickable>() != (Object)null) { Interiors[stableHashCode] = new Interior { Kind = Kind.Pickable, Name = ((Object)prefab).name }; } } private static void ClassifyChests(HashSet<string> spillCrates) { HashSet<string> hashSet = new HashSet<string>(); if ((Object)(object)ObjectDB.instance != (Object)null) { foreach (GameObject item in ObjectDB.instance.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null || val.m_itemData.m_shared == null) { continue; } PieceTable buildPieces = val.m_itemData.m_shared.m_buildPieces; if ((Object)(object)buildPieces == (Object)null) { continue; } foreach (GameObject piece in buildPieces.m_pieces) { if ((Object)(object)piece != (Object)null) { hashSet.Add(((Object)piece).name); } } } } else { ModLog.Warning("ObjectDB isn't ready, so chests on build menus can't be told apart by type."); } foreach (Interior item2 in Interiors.Values.Where((Interior i) => i.Kind == Kind.Chest)) { if (item2.ChestType != ChestType.Tombstone) { if (spillCrates.Contains(item2.Name)) { item2.ChestType = ChestType.SpillCrate; } else if (hashSet.Contains(item2.Name)) { item2.ChestType = ChestType.Buildable; } } } List<Interior> chests = Interiors.Values.Where((Interior i) => i.Kind == Kind.Chest).ToList(); ModLog.Info("Chest types: world chests: " + Names(chests, ChestType.World) + ". On a build menu: " + Names(chests, ChestType.Buildable) + ". Tombstones: " + Names(chests, ChestType.Tombstone) + ". Crates for destroyed things' contents: " + Names(chests, ChestType.SpillCrate) + "."); } private static string Names(IEnumerable<Interior> chests, ChestType type) { string[] array = (from c in chests where c.ChestType == type select c.Name).OrderBy<string, string>((string n) => n, StringComparer.OrdinalIgnoreCase).ToArray(); if (array.Length != 0) { return string.Join(", ", array); } return "none"; } private static void IndexLocations() { if ((Object)(object)ZoneSystem.instance == (Object)null) { return; } foreach (KeyValuePair<int, ZoneLocation> item in ZoneSystem.instance.m_locationsByHash) { if (!string.IsNullOrEmpty(item.Value.m_prefabName)) { Locations[item.Key] = item.Value.m_prefabName; } } ModLog.Info($"Location IDs ({Locations.Count}): {NameMatcher.DescribeFamilies(Locations.Values)}"); } private static IEnumerable<DropList> GetDrops(GameObject prefab) { CharacterDrop component = prefab.GetComponent<CharacterDrop>(); if ((Object)(object)component != (Object)null) { yield return ListOf("creature", component.m_drops.Select((Drop d) => d?.m_prefab)); } DropOnDestroyed component2 = prefab.GetComponent<DropOnDestroyed>(); if ((Object)(object)component2 != (Object)null) { yield return ListOf("breakable", TableItems(component2.m_dropWhenDestroyed)); } MineRock5 component3 = prefab.GetComponent<MineRock5>(); if ((Object)(object)component3 != (Object)null) { yield return ListOf("mineable", TableItems(component3.m_dropItems)); } MineRock component4 = prefab.GetComponent<MineRock>(); if ((Object)(object)component4 != (Object)null) { yield return ListOf("mineable", TableItems(component4.m_dropItems)); } TreeBase component5 = prefab.GetComponent<TreeBase>(); if ((Object)(object)component5 != (Object)null) { yield return ListOf("tree", TableItems(component5.m_dropWhenDestroyed)); } TreeLog component6 = prefab.GetComponent<TreeLog>(); if ((Object)(object)component6 != (Object)null) { yield return ListOf("log", TableItems(component6.m_dropWhenDestroyed)); } Pickable component7 = prefab.GetComponent<Pickable>(); if ((Object)(object)component7 != (Object)null) { yield return ListOf("pickable", TableItems(component7.m_extraDrops).Concat((IEnumerable<GameObject>)(object)new GameObject[1] { component7.m_itemPrefab })); } } private static DropList ListOf(string kind, IEnumerable<GameObject> drops) { return new DropList { Kind = kind, Items = new HashSet<string>(from d in drops where (Object)(object)d != (Object)null select ((Object)d).name, StringComparer.OrdinalIgnoreCase) }; } private static IEnumerable<GameObject> TableItems(DropTable table) { if (table != null && table.m_drops != null) { return table.m_drops.Select((DropData d) => d.m_item); } return Enumerable.Empty<GameObject>(); } } internal static class Sites { private sealed class Site { public ZNetView Marker; public int LocationHash; public bool HasInterior; public Transform Portal; public Target PortalTarget; public float SpawnedAt; public bool Announced; public readonly List<Target> InsideTargets = new List<Target>(); public readonly HashSet<ZDOID> Seen = new HashSet<ZDOID>(); public readonly List<ZNetView> Inside = new List<ZNetView>(); public float LastNewInside = float.MinValue; public int ReportedCount = -1; public bool IsLoaded { get { if ((Object)(object)Marker != (Object)null) { return Marker.IsValid(); } return false; } } } private sealed class Group { public int Count; public int Holding; public int Built; public int Picked; public string Note = ""; public readonly SortedDictionary<string, int> Items = new SortedDictionary<string, int>(); public void Add(string item, int amount) { Items.TryGetValue(item, out var value); Items[item] = value + amount; } } private const float TickInterval = 1f; private const float ReportQuietTime = 5f; private static readonly Dictionary<Vector2s, Site> All = new Dictionary<Vector2s, Site>(); private static float _nextTick; public static void Clear() { All.Clear(); } public static void OnLocationSpawned(LocationProxy proxy, out bool hasInterior, out bool portalFound) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) hasInterior = (portalFound = false); ZNetView nview = proxy.m_nview; GameObject instance = proxy.m_instance; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && !((Object)(object)instance == (Object)null)) { Location component = instance.GetComponent<Location>(); Site site = Get(ZoneSystem.GetZone(((Component)proxy).transform.position)); site.Marker = nview; site.LocationHash = nview.GetZDO().GetInt(ZDOVars.s_location, 0); site.HasInterior = (Object)(object)component != (Object)null && component.m_hasInterior; site.Portal = (site.HasInterior ? FindPortal(instance) : null); site.PortalTarget = new Target(nview, ((Object)(object)site.Portal != (Object)null) ? site.Portal : ((Component)nview).transform, LocationName(site)); site.SpawnedAt = Time.time; site.Inside.RemoveAll((ZNetView v) => (Object)(object)v == (Object)null || !v.IsValid()); hasInterior = site.HasInterior; portalFound = (Object)(object)site.Portal != (Object)null; Announce(site); } } public static bool OnInteriorObject(ZDO zdo, ZNetView view, Vector3 position) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (!PrefabIndex.Interiors.ContainsKey(zdo.GetPrefab())) { return false; } Site site = Get(ZoneSystem.GetZone(position)); site.Inside.Add(view); if (site.Seen.Add(zdo.m_uid)) { site.LastNewInside = Time.time; } return true; } public static void AddInsideTarget(Vector3 position, Target target) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) Get(ZoneSystem.GetZone(position)).InsideTargets.Add(target); } public static void ClearInsideTargets() { foreach (Site value in All.Values) { value.InsideTargets.Clear(); } } public static List<Target> InsideTargetsAt(Vector3 position) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (!All.TryGetValue(ZoneSystem.GetZone(position), out var value)) { return null; } return value.InsideTargets; } public static int FindNearest(Vector3 from, float range, ref Target best, ref float bestDistance) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) int num = 0; foreach (Site value in All.Values) { if (!value.IsLoaded || value.PortalTarget == null || !value.PortalTarget.IsAlive) { continue; } float num2 = Vector3.Distance(from, value.PortalTarget.Point.position); if (!(num2 >= range) && !(num2 >= bestDistance)) { bool flag = SourceTable.IsLocationTarget(value.LocationHash, (Object)(object)value.Portal != (Object)null); if (!flag && (Object)(object)value.Portal != (Object)null && !SourceTable.IsIgnoredLocation(value.LocationHash)) { num++; flag = HasTrackedInside(value); } if (flag) { best = value.PortalTarget; bestDistance = num2; } } } return num; } public static void AnnounceTrackedLocations() { foreach (Site value in All.Values) { if (value.IsLoaded) { Announce(value); } } } public static void Tick() { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) if (Time.time < _nextTick || (Object)(object)ZNetScene.instance == (Object)null) { return; } _nextTick = Time.time + 1f; SourceTable.EnsureBuilt(); long start = Timings.Start(); int num = 0; try { List<Vector2s> list = null; foreach (KeyValuePair<Vector2s, Site> item in All) { Site value = item.Value; if (!value.HasInterior) { if (!value.IsLoaded && value.Seen.Count == 0 && value.InsideTargets.Count == 0) { if (list == null) { list = new List<Vector2s>(); } list.Add(item.Key); } } else if (Plugin.LogDungeons.Value && num <= 0 && value.IsLoaded && value.Seen.Count != value.ReportedCount && !(Time.time - value.SpawnedAt < 5f) && !(Time.time - value.LastNewInside < 5f)) { ModLog.Info(Describe(value)); value.ReportedCount = value.Seen.Count; num++; } } if (list == null) { return; } foreach (Vector2s item2 in list) { All.Remove(item2); } } finally { Timings.Stop(Timings.Report, start, 0L, num, 0L); } } private static Site Get(Vector2s zone) { //IL_0005: 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) if (!All.TryGetValue(zone, out var value)) { value = (All[zone] = new Site()); } return value; } private static Transform FindPortal(GameObject instance) { //IL_0023: 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) Teleport[] componentsInChildren = instance.GetComponentsInChildren<Teleport>(); foreach (Teleport val in componentsInChildren) { if ((Object)(object)val.m_targetPoint != (Object)null && !Character.InInterior(((Component)val).transform.position) && Character.InInterior(((Component)val.m_targetPoint).transform.position)) { return ((Component)val).transform; } } return null; } private static bool HasTrackedInside(Site site) { List<Target> insideTargets = site.InsideTargets; for (int num = insideTargets.Count - 1; num >= 0; num--) { Target target = insideTargets[num]; if (!target.IsAlive) { insideTargets.RemoveAt(num); } else if (target.IsWanted) { return true; } } return false; } private static void Announce(Site site) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (!site.HasInterior && !site.Announced && SourceTable.IsLocationTarget(site.LocationHash, hasPortal: false)) { site.Announced = true; Vector3 position = ((Component)site.Marker).transform.position; ModLog.Info($"Tracked location {LocationName(site)} at ({position.x:0}, {position.z:0}) has no portal, so the wisp goes to its center."); } } private static string LocationName(Site site) { if (!PrefabIndex.Locations.TryGetValue(site.LocationHash, out var value)) { return $"#{site.LocationHash}"; } return value; } private static string Describe(Site site) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)site.Marker).transform.position; string text = (((Object)(object)site.Portal != (Object)null) ? $"portal found {Vector3.Distance(position, site.Portal.position):0} m from its center" : "no portal found"); site.InsideTargets.RemoveAll((Target t) => !t.IsAlive); string[] array = (from t in site.InsideTargets where t.IsWanted group t by t.Describe() into g orderby g.Key select (g.Count() <= 1) ? g.Key : $"{g.Key} x{g.Count()}").ToArray(); string text2 = (SourceTable.IsLocationTarget(site.LocationHash, (Object)(object)site.Portal != (Object)null) ? "a target (tracked by ID, or FindDungeons)" : ((!((Object)(object)site.Portal != (Object)null) || array.Length == 0 || SourceTable.IsIgnoredLocation(site.LocationHash)) ? "not a target" : "a target (tracked items inside)")); string text3 = ((array.Length != 0) ? string.Join(", ", array) : "nothing"); string text4 = ((site.ReportedCount >= 0) ? " More has loaded since the last report." : ""); return $"Dungeon {LocationName(site)} at ({position.x:0}, {position.z:0}): {text}; {text2}.{text4} " + "Tracked inside: " + text3 + ". Loaded inside: " + DescribeInside(site) + "."; } private static string DescribeInside(Site site) { site.Inside.RemoveAll((ZNetView v) => (Object)(object)v == (Object)null || !v.IsValid()); SortedDictionary<string, Group> sortedDictionary = new SortedDictionary<string, Group>(); SortedDictionary<string, Group> sortedDictionary2 = new SortedDictionary<string, Group>(); SortedDictionary<string, Group> sortedDictionary3 = new SortedDictionary<string, Group>(); foreach (ZNetView item in site.Inside) { ZDO zDO = item.GetZDO(); if (!PrefabIndex.Interiors.TryGetValue(zDO.GetPrefab(), out var value)) { continue; } switch (value.Kind) { case PrefabIndex.Kind.Chest: { Group obj2 = GroupFor(sortedDictionary, value.Name); obj2.Note = ChestTypeLabel(value.ChestType); if (zDO.GetLong(ZDOVars.s_creator, 0L) != 0L) { obj2.Built++; } Container component2 = ((Component)item).GetComponent<Container>(); Inventory val = (((Object)(object)component2 != (Object)null) ? component2.GetInventory() : null); List<ItemData> obj3 = ((val != null) ? val.GetAllItems() : new List<ItemData>()); if (obj3.Count > 0) { obj2.Holding++; } foreach (ItemData item2 in obj3) { obj2.Add(((Object)(object)item2.m_dropPrefab != (Object)null) ? ((Object)item2.m_dropPrefab).name : item2.m_shared.m_name, item2.m_stack); } break; } case PrefabIndex.Kind.Pickup: { Group obj4 = GroupFor(sortedDictionary2, value.Name); PickableItem component3 = ((Component)item).GetComponent<PickableItem>(); if ((Object)(object)component3 != (Object)null && (Object)(object)component3.m_itemPrefab != (Object)null) { obj4.Add(((Object)component3.m_itemPrefab).name, component3.m_stack); } break; } case PrefabIndex.Kind.Pickable: { Group obj = GroupFor(sortedDictionary3, value.Name); Pickable component = ((Component)item).GetComponent<Pickable>(); if (!((Object)(object)component == (Object)null)) { obj.Note = ((component.m_respawnTimeMinutes > 0f) ? "regrows" : "one-time"); if (component.m_picked) { obj.Picked++; } if ((Object)(object)component.m_itemPrefab != (Object)null) { obj.Add(((Object)component.m_itemPrefab).name, 0); } } break; } } } List<string> list = new List<string>(); if (sortedDictionary.Count > 0) { list.Add("chests " + string.Join(", ", sortedDictionary.Select((KeyValuePair<string, Group> c) => DescribeChests(c.Key, c.Value)).ToArray())); } if (sortedDictionary2.Count > 0) { list.Add("pickups " + string.Join(", ", sortedDictionary2.Select((KeyValuePair<string, Group> p) => $"{p.Key} x{p.Value.Count}: {DescribeItems(p.Value)}").ToArray())); } if (sortedDictionary3.Count > 0) { list.Add("pickables " + string.Join(", ", sortedDictionary3.Select((KeyValuePair<string, Group> p) => string.Format("{0} x{1} ({2}, {3} picked): {4}", p.Key, p.Value.Count, p.Value.Note, p.Value.Picked, string.Join(", ", p.Value.Items.Keys.ToArray()))).ToArray())); } if (list.Count <= 0) { return "nothing yet"; } return string.Join("; ", list.ToArray()); } private static Group GroupFor(SortedDictionary<string, Group> groups, string name) { if (!groups.TryGetValue(name, out var value)) { value = (groups[name] = new Group()); } value.Count++; return value; } private static string DescribeChests(string name, Group group) { string text = ((group.Built > 0) ? $", {group.Built} built by a player" : ""); string text2 = ((group.Holding > 0) ? $"{group.Holding} holding {DescribeItems(group)}" : "all empty"); return $"{name} x{group.Count} ({group.Note}{text}): {text2}"; } private static string DescribeItems(Group group) { if (group.Items.Count != 0) { return string.Join(", ", group.Items.Select((KeyValuePair<string, int> i) => (i.Value <= 1) ? i.Key : $"{i.Key} x{i.Value}").ToArray()); } return "nothing"; } private static string ChestTypeLabel(PrefabIndex.ChestType type) { return type switch { PrefabIndex.ChestType.Buildable => "on a build menu", PrefabIndex.ChestType.Tombstone => "tombstone", PrefabIndex.ChestType.SpillCrate => "spill crate", _ => "world chest", }; } } internal class Target(ZNetView view, Transform point, string name) { public readonly ZNetView View = view; public readonly Transform Point = point; public readonly string Name = name; public bool IsAlive { get { if ((Object)(object)view != (Object)null && view.IsValid()) { return (Object)(object)point != (Object)null; } return false; } } public virtual bool IsWanted => true; public virtual string Describe() { return name; } } internal sealed class PickupTarget : Target { private readonly PickableItem _pickup; private string _item; public override bool IsWanted { get { if (Item != null) { return SourceTable.IsTrackedItem(Item); } return false; } } private string Item { get { if (_item == null && (Object)(object)_pickup.m_itemPrefab != (Object)null) { _item = ((Object)_pickup.m_itemPrefab).name; } return _item; } } public PickupTarget(ZNetView view, PickableItem pickup, string name) { _pickup = pickup; base..ctor(view, ((Component)view).transform, name); } public override string Describe() { return name + " (" + Item + ")"; } } internal sealed class PickableTarget : Target { private readonly Pickable _pickable; public override bool IsWanted => _pickable.CanBePicked(); public PickableTarget(ZNetView view, Pickable pickable, string name) { _pickable = pickable; base..ctor(view, ((Component)view).transform, name); } } internal sealed class ChestTarget : Target { public static int Reads; private readonly Container _container; private readonly bool _anyItem; private bool _known; private uint _revision; private bool _wanted; public override bool IsWanted { get { uint lastRevision = _container.m_lastRevision; if (lastRevision == uint.MaxValue) { return false; } if (_known && lastRevision == _revision) { return _wanted; } Reads++; _known = true; _revision = lastRevision; _wanted = WantedItems().Any(); return _wanted; } } public ChestTarget(ZNetView view, Container container, string name, bool anyItem) { _container = container; _anyItem = anyItem; base..ctor(view, ((Component)view).transform, name); } public override string Describe() { return name + " (" + string.Join(", ", WantedItems().Distinct().ToArray()) + ")"; } private IEnumerable<string> WantedItems() { Inventory inventory = _container.GetInventory(); if (inventory == null) { yield break; } foreach (ItemData allItem in inventory.GetAllItems()) { if ((Object)(object)allItem.m_dropPrefab != (Object)null && (_anyItem || SourceTable.IsTrackedItem(((Object)allItem.m_dropPrefab).name))) { yield return ((Object)allItem.m_dropPrefab).name; } } } } internal static class SourceRegistry { private static readonly List<Target> Live = new List<Target>(); public static List<Target> Targets => Live; public static bool OnInstanceAdded(ZDO zdo, ZNetView view, Vector3 position, bool inInterior) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) Target target = Classify(zdo, view); if (target == null) { return false; } if (inInterior) { Sites.AddInsideTarget(position, target); } else { Live.Add(target); } return true; } public static void Reseed() { //IL_003e: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) Live.Clear(); Sites.ClearInsideTargets(); if ((Object)(object)ZNetScene.instance == (Object)null) { return; } foreach (KeyValuePair<ZDO, ZNetView> instance in ZNetScene.instance.m_instances) { Vector3 position = instance.Key.GetPosition(); OnInstanceAdded(instance.Key, instance.Value, position, Character.InInterior(position)); } Sites.AnnounceTrackedLocations(); } private static Target Classify(ZDO zdo, ZNetView view) { int prefab = zdo.GetPrefab(); PrefabIndex.Interior value; bool flag = PrefabIndex.Interiors.TryGetValue(prefab, out value); if (SourceTable.IsSource(prefab)) { string name = SourceTable.SourceName(prefab); if (flag && value.Kind == PrefabIndex.Kind.Pickable) { Pickable component = ((Component)view).GetComponent<Pickable>(); if ((Object)(object)component != (Object)null) { return new PickableTarget(view, component, name); } } if (flag && value.Kind == PrefabIndex.Kind.Chest && SourceTable.IsTrackedObject(prefab)) { Container component2 = ((Component)view).GetComponent<Container>(); if ((Object)(object)component2 != (Object)null) { return new ChestTarget(view, component2, name, anyItem: true); } } return new Target(view, ((Component)view).transform, name); } if (!SourceTable.TracksItems || !flag || SourceTable.IsIgnoredContainer(prefab)) { return null; } if (value.Kind == PrefabIndex.Kind.Pickup) { PickableItem component3 = ((Component)view).GetComponent<PickableItem>(); if ((Object)(object)component3 == (Object)null || ((Object)(object)component3.m_itemPrefab != (Object)null && !SourceTable.IsTrackedItem(((Object)component3.m_itemPrefab).name))) { return null; } return new PickupTarget(view, component3, value.Name); } if (value.Kind == PrefabIndex.Kind.Chest && value.ChestType == PrefabIndex.ChestType.World && zdo.GetLong(ZDOVars.s_creator, 0L) == 0L) { Container component4 = ((Component)view).GetComponent<Container>(); if (!((Object)(object)component4 != (Object)null)) { return null; } return new ChestTarget(view, component4, value.Name, anyItem: false); } return null; } } internal static class SourceTable { private sealed class Source { public string Kind; public readonly HashSet<string> Items = new HashSet<string>(StringComparer.OrdinalIgnoreCase); } private const float SettleTime = 1f; private static readonly HashSet<int> SourceHashes = new HashSet<int>(); private static readonly Dictionary<int, string> SourceNames = new Dictionary<int, string>(); private static readonly HashSet<int> TrackedObjects = new HashSet<int>(); private static readonly HashSet<string> TrackedItems = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<int> IgnoredContainers = new HashSet<int>(); private static readonly HashSet<int> TrackedLocations = new HashSet<int>(); private static readonly HashSet<int> IgnoredLocations = new HashSet<int>(); private static bool _dirty; private static float _lastEdit; public static bool TracksItems => TrackedItems.Count > 0; public static bool TracksAnything { get { if (SourceHashes.Count <= 0 && TrackedItems.Count <= 0 && TrackedLocations.Count <= 0) { return Plugin.FindDungeons.Value; } return true; } } public static bool IsSource(int prefabHash) { return SourceHashes.Contains(prefabHash); } public static string SourceName(int prefabHash) { if (!SourceNames.TryGetValue(prefabHash, out var value)) { return $"#{prefabHash}"; } return value; } public static bool IsTrackedObject(int prefabHash) { return TrackedObjects.Contains(prefabHash); } public static bool IsTrackedItem(string item) { return TrackedItems.Contains(item); } public static bool IsIgnoredContainer(int prefabHash) { return IgnoredContainers.Contains(prefabHash); } public static bool IsLocationTarget(int locationHash, bool hasPortal) { if (!IgnoredLocations.Contains(locationHash)) { if (!TrackedLocations.Contains(locationHash)) { if (hasPortal) { return Plugin.FindDungeons.Value; } return false; } return true; } return false; } public static bool IsIgnoredLocation(int locationHash) { return IgnoredLocations.Contains(locationHash); } public static void MarkDirty() { _dirty = true; _lastEdit = Time.realtimeSinceStartup; } public static void EnsureBuilt() { if (_dirty && Time.realtimeSinceStartup - _lastEdit >= 1f) { Rebuild(); } } public static void Rebuild() { _dirty = false; SourceHashes.Clear(); SourceNames.Clear(); TrackedObjects.Clear(); TrackedItems.Clear(); IgnoredContainers.Clear(); TrackedLocations.Clear(); IgnoredLocations.Clear(); if (!PrefabIndex.Built) { SourceRegistry.Reseed(); return; } long start = Timings.Start(); try { BuildTable(); SourceRegistry.Reseed(); } finally { Timings.Stop(Timings.Rebuild, start, SourceHashes.Count, 0L, 0L); } } private static void BuildTable() { NameMatcher nameMatcher = new NameMatcher(Plugin.Tracked.Value); NameMatcher nameMatcher2 = new NameMatcher(Plugin.IgnoredSources.Value); nameMatcher.Entries.RemoveAll(delegate(NameMatcher.Entry entry) { if (entry.MatchesEverything) { ModLog.Warning("Skipping '" + entry.Text + "' in Tracked: it would make everything a target."); } return entry.MatchesEverything; }); HashSet<string> hashSet = new HashSet<string>(PrefabIndex.Items.Where(nameMatcher.IsMatch), StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet2 = new HashSet<string>(PrefabIndex.Objects.Where(nameMatcher.IsMatch), StringComparer.OrdinalIgnoreCase); Dictionary<string, Source> dictionary = new Dictionary<string, Source>(StringComparer.OrdinalIgnoreCase); foreach (string item in hashSet) { Source source = new Source { Kind = "on the ground" }; source.Items.Add(item); dictionary[item] = source; } foreach (PrefabIndex.Dropper dropper in PrefabIndex.Droppers) { foreach (PrefabIndex.DropList list in dropper.Lists) { foreach (string item2 in list.Items) { if (hashSet.Contains(item2)) { if (!dictionary.TryGetValue(dropper.Name, out var value)) { string name = dropper.Name; Source obj = new Source { Kind = list.Kind }; value = obj; dictionary[name] = obj; } value.Items.Add(item2); } } } } foreach (string item3 in hashSet2) { if (!dictionary.ContainsKey(item3)) { dictionary[item3] = new Source { Kind = "tracked by ID" }; } } bool flag = true; while (flag) { flag = false; foreach (PrefabIndex.Swap swap in PrefabIndex.Swaps) { if (dictionary.ContainsKey(swap.Intact)) { continue; } string[] becomes = swap.Becomes; foreach (string text in becomes) { if (dictionary.TryGetValue(text, out var value2)) { Source source2 = new Source { Kind = "breakable, becomes " + text }; source2.Items.UnionWith(value2.Items); dictionary[swap.Intact] = source2; flag = true; break; } } } } foreach (KeyValuePair<string, Source> item4 in dictionary) { if (!nameMatcher2.IsMatch(item4.Key)) { int stableHashCode = StringExtensionMethods.GetStableHashCode(item4.Key); SourceHashes.Add(stableHashCode); SourceNames[stableHashCode] = item4.Key; if (hashSet2.Contains(item4.Key)) { TrackedObjects.Add(stableHashCode); } } } TrackedItems.UnionWith(hashSet); foreach (KeyValuePair<int, PrefabIndex.Interior> interior in PrefabIndex.Interiors) { if (interior.Value.Kind != PrefabIndex.Kind.Pickable && nameMatcher2.IsMatch(interior.Value.Name)) { IgnoredContainers.Add(interior.Key); } } foreach (KeyValuePair<int, string> location in PrefabIndex.Locations) { if (nameMatcher2.IsMatch(location.Value)) { IgnoredLocations.Add(location.Key); } else if (nameMatcher.IsMatch(location.Value)) { TrackedLocations.Add(location.Key); } } LogMatches(nameMatcher, nameMatcher2, hashSet, hashSet2, dictionary); } private static void LogMatches(NameMatcher tracked, NameMatcher ignored, HashSet<string> trackedItems, HashSet<string> trackedObjects, Dictionary<string, Source> sources) { foreach (NameMatcher.Entry entry in tracked.Entries) { List<string> list = trackedItems.Where((string i) => entry.IsMatch(i, NameMatcher.Family(i))).OrderBy<string, string>((string i) => i, StringComparer.OrdinalIgnoreCase).ToList(); List<string> list2 = Matches(entry, trackedObjects, ignored); List<string> list3 = Matches(entry, PrefabIndex.Locations.Values, ignored); if (list.Count == 0 && list2.Count == 0 && list3.Count == 0) { ModLog.Warning("No item, object or location called '" + entry.Text + "'. Check the ID in Tracked."); } if (list2.Count > 0) { ModLog.Info("Objects matching '" + entry.Text + "': " + string.Join(", ", list2.ToArray())); } if (list3.Count > 0) { ModLog.Info("Locations matching '" + entry.Text + "': " + string.Join(", ", list3.ToArray())); } foreach (string item in list) { string[] value = (from s in sources where s.Value.Items.Contains(item) select s.Key + " (" + s.Value.Kind + ")" + (ignored.IsMatch(s.Key) ? " [ignored]" : "")).ToArray(); ModLog.Info("Sources of " + item + ": " + string.Join(", ", value)); } } string text = ((trackedItems.Count > 0) ? ", world chests and pickups holding any of them" : ""); string arg = (Plugin.FindDungeons.Value ? ", plus every dungeon (FindDungeons)" : ""); ModLog.Info($"Tracking {trackedItems.Count} items, {TrackedObjects.Count} object types, {SourceHashes.Count} source types{text}, " + $"and {TrackedLocations.Count} location types{arg}."); } private static List<string> Matches(NameMatcher.Entry entry, IEnumerable<string> ids, NameMatcher ignored) { return (from id in ids.Where((string id) => entry.IsMatch(id, NameMatcher.Family(id))).OrderBy<string, string>((string id) => id, StringComparer.OrdinalIgnoreCase) select (!ignored.IsMatch(id)) ? id : (id + " [ignored]")).ToList(); } } internal static class SourceTracker { private const float ScanInterval = 1f; private static float _nextScan; private static Target _nearest; private static bool _wasInside; public static Transform GetNearest(Vector3 from) { //IL_0000: 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) bool flag = Character.InInterior(from); bool flag2 = _nearest != null && (!_nearest.IsAlive || !_nearest.IsWanted); if (Time.time >= _nextScan || flag2 || flag != _wasInside) { _wasInside = flag; _nextScan = Time.time + 1f; _nearest = Scan(from); } if (_nearest == null || !_nearest.IsAlive) { return null; } return _nearest.point; } private static Target Scan(Vector3 from) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) SourceTable.EnsureBuilt(); if ((Object)(object)ZNetScene.instance == (Object)null || !SourceTable.TracksAnything) { return null; } bool flag = Character.InInterior(from); List<Target> list = (flag ? Sites.InsideTargetsAt(from) : SourceRegistry.Targets); int num = list?.Count ?? 0; ChestTarget.Reads = 0; int num2 = 0; long start = Timings.Start(); try { float value = Plugin.Range.Value; Target best = null; float bestDistance = float.MaxValue; if (list != null) { FindNearest(list, from, value, ref best, ref bestDistance); } num2 = ChestTarget.Reads; if (!flag) { FindNearestLocation(from, value, ref best, ref bestDistance); } return best; } finally { Timings.Stop(Timings.Scan, start, num, num2, 0L); } } private static void FindNearest(List<Target> targets, Vector3 from, float range, ref Target best, ref float bestDistance) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) for (int num = targets.Count - 1; num >= 0; num--) { Target target = targets[num]; if (!target.IsAlive) { targets.RemoveAt(num); } else { float num2 = Vector3.Distance(from, target.point.position); if (num2 < range && num2 < bestDistance && target.IsWanted) { best = target; bestDistance = num2; } } } } private static void FindNearestLocation(Vector3 from, float range, ref Target best, ref float bestDistance) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) int reads = ChestTarget.Reads; long start = Timings.Start(); int num = 0; try { num = Sites.FindNearest(from, range, ref best, ref bestDistance); } finally { Timings.Stop(Timings.DungeonCheck, start, 0L, num, ChestTarget.Reads - reads); } } } internal static class Timings { internal sealed class Stat(string name, string itemLabel = null, string countLabel = null, string count2Label = null) { public readonly string Name = name; private readonly string _itemLabel = itemLabel; private readonly string _countLabel = countLabel; private readonly string _count2Label = count2Label; private int _calls; private double _maxMs; private long _items; private long _count; private long _count2; public double TotalMs; public double WorstFrameMs; public int Frame = -1; public double FrameMs; public void Record(double ms, long items, long count, long count2, int frame) { _calls++; TotalMs += ms; _items += items; _count += count; _count2 += count2; if (ms > _maxMs) { _maxMs = ms; } if (frame != Frame) { Frame = frame; FrameMs = 0.0; } FrameMs += ms; if (FrameMs > WorstFrameMs) { WorstFrameMs = FrameMs; } } public void Reset() { _calls = 0; TotalMs = (_maxMs = (WorstFrameMs = (FrameMs = 0.0))); _items = (_count = (_count2 = 0L)); Frame = -1; } public string Describe(double allPartsMs) { if (_calls == 0) { return $"{name,-16} 0x"; } List<string> list = new List<string>(); if (itemLabel != null) { list.Add($"{_items / _calls} {itemLabel} each"); } if (countLabel != null) { list.Add($"{_count} {countLabel}"); } if (count2Label != null) { list.Add($"{_count2} {count2Label}"); } string text = ((allPartsMs > 0.0) ? $"{100.0 * TotalMs / allPartsMs,5:0.0}%" : " -"); string text2 = ((list.Count > 0) ? (" | " + string.Join(", ", list.ToArray())) : ""); return $"{name,-16} {_calls,7}x avg {TotalMs / (double)_calls:0.00000}ms max {_maxMs:0.000}ms worst frame {WorstFrameMs:0.000}ms {text}{text2}"; } } public static readonly Stat ObjectLoad = new Stat("object loading", null, "filed"); public static readonly Stat LocationLoad = new Stat("location loading", null, "dungeons", "portals found"); public static readonly Stat Scan = new Stat("scan", "targets", "chests re-read"); public static readonly Stat DungeonCheck = new Stat("dungeon check", null, "dungeons looked inside", "chests re-read"); public static readonly Stat Report = new Stat("dungeon report", null, "reported"); public static readonly Stat Wisp = new Stat("wisp update"); public static readonly Stat Ping = new Stat("ping"); public static readonly Stat LogWrite = new Stat("log writing", "characters"); public static readonly Stat WarmUp = new Stat("warm-up", "methods"); public static readonly Stat Index = new Stat("world index", "prefabs"); public static readonly Stat Rebuild = new Stat("rebuild", "sources"); private static readonly Stat[] All = new Stat[11] { ObjectLoad, LocationLoad, Scan, DungeonCheck, Report, Wisp, Ping, LogWrite, WarmUp, Index, Rebuild }; private const float ReportInterval = 30f; private const int MaxDepth = 16; private const int CalibrationSamples = 5000; private static bool _enabled; private static double _msPerTick; private static double _clockReadTicks; private static int _depth; private static readonly long[] ChildTicks = new long[16]; private static int _nested; private static double _modMs; private static int _modFrame = -1; private static double _modFrameMs; private static double _worstFrameMs; private static string _worstFramePart = ""; private static float _windowStart = -1f; private static int _windowStartFrame; public static long Start() { if (!Plugin.LogTimings.Value) { _enabled = false; return 0L; } if (!_enabled) { Enable(); } if (_depth < 16) { ChildTicks[_depth] = 0L; } _depth++; return Stopwatch.GetTimestamp(); } public static void Stop(Stat stat, long start, long items = 0L, long count = 0L, long count2 = 0L) { if (start == 0L || _depth == 0) { return; } long num = Stopwatch.GetTimestamp() - start; int num2 = --_depth; long num3 = ((num2 < 16) ? ChildTicks[num2] : 0); int frameCount = Time.frameCount; stat.Record(Math.Max(0.0, (double)(num - num3) - _clockReadTicks) * _msPerTick, items, count, count2, frameCount); if (num2 > 0) { if (num2 - 1 < 16) { ChildTicks[num2 - 1] += num; } _nested++; return; } double num4 = Math.Max(0.0, (double)num - (double)(1 + _nested) * _clockReadTicks) * _msPerTick; _nested = 0; _modMs += num4; if (frameCount != _modFrame) { _modFrame = frameCount; _modFrameMs = 0.0; } _modFrameMs += num4; if (_modFrameMs > _worstFrameMs) { _worstFrameMs = _modFrameMs; _worstFramePart = BusiestPart(frameCount); } MaybeReport(); } private static void Enable() { _enabled = true; _msPerTick = 1000.0 / (double)Stopwatch.Frequency; long num = 0L; for (int i = 0; i < 5000; i++) { long timestamp = Stopwatch.GetTimestamp(); num += Stopwatch.GetTimestamp() - timestamp; } _clockReadTicks = (double)num / 5000.0; Stat[] all = All; for (int j = 0; j < all.Length; j++) { all[j].Reset(); } _depth = (_nested = 0); _modMs = (_modFrameMs = (_worstFrameMs = 0.0)); _modFrame = -1; _worstFramePart = ""; _windowStart = -1f; Plugin.Log.LogInfo((object)($"Timings on. The clock ticks every {1000000000.0 / (double)Stopwatch.Frequency:0} ns and reading it costs about " + $"{_clockReadTicks * _msPerTick * 1000000.0:0} ns, which is subtracted from every measurement.")); } private static string BusiestPart(int frame) { Stat stat = null; Stat[] all = All; foreach (Stat stat2 in all) { if (stat2.Frame == frame && (stat == null || stat2.FrameMs > stat.FrameMs)) { stat = stat2; } } if (stat == null) { return ""; } return stat.name; } private static void MaybeReport() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_windowStart < 0f) { StartWindow(realtimeSinceStartup); } else if (!(realtimeSinceStartup - _windowStart < 30f)) { int num = Mathf.Max(1, Time.frameCount - _windowStartFrame); float num2 = realtimeSinceStartup - _windowStart; double num3 = (double)num2 * 1000.0 / (double)num; double num4 = (float)num / num2; double num5 = _modMs / (double)num; double num6 = 1000.0 / (num3 - num5); double num7 = 0.0; Stat[] all = All; foreach (Stat stat in all) { num7 += stat.TotalMs; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"Timings, last {num2:0}s: {num4:0.0} fps ({num3:0.00} ms/frame). ").Append($"Mod used {num5:0.0000} ms/frame = {100.0 * num5 / num3:0.000}% of frame time, costing ~{num6 - num4:0.00} fps. ").Append($"Its worst frame: {_worstFrameMs:0.000} ms, mostly {_worstFramePart}."); all = All; foreach (Stat stat2 in all) { stringBuilder.Append("\n ").Append(stat2.Describe(num7)); } Plugin.Log.LogInfo((object)stringBuilder.ToString()); all = All; for (int i = 0; i < all.Length; i++) { all[i].Reset(); } _worstFrameMs = 0.0; _worstFramePart = ""; StartWindow(realtimeSinceStartup); } } private static void StartWindow(float now) { _windowStart = now; _windowStartFrame = Time.frameCount; _modMs = 0.0; } } internal static class WarmUp { private const BindingFlags Declared = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static bool _done; public static void Run() { if (_done) { return; } _done = true; long start = Timings.Start(); int num = 0; try { Type[] types; try { types = typeof(Plugin).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } Type[] array = types; foreach (Type type in array) { if (!(type == null) && !type.ContainsGenericParameters) { MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodBase method in methods) { num += Prepare(method); } ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodBase method2 in constructors) { num += Prepare(method2); } } } } finally { Timings.Stop(Timings.WarmUp, start, num, 0L, 0L); } } private static int Prepare(MethodBase method) { if (method.IsAbstract || method.ContainsGenericParameters) { return 0; } try { RuntimeHelpers.PrepareMethod(method.MethodHandle); return 1; } catch (Exception) { return 0; } } } internal static class WispPing { private static bool _initialized; private static EffectList _near; private static EffectList _medium; private static EffectList _far; private static float _closeInterval = 1f; private static float _distantInterval = 5f; private static float _timer; public static void Update(Player player, Transform source, float dt) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: 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) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) float num = (((Object)(object)source != (Object)null) ? Vector3.Distance(((Component)player).transform.position, source.position) : 0f); if (!Plugin.Ping.Value || (Object)(object)source == (Object)null || num <= Plugin.PingSilentWithin.Value || !EnsureInitialized()) { _timer = 0f; return; } float num2 = Mathf.Clamp01(num / Plugin.Range.Value); _timer += dt; if (_timer < Mathf.Lerp(_closeInterval, _distantInterval, num2)) { return; } _timer = 0f; EffectList val = ((num2 < 0.2f) ? _near : ((num2 < 0.6f) ? _medium : _far)); long start = Timings.Start(); try { val.Create(((Component)player).transform.position, ((Component)player).transform.rotation, ((Component)player).transform, 1f, -1, default(ZDOID)); } finally { Timings.Stop(Timings.Ping, start, 0L, 0L, 0L); } } private static bool EnsureInitialized() { if (_initialized) { return _near != null; } if ((Object)(object)ObjectDB.instance == (Object)null) { return false; } _initialized = true; long start = Timings.Start(); try { return Initialize(); } finally { Timings.Stop(Timings.Ping, start, 0L, 0L, 0L); } } private static bool Initialize() { SE_Finder val = ObjectDB.instance.m_StatusEffects.OfType<SE_Finder>().FirstOrDefault(); if ((Object)(object)val == (Object)null) { ModLog.Warning("Couldn't find the Wishbone's finder effect; the ping is disabled."); return false; } _closeInterval = val.m_closeFrequency; _distantInterval = val.m_distantFrequency; _near = SoundOnly(val.m_pingEffectNear, "near"); _medium = SoundOnly(val.m_pingEffectMed, "medium"); _far = SoundOnly(val.m_pingEffectFar, "far"); if (_near.m_effectPrefabs.Length + _medium.m_effectPrefabs.Length + _far.m_effectPrefabs.Length == 0) { ModLog.Warning("The Wishbone's ping has no sound-only effect to borrow; the ping is disabled."); _near = null; } return _near != null; } private static EffectList SoundOnly(EffectList source, string label) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown EffectData[] source2 = (EffectData[])(((object)source?.m_effectPrefabs) ?? ((object)new EffectData[0])); EffectData[] sounds = source2.Where((EffectData e) => e != null && (Object)(object)e.m_prefab != (Object)null && (Object)(object)e.m_prefab.GetComponentInChildren<ZSFX>(true) != (Object)null && (Object)(object)e.m_prefab.GetComponentInChildren<ParticleSystem>(true) == (Object)null).ToArray(); string text = string.Join(", ", (from e in source2 where e != null && (Object)(object)e.m_prefab != (Object)null select ((Object)e.m_prefab).name + (sounds.Contains(e) ? "" : " (skipped)")).ToArray()); ModLog.Info("Ping " + label + ": " + text); return new EffectList { m_effectPrefabs = sounds }; } } internal static class ModInfo { public const string Guid = "com.adapperbadger.wispbone"; public const string Name = "Wispbone"; public const string Version = "0.3.0"; } } namespace Wispbone.Patches { [HarmonyPatch(typeof(ZoneSystem), "Start")] internal static class ZoneSystemStartPatch { private static void Postfix() { WarmUp.Run(); Sites.Clear(); PrefabIndex.Build(); SourceTable.Rebuild(); } } [HarmonyPatch(typeof(LocationProxy), "SpawnLocation")] internal static class LocationProxySpawnPatch { private static void Postfix(LocationProxy __instance, bool __result) { if (!__result) { return; } long start = Timings.Start(); bool hasInterior = false; bool portalFound = false; try { Sites.OnLocationSpawned(__instance, out hasInterior, out portalFound); } finally { Timings.Stop(Timings.LocationLoad, start, 0L, hasInterior ? 1 : 0, portalFound ? 1 : 0); } } } [HarmonyPatch(typeof(SE_Demister), "UpdateStatusEffect")] internal static class WispAttractionPatch { private sealed class VanillaOffsets { public Vector3 Outdoor; public Vector3 Interior; } private const float LeanDistance = 3f; private const float HoverHeight = 2f; private static readonly ConditionalWeakTable<SE_Demister, VanillaOffsets> Vanilla = new ConditionalWeakTable<SE_Demister, VanillaOffsets>(); private static void Prefix(SE_Demister __instance, float dt) { Character character = ((StatusEffect)__instance).m_character; Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return; } long start = Timings.Start(); try { Steer(__instance, val, dt); } finally { Timings.Stop(Timings.Wisp, start, 0L, 0L, 0L); } } private static void Steer(SE_Demister demister, Player player, float dt) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) VanillaOffsets value = Vanilla.GetValue(demister, (SE_Demister se) => new VanillaOffsets { Outdoor = se.m_offset, Interior = se.m_offsetInterior }); demister.m_offset = value.Outdoor; demister.m_offsetInterior = value.Interior; if (!Plugin.Enabled.Value) { return; } Transform nearest = SourceTracker.GetNearest(((Component)player).transform.position); WispPing.Update(player, nearest, dt); if (!((Object)(object)nearest == (Object)null)) { Transform transform = ((Component)player).transform; Vector3 val = nearest.position - transform.position; Vector3 val2; if (((Vector3)(ref val)).magnitude <= Plugin.HoverDistance.Value) { val2 = nearest.position + Vector3.up * 2f; } else { Vector3 val3 = new Vector3(val.x, 0f, val.z); Vector3 normalized = ((Vector3)(ref val3)).normalized; val2 = transform.position + transform.TransformVector(value.Outdoor) + normalized * 3f; } demister.m_offsetInterior = (demister.m_offset = transform.InverseTransformVector(val2 - transform.position)); } } } [HarmonyPatch(typeof(ZNetScene), "AddInstance")] internal static class ZNetSceneAddInstancePatch { private static void Postfix(ZDO zdo, ZNetView nview) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) long start = Timings.Start(); int num = 0; try { Vector3 position = zdo.GetPosition(); bool flag = Character.InInterior(position); if (SourceRegistry.OnInstanceAdded(zdo, nview, position, flag)) { num++; } if (flag && Sites.OnInteriorObject(zdo, nview, position)) { num++; } } finally { Timings.Stop(Timings.ObjectLoad, start, 0L, num, 0L); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }