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 HitchFix v2.0.0
plugins/ValheimPerformanceFixes.dll
Decompiled 20 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.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DropNSpawnCache; using HarmonyLib; using Microsoft.CodeAnalysis; using MinimapCache; using RenderTrim; using Riverheim; using Riverheim.Pipeline; using Riverheim.Rendering; using Riverheim.Util.Random; using Riverheim.World; using RiverheimCache; using SoftReferenceableAssets; using SpawnBudget; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Rendering; using UnityEngine.Scripting; using ValheimPerformanceFixes; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ValheimPerformanceFixes")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0")] [assembly: AssemblyProduct("ValheimPerformanceFixes")] [assembly: AssemblyTitle("ValheimPerformanceFixes")] [assembly: AssemblyVersion("2.0.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; } } } public static class SpawnBudgetPressure { public static float Value; public static float Raw; public static float BudgetMs; public static int Instances; public static int PendingNear; public static int PendingDistant; public static bool Combat; } namespace RiverheimCache { public class Plugin : ModuleBehaviour { internal static ManualLogSource L; private const int FORMAT = 1; private const string MAGIC = "RHCACHE1"; private static ConfigEntry<bool> Enabled; private static ConfigEntry<bool> VerifyFirstLoad; private static ConfigEntry<bool> VerifyEveryLoad; private static ConfigEntry<int> SamplePoints; private static volatile WorldState capWs; private static volatile HeightMap capHm; private static string curKey; private static string curPath; private static int curSeed; private static bool curVerifyRun; private static byte[] curLoadedSha; private static WorldRenderer curLoadedRenderer; private void Awake() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //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_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_0113: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Expected O, but got Unknown //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown L = base.Logger; Enabled = base.Config.Bind<bool>("Optional - Riverheim Cache - General", "Enabled", true, "Cache Riverheim worldgen results on disk (BepInEx/cache/RiverheimCache). Disable to always compute normally."); VerifyFirstLoad = base.Config.Bind<bool>("Optional - Riverheim Cache - Safety", "VerifyFirstLoad", true, "The first time a newly written cache file would be used, also compute normally and require the computed data to be byte-identical to the cache (proves determinism). The computed result is used for that join. Afterwards the cache is trusted (still SHA256-checked on every load)."); VerifyEveryLoad = base.Config.Bind<bool>("Optional - Riverheim Cache - Safety", "VerifyEveryLoad", false, "Debug: verify against a full computation on every join (no time saving)."); SamplePoints = base.Config.Bind<int>("Optional - Riverheim Cache - Safety", "SamplePoints", 4096, "Number of world points sampled to compare the cache-built renderer against the computed one when a cache is written/verified."); SeedFromPluginFolder(); try { Harmony val = new Harmony("vpf.ValheimPerformanceFixes.RiverheimCache"); val.Patch((MethodBase)AccessTools.Method(typeof(GeneratorPipeline), "Compute", new Type[2] { typeof(int), typeof(object) }, (Type[])null), new HarmonyMethod(typeof(Plugin), "ComputePre", (Type[])null) { priority = 800 }, new HarmonyMethod(typeof(Plugin), "ComputePost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(WorldStateCompiler), "Compute", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "WsPost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(HeightMapBuilder), "Compute", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "HmPost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); L.LogInfo((object)"RiverheimCache patches applied"); } catch (Exception ex) { L.LogError((object)("RiverheimCache disabled (patch failed): " + ex)); } } private static void WsPost(WorldState __result) { capWs = __result; } private static void HmPost(HeightMap __result) { capHm = __result; } private static string Sha(byte[] b) { using SHA256 sHA = SHA256.Create(); return Hex(sHA.ComputeHash(b)); } private void SeedFromPluginFolder() { try { string directoryName = Path.GetDirectoryName(base.Info.Location); if (string.IsNullOrEmpty(directoryName) || !Directory.Exists(directoryName)) { return; } string text = Path.Combine(Paths.CachePath, "RiverheimCache"); Directory.CreateDirectory(text); int num = 0; foreach (string item in from x in Directory.GetFiles(directoryName) where x.EndsWith(".rhc", StringComparison.OrdinalIgnoreCase) || x.EndsWith(".rhc.verified", StringComparison.OrdinalIgnoreCase) select x) { string text2 = Path.Combine(text, Path.GetFileName(item)); if (!File.Exists(text2) && !File.Exists(text2 + ".bad") && !File.Exists(Path.ChangeExtension(text2, null) + ".bad")) { try { File.Copy(item, text2, overwrite: false); num++; } catch (Exception ex) { L.LogWarning((object)("RiverheimCache seed copy failed (harmless): " + Path.GetFileName(item) + " " + ex.Message)); } } } if (num > 0) { L.LogMessage((object)("RiverheimCache seeded " + num + " file(s) from plugin folder into " + text)); } } catch (Exception ex2) { L.LogWarning((object)("RiverheimCache seeding failed (harmless): " + ex2.Message)); } } private static string Hex(byte[] b) { return BitConverter.ToString(b).Replace("-", ""); } private static string BuildKey(int seed, WorldGenerationConfig cfg) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("fmt=").Append(1).Append("|seed=") .Append(seed); PluginInfo val = Chainloader.PluginInfos["dev.gurebu.riverheim.stable"]; stringBuilder.Append("|rh=").Append(val.Metadata.Version); stringBuilder.Append("|rhdll=").Append(Sha(File.ReadAllBytes(typeof(GeneratorPipeline).Assembly.Location))); stringBuilder.Append("|game=").Append(Version.GetVersionString(false)); stringBuilder.Append("|cfg=").Append(Sha(GraphSerializer.Serialize(cfg))); return stringBuilder.ToString(); } private static bool ComputePre(GeneratorPipeline __instance, int seed, object masterConfig) { //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_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Expected O, but got Unknown //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Expected O, but got Unknown //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) capWs = null; capHm = null; curKey = null; curPath = null; curVerifyRun = false; curLoadedSha = null; curLoadedRenderer = null; if (!Enabled.Value) { return true; } try { if (!(masterConfig is WorldGenerationConfig cfg)) { return true; } curSeed = seed; curKey = BuildKey(seed, cfg); string text; using (SHA256 sHA = SHA256.Create()) { text = Hex(sHA.ComputeHash(Encoding.UTF8.GetBytes(curKey))).Substring(0, 16); } string text2 = Path.Combine(Paths.CachePath, "RiverheimCache"); Directory.CreateDirectory(text2); string text3 = "world"; try { text3 = San(ZNet.World?.m_name ?? "world"); } catch { } curPath = Path.Combine(text2, text3 + "_" + seed + "_" + text + ".rhc"); if (File.Exists(curPath + ".bad")) { L.LogWarning((object)"cache key previously failed verification; computing normally"); curPath = null; return true; } if (!File.Exists(curPath)) { L.LogMessage((object)("Riverheim cache MISS (" + curPath + "); computing normally, will save")); return true; } Stopwatch stopwatch = Stopwatch.StartNew(); byte[] sha; object[] obj2 = (object[])GraphSerializer.Deserialize(ReadFile(curPath, curKey, out sha)); WorldRenderer r = MakeRenderer(ws: (WorldState)obj2[0], hm: (HeightMap)obj2[1], seed: seed, cfg: cfg); long elapsedMilliseconds = stopwatch.ElapsedMilliseconds; if (VerifyEveryLoad.Value || (VerifyFirstLoad.Value && !File.Exists(curPath + ".verified"))) { curVerifyRun = true; curLoadedSha = sha; curLoadedRenderer = r; L.LogMessage((object)$"Riverheim cache loaded in {elapsedMilliseconds}ms; VERIFY run: computing normally too and comparing"); return true; } PutResult(__instance, r); L.LogMessage((object)$"Riverheim cache HIT: loaded world gen from cache in {elapsedMilliseconds}ms (skipped pipeline) {curPath}"); curPath = null; return false; } catch (Exception ex) { L.LogWarning((object)("Riverheim cache load failed, computing normally: " + ex)); try { if (curPath != null && File.Exists(curPath)) { File.Delete(curPath); } } catch { } curVerifyRun = false; curLoadedRenderer = null; return true; } } private static void ComputePost(GeneratorPipeline __instance, int seed, object masterConfig) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) try { if (curPath == null || !(masterConfig is WorldGenerationConfig)) { return; } WorldGenerationConfig cfg = (WorldGenerationConfig)masterConfig; WorldState ws = capWs; HeightMap hm = capHm; if (ws == null || hm == (HeightMap)null) { L.LogWarning((object)"did not capture WorldState/HeightMap; not caching"); return; } WorldRenderer real = __instance.GetResult<WorldRenderer>("Rendering/WorldRenderer"); string path = curPath; string key = curKey; bool verifyRun = curVerifyRun; byte[] loadedSha = curLoadedSha; WorldRenderer loadedR = curLoadedRenderer; Thread thread = new Thread((ThreadStart)delegate { //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Expected O, but got Unknown //IL_01ad: Expected O, but got Unknown try { Stopwatch stopwatch = Stopwatch.StartNew(); byte[] array = GraphSerializer.Serialize(new object[2] { ws, hm }); byte[] array2; using (SHA256 sHA = SHA256.Create()) { array2 = sHA.ComputeHash(array); } if (verifyRun) { bool flag = loadedSha != null && array2.SequenceEqual(loadedSha); string why; bool flag2 = flag && SamplesEqual(real, loadedR, SamplePoints.Value, out why); if (flag && flag2) { File.WriteAllText(path + ".verified", DateTime.Now.ToString("o")); L.LogMessage((object)$"Riverheim cache VERIFIED: computed data byte-identical to cache ({(double)array.Length / 1048576.0:0.0} MB) and {SamplePoints.Value} samples match; future joins use cache"); } else { File.WriteAllText(path + ".bad", "mismatch bytes=" + flag + " samples=" + flag2); TryDel(path); L.LogError((object)("Riverheim cache verification FAILED (data " + (flag ? "same" : "differs") + "); cache disabled for this key")); } } else { object[] array3 = (object[])GraphSerializer.Deserialize(array); if (!GraphSerializer.Serialize(array3).SequenceEqual(array)) { L.LogError((object)"round-trip mismatch; not caching"); } else { WorldRenderer b = MakeRenderer(seed, cfg, (WorldState)array3[0], (HeightMap)array3[1]); if (!SamplesEqual(real, b, SamplePoints.Value, out var why2)) { L.LogError((object)("cache-built renderer differs from computed (" + why2 + "); not caching")); File.WriteAllText(path + ".bad", why2); } else { WriteFile(path, key, array, array2); Prune(path); L.LogMessage((object)$"Riverheim cache SAVED {(double)array.Length / 1048576.0:0.0} MB in {stopwatch.ElapsedMilliseconds}ms (round-trip + {SamplePoints.Value} samples OK): {path}"); } } } } catch (Exception ex2) { L.LogWarning((object)("Riverheim cache save failed (harmless): " + ex2)); } }); thread.IsBackground = true; thread.Priority = ThreadPriority.BelowNormal; thread.Name = "RiverheimCacheSave"; thread.Start(); } catch (Exception ex) { L.LogWarning((object)("Riverheim cache post failed (harmless): " + ex)); } finally { capWs = null; capHm = null; } } private static WorldRenderer MakeRenderer(int seed, WorldGenerationConfig cfg, WorldState ws, HeightMap hm) { //IL_0001: 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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown return new WorldRenderer(new RandomDirectory(seed).Sub("Rendering"), cfg.common, cfg.rendering.renderer, ws, hm); } private static void PutResult(GeneratorPipeline p, WorldRenderer r) { object value = AccessTools.Field(typeof(GeneratorPipeline), "cache").GetValue(p); AccessTools.Method(value.GetType(), "Put", (Type[])null, (Type[])null).Invoke(value, new object[2] { "Rendering/WorldRenderer", r }); } private unsafe static bool SamplesEqual(WorldRenderer a, WorldRenderer b, int n, out string why) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_0069: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0079: 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_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00ed: 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_00f4: 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_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) why = null; Random random = new Random(12345); double num = 10600.0; Rfloat2 val = default(Rfloat2); for (int i = 0; i < n; i++) { ((Rfloat2)(ref val))..ctor((random.NextDouble() * 2.0 - 1.0) * num, (random.NextDouble() * 2.0 - 1.0) * num); PointData val2 = a.RenderPoint(val); PointData val3 = b.RenderPoint(val); if (val2.biome != val3.biome || val2.height != val3.height || val2.fertility != val3.fertility || val2.lava != val3.lava || val2.snow != val3.snow) { why = $"RenderPoint differs at {val}: {val2.height} vs {val3.height}"; return false; } if (a.GetBiome(val) != b.GetBiome(val)) { why = "GetBiome differs at " + ((object)(*(Rfloat2*)(&val))/*cast due to .constrained prefix*/).ToString(); return false; } if (a.GetForestation(val) != b.GetForestation(val)) { why = "GetForestation differs at " + ((object)(*(Rfloat2*)(&val))/*cast due to .constrained prefix*/).ToString(); return false; } } return true; } private static void WriteFile(string path, string key, byte[] payload, byte[] sha) { string text = path + ".tmp"; using (FileStream output = new FileStream(text, FileMode.Create, FileAccess.Write)) { using BinaryWriter binaryWriter = new BinaryWriter(output); binaryWriter.Write("RHCACHE1"); binaryWriter.Write(key); binaryWriter.Write(payload.Length); binaryWriter.Write(sha); binaryWriter.Write(payload); } TryDel(path); TryDel(path + ".verified"); File.Move(text, path); } private static byte[] ReadFile(string path, string key, out byte[] sha) { using FileStream input = File.OpenRead(path); using BinaryReader binaryReader = new BinaryReader(input); if (binaryReader.ReadString() != "RHCACHE1") { throw new InvalidDataException("bad magic"); } if (binaryReader.ReadString() != key) { throw new InvalidDataException("key mismatch"); } int num = binaryReader.ReadInt32(); sha = binaryReader.ReadBytes(32); byte[] array = binaryReader.ReadBytes(num); if (array.Length != num) { throw new InvalidDataException("truncated"); } using (SHA256 sHA = SHA256.Create()) { if (!sHA.ComputeHash(array).SequenceEqual(sha)) { throw new InvalidDataException("checksum mismatch"); } } return array; } private static void Prune(string keep) { try { string fileName = Path.GetFileName(keep); string text = fileName.Substring(0, fileName.LastIndexOf('_') + 1); string[] files = Directory.GetFiles(Path.GetDirectoryName(keep), text + "*"); foreach (string text2 in files) { if (!text2.StartsWith(keep)) { TryDel(text2); } } } catch { } } private static void TryDel(string f) { try { if (File.Exists(f)) { File.Delete(f); } } catch { } } private static string San(string s) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { s = s.Replace(oldChar, '_'); } return s; } } public static class GraphSerializer { private sealed class RefEq : IEqualityComparer<object> { public new bool Equals(object a, object b) { return a == b; } public int GetHashCode(object o) { return RuntimeHelpers.GetHashCode(o); } } private static readonly Dictionary<Type, FieldInfo[]> fieldCache = new Dictionary<Type, FieldInfo[]>(); private static readonly HashSet<string> okBclGeneric = new HashSet<string> { "System.Collections.Generic.List`1", "System.Collections.Generic.Dictionary`2", "System.Collections.Generic.HashSet`1", "System.Nullable`1", "System.Collections.Generic.KeyValuePair`2", "System.ValueTuple`1", "System.ValueTuple`2", "System.ValueTuple`3", "System.ValueTuple`4", "System.ValueTuple`5", "System.ValueTuple`6", "System.ValueTuple`7" }; private static FieldInfo[] Fields(Type t) { lock (fieldCache) { if (fieldCache.TryGetValue(t, out var value)) { return value; } List<FieldInfo> list = new List<FieldInfo>(); Type type = t; while (type != null && type != typeof(object) && type != typeof(ValueType)) { list.InsertRange(0, from x in type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) orderby x.MetadataToken select x); type = type.BaseType; } value = list.ToArray(); fieldCache[t] = value; return value; } } private static bool IsDict(Type t) { if (t.IsGenericType) { return t.GetGenericTypeDefinition() == typeof(Dictionary<, >); } return false; } private static bool IsSet(Type t) { if (t.IsGenericType) { return t.GetGenericTypeDefinition() == typeof(HashSet<>); } return false; } private static void Check(Type t) { if (t.IsPointer || t.IsByRef || t == typeof(IntPtr) || t == typeof(UIntPtr) || typeof(Type).IsAssignableFrom(t) || typeof(MemberInfo).IsAssignableFrom(t)) { throw new NotSupportedException("unsupported type " + t); } if (t.IsArray) { Check(t.GetElementType()); return; } if (t.IsGenericType) { Type[] genericArguments = t.GetGenericArguments(); for (int i = 0; i < genericArguments.Length; i++) { Check(genericArguments[i]); } } Assembly assembly = t.Assembly; string name = assembly.GetName().Name; if (!(assembly == typeof(object).Assembly)) { switch (name) { case "System": case "System.Core": case "netstandard": break; default: if (typeof(Object).IsAssignableFrom(t)) { throw new NotSupportedException("unity object " + t); } return; } } if (t.IsPrimitive || t.IsEnum || t == typeof(string) || t == typeof(object) || t == typeof(decimal) || (t.IsGenericType && okBclGeneric.Contains(t.GetGenericTypeDefinition().FullName))) { return; } throw new NotSupportedException("unsupported BCL type " + t); } public static byte[] Serialize(object root) { MemoryStream memoryStream = new MemoryStream(); BinaryWriter w = new BinaryWriter(memoryStream); Dictionary<object, int> ids = new Dictionary<object, int>(new RefEq()); Queue<object> queue = new Queue<object>(); Dictionary<Type, int> types = new Dictionary<Type, int>(); WRef(root); while (queue.Count > 0) { object obj = queue.Dequeue(); Type type = obj.GetType(); if (obj is Array array) { Type elementType = type.GetElementType(); if (elementType.IsPrimitive) { byte[] array2 = new byte[Buffer.ByteLength(array)]; Buffer.BlockCopy(array, 0, array2, 0, array2.Length); w.Write(array2); continue; } int[] array3 = new int[array.Rank]; int length = array.Length; for (int i = 0; i < length; i++) { Unflat(array, i, array3); WVal(elementType, array.GetValue(array3)); } } else if (IsDict(type)) { Type[] genericArguments = type.GetGenericArguments(); IDictionary dictionary = (IDictionary)obj; w.Write(dictionary.Count); foreach (DictionaryEntry item in dictionary) { WVal(genericArguments[0], item.Key); WVal(genericArguments[1], item.Value); } } else if (IsSet(type)) { Type[] genericArguments2 = type.GetGenericArguments(); List<object> list = ((IEnumerable)obj).Cast<object>().ToList(); w.Write(list.Count); foreach (object item2 in list) { WVal(genericArguments2[0], item2); } } else { FieldInfo[] array4 = Fields(type); foreach (FieldInfo fieldInfo in array4) { WVal(fieldInfo.FieldType, fieldInfo.GetValue(obj)); } } } w.Flush(); return memoryStream.ToArray(); void WRef(object o) { int value; if (o == null) { w.Write(-1); } else if (ids.TryGetValue(o, out value)) { w.Write(value); } else { value = ids.Count; ids[o] = value; w.Write(value); Type type2 = o.GetType(); if (type2.IsPrimitive || type2.IsEnum) { throw new NotSupportedException("boxed primitive " + type2); } WType(type2); if (o is string value2) { w.Write(value2); } else if (o is Delegate obj2) { if (obj2.GetInvocationList().Length != 1) { throw new NotSupportedException("multicast delegate"); } MethodInfo method = obj2.Method; object target = obj2.Target; byte value3; if (target == null) { value3 = 0; } else { FieldInfo field = target.GetType().GetField("<>9", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (!(field != null) || field.GetValue(null) != target) { throw new NotSupportedException("delegate with captured target " + method); } value3 = 1; } if (method.DeclaringType.Assembly == typeof(object).Assembly) { throw new NotSupportedException("BCL delegate " + method); } w.Write(value3); WType(method.DeclaringType); w.Write(method.MetadataToken); } else { if (o is Array array5) { w.Write(array5.Rank); for (int k = 0; k < array5.Rank; k++) { w.Write(array5.GetLength(k)); } } if (IsDict(type2) || IsSet(type2)) { object value4 = type2.GetProperty("Comparer").GetValue(o); object value5 = typeof(EqualityComparer<>).MakeGenericType(type2.GetGenericArguments()[0]).GetProperty("Default").GetValue(null); if (value4.GetType() != value5.GetType()) { throw new NotSupportedException("custom comparer on " + type2); } } queue.Enqueue(o); } } } void WType(Type t) { if (types.TryGetValue(t, out var value)) { w.Write(value); } else { Check(t); value = types.Count; types[t] = value; w.Write(value); if (t.IsArray) { w.Write((byte)2); WType(t.GetElementType()); int arrayRank = t.GetArrayRank(); bool flag = t == t.GetElementType().MakeArrayType(); w.Write((!flag) ? arrayRank : 0); } else if (t.IsGenericType && !t.IsGenericTypeDefinition) { w.Write((byte)1); WType(t.GetGenericTypeDefinition()); Type[] genericArguments3 = t.GetGenericArguments(); w.Write(genericArguments3.Length); Type[] array5 = genericArguments3; for (int k = 0; k < array5.Length; k++) { WType(array5[k]); } } else { w.Write((byte)0); w.Write(t.Assembly.GetName().Name); w.Write(t.FullName); } } } void WVal(Type t, object v) { if (t.IsEnum) { t = Enum.GetUnderlyingType(t); } if (t.IsPrimitive) { WPrim(w, t, v); } else if (t == typeof(decimal)) { w.Write((decimal)v); } else if (t.IsValueType) { Check(t); FieldInfo[] array5 = Fields(t); foreach (FieldInfo fieldInfo2 in array5) { WVal(fieldInfo2.FieldType, fieldInfo2.GetValue(v)); } } else { WRef(v); } } } private static void Unflat(Array a, int k, int[] idx) { for (int num = a.Rank - 1; num >= 0; num--) { int length = a.GetLength(num); idx[num] = k % length; k /= length; } } private static void WPrim(BinaryWriter w, Type t, object v) { switch (Type.GetTypeCode(t)) { case TypeCode.Boolean: w.Write((bool)v); break; case TypeCode.Char: w.Write((ushort)(char)v); break; case TypeCode.SByte: w.Write((sbyte)v); break; case TypeCode.Byte: w.Write((byte)v); break; case TypeCode.Int16: w.Write((short)v); break; case TypeCode.UInt16: w.Write((ushort)v); break; case TypeCode.Int32: w.Write((int)v); break; case TypeCode.UInt32: w.Write((uint)v); break; case TypeCode.Int64: w.Write((long)v); break; case TypeCode.UInt64: w.Write((ulong)v); break; case TypeCode.Single: w.Write((float)v); break; case TypeCode.Double: w.Write((double)v); break; default: throw new NotSupportedException("primitive " + t); } } private static object RPrim(BinaryReader r, Type t) { return Type.GetTypeCode(t) switch { TypeCode.Boolean => r.ReadBoolean(), TypeCode.Char => (char)r.ReadUInt16(), TypeCode.SByte => r.ReadSByte(), TypeCode.Byte => r.ReadByte(), TypeCode.Int16 => r.ReadInt16(), TypeCode.UInt16 => r.ReadUInt16(), TypeCode.Int32 => r.ReadInt32(), TypeCode.UInt32 => r.ReadUInt32(), TypeCode.Int64 => r.ReadInt64(), TypeCode.UInt64 => r.ReadUInt64(), TypeCode.Single => r.ReadSingle(), TypeCode.Double => r.ReadDouble(), _ => throw new NotSupportedException("primitive " + t), }; } public static object Deserialize(byte[] data) { BinaryReader r = new BinaryReader(new MemoryStream(data, writable: false)); List<object> objs = new List<object>(); Queue<object> queue = new Queue<object>(); List<Type> types = new List<Type>(); List<Action> list = new List<Action>(); object result = RRef(); while (queue.Count > 0) { object o = queue.Dequeue(); Type type = o.GetType(); if (o is Array array) { Type elementType = type.GetElementType(); if (elementType.IsPrimitive) { int num = Buffer.ByteLength(array); byte[] array2 = r.ReadBytes(num); if (array2.Length != num) { throw new EndOfStreamException(); } Buffer.BlockCopy(array2, 0, array, 0, num); } else { int[] array3 = new int[array.Rank]; int length = array.Length; for (int i = 0; i < length; i++) { Unflat(array, i, array3); array.SetValue(RVal(elementType), array3); } } } else if (IsDict(type)) { Type[] genericArguments = type.GetGenericArguments(); int num2 = r.ReadInt32(); IDictionary d = (IDictionary)o; List<(object, object)> kv = new List<(object, object)>(num2); for (int j = 0; j < num2; j++) { object item = RVal(genericArguments[0]); object item2 = RVal(genericArguments[1]); kv.Add((item, item2)); } list.Add(delegate { foreach (var (key, value) in kv) { d.Add(key, value); } }); } else if (IsSet(type)) { Type[] genericArguments2 = type.GetGenericArguments(); int num3 = r.ReadInt32(); List<object> items = new List<object>(num3); for (int num4 = 0; num4 < num3; num4++) { items.Add(RVal(genericArguments2[0])); } MethodInfo add = type.GetMethod("Add"); list.Add(delegate { foreach (object item3 in items) { add.Invoke(o, new object[1] { item3 }); } }); } else { FieldInfo[] array4 = Fields(type); foreach (FieldInfo fieldInfo in array4) { fieldInfo.SetValue(o, RVal(fieldInfo.FieldType)); } } } foreach (Action item4 in list) { item4(); } if (r.BaseStream.Position != r.BaseStream.Length) { throw new InvalidDataException("trailing data"); } return result; object RRef() { int num6 = r.ReadInt32(); if (num6 == -1) { return null; } if (num6 < objs.Count) { return objs[num6]; } if (num6 != objs.Count) { throw new InvalidDataException("object id order"); } Type type2 = RType(); object obj; if (type2 == typeof(string)) { obj = r.ReadString(); objs.Add(obj); return obj; } if (typeof(Delegate).IsAssignableFrom(type2)) { byte num7 = r.ReadByte(); Type type3 = RType(); int metadataToken = r.ReadInt32(); MethodInfo methodInfo = (MethodInfo)type3.Module.ResolveMethod(metadataToken, type3.IsGenericType ? type3.GetGenericArguments() : null, null); if (type3.IsGenericType) { methodInfo = (MethodInfo)MethodBase.GetMethodFromHandle(methodInfo.MethodHandle, type3.TypeHandle); } object obj2 = ((num7 == 0) ? null : type3.GetField("<>9", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(null)); obj = ((obj2 == null) ? Delegate.CreateDelegate(type2, methodInfo) : Delegate.CreateDelegate(type2, obj2, methodInfo)); objs.Add(obj); return obj; } if (!type2.IsArray) { obj = ((!IsDict(type2) && !IsSet(type2)) ? FormatterServices.GetUninitializedObject(type2) : Activator.CreateInstance(type2)); } else { int num8 = r.ReadInt32(); int[] array5 = new int[num8]; for (int k = 0; k < num8; k++) { array5[k] = r.ReadInt32(); } obj = Array.CreateInstance(type2.GetElementType(), array5); } objs.Add(obj); queue.Enqueue(obj); return obj; } Type RType() { int num6 = r.ReadInt32(); if (num6 < types.Count) { return types[num6]; } if (num6 != types.Count) { throw new InvalidDataException("type id order"); } types.Add(null); Type type2; switch (r.ReadByte()) { case 2: { Type type4 = RType(); int num9 = r.ReadInt32(); type2 = ((num9 == 0) ? type4.MakeArrayType() : type4.MakeArrayType(num9)); break; } case 1: { Type type3 = RType(); int num7 = r.ReadInt32(); Type[] array5 = new Type[num7]; for (int num8 = 0; num8 < num7; num8++) { array5[num8] = RType(); } type2 = type3.MakeGenericType(array5); break; } default: { string an = r.ReadString(); string name = r.ReadString(); type2 = (AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly x) => x.GetName().Name == an) ?? throw new InvalidDataException("assembly " + an)).GetType(name, throwOnError: true); break; } } Check(type2); types[num6] = type2; return type2; } object RVal(Type t) { if (t.IsEnum) { return Enum.ToObject(t, RPrim(r, Enum.GetUnderlyingType(t))); } if (t.IsPrimitive) { return RPrim(r, t); } if (t == typeof(decimal)) { return r.ReadDecimal(); } if (t.IsValueType) { object uninitializedObject = FormatterServices.GetUninitializedObject(t); FieldInfo[] array5 = Fields(t); foreach (FieldInfo fieldInfo2 in array5) { fieldInfo2.SetValue(uninitializedObject, RVal(fieldInfo2.FieldType)); } return uninitializedObject; } return RRef(); } } } } namespace DropNSpawnCache { public class Plugin : ModuleBehaviour { internal static ManualLogSource Log; internal static ConfigEntry<bool> cSig; internal static ConfigEntry<bool> cPick; internal static ConfigEntry<bool> cNull; internal static ConfigEntry<bool> cStats; internal static ConfigEntry<float> cRevalidate; internal static ConfigEntry<float> cSlice; internal static ConfigEntry<float> cDeferSec; internal static ConfigEntry<bool> cPace; internal static ConfigEntry<bool> cDefer; private Harmony h; private float nextStats; private void Awake() { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown Log = base.Logger; cSig = base.Config.Bind<bool>("Optional - DropNSpawn Cache - Features", "CacheSignatures", true, "Cache DropNSpawn object-domain prefab signatures (invalidated when ZNetScene/ObjectDB prefab lists change, on DropNSpawn game-data refresh, and every RevalidateSeconds)."); cRevalidate = base.Config.Bind<float>("Optional - DropNSpawn Cache - Features", "RevalidateSeconds", 30f, "Force a real signature recompute at most this often (safety net). 0 = never."); cPick = base.Config.Bind<bool>("Optional - DropNSpawn Cache - Features", "SkipRedundantPickableReconcile", true, "Skip queuing live reconcile for freshly spawned Pickables whose prefab only has unconditional Pickable overrides and whose values already match the configured prefab."); cNull = base.Config.Bind<bool>("Optional - DropNSpawn Cache - Features", "GuardCloneDropTable", true, "Return null / empty copy instead of throwing when DropNSpawn snapshots a null DropTable."); cStats = base.Config.Bind<bool>("Optional - DropNSpawn Cache - Diagnostics", "LogStats", true, "Log a one-line counter summary every 60 s while counters change."); cPace = base.Config.Bind<bool>("Optional - DropNSpawn Cache - Pacing", "PaceRuntimeWork", true, "1.0.1: DropNSpawn's per-frame runtime work (payload sync, snapshot build, reconcile) has a 1 ms target but a single step can take much longer. After a frame where it took longer than RefreshSliceMs, skip it for ceil(took/RefreshSliceMs) frames so slow steps do not stack into a stutter. Never paces while loading or with no local player."); cSlice = base.Config.Bind<float>("Optional - DropNSpawn Cache - Pacing", "RefreshSliceMs", 8f, new ConfigDescription("Per-frame cost above which the following frames are skipped.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 50f), Array.Empty<object>())); cDefer = base.Config.Bind<bool>("Optional - DropNSpawn Cache - Pacing", "DeferUntilAfterSpawn", false, "1.0.1: skip DropNSpawn runtime work entirely until DeferSec after the local player spawns (and after each teleport). Off by default: work queued during load then runs a few seconds later."); cDeferSec = base.Config.Bind<float>("Optional - DropNSpawn Cache - Pacing", "DeferSec", 3f, new ConfigDescription("Seconds after spawn/teleport for DeferUntilAfterSpawn.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); h = new Harmony("vpf.ValheimPerformanceFixes.DropNSpawnCache"); try { DNS.Init(h); } catch (Exception ex) { Log.LogError((object)("init failed, plugin inactive: " + ex)); } } private void Update() { if (cStats.Value && !(Time.realtimeSinceStartup < nextStats)) { nextStats = Time.realtimeSinceStartup + 60f; DNS.LogStats(); } } } internal static class DNS { private struct Slot { public bool valid; public long key; public int value; public float at; } private static Type odm; private static Type plugin; private static FieldInfo fPending; private static FieldInfo fLastApplied; private static FieldInfo fSyncReady; private static FieldInfo fProfile; private static FieldInfo fRuntime; private static MethodInfo mTryKinds; private static MethodInfo mHasCond; private static MethodInfo mDeferred; private static MethodInfo mTrack; private static PropertyInfo pActive; private static PropertyInfo pCfgSig; private static Func<GameObject, string> getPrefabName; private static object reloadObject; private static long sigHits; private static long sigMiss; private static long pickSkip; private static long pickQueue; private static long nullGuards; private static double sigMissMs; private static long lastTotal = -1L; private static Slot sGame; private static Slot sSnap; private static readonly Stopwatch sw = new Stopwatch(); private static long pendingKey; private static bool computing; private static string condCacheSig; private static readonly Dictionary<string, bool> condFree = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); private static readonly object[] kindArgs = new object[2]; [ThreadStatic] private static string capturing; private static readonly HashSet<string> warned = new HashSet<string>(); private static int skipFrames; private static long paceSkipped; private static long slowSteps; private static long deferredFrames; private static double slowMs; private static double maxMs; private static readonly Stopwatch psw = new Stopwatch(); private static float readySince = -1f; public static void Init(Harmony h) { //IL_0770: Unknown result type (might be due to invalid IL or missing references) //IL_077e: Expected O, but got Unknown //IL_0799: Unknown result type (might be due to invalid IL or missing references) //IL_07ab: Unknown result type (might be due to invalid IL or missing references) //IL_07b8: Expected O, but got Unknown //IL_07b8: Expected O, but got Unknown //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Expected O, but got Unknown //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Expected O, but got Unknown //IL_0222: Expected O, but got Unknown //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Expected O, but got Unknown //IL_0330: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Expected O, but got Unknown //IL_0692: Unknown result type (might be due to invalid IL or missing references) //IL_06a0: Expected O, but got Unknown Assembly assembly = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { if (assembly2.GetName().Name == "DropNSpawn") { assembly = assembly2; break; } } if (assembly == null) { Plugin.Log.LogWarning((object)"DropNSpawn assembly not loaded; nothing to do."); return; } try { InitPacing(h, assembly); } catch (Exception ex) { Plugin.Log.LogWarning((object)("pacing init failed: " + ex.Message)); } odm = assembly.GetType("DropNSpawn.ObjectDropManager"); plugin = assembly.GetType("DropNSpawn.DropNSpawnPlugin"); if (odm == null) { Plugin.Log.LogWarning((object)"DropNSpawn.ObjectDropManager not found; nothing to do."); return; } Type type = plugin; object obj; if ((object)type == null) { obj = null; } else { BepInPlugin? customAttribute = ((MemberInfo)type).GetCustomAttribute<BepInPlugin>(); obj = ((customAttribute == null) ? null : customAttribute.Version?.ToString()); } if (obj == null) { obj = "?"; } string text = (string)obj; Plugin.Log.LogInfo((object)("DropNSpawn " + text + " detected" + ((text != "1.4.8") ? " (written for 1.4.8 - patches are guarded, check log)" : ""))); Type typeFromHandle = typeof(DNS); if (Plugin.cSig.Value) { int num = 0; string[] array = new string[2] { "ComputeGameDataSignatureCore", "ComputeSnapshotSignatureCore" }; foreach (string text2 in array) { MethodInfo methodInfo = AccessTools.Method(odm, text2, Type.EmptyTypes, (Type[])null); if (methodInfo == null || methodInfo.ReturnType != typeof(int)) { Plugin.Log.LogWarning((object)("signature cache: " + text2 + " not found, skipped")); continue; } h.Patch((MethodBase)methodInfo, new HarmonyMethod(typeFromHandle.GetMethod((text2 == "ComputeGameDataSignatureCore") ? "PreGame" : "PreSnap", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) { priority = 800 }, new HarmonyMethod(typeFromHandle.GetMethod((text2 == "ComputeGameDataSignatureCore") ? "PostGame" : "PostSnap", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } Type type2 = assembly.GetType("DropNSpawn.PluginRuntimeWorkCoordinator"); MethodInfo methodInfo2 = ((type2 == null) ? null : AccessTools.Method(type2, "QueueGameDataRefresh", (Type[])null, (Type[])null)); if (methodInfo2 != null) { h.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeFromHandle.GetMethod("Invalidate", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Type[] array2 = new Type[2] { typeof(ZNetScene), typeof(ObjectDB) }; for (int i = 0; i < array2.Length; i++) { MethodInfo methodInfo3 = AccessTools.Method(array2[i], "Awake", (Type[])null, (Type[])null); if (methodInfo3 != null) { h.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeFromHandle.GetMethod("Invalidate", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } MethodInfo methodInfo4 = AccessTools.Method(typeof(ObjectDB), "CopyOtherDB", (Type[])null, (Type[])null); if (methodInfo4 != null) { h.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeFromHandle.GetMethod("Invalidate", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Plugin.Log.LogInfo((object)("signature cache: " + num + "/2 patched")); } if (Plugin.cPick.Value) { try { fPending = AccessTools.Field(odm, "_pendingSnapshotBuild"); fLastApplied = AccessTools.Field(odm, "_lastAppliedGameDataSignature"); fSyncReady = AccessTools.Field(odm, "_synchronizedPayloadReady"); fProfile = AccessTools.Field(odm, "PrefabProfileCatalogState"); fRuntime = AccessTools.Field(odm, "RuntimeState"); mTryKinds = ((fProfile == null) ? null : AccessTools.Method(fProfile.FieldType, "TryGetReconcileKinds", (Type[])null, (Type[])null)); pActive = ((fRuntime == null) ? null : AccessTools.Property(fRuntime.FieldType, "ActiveEntriesByPrefab")); pCfgSig = ((fRuntime == null) ? null : AccessTools.Property(fRuntime.FieldType, "ConfigurationSignature")); Type type3 = assembly.GetType("DropNSpawn.DropConditionEvaluator"); mHasCond = ((type3 == null) ? null : AccessTools.Method(type3, "HasConditions", (Type[])null, (Type[])null)); mDeferred = ((plugin == null) ? null : AccessTools.Method(plugin, "IsGameDataRefreshDeferred", (Type[])null, (Type[])null)); Type type4 = plugin?.GetNestedType("ReloadDomain", BindingFlags.Public | BindingFlags.NonPublic); reloadObject = ((type4 == null) ? null : Enum.ToObject(type4, 1)); mTrack = AccessTools.Method(odm, "TrackObjectInstance", new Type[1] { typeof(GameObject) }, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method(odm, "GetPrefabName", new Type[1] { typeof(GameObject) }, (Type[])null); if (methodInfo5 != null && methodInfo5.ReturnType == typeof(string)) { getPrefabName = (Func<GameObject, string>)Delegate.CreateDelegate(typeof(Func<GameObject, string>), methodInfo5); } Type nestedType = odm.GetNestedType("LiveObjectComponentKind", BindingFlags.Public | BindingFlags.NonPublic); MethodInfo methodInfo6 = ((nestedType == null) ? null : AccessTools.Method(odm, "QueueObjectInstanceReconcile", new Type[2] { typeof(GameObject), nestedType }, (Type[])null)); if (methodInfo6 != null && fPending != null && fLastApplied != null && fLastApplied.FieldType == typeof(int?) && fSyncReady != null && mTryKinds != null && pActive != null && pCfgSig != null && mHasCond != null && mDeferred != null && reloadObject != null && mTrack != null && getPrefabName != null && nestedType != null && Convert.ToInt32(Enum.Parse(nestedType, "Pickable")) == 64) { h.Patch((MethodBase)methodInfo6, new HarmonyMethod(typeFromHandle.GetMethod("PreQueue", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"pickable reconcile skip: patched"); } else { Plugin.Log.LogWarning((object)"pickable reconcile skip: a required member is missing, skipped"); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("pickable reconcile skip: setup failed, skipped: " + ex2.Message)); } } if (!Plugin.cNull.Value) { return; } MethodInfo methodInfo7 = AccessTools.Method(odm, "CloneDropTable", new Type[1] { typeof(DropTable) }, (Type[])null); MethodInfo methodInfo8 = AccessTools.Method(odm, "CaptureSnapshot", new Type[1] { typeof(GameObject) }, (Type[])null); if (methodInfo7 != null && methodInfo7.ReturnType == typeof(DropTable)) { h.Patch((MethodBase)methodInfo7, new HarmonyMethod(typeFromHandle.GetMethod("PreClone", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (methodInfo8 != null) { h.Patch((MethodBase)methodInfo8, new HarmonyMethod(typeFromHandle.GetMethod("PreCapture", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), new HarmonyMethod(typeFromHandle.GetMethod("PostCapture", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Plugin.Log.LogInfo((object)"CloneDropTable null guard: patched"); } else { Plugin.Log.LogWarning((object)"CloneDropTable null guard: target missing, skipped"); } } private static void Invalidate() { sGame.valid = false; sSnap.valid = false; } private static long Fingerprint() { ZNetScene instance = ZNetScene.instance; ObjectDB instance2 = ObjectDB.instance; if (instance == null || instance2 == null || !Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)instance2)) { return 0L; } long num = 1469598103934665603L; num = (num ^ ((Object)instance).GetInstanceID()) * 1099511628211L; num = (num ^ ((Object)instance2).GetInstanceID()) * 1099511628211L; num = Mix(num, instance.m_prefabs); num = Mix(num, instance.m_nonNetViewPrefabs); num = Mix(num, instance2.m_items); if (num != 0L) { return num; } return 1L; } private static long Mix(long hsh, List<GameObject> l) { if (l == null) { return hsh * 31; } hsh = (hsh ^ l.Count) * 1099511628211L; for (int i = 0; i < l.Count; i++) { GameObject val = l[i]; hsh = (hsh ^ ((val != null) ? ((Object)val).GetInstanceID() : 0)) * 1099511628211L; } return hsh; } private static bool Pre(ref Slot s, ref int __result) { long num = Fingerprint(); if (num == 0L) { s.valid = false; computing = false; return true; } float value = Plugin.cRevalidate.Value; if (s.valid && s.key == num && (value <= 0f || Time.realtimeSinceStartup - s.at < value)) { __result = s.value; sigHits++; return false; } pendingKey = num; computing = true; sw.Restart(); return true; } private static void Post(ref Slot s, int __result) { if (computing) { computing = false; sw.Stop(); sigMiss++; sigMissMs += sw.Elapsed.TotalMilliseconds; if (__result == 0) { s.valid = false; return; } s.valid = true; s.key = pendingKey; s.value = __result; s.at = Time.realtimeSinceStartup; } } private static bool PreGame(ref int __result) { return Pre(ref sGame, ref __result); } private static void PostGame(int __result) { Post(ref sGame, __result); } private static bool PreSnap(ref int __result) { return Pre(ref sSnap, ref __result); } private static void PostSnap(int __result) { Post(ref sSnap, __result); } private static bool PreQueue(object[] __args) { try { if (Convert.ToInt32(__args[1]) != 64) { return true; } object obj = __args[0]; GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (!Object.op_Implicit((Object)(object)val)) { return true; } if (fPending.GetValue(null) != null) { return true; } if (!((int?)fLastApplied.GetValue(null)).HasValue) { return true; } if (!(bool)fSyncReady.GetValue(null)) { return true; } if ((bool)mDeferred.Invoke(null, new object[1] { reloadObject })) { return true; } string text = getPrefabName(val); if (string.IsNullOrEmpty(text)) { return true; } object value = fProfile.GetValue(null); kindArgs[0] = text; kindArgs[1] = null; if (!(bool)mTryKinds.Invoke(value, kindArgs) || Convert.ToInt32(kindArgs[1]) != 64) { return true; } if (!ConditionFree(text)) { return true; } ZNetScene instance = ZNetScene.instance; if (!Object.op_Implicit((Object)(object)instance)) { return true; } GameObject prefab = instance.GetPrefab(text); if (!Object.op_Implicit((Object)(object)prefab) || (Object)(object)prefab == (Object)(object)val) { return true; } Pickable component = val.GetComponent<Pickable>(); Pickable component2 = prefab.GetComponent<Pickable>(); if (!Object.op_Implicit((Object)(object)component) || !Object.op_Implicit((Object)(object)component2) || !Same(component, component2)) { return true; } mTrack.Invoke(null, new object[1] { val }); pickSkip++; return false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("pickable skip error, falling back: " + ex.Message)); return true; } } private static bool ConditionFree(string prefab) { object value = fRuntime.GetValue(null); string a = (pCfgSig.GetValue(value) as string) ?? ""; if (!string.Equals(a, condCacheSig, StringComparison.Ordinal)) { condFree.Clear(); condCacheSig = a; } if (condFree.TryGetValue(prefab, out var value2)) { return value2; } value2 = false; if (pActive.GetValue(value) is IDictionary dictionary && dictionary.Contains(prefab) && dictionary[prefab] is IList { Count: >0 } list) { value2 = true; foreach (object item in list) { PropertyInfo propertyInfo = item?.GetType().GetProperty("Conditions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (item == null || propertyInfo == null || (bool)mHasCond.Invoke(null, new object[1] { propertyInfo.GetValue(item) })) { value2 = false; break; } } } condFree[prefab] = value2; return value2; } private static bool Same(Pickable a, Pickable b) { if ((Object)(object)a.m_itemPrefab != (Object)(object)b.m_itemPrefab || a.m_amount != b.m_amount || a.m_minAmountScaled != b.m_minAmountScaled || a.m_dontScale != b.m_dontScale || !string.Equals(a.m_overrideName, b.m_overrideName, StringComparison.Ordinal)) { return false; } return SameTable(a.m_extraDrops, b.m_extraDrops); } private static bool SameTable(DropTable x, DropTable y) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) if (x == null || y == null) { return x == y; } if (x.m_dropMin != y.m_dropMin || x.m_dropMax != y.m_dropMax || x.m_dropChance != y.m_dropChance || x.m_oneOfEach != y.m_oneOfEach) { return false; } List<DropData> drops = x.m_drops; List<DropData> drops2 = y.m_drops; if (drops == null || drops2 == null) { return drops == drops2; } if (drops.Count != drops2.Count) { return false; } for (int i = 0; i < drops.Count; i++) { DropData val = drops[i]; DropData val2 = drops2[i]; if ((Object)(object)val.m_item != (Object)(object)val2.m_item || val.m_stackMin != val2.m_stackMin || val.m_stackMax != val2.m_stackMax || val.m_weight != val2.m_weight || val.m_dontScale != val2.m_dontScale) { return false; } } return true; } private static void PreCapture(GameObject prefab) { capturing = (Object.op_Implicit((Object)(object)prefab) ? ((Object)prefab).name : "?"); } private static void PostCapture() { capturing = null; } private static bool PreClone(DropTable source, ref DropTable __result) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_00ab: 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_00c3: Expected O, but got Unknown if (source != null && source.m_drops != null) { return true; } nullGuards++; string text = capturing ?? "(runtime)"; if (warned.Add(text)) { Plugin.Log.LogInfo((object)("CloneDropTable guard: " + ((source == null) ? "null DropTable" : "DropTable with null m_drops") + " on " + text + " (DropNSpawn would have thrown NRE)")); } if (source == null) { __result = null; return false; } __result = new DropTable { m_dropMin = source.m_dropMin, m_dropMax = source.m_dropMax, m_dropChance = source.m_dropChance, m_oneOfEach = source.m_oneOfEach, m_drops = new List<DropData>() }; return false; } public static void InitPacing(Harmony h, Assembly asm) { //IL_0057: 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_007f: Expected O, but got Unknown //IL_007f: Expected O, but got Unknown Type type = asm.GetType("DropNSpawn.PluginRuntimeWorkCoordinator"); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "ProcessUpdateFrame", (Type[])null, (Type[])null) : null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"pacing: PluginRuntimeWorkCoordinator.ProcessUpdateFrame not found; pacing inactive"); return; } h.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(DNS).GetMethod("PacePre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(DNS).GetMethod("PaceFin", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null); Plugin.Log.LogInfo((object)"pacing: patched ProcessUpdateFrame"); } private static bool Ready() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsTeleporting()) { return false; } Hud instance = Hud.instance; if ((Object)(object)instance != (Object)null) { try { Traverse val = Traverse.Create((object)instance).Field("m_loadingScreen"); object value = val.GetValue(); Component val2 = (Component)((value is Component) ? value : null); if ((Object)(object)val2 != (Object)null && val2.gameObject.activeInHierarchy && val.Property("alpha", (object[])null).GetValue<float>() > 0.01f) { return false; } } catch { } } return true; } private static bool PacePre() { bool flag = Ready(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (!flag) { readySince = -1f; skipFrames = 0; } else if (readySince < 0f) { readySince = realtimeSinceStartup; } if (Plugin.cDefer.Value && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && (!flag || realtimeSinceStartup - readySince < Plugin.cDeferSec.Value)) { deferredFrames++; return false; } if (Plugin.cPace.Value && flag && skipFrames > 0) { skipFrames--; paceSkipped++; return false; } psw.Restart(); return true; } private static Exception PaceFin(Exception __exception) { if (psw.IsRunning) { psw.Stop(); double totalMilliseconds = psw.Elapsed.TotalMilliseconds; if (totalMilliseconds > maxMs) { maxMs = totalMilliseconds; } if (Plugin.cPace.Value && totalMilliseconds > (double)Plugin.cSlice.Value) { slowSteps++; slowMs += totalMilliseconds; if (Ready()) { skipFrames = (int)Math.Ceiling(totalMilliseconds / (double)Plugin.cSlice.Value); } } } return __exception; } public static void LogStats() { if (slowSteps + paceSkipped + deferredFrames > 0) { Plugin.Log.LogInfo((object)("pacing: slow steps=" + slowSteps + " (avg " + ((slowSteps > 0) ? (slowMs / (double)slowSteps).ToString("F1") : "-") + " ms, max " + maxMs.ToString("F1") + " ms) frames skipped=" + paceSkipped + " deferred=" + deferredFrames)); } long num = sigHits + sigMiss + pickSkip + nullGuards; if (num != lastTotal) { lastTotal = num; Plugin.Log.LogInfo((object)("stats: signature cache hits=" + sigHits + " misses=" + sigMiss + " (avg miss " + ((sigMiss > 0) ? (sigMissMs / (double)sigMiss).ToString("F2") : "-") + " ms) | pickable reconciles skipped=" + pickSkip + " | CloneDropTable guards=" + nullGuards)); } } } } namespace SpawnBudget { public class Plugin : ModuleBehaviour { private class PStat { public int hash; public string name; public int n; public int nLoad; public double total; public double totalLoad; public double max; } public const string Ver = "1.7.5"; private static ConfigEntry<bool> Enabled; private static ConfigEntry<bool> SkipDistant; private static ConfigEntry<bool> LogSlow; private static ConfigEntry<bool> HmEnabled; private static ConfigEntry<float> BudgetMs; private static ConfigEntry<float> SlowMs; private static ConfigEntry<float> HmBudgetMs; private static ConfigEntry<float> FrameBudgetMs; private static ConfigEntry<float> HmProtect; private static ConfigEntry<float> ReportSec; private static ConfigEntry<int> HmMaxDefer; private static ConfigEntry<int> MaxPerFrame; private static ConfigEntry<bool> HmAllPaths; private static ConfigEntry<float> TailSec; private static ConfigEntry<float> ListObjMs; private static readonly Stopwatch frameSw = new Stopwatch(); private static int frame = -1; private static double spawnMs; private static double hmMs; private static int hmRuns; private static Func<ZNetScene, ZDO, GameObject> createObj; private static bool objOk = true; private static bool engagedLogged; private static ConfigEntry<float> PrioRadius; private static ConfigEntry<float> CmbBudget; private static ConfigEntry<float> CmbDetect; private static ConfigEntry<float> CmbExit; private static ConfigEntry<float> CmbEase; private static ConfigEntry<int> PrioMax; private static ConfigEntry<bool> CmbOn; private static ConfigEntry<bool> AdOn; private static ConfigEntry<float> AdLen; private static ConfigEntry<float> AdAgg; private static ConfigEntry<float> AdLow; private static ConfigEntry<float> AdHigh; private static ConfigEntry<float> AdStr; private static ConfigEntry<float> AdRise; private static ConfigEntry<float> AdFall; private static ConfigEntry<float> AdFrameMs; private static int lastPendNear; private static int lastPendDist; private static float dtEma = 0.016f; private static float nextAdLog; private static ConfigEntry<bool> ArOn; private static ConfigEntry<float> ArBudget; private static ConfigEntry<float> ArRamp; private static ConfigEntry<float> PendStale; private static ConfigEntry<float> LocOverMs; private static ConfigEntry<float> CmbEnterHold; private static ConfigEntry<float> CmbExit2; private static ConfigEntry<int> ArDistMax; private static ConfigEntry<int> DistMax; private static ConfigEntry<int> LocMaxPF; private static float cmbAiSince = -1f; private static float cmbEnterAt; private static float arrivalEndAt = -999f; private static string cmbWhy = ""; private static int sArrFrames; private static int sArrObjs; private static int sArrHit; private static int sArrEntered; private static int sLocDupTracked; private static int sLocSamePos; private static int sLocCapDelayed; private static int sLocSlowFrameDelayed; private static int lastPendDistStale; private static double sArrMax; private static bool arrivalWas; private static readonly Dictionary<ZDO, float> distSeen = new Dictionary<ZDO, float>(); private static readonly Dictionary<ZDO, float> distSeen2 = new Dictionary<ZDO, float>(); private static float nextPendCount; private static readonly List<ZDO> tmpD = new List<ZDO>(); private static float cmbF; private static float cmbLastSig = -999f; private static float cmbNext; private static float cmbLastHit = -999f; private static float cmbLastT; private static bool cmbIn; private static int sCombatFrames; private static int sPrio; private static readonly List<ZDO> tmp = new List<ZDO>(); private static readonly List<ZDO> empty = new List<ZDO>(); private static readonly Dictionary<Heightmap, int> deferred = new Dictionary<Heightmap, int>(); private static int sBudgetHitFrames; private static int sNearDeferred; private static int sDistantSkips; private static int sHmDeferrals; private static int sHmForced; private static int sHmRuns; private static int sObjs; private static int sActiveFrames; private static int sOverOne; private static double sHmMax; private static double sHmTotal; private static double sMaxSpawn; private static double sSpawnTotal; private static float nextReport; private static StreamWriter log; private static readonly Stopwatch objSw = new Stopwatch(); private static readonly Comparison<ZDO> cmp = Cmp; private static ConfigEntry<bool> Mirror; private static ConfigEntry<bool> DgSpread; private static ConfigEntry<bool> LocSerialize; private static ConfigEntry<bool> ZoneThrottle; private static ConfigEntry<float> DgBudgetMs; private static ConfigEntry<float> LocBudgetMs; private static ConfigEntry<int> LocMaxDefer; private static ConfigEntry<int> ZoneMaxDefer; private static ConfigEntry<int> DgMaxLoads; private static ConfigEntry<bool> DgStagger; private static ConfigEntry<bool> LocHoldFrame; private static ConfigEntry<bool> LocPrewarm; private static ConfigEntry<float> ZoneHeavyMs; private static ConfigEntry<float> SettleGrace; private static ConfigEntry<bool> LocSplit; private static ConfigEntry<bool> LocSoftCap; private static ConfigEntry<bool> LocPreInst; private static ConfigEntry<bool> DgSoftEntry; private static ConfigEntry<bool> DistBacklogAware; private static ConfigEntry<float> LocHeavyMs; private static ConfigEntry<float> LocSoftCapMs; private static ConfigEntry<float> LocProxySoftMs; private static ConfigEntry<float> DgWallMs; private static ConfigEntry<int> LocHeavyPieces; private static ConfigEntry<int> NearBacklog; private static readonly Dictionary<int, int> locPieces = new Dictionary<int, int>(); private static ConfigEntry<string> LocHeavyList; private static readonly List<GameObject> locPendingSnap = new List<GameObject>(); private static readonly Dictionary<int, string> locPendingNames = new Dictionary<int, string>(); private static readonly HashSet<int> proxyHeavyHashes = new HashSet<int>(); private static ManualLogSource BLog; private static Plugin inst; private static int locFrame = -1; private static int locDeferRun; private static int locCount; private static int sLocSpawns; private static int sLocDelayed; private static int sLocForced; private static double sLocMax; private static readonly Stopwatch locSw = new Stopwatch(); private static readonly Dictionary<int, int> locLoadedSeen = new Dictionary<int, int>(); private static int sLocHeld; private static int sLocPrewarm; private static bool inPrewarm; private static string sLocMaxName = "-"; private static bool locSplitOk; private static bool locDeferNext; private static GameObject locAssetOff; private static int sLocSplit; private static readonly Dictionary<int, double> locLastMs = new Dictionary<int, double>(); private static readonly List<GameObject> locPendingAct = new List<GameObject>(); private static int locPendingFrame; private static HashSet<string> heavyNames; private static string heavyListSeen; private static bool locAssetDisabled; private static int locCurHash; private static int sLocPreInst; private static int sLocPreInstUsed; private static int sLocPreInstSync; private static readonly Dictionary<int, AsyncInstantiateOperation<GameObject>> preOps = new Dictionary<int, AsyncInstantiateOperation<GameObject>>(); private static readonly Dictionary<int, GameObject> preReady = new Dictionary<int, GameObject>(); private static readonly Vector3 prePark = new Vector3(0f, -9000f, 0f); private static readonly List<int> tmpPreKeys = new List<int>(); private static int heavyJobFrame = -1; private static string heavyJobWhy = "-"; private static int prevHfFrame = -1; private static string prevHfName = "-"; private static string prevHfOwner = "-"; private static double prevHfMs; private static int curHfFrame = -1; private static string curHfName = "-"; private static string curHfOwner = "-"; private static double curHfMs; private static readonly List<ZDO> rmDead = new List<ZDO>(); private static int sRmPurged; private static FieldInfo fInst; private static ConfigEntry<bool> HyOn; private static ConfigEntry<float> HyKeepSec; private static ConfigEntry<float> HyCooldown; private static ConfigEntry<float> HyClose; private static ConfigEntry<int> HyExtra; private static readonly List<ZNetView> proxies = new List<ZNetView>(); private static readonly HashSet<ZNetView> proxySet = new HashSet<ZNetView>(); private static readonly Dictionary<ZDO, float> proxyOutSince = new Dictionary<ZDO, float>(); private static readonly Dictionary<ZDOID, float> proxyUnloadedAt = new Dictionary<ZDOID, float>(); private static readonly HashSet<ZDOID> proxySuppressedOnce = new HashSet<ZDOID>(); private static int kaFrame = -1; private static readonly HashSet<ZDO> kaSeen = new HashSet<ZDO>(); private static readonly HashSet<ZDO> keptNow = new HashSet<ZDO>(); private static readonly Dictionary<Vector3Int, ZDOID> kaPos = new Dictionary<Vector3Int, ZDOID>(); private static readonly List<string> kaNew = new List<string>(); private static int sLocKept; private static int sLocKeptFrames; private static int sLocSuppressed; private static int sLocUnloaded; private static int sLocDup; private static FieldRef<LocationProxy, GameObject> lpInst; private static FieldRef<LocationProxy, bool> lpNeeds; private static MethodInfo getLoc; private static readonly Dictionary<int, string> locNames = new Dictionary<int, string>(); private static readonly int proxyHash = StringExtensionMethods.GetStableHashCode("LocationProxy"); private static readonly HashSet<int> prewarmed = new HashSet<int>(); private static float nextPrewarmClear; private static int zoneFrame = -1; private static int zoneDefer; private static int sZones; private static int sZoneDeferred; private static bool dgOk = true; private static int dgActive; private static int sDungeons; private static int sDgFrames; private static int sDgAborted; private static double sDgMaxFrame; private static MethodInfo placeRoom; private static MethodInfo release; private static MethodInfo dnsBegin; private static MethodInfo dnsEnd; private static int dgLoading; private static int sDgStaggered; private static double sDgStaggerWall; private static readonly FieldRef<DungeonGenerator, List<IReferenceCounted>> heldRefs = AccessTools.FieldRefAccess<DungeonGenerator, List<IReferenceCounted>>("m_heldReferences"); private static readonly FieldRef<DungeonGenerator, ZDO> dgLoadingZdo = AccessTools.FieldRefAccess<DungeonGenerator, ZDO>("m_zdoSetToBeLoadingInZone"); private static readonly FieldRef<DungeonGenerator, ZNetView> dgNview = AccessTools.FieldRefAccess<DungeonGenerator, ZNetView>("m_nview"); private static Action<DungeonGenerator, AssetID, LoadResult> onRoomLoaded; private static FieldInfo fRd; private static FieldInfo fPos; private static FieldInfo fRot; private static readonly FieldRef<DungeonGenerator, int> roomsToLoad = AccessTools.FieldRefAccess<DungeonGenerator, int>("m_roomsToLoad"); private static readonly FieldRef<DungeonGenerator, Array> loadedRooms = AccessTools.FieldRefAccess<DungeonGenerator, Array>("m_loadedRooms"); private static readonly string[] Others = new string[3] { "com.Fire.FiresGhettoNetworkMod", "vpf.ValheimPerformanceFixes.RenderTrim", "com.Skarif.ValheimPerformanceOverhaul_WATER" }; private static int phaseFrame = -1; private static bool phaseIn; private static float tailUntil; private static bool wasLoading = true; private static float settledSince = -1f; private static double prevSpawnMs; private static readonly Comparison<ZDO> cmpD = (ZDO x, ZDO y) => x.m_tempSortValue.CompareTo(y.m_tempSortValue); private static readonly Stopwatch coSw = Stopwatch.StartNew(); private static readonly Stack<double> coStart = new Stack<double>(); private static int coFrame = -1; private static double coFrameMs; private static int sLoadFrames; private static double sLoadMax; private static double sLoadTotal; private static int sLoadObjs; private static int sTails; private static int sTailFrames; private static int sTailHit; private static double sTailMax; private static readonly Dictionary<int, PStat> pstats = new Dictionary<int, PStat>(); private static readonly Dictionary<int, string> owners = new Dictionary<int, string>(); private static Dictionary<Assembly, string> asmToMod; private static bool inLateRegen; private static bool inForce; private static int sRegenDeferred; private static readonly FieldRef<Heightmap, Mesh> collMesh = AccessTools.FieldRefAccess<Heightmap, Mesh>("m_collisionMesh"); private static void UpdatePressure(float dt) { if (!(dt <= 0f)) { float num = Mathf.Min(Time.unscaledDeltaTime, 0.25f); dtEma += (num - dtEma) * Mathf.Clamp01(dt / 1f); float num2 = AdFrameMs.Value / 1000f; float num3 = Mathf.Clamp01((dtEma - num2) / num2); float num4 = Mathf.Max(Mathf.Clamp01((float)lastPendNear / 150f), 0.7f * Mathf.Clamp01((float)lastPendDist / 600f)); float num5 = Mathf.Clamp01((float)zoneDefer / 5f + (float)locDeferRun / 10f); int num6 = 0; try { num6 = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.NrOfInstances() : 0); } catch { } float num7 = Mathf.Clamp01(((float)num6 - 2000f) / 6000f); float num8 = Mathf.Clamp01(0.5f * num4 + 0.15f * num5 + 0.2f * num3 + 0.15f * num7); float value = SpawnBudgetPressure.Value; float num9 = ((num8 > value) ? AdRise.Value : AdFall.Value); SpawnBudgetPressure.Value = value + (num8 - value) * Mathf.Clamp01(dt / num9); SpawnBudgetPressure.Raw = num8; SpawnBudgetPressure.Instances = num6; SpawnBudgetPressure.PendingNear = lastPendNear; SpawnBudgetPressure.PendingDistant = lastPendDist; } } private static float AdT() { float value = AdLow.Value; float num = Mathf.Max(AdHigh.Value, value + 0.01f); return Mathf.Clamp01((SpawnBudgetPressure.Value - value) / (num - value)) * AdStr.Value; } private static void CountPendDist(List<ZDO> dist) { float unscaledTime = Time.unscaledTime; if (unscaledTime < nextPendCount) { return; } nextPendCount = unscaledTime + 0.5f; if (dist == null) { lastPendDist = 0; return; } float value = PendStale.Value; int num = 0; int num2 = 0; distSeen2.Clear(); for (int i = 0; i < dist.Count; i++) { ZDO val = dist[i]; if (val != null && !val.Created) { if (!distSeen.TryGetValue(val, out var value2)) { value2 = unscaledTime; } distSeen2[val] = value2; if (value > 0f && unscaledTime - value2 > value) { num2++; } else { num++; } } } distSeen.Clear(); foreach (KeyValuePair<ZDO, float> item in distSeen2) { distSeen[item.Key] = item.Value; } distSeen2.Clear(); lastPendDist = num; lastPendDistStale = num2; } private static bool ArrivalOk() { try { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return false; } Player localPlayer = Player.m_localPlayer; return (Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && (Object)(object)ZoneSystem.instance != (Object)null; } catch { return false; } } private static void DmgPost(Character __instance, HitData hit) { try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && ((Object)(object)__instance == (Object)(object)localPlayer || (hit != null && (Object)(object)hit.GetAttacker() == (Object)(object)localPlayer))) { cmbLastHit = Time.unscaledTime; } } catch { } } private static double EffBudget() { //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) float unscaledTime = Time.unscaledTime; float num = Mathf.Clamp(unscaledTime - cmbLastT, 0f, 0.5f); cmbLastT = unscaledTime; UpdatePressure(num); double num2 = (AdOn.Value ? Mathf.Lerp(AdLen.Value, AdAgg.Value, AdT()) : BudgetMs.Value); SpawnBudgetPressure.BudgetMs = (float)num2; if (unscaledTime >= nextAdLog) { nextAdLog = unscaledTime + 30f; Log("ADAPTIVE pressure=" + SpawnBudgetPressure.Value.ToString("0.00") + " raw=" + SpawnBudgetPressure.Raw.ToString("0.00") + " pendNear=" + lastPendNear + " pendDist=" + lastPendDist + "(+" + lastPendDistStale + " stale) inst=" + SpawnBudgetPressure.Instances + " frame=" + (dtEma * 1000f).ToString("0.0") + "ms budget=" + num2.ToString("0.0") + "ms mode=" + (cmbIn ? "COMBAT" : "normal")); } if (!CmbOn.Value) { cmbIn = false; cmbF = 0f; return num2; } if (unscaledTime >= cmbNext) { cmbNext = unscaledTime + 0.25f; bool flag = unscaledTime - cmbLastHit < 1f; Player localPlayer = Player.m_localPlayer; if (!flag && (Object)(object)localPlayer != (Object)null) { Vector3 position = ((Component)localPlayer).transform.position; float num3 = CmbDetect.Value * CmbDetect.Value; foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsPlayer() || allCharacter.IsTamed() || allCharacter.IsDead()) { continue; } Vector3 val = ((Component)allCharacter).transform.position - position; if (!(((Vector3)(ref val)).sqrMagnitude > num3)) { BaseAI baseAI = allCharacter.GetBaseAI(); if ((Object)(object)baseAI != (Object)null && baseAI.IsAlerted() && BaseAI.IsEnemy(allCharacter, (Character)(object)localPlayer)) { flag = true; break; } } } } bool flag2 = unscaledTime - cmbLastHit < 1f; bool flag3 = flag && !flag2; if (flag3) { if (cmbAiSince < 0f) { cmbAiSince = unscaledTime; } } else if (!flag2) { cmbAiSince = -1f; } bool flag4; if (cmbIn) { if (flag) { cmbLastSig = unscaledTime; } flag4 = (Object)(object)localPlayer != (Object)null && unscaledTime - cmbLastSig < CmbExit2.Value; } else { flag4 = (Object)(object)localPlayer != (Object)null && (flag2 || (flag3 && unscaledTime - cmbAiSince >= CmbEnterHold.Value)); if (flag4) { cmbLastSig = unscaledTime; cmbWhy = (flag2 ? "damage" : ("alerted hostile for " + (unscaledTime - cmbAiSince).ToString("0.0") + "s")); } } if (flag4 != cmbIn) { cmbIn = flag4; if (flag4) { cmbEnterAt = unscaledTime; Log("combat mode ENTER (" + cmbWhy + ")"); } else { Log("combat mode EXIT after " + (unscaledTime - cmbEnterAt).ToString("0") + "s (no signal for " + CmbExit2.Value.ToString("0") + "s)"); } } } cmbF = Mathf.MoveTowards(cmbF, cmbIn ? 1f : 0f, num / CmbEase.Value); if (cmbF > 0f) { sCombatFrames++; } SpawnBudgetPressure.Combat = cmbIn; return Mathf.Lerp((float)num2, Mathf.Min((float)num2, CmbBudget.Value), cmbF); } private void Awake() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_0d7c: Unknown result type (might be due to invalid IL or missing references) //IL_0d82: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Expected O, but got Unknown //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Expected O, but got Unknown //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Expected O, but got Unknown //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Expected O, but got Unknown //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Expected O, but got Unknown //IL_03f2: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Expected O, but got Unknown //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Expected O, but got Unknown //IL_0490: Unknown result type (might be due to invalid IL or missing references) //IL_049a: Expected O, but got Unknown //IL_0593: Unknown result type (might be due to invalid IL or missing references) //IL_059d: Expected O, but got Unknown //IL_05d0: Unknown result type (might be due to invalid IL or missing references) //IL_05da: Expected O, but got Unknown //IL_061b: Unknown result type (might be due to invalid IL or missing references) //IL_0625: Expected O, but got Unknown //IL_0678: Unknown result type (might be due to invalid IL or missing references) //IL_0682: Expected O, but got Unknown //IL_06b5: Unknown result type (might be due to invalid IL or missing references) //IL_06bf: Expected O, but got Unknown //IL_06f2: Unknown result type (might be due to invalid IL or missing references) //IL_06fc: Expected O, but got Unknown //IL_072f: Unknown result type (might be due to invalid IL or missing references) //IL_0739: Expected O, but got Unknown //IL_076c: Unknown result type (might be due to invalid IL or missing references) //IL_0776: Expected O, but got Unknown //IL_07a9: Unknown result type (might be due to invalid IL or missing references) //IL_07b3: Expected O, but got Unknown //IL_07e6: Unknown result type (might be due to invalid IL or missing references) //IL_07f0: Expected O, but got Unknown //IL_0838: Unknown result type (might be due to invalid IL or missing references) //IL_0842: Expected O, but got Unknown //IL_0895: Unknown result type (might be due to invalid IL or missing references) //IL_089f: Expected O, but got Unknown //IL_08d2: Unknown result type (might be due to invalid IL or missing references) //IL_08dc: Expected O, but got Unknown //IL_090f: Unknown result type (might be due to invalid IL or missing references) //IL_0919: Expected O, but got Unknown //IL_094c: Unknown result type (might be due to invalid IL or missing references) //IL_0956: Expected O, but got Unknown //IL_0b43: Unknown result type (might be due to invalid IL or missing references) //IL_0b4d: Expected O, but got Unknown //IL_0b80: Unknown result type (might be due to invalid IL or missing references) //IL_0b8a: Expected O, but got Unknown //IL_0bb6: Unknown result type (might be due to invalid IL or missing references) //IL_0bc0: Expected O, but got Unknown //IL_0bec: Unknown result type (might be due to invalid IL or missing references) //IL_0bf6: Expected O, but got Unknown //IL_0c29: Unknown result type (might be due to invalid IL or missing references) //IL_0c33: Expected O, but got Unknown //IL_0c5b: Unknown result type (might be due to invalid IL or missing references) //IL_0c65: Expected O, but got Unknown //IL_0c98: Unknown result type (might be due to invalid IL or missing references) //IL_0ca2: Expected O, but got Unknown //IL_0cd5: Unknown result type (might be due to invalid IL or missing references) //IL_0cdf: Expected O, but got Unknown //IL_0d12: Unknown result type (might be due to invalid IL or missing references) //IL_0d1c: Expected O, but got Unknown //IL_0daa: Unknown result type (might be due to invalid IL or missing references) //IL_0db7: Expected O, but got Unknown //IL_0e4f: Unknown result type (might be due to invalid IL or missing references) //IL_0e55: Expected O, but got Unknown //IL_1216: Unknown result type (might be due to invalid IL or missing references) //IL_1222: Expected O, but got Unknown //IL_139e: Unknown result type (might be due to invalid IL or missing references) //IL_13a3: Unknown result type (might be due to invalid IL or missing references) //IL_13ba: Unknown result type (might be due to invalid IL or missing references) //IL_13c7: Expected O, but got Unknown //IL_13c7: Expected O, but got Unknown //IL_0f0d: Unknown result type (might be due to invalid IL or missing references) //IL_0f12: Unknown result type (might be due to invalid IL or missing references) //IL_0f1d: Unknown result type (might be due to invalid IL or missing references) //IL_0f38: Unknown result type (might be due to invalid IL or missing references) //IL_0f3d: Unknown result type (might be due to invalid IL or missing references) //IL_0f44: Unknown result type (might be due to invalid IL or missing references) //IL_0f57: Expected O, but got Unknown //IL_0f57: Expected O, but got Unknown //IL_0fad: Unknown result type (might be due to invalid IL or missing references) //IL_0fb2: Unknown result type (might be due to invalid IL or missing references) //IL_0fc6: Expected O, but got Unknown //IL_0fee: Unknown result type (might be due to invalid IL or missing references) //IL_1005: Unknown result type (might be due to invalid IL or missing references) //IL_1010: Expected O, but got Unknown //IL_1010: Expected O, but got Unknown //IL_1083: Unknown result type (might be due to invalid IL or missing references) //IL_1098: Unknown result type (might be due to invalid IL or missing references) //IL_10a5: Expected O, but got Unknown //IL_10a5: Expected O, but got Unknown //IL_1119: Unknown result type (might be due to invalid IL or missing references) //IL_1126: Expected O, but got Unknown //IL_114e: Unknown result type (might be due to invalid IL or missing references) //IL_1163: Unknown result type (might be due to invalid IL or missing references) //IL_1179: Unknown result type (might be due to invalid IL or missing references) //IL_1184: Expected O, but got Unknown //IL_1184: Expected O, but got Unknown //IL_1184: Expected O, but got Unknown //IL_12e6: Unknown result type (might be due to invalid IL or missing references) //IL_12eb: Unknown result type (might be due to invalid IL or missing references) //IL_12ff: Expected O, but got Unknown //IL_11ac: Unknown result type (might be due to invalid IL or missing references) //IL_11b1: Unknown result type (might be due to invalid IL or missing references) //IL_11c5: Expected O, but got Unknown //IL_1696: Unknown result type (might be due to invalid IL or missing references) //IL_169b: Unknown result type (might be due to invalid IL or missing references) //IL_16ab: Expected O, but got Unknown //IL_16d3: Unknown result type (might be due to invalid IL or missing references) //IL_16d8: Unknown result type (might be due to invalid IL or missing references) //IL_16e8: Expected O, but got Unknown inst = this; BLog = base.Logger; if (Application.isBatchMode || (int)SystemInfo.graphicsDeviceType == 4) { base.Logger.LogInfo((object)"SpawnBudget 1.7.5: headless/server process detected, not patching anything."); return; } Mirror = base.Config.Bind<bool>("Spawn Budget - Diagnostics", "MirrorToLogOutput", true, "Also write patch status, ENGAGED and the STATS headline to BepInEx LogOutput.log (full detail stays in BepInEx/SpawnBudget.log)."); DgSpread = base.Config.Bind<bool>("Spawn Budget - Dungeon", "SpreadRooms", false, "EXPERIMENTAL, off by default. Client only, world visible: place a dungeon's rooms over several frames (RoomBudgetMs per frame, >=1 room/frame) instead of all in one frame. Rooms are seeded per position, so the result is identical; the zone stays 'loading' until done (as vanilla during asset load). Disabled automatically if another mod patches DungeonGenerator.Spawn."); DgStagger = base.Config.Bind<bool>("Spawn Budget - Dungeon", "StaggerRoomLoads", true, "1.5.0: client, world visible: issue a dungeon's room prefab async loads a few unique assets at a time instead of all at once, so Unity integrates them on the main thread over several frames. Room list/positions come from the ZDO (unchanged); only load timing changes."); DgMaxLoads = base.Config.Bind<int>("Spawn Budget - Dungeon", "MaxRoomLoadsInFlight", 1, new ConfigDescription("1.7.5: unique room prefabs loading at the same time while staggering. Default 1 so SoftEntry crypt loads do not stack EarlyUpdate spikes.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), Array.Empty<object>())); LocHoldFrame = base.Config.Bind<bool>("Spawn Budget - Locations", "HoldOneFrameAfterLoad", true, "1.5.0: when a location's asset has just finished loading, delay its instantiate to the next frame so asset integration and instantiate do not land in the same frame."); LocPrewarm = base.Config.Bind<bool>("Spawn Budget - Locations", "PrewarmFromQueue", true, "1.5.0: request the location asset load (vanilla PokeCanSpawnLocation via ShouldDelayProxyLocationSpawning) as soon as a LocationProxy ZDO is in the pending creation queue, before the proxy is instantiated."); LocSplit = base.Config.Bind<bool>("Spawn Budget - Locations", "SplitHeavyLocationActivation", true, "1.5.2: client, settled in-game only: a heavy location is instantiated inactive in one frame and activated (Awake/OnEnable, renderers, SnapToGround) in the next frame, so its cost is spread over 2 frames. The next location is held until the activation is done."); LocHeavyMs = base.Config.Bind<float>("Spawn Budget - Locations", "HeavyLocationMs", 8f, "1.5.2: generic rule for ALL locations (vanilla and modded): a location whose last unsplit spawn took longer than this is treated as heavy (learned per session)."); LocHeavyPieces = base.Config.Bind<int>("Spawn Budget - Locations", "HeavyLocationTransforms", 80, "1.7.5: a location prefab with more than this many child transforms is treated as heavy from its first spawn (0 = split every location). Lower = more POIs split across frames; default 80 covers more swamp/MWL buildings."); LocHeavyList = base.Config.Bind<string>("Spawn Budget - Locations", "HeavyLocations", "", "1.5.2: optional extra names (normally empty; the ms/transform rules already cover every location)."); LocSoftCap = base.Config.Bind<bool>("Spawn Budget - Locations", "SoftCapHeavyActivation", true, "1.7.5: after a heavy location is instantiated inactive, activate it under SoftCapBudgetMs and defer SnapToGround to the next frame so activation does not hitch. Off = old 2-frame split only."); LocPreInst = base.Config.Bind<bool>("Spawn Budget - Locations", "PreInstantiateHeavy", true, "1.7.5: for heavy overworld LocationProxy locations, start Unity InstantiateAsync while approaching AND during arrival/teleport screens so SoftReferenceableAssets.Instantiate is not paid on the spawn frame. Spawn waits (up to MaxDeferFrames) for the clone."); LocSoftCapMs = base.Config.Bind<float>("Spawn Budget - Locations", "SoftCapBudgetMs", 10f, new ConfigDescription("1.7.5: per-frame ms budget for deferred heavy-location activation (Awake/OnEnable). SnapToGround runs on the following frame. Default 10.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(4f, 40f), Array.Empty<object>())); LocProxySoftMs = base.Config.Bind<float>("Spawn Budget - Locations", "SoftCapProxyCreateMs", 12f, new ConfigDescription("1.7.5: if creating a LocationProxy (or its location spawn) took longer than this many ms, SoftCap that location on the next activation path so Awake/SnapToGround do not stack on the same hitch.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(4f, 40f), Array.Empty<object>())); DgSoftEntry = base.Config.Bind<bool>("Spawn Budget - Dungeon", "SoftEntry", true, "1.7.4: when entering a crypt/dungeon, stagger room asset loads AND place rooms across frames with SoftEntryWallMs as a hard per-frame wall (~18 ms) so opening a crypt does not freeze for hundreds of ms. Starts as soon as the world is visible (loads before you can reach the next rooms). Independent of SpreadRooms; leave SpreadRooms off unless you want spread without SoftEntry."); DgWallMs = base.Config.Bind<float>("Spawn Budget - Dungeon", "SoftEntryWallMs", 18f, new ConfigDescription("1.7.4: hard per-frame wall (ms) for SoftEntry room loads and room placement. Aim: no dungeon wall above ~20 ms.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(6f, 40f), Array.Empty<object>())); DistBacklogAware = base.Config.Bind<bool>("Spawn Budget - General", "BacklogAwareDistantSkip", true, "1.7.4: also skip distant object creation (LocationProxy etc.) while many nearby objects are still waiting (NearBacklogThreshold), so nearby scenery fills in first."); NearBacklog = base.Config.Bind<int>("Spawn Budget - General", "NearBacklogThreshold", 40, new ConfigDescription("1.7.4: with BacklogAwareDistantSkip, skip distant creation while at least this many nearby objects are still waiting to appear.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 300), Array.Empty<object>())); HyOn = base.Config.Bind<bool>("Spawn Budget - Location Hysteresis", "Enabled", true, "1.7.2: client, settled in-game only: stop the unload/reload loop of locations at the rim of the loaded area when you move back and forth over a zone line (fights, kiting). A location that just left the area stays loaded for KeepLoadedSec if it is within KeepExtraZones of the rim, and one that was unloaded anyway is not re-created for ReloadCooldownSec unless it is within AlwaysReloadWithinMeters. Nearby objects are never affected."); HyKeepSec = base.Config.Bind<float>("Spawn Budget - Location Hysteresis", "KeepLoadedSec", 30f, new ConfigDescription("Seconds a location that left the loaded area is kept loaded before it is unloaded (0 = unload immediately, vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())); HyExtra = base.Config.Bind<int>("Spawn Budget - Location Hysteresis", "KeepExtraZones", 1, new ConfigDescription("Only keep a location loaded while it is at most this many zones (64 m) beyond the vanilla loaded area.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>())); HyCooldown = base.Config.Bind<float>("Spawn Budget - Location Hysteresis", "ReloadCooldownSec", 10f, new ConfigDescription("After a location was unloaded, do not re-create it for this many seconds unless you are close to it (0 = off).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), Array.Empty<object>())); HyClose = base.Config.Bind<float>("Spawn Budget - Location Hysteresis", "AlwaysReloadWithinMeters", 128f, new ConfigDescription("A location within this distance (XZ) of you is always re-created immediately, cooldown or not.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 500f), Array.Empty<object>())); SettleGrace = base.Config.Bind<float>("Spawn Budget - General", "SettleGraceSec", 10f, new ConfigDescription("1.5.1: zone throttle / location hold+prewarm / room-load stagger stay vanilla until the player has spawned and the loading screen has been gone this long.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), Array.Empty<object>())); ZoneHeavyMs = base.Config.Bind<float>("Spawn Budget - Zones", "PostponeIfLastFrameOverMs", 40f, "1.5.0: postpone a client zone spawn if the previous frame took longer than this (or spent the object budget, or spawned a location)."); DgBudgetMs = base.Config.Bind<float>("Spawn Budget - Dungeon", "RoomBudgetMs", 8f, new ConfigDescription("1.7.4: per-frame ms for dungeon room placement when SoftEntry/SpreadRooms is on. SoftEntry also clamps to SoftEntryWallMs.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f,