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 LeanHeim v0.5.2
plugins/LeanHeim.dll
Decompiled 4 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using LeanHeim.Patches; using Microsoft.CodeAnalysis; using Unity.Profiling; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Scripting; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.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; } } } namespace LeanHeim { [BepInPlugin("leanheim.performance", "LeanHeim", "0.5.2")] public class Plugin : BaseUnityPlugin { public const string Guid = "leanheim.performance"; public const string Name = "LeanHeim"; public const string Version = "0.5.2"; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Settings.Bind(((BaseUnityPlugin)this).Config); if ((int)SystemInfo.graphicsDeviceType == 4) { Log.LogInfo((object)"Headless (no graphics device) - LeanHeim does nothing here."); return; } _harmony = new Harmony("leanheim.performance"); if (Settings.ProfilerEnabled.Value) { ProfilerPatches.Install(_harmony); } Patch(typeof(WeatherPatches), Settings.WeatherEnabled.Value); Patch(typeof(LightPatches), Settings.LightsEnabled.Value); Patch(typeof(RenderingPatches), Settings.RenderingEnabled.Value); Patch(typeof(GpuPatches), Settings.GpuEnabled.Value); Patch(typeof(CharacterPatches), Settings.CharactersEnabled.Value); Patch(typeof(WearNTearPatches), Settings.BuildingsEnabled.Value); Patch(typeof(MemoryPatches), Settings.MemoryEnabled.Value); if (Settings.GpuEnabled.Value) { Settings.ReflectionInterval.SettingChanged += delegate { GpuPatches.Reapply(); }; Settings.ReflectionResolution.SettingChanged += delegate { GpuPatches.Reapply(); }; Settings.TextureMipmapLimit.SettingChanged += delegate { GpuPatches.Reapply(); }; Settings.LodBiasScale.SettingChanged += delegate { GpuPatches.Reapply(); }; Settings.ShadowCascades.SettingChanged += delegate { GpuPatches.Reapply(); }; } if (Settings.MemoryEnabled.Value) { MemoryPatches.ApplyGc(); Settings.GcTimeSliceMs.SettingChanged += delegate { MemoryPatches.ApplyGc(); }; } Settings.WeatherDensity.SettingChanged += delegate { WeatherPatches.ReapplyAll(); }; Settings.WeatherNoCollision.SettingChanged += delegate { WeatherPatches.ReapplyAll(); }; Settings.WeatherNoLights.SettingChanged += delegate { WeatherPatches.ReapplyAll(); }; Settings.WeatherNoTrails.SettingChanged += delegate { WeatherPatches.ReapplyAll(); }; Settings.SmallObjectDistance.SettingChanged += delegate { RenderingPatches.ApplyCullDistances(); }; Settings.ParticleRaycastBudget.SettingChanged += delegate { RenderingPatches.ApplyQuality(); }; Settings.MaxShadowDistance.SettingChanged += delegate { RenderingPatches.ApplyQuality(); }; } private void Patch(Type patchClass, bool enabled) { if (enabled) { _harmony.CreateClassProcessor(patchClass).Patch(); Log.LogInfo((object)("Enabled module " + patchClass.Name)); } } private void Update() { if (_harmony != null) { if (Settings.MemoryEnabled.Value) { MemoryPatches.Tick(); } if (Settings.ProfilerEnabled.Value) { ProfilerPatches.Tick(); } } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class Settings { public static ConfigEntry<bool> ProfilerEnabled; public static ConfigEntry<float> ProfilerInterval; public static ConfigEntry<bool> WeatherEnabled; public static ConfigEntry<float> WeatherDensity; public static ConfigEntry<bool> WeatherNoCollision; public static ConfigEntry<bool> WeatherNoLights; public static ConfigEntry<bool> WeatherNoTrails; public static ConfigEntry<bool> LightsEnabled; public static ConfigEntry<float> LightDistanceScale; public static ConfigEntry<float> ShadowDistanceScale; public static ConfigEntry<bool> RenderingEnabled; public static ConfigEntry<int> ParticleRaycastBudget; public static ConfigEntry<float> MaxShadowDistance; public static ConfigEntry<float> SmallObjectDistance; public static ConfigEntry<bool> GpuEnabled; public static ConfigEntry<float> ReflectionInterval; public static ConfigEntry<int> ReflectionResolution; public static ConfigEntry<int> TextureMipmapLimit; public static ConfigEntry<float> LodBiasScale; public static ConfigEntry<int> ShadowCascades; public static ConfigEntry<float> GrassDistanceScale; public static ConfigEntry<float> GrassDensityScale; public static ConfigEntry<bool> CharactersEnabled; public static ConfigEntry<bool> VisualOncePerFrame; public static ConfigEntry<float> TiltMaxDistance; public static ConfigEntry<float> HitchThresholdMs; public static ConfigEntry<bool> BuildingsEnabled; public static ConfigEntry<bool> SnowFixFps; public static ConfigEntry<float> SnowReferenceFps; public static ConfigEntry<bool> SnowQuantizeWrites; public static ConfigEntry<bool> SpreadCoverPass; public static ConfigEntry<int> CoverPassPiecesPerFrame; public static ConfigEntry<bool> MemoryEnabled; public static ConfigEntry<bool> CleanupOnTeleport; public static ConfigEntry<bool> CleanupOnRespawn; public static ConfigEntry<bool> DeferHourlyCleanup; public static ConfigEntry<float> DeferMaxMinutes; public static ConfigEntry<float> GcTimeSliceMs; public static void Bind(ConfigFile c) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Expected O, but got Unknown //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Expected O, but got Unknown //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Expected O, but got Unknown //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Expected O, but got Unknown //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Expected O, but got Unknown //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Expected O, but got Unknown //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Expected O, but got Unknown //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Expected O, but got Unknown //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Expected O, but got Unknown //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Expected O, but got Unknown //IL_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_03d7: Expected O, but got Unknown //IL_0405: Unknown result type (might be due to invalid IL or missing references) //IL_040f: Expected O, but got Unknown //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_047d: Expected O, but got Unknown //IL_04e1: Unknown result type (might be due to invalid IL or missing references) //IL_04eb: Expected O, but got Unknown //IL_054c: Unknown result type (might be due to invalid IL or missing references) //IL_0556: Expected O, but got Unknown //IL_05f0: Unknown result type (might be due to invalid IL or missing references) //IL_05fa: Expected O, but got Unknown //IL_0628: Unknown result type (might be due to invalid IL or missing references) //IL_0632: Expected O, but got Unknown ProfilerEnabled = c.Bind<bool>("1 - Profiler", "Enabled", false, "Log average/max time per game subsystem (MonoUpdaters, WearNTear, lights...) to the BepInEx log. Use it to find what is actually slow in YOUR world. Costs a little CPU, keep off for normal play. Needs a game restart to toggle."); ProfilerInterval = c.Bind<float>("1 - Profiler", "ReportIntervalSeconds", 15f, new ConfigDescription("How often the profiler writes its report.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 120f), Array.Empty<object>())); HitchThresholdMs = c.Bind<float>("1 - Profiler", "HitchThresholdMs", 100f, new ConfigDescription("Log every frame slower than this (a freeze) with what took the time. 0 = off. Only works while the profiler is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2000f), Array.Empty<object>())); WeatherEnabled = c.Bind<bool>("2 - Weather", "Enabled", true, "Tune rain/snow/ash/mist particle systems. Client-side only, purely visual. Needs a game restart to toggle."); WeatherDensity = c.Bind<float>("2 - Weather", "ParticleDensity", 0.6f, new ConfigDescription("Multiplier for weather particle emission and max particles (1 = vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f), Array.Empty<object>())); WeatherNoCollision = c.Bind<bool>("2 - Weather", "DisableParticleCollision", true, "Turn off world collision on weather particles (raindrops hitting things). Usually the most expensive part of storms."); WeatherNoLights = c.Bind<bool>("2 - Weather", "DisableParticleLights", true, "Turn off per-particle lights on weather particles."); WeatherNoTrails = c.Bind<bool>("2 - Weather", "DisableParticleTrails", false, "Turn off particle trails on weather particles."); LightsEnabled = c.Bind<bool>("3 - Lights", "Enabled", false, "Scale the game's own light LOD distances (LightLod). Client-side only. Needs a game restart to toggle."); LightDistanceScale = c.Bind<float>("3 - Lights", "LightDistanceScale", 0.85f, new ConfigDescription("Multiplier for the distance at which lights switch off (1 = vanilla). Applies to lights created after the change.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1.5f), Array.Empty<object>())); ShadowDistanceScale = c.Bind<float>("3 - Lights", "ShadowDistanceScale", 0.7f, new ConfigDescription("Multiplier for the distance at which light shadows switch off (1 = vanilla). Light shadows are expensive in bases with many torches. Applies to lights created after the change.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 1.5f), Array.Empty<object>())); RenderingEnabled = c.Bind<bool>("4 - Rendering", "Enabled", true, "Apply extra Unity quality/culling tweaks after the game applies its own graphics settings. Needs a game restart to toggle."); ParticleRaycastBudget = c.Bind<int>("4 - Rendering", "ParticleRaycastBudget", 64, new ConfigDescription("Max raycasts per frame for particle collisions (Unity default 256). 0 = leave untouched.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 4096), Array.Empty<object>())); MaxShadowDistance = c.Bind<float>("4 - Rendering", "MaxShadowDistance", 0f, new ConfigDescription("Cap on directional shadow distance in metres. 0 = leave the game's value untouched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())); SmallObjectDistance = c.Bind<float>("4 - Rendering", "SmallObjectCullDistance", 0f, new ConfigDescription("Stop drawing objects on the Default_small layer (small clutter) beyond this many metres. 0 = off. Rendering only, no gameplay effect.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())); GpuEnabled = c.Bind<bool>("5 - Low-end GPU", "Enabled", true, "Settings that go below the lowest in-game graphics preset. Meant for weak/laptop GPUs. Needs a game restart to toggle."); ReflectionInterval = c.Bind<float>("5 - Low-end GPU", "ReflectionUpdateSeconds", 10f, new ConfigDescription("How often the world reflection probe is re-rendered (vanilla 3). Each update renders the scene 6 times, which causes stutter on weak GPUs.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 60f), Array.Empty<object>())); ReflectionResolution = c.Bind<int>("5 - Low-end GPU", "ReflectionResolution", 64, new ConfigDescription("Resolution of the world reflection probe. Lower = cheaper, blurrier reflections on water/metal. 0 = leave untouched.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[6] { 0, 16, 32, 64, 128, 256 }), Array.Empty<object>())); TextureMipmapLimit = c.Bind<int>("5 - Low-end GPU", "TextureQualityReduction", 0, new ConfigDescription("0 = full textures, 1 = half resolution, 2 = quarter. 1 is a big VRAM saver on 2 GB cards.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3), Array.Empty<object>())); LodBiasScale = c.Bind<float>("5 - Low-end GPU", "LodBiasScale", 1f, new ConfigDescription("Multiplier for the game's LOD bias. Below 1 switches objects to simpler models sooner (vanilla lowest setting = 1.0 bias).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 1f), Array.Empty<object>())); ShadowCascades = c.Bind<int>("5 - Low-end GPU", "ShadowCascades", 0, new ConfigDescription("Number of sun shadow cascades. 1 is cheapest. 0 = leave the game's value untouched.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 0, 1, 2, 4 }), Array.Empty<object>())); GrassDistanceScale = c.Bind<float>("5 - Low-end GPU", "GrassDistanceScale", 1f, new ConfigDescription("Multiplier for how far grass is drawn. Needs a game restart.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 1f), Array.Empty<object>())); GrassDensityScale = c.Bind<float>("5 - Low-end GPU", "GrassDensityScale", 1f, new ConfigDescription("Multiplier for grass density. Needs a game restart.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1f), Array.Empty<object>())); CharactersEnabled = c.Bind<bool>("6 - Characters", "Enabled", true, "Reduce per-creature CPU work without changing AI, combat or movement. Needs a game restart to toggle."); VisualOncePerFrame = c.Bind<bool>("6 - Characters", "VisualUpdatesOncePerFrame", true, "Run purely visual creature updates (slope tilt, water/slide effects) once per frame instead of every physics step. Big win at low FPS, where there are 3-5 physics steps per frame."); TiltMaxDistance = c.Bind<float>("6 - Characters", "SlopeTiltMaxDistance", 60f, new ConfigDescription("Creatures farther than this (metres) skip slope-tilt updates. 0 = never skip.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())); BuildingsEnabled = c.Bind<bool>("7 - Buildings", "Enabled", true, "Fixes for building pieces (snow, per-second update spike). Gameplay-equivalent to vanilla. Needs a game restart to toggle."); SnowFixFps = c.Bind<bool>("7 - Buildings", "FixSnowFpsDependency", true, "Vanilla snow on buildings grows faster at low FPS (bug). When on, it grows at the same speed as vanilla at ReferenceFps, for everyone."); SnowReferenceFps = c.Bind<float>("7 - Buildings", "SnowReferenceFps", 60f, new ConfigDescription("FPS at which vanilla snow speed is matched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 240f), Array.Empty<object>())); SnowQuantizeWrites = c.Bind<bool>("7 - Buildings", "ReduceSnowNetworkWrites", true, "Only write the snow level to the network when it changed by a visible amount (2%) instead of every second for every piece. Less CPU and network traffic in snowstorms."); SpreadCoverPass = c.Bind<bool>("7 - Buildings", "SpreadPerSecondPass", true, "Vanilla updates roof/ash state of ALL pieces in one frame once per second (a spike on big bases). Spread it over several frames instead."); CoverPassPiecesPerFrame = c.Bind<int>("7 - Buildings", "PiecesPerFrame", 1000, new ConfigDescription("How many pieces the spread pass handles per frame.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 10000), Array.Empty<object>())); MemoryEnabled = c.Bind<bool>("8 - Memory", "Enabled", true, "Run 'unload unused assets' only while the screen is black, instead of at random moments. Needs a game restart to toggle."); CleanupOnTeleport = c.Bind<bool>("8 - Memory", "OnTeleport", true, "Clean up during long-distance teleports (portals, dev commands). The loading screen stays a moment longer instead of freezing later."); CleanupOnRespawn = c.Bind<bool>("8 - Memory", "OnRespawn", true, "Run vanilla's respawn cleanup (same 20-minute rule) right when respawn starts, while the screen is black, instead of after the player appears."); DeferHourlyCleanup = c.Bind<bool>("8 - Memory", "DeferHourlyCleanup", true, "Vanilla also cleans up once per hour at any moment (a freeze, even in combat). Postpone it to the next teleport/sleep/respawn."); DeferMaxMinutes = c.Bind<float>("8 - Memory", "DeferMaxMinutes", 20f, new ConfigDescription("If no teleport/sleep/respawn happens within this time, do the postponed cleanup anyway.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), Array.Empty<object>())); GcTimeSliceMs = c.Bind<float>("8 - Memory", "IncrementalGcTimeSliceMs", 0f, new ConfigDescription("Only if the game build uses incremental GC (see 'GC mode' line in the log at startup): max milliseconds per frame the garbage collector may work, so one long pause becomes many short ones. 0 = leave the game's value untouched. Does not change gameplay. Setting back to 0 restores the original value.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); } } } namespace LeanHeim.Patches { internal static class BuildingStats { private sealed class Counts { public int Pieces; public int Renderers; public int Active; public int Visible; public int ShadowCasters; public int VisibleShadowCasters; public int WithPropertyBlock; public int InstancingMaterials; public int Lods; public readonly HashSet<Material> Materials = new HashSet<Material>(); public readonly HashSet<Mesh> Meshes = new HashSet<Mesh>(); } private static readonly FieldRef<WearNTear, List<Renderer>> RenderersRef = AccessTools.FieldRefAccess<WearNTear, List<Renderer>>("m_renderers"); private const int PiecesPerFrame = 300; private static Counts _scan = new Counts(); private static int _index; private static string _lastReport; internal static void Tick() { List<WearNTear> allInstances = WearNTear.GetAllInstances(); int num = Mathf.Min(_index + 300, allInstances.Count); for (int i = _index; i < num; i++) { Scan(allInstances[i]); } _index = num; if (_index >= allInstances.Count) { if (_scan.Pieces > 0) { _lastReport = Format(_scan); } _scan = new Counts(); _index = 0; } } private static void Scan(WearNTear w) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 if ((Object)(object)w == (Object)null) { return; } _scan.Pieces++; if ((Object)(object)((Component)w).GetComponent<LODGroup>() != (Object)null) { _scan.Lods++; } List<Renderer> list = RenderersRef.Invoke(w); if (list == null) { return; } MeshFilter val = default(MeshFilter); foreach (Renderer item in list) { if ((Object)(object)item == (Object)null) { continue; } _scan.Renderers++; if (!item.enabled || !((Component)item).gameObject.activeInHierarchy) { continue; } _scan.Active++; bool flag = (int)item.shadowCastingMode > 0; if (item.isVisible) { _scan.Visible++; if (flag) { _scan.VisibleShadowCasters++; } } if (flag) { _scan.ShadowCasters++; } if (item.HasPropertyBlock()) { _scan.WithPropertyBlock++; } Material sharedMaterial = item.sharedMaterial; if ((Object)(object)sharedMaterial != (Object)null) { _scan.Materials.Add(sharedMaterial); if (sharedMaterial.enableInstancing) { _scan.InstancingMaterials++; } } if (item is MeshRenderer && ((Component)item).TryGetComponent<MeshFilter>(ref val) && (Object)(object)val.sharedMesh != (Object)null) { _scan.Meshes.Add(val.sharedMesh); } } } private static string Format(Counts c) { return $" Buildings: pieces {c.Pieces} (with LODGroup {c.Lods}), renderers {c.Renderers}, active {c.Active}, " + $"visible now {c.Visible} (shadow casters among them {c.VisibleShadowCasters}), shadow casters total {c.ShadowCasters}, " + $"with property block {c.WithPropertyBlock}, instancing-enabled {c.InstancingMaterials}/{c.Active}, " + $"unique materials {c.Materials.Count}, unique meshes {c.Meshes.Count}"; } internal static string Report() { return _lastReport; } } [HarmonyPatch(typeof(Character))] internal static class CharacterPatches { private sealed class State { public int TiltFrame = -1; public float PendingTiltDt; public int FxFrame = -1; } private const float MaxPendingDt = 0.25f; private static readonly ConditionalWeakTable<Character, State> States = new ConditionalWeakTable<Character, State>(); private static Vector3 _camPos; private static int _camFrame = -1; private static Vector3 CameraPos() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0035: Unknown result type (might be due to invalid IL or missing references) int frameCount = Time.frameCount; if (_camFrame != frameCount) { _camFrame = frameCount; Camera mainCamera = Utils.GetMainCamera(); _camPos = (((Object)(object)mainCamera != (Object)null) ? ((Component)mainCamera).transform.position : Vector3.zero); } return _camPos; } [HarmonyPatch("UpdateGroundTilt")] [HarmonyPrefix] private static bool UpdateGroundTilt_Prefix(Character __instance, ref float dt) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (__instance is Player) { return true; } State orCreateValue = States.GetOrCreateValue(__instance); int frameCount = Time.frameCount; float value = Settings.TiltMaxDistance.Value; if (value > 0f) { Vector3 val = ((Component)__instance).transform.position - CameraPos(); if (((Vector3)(ref val)).sqrMagnitude > value * value) { goto IL_006b; } } if (!Settings.VisualOncePerFrame.Value || orCreateValue.TiltFrame != frameCount) { dt = Mathf.Min(dt + orCreateValue.PendingTiltDt, 0.25f); orCreateValue.PendingTiltDt = 0f; orCreateValue.TiltFrame = frameCount; return true; } goto IL_006b; IL_006b: orCreateValue.PendingTiltDt = Mathf.Min(orCreateValue.PendingTiltDt + dt, 0.25f); return false; } [HarmonyPatch("UpdateContinousEffects")] [HarmonyPrefix] private static bool UpdateContinousEffects_Prefix(Character __instance) { if (!Settings.VisualOncePerFrame.Value || __instance is Player) { return true; } State orCreateValue = States.GetOrCreateValue(__instance); int frameCount = Time.frameCount; if (orCreateValue.FxFrame == frameCount) { return false; } orCreateValue.FxFrame = frameCount; return true; } } [HarmonyPatch] internal static class GpuPatches { [HarmonyPatch(typeof(ReflectionUpdate), "Start")] [HarmonyPostfix] private static void ReflectionUpdate_Start_Postfix(ReflectionUpdate __instance) { ApplyReflection(__instance); } [HarmonyPatch(typeof(ClutterSystem), "Awake")] [HarmonyPostfix] private static void ClutterSystem_Awake_Postfix(ClutterSystem __instance) { __instance.m_distance *= Settings.GrassDistanceScale.Value; __instance.m_amountScale *= Settings.GrassDensityScale.Value; } [HarmonyPatch(typeof(GraphicsSettingsManager), "ApplyQualitySettings")] [HarmonyPostfix] private static void ApplyQualitySettings_Postfix() { ApplyQuality(); } internal static void ApplyReflection(ReflectionUpdate r) { if (!((Object)(object)r == (Object)null)) { r.m_interval = Settings.ReflectionInterval.Value; r.m_transitionDuration = Mathf.Min(r.m_transitionDuration, r.m_interval); SetupProbe(r.m_probe1); SetupProbe(r.m_probe2); } } private static void SetupProbe(ReflectionProbe probe) { if (!((Object)(object)probe == (Object)null)) { int value = Settings.ReflectionResolution.Value; if (value > 0) { probe.resolution = value; } probe.timeSlicingMode = (ReflectionProbeTimeSlicingMode)1; } } internal static void ApplyQuality() { QualitySettings.globalTextureMipmapLimit = Settings.TextureMipmapLimit.Value; float value = Settings.LodBiasScale.Value; if (value < 1f) { QualitySettings.lodBias *= value; } int value2 = Settings.ShadowCascades.Value; if (value2 > 0) { QualitySettings.shadowCascades = value2; } } internal static void Reapply() { GraphicsSettingsManager instance = GraphicsSettingsManager.Instance; if ((Object)(object)instance != (Object)null) { AccessTools.Method(typeof(GraphicsSettingsManager), "ApplyGraphicsSettingsToCurrentSession", (Type[])null, (Type[])null)?.Invoke(instance, null); } else { ApplyQuality(); } ApplyReflection(ReflectionUpdate.instance); } } [HarmonyPatch(typeof(LightLod))] internal static class LightPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] private static void Awake_Postfix(LightLod __instance) { if (__instance.m_lightLod) { __instance.m_lightDistance *= Settings.LightDistanceScale.Value; } if (__instance.m_shadowLod) { __instance.m_shadowDistance *= Settings.ShadowDistanceScale.Value; } } } [HarmonyPatch] internal static class MemoryPatches { private static readonly TimeSpan VanillaCooldown = TimeSpan.FromSeconds(1200.0); private static readonly FieldRef<Game, DateTime> LastCollect = AccessTools.FieldRefAccess<Game, DateTime>("m_lastCollectResources"); private static readonly FieldRef<Player, bool> Teleporting = AccessTools.FieldRefAccess<Player, bool>("m_teleporting"); private static readonly FieldRef<Player, bool> DistantTeleport = AccessTools.FieldRefAccess<Player, bool>("m_distantTeleport"); private static readonly FieldRef<Player, float> TeleportTimer = AccessTools.FieldRefAccess<Player, float>("m_teleportTimer"); private static readonly FieldRef<Player, Vector3> TeleportTargetPos = AccessTools.FieldRefAccess<Player, Vector3>("m_teleportTargetPos"); private static readonly FieldRef<Player, Quaternion> TeleportTargetRot = AccessTools.FieldRefAccess<Player, Quaternion>("m_teleportTargetRot"); private static readonly FieldRef<Character, Rigidbody> Body = AccessTools.FieldRefAccess<Character, Rigidbody>("m_body"); private static AsyncOperation _op; private static float _opStarted; private static bool _teleportHandled; private static bool _hourlyPending; private static float _hourlyPendingSince; private static DateTime _hourlyPendingSinceDate; private const float MaxHoldSeconds = 10f; private static ulong _originalSliceNs; private static bool _originalKnown; private static void StartCleanup(string reason) { Game instance = Game.instance; if (!((Object)(object)instance == (Object)null) && _op == null) { Plugin.Log.LogInfo((object)("Unloading unused assets (" + reason + ")")); _op = Resources.UnloadUnusedAssets(); _opStarted = Time.realtimeSinceStartup; LastCollect.Invoke(instance) = DateTime.Now; _hourlyPending = false; } } private static bool CleanupRunning() { if (_op == null) { return false; } if (_op.isDone || Time.realtimeSinceStartup - _opStarted > 10f) { _op = null; return false; } return true; } [HarmonyPatch(typeof(Player), "UpdateTeleport")] [HarmonyPrefix] private static bool UpdateTeleport_Prefix(Player __instance) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if (!Settings.CleanupOnTeleport.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (!Teleporting.Invoke(__instance)) { _teleportHandled = false; return true; } if (!DistantTeleport.Invoke(__instance) || _teleportHandled) { return true; } Vector3 val = TeleportTargetPos.Invoke(__instance); if (TeleportTimer.Invoke(__instance) <= 8f || (Object)(object)ZNetScene.instance == (Object)null || !ZNetScene.instance.IsAreaReady(val)) { return true; } if (_op == null) { StartCleanup("teleport"); } if (CleanupRunning()) { ((Component)__instance).transform.position = val; ((Component)__instance).transform.rotation = TeleportTargetRot.Invoke(__instance); Rigidbody val2 = Body.Invoke((Character)(object)__instance); if ((Object)(object)val2 != (Object)null) { val2.linearVelocity = Vector3.zero; } return false; } _teleportHandled = true; return true; } [HarmonyPatch(typeof(Game), "_RequestRespawn")] [HarmonyPostfix] private static void RequestRespawn_Postfix() { Game instance = Game.instance; if (Settings.CleanupOnRespawn.Value && !((Object)(object)instance == (Object)null) && (_hourlyPending || DateTime.Now - LastCollect.Invoke(instance) > VanillaCooldown)) { StartCleanup("respawn"); } } [HarmonyPatch(typeof(Game), "CollectResourcesCheckPeriodic")] [HarmonyPrefix] private static bool CollectResourcesCheckPeriodic_Prefix() { if (!Settings.DeferHourlyCleanup.Value) { return true; } if (!_hourlyPending) { _hourlyPending = true; _hourlyPendingSince = Time.realtimeSinceStartup; _hourlyPendingSinceDate = DateTime.Now; } return false; } internal static void ApplyGc() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { bool isIncremental = GarbageCollector.isIncremental; Mode gCMode = GarbageCollector.GCMode; ulong num = (isIncremental ? GarbageCollector.incrementalTimeSliceNanoseconds : 0); if (!_originalKnown) { _originalSliceNs = num; _originalKnown = true; Plugin.Log.LogInfo((object)$"GC mode: {gCMode}, incremental {isIncremental}, time slice {(double)num / 1000000.0:0.##} ms, mono heap {GC.GetTotalMemory(forceFullCollection: false) / 1048576} MB"); } float value = Settings.GcTimeSliceMs.Value; if (!isIncremental) { if (value > 0f) { Plugin.Log.LogWarning((object)"IncrementalGcTimeSliceMs is set but this build does not use incremental GC - ignored."); } return; } ulong num2 = ((value > 0f) ? ((ulong)(value * 1000000f)) : _originalSliceNs); if (num2 != num) { GarbageCollector.incrementalTimeSliceNanoseconds = num2; Plugin.Log.LogInfo((object)($"GC time slice {(double)num / 1000000.0:0.##} ms -> {(double)num2 / 1000000.0:0.##} ms" + ((value > 0f) ? "" : " (restored original)"))); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("GC settings not available: " + ex.Message)); } } internal static void Tick() { CleanupRunning(); if (!_hourlyPending) { return; } Game instance = Game.instance; if ((Object)(object)instance != (Object)null && LastCollect.Invoke(instance) >= _hourlyPendingSinceDate) { _hourlyPending = false; } else if (Time.realtimeSinceStartup - _hourlyPendingSince > Settings.DeferMaxMinutes.Value * 60f) { _hourlyPending = false; if (instance != null) { instance.CollectResources(true); } } } } internal static class ProfilerPatches { private sealed class Stat { public long Ticks; public long MaxTicks; public int Calls; } private sealed class Marker { public string Name; public ProfilerRecorder Recorder; public double TotalNs; } [HarmonyPatch] private static class MonoUpdaterScopes { private static IEnumerable<MethodBase> TargetMethods() { string[] array = new string[4] { "CustomUpdate", "CustomFixedUpdate", "CustomLateUpdate", "UpdateAI" }; foreach (string text in array) { MethodInfo methodInfo = AccessTools.Method(typeof(MonoUpdatersExtra), text, (Type[])null, (Type[])null); if (methodInfo != null) { yield return methodInfo; } } } private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(string profileScope, long __state) { Record(profileScope, __state); } } [HarmonyPatch] private static class NamedScopes { private static readonly string[] Targets = new string[27] { "WearNTearUpdater:Update", "LightLod:UpdateLights", "EnvMan:Update", "ZNetScene:Update", "ZDOMan:Update", "ZoneSystem:Update", "Player:Update", "Player:FixedUpdate", "Hud:Update", "GameCamera:LateUpdate", "Piece:Awake", "ZNetView:Awake", "WearNTear:UpdateWear", "WearNTear:UpdateCover", "WearNTear:UpdateSupport", "WearNTear:UpdateAshlandsMaterialValues", "Character:CalculateLiquidDepth", "Character:UpdateContinousEffects", "Character:UpdateWater", "Character:UpdateGroundTilt", "Character:SetVisible", "Character:UpdateGroundContact", "Character:UpdateMotion", "Character:UpdateNoise", "Character:UpdatePheromones", "Character:UpdateHeatEffects", "SEMan:Update" }; private static readonly Dictionary<MethodBase, string> Names = new Dictionary<MethodBase, string>(); private static IEnumerable<MethodBase> TargetMethods() { string[] targets = Targets; foreach (string text in targets) { string[] array = text.Split(new char[1] { ':' }); Type type = AccessTools.TypeByName(array[0]); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, array[1], (Type[])null, (Type[])null)); if (methodInfo == null) { Plugin.Log.LogDebug((object)("Profiler: skipping missing target " + text)); } else { yield return methodInfo; } } } private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(MethodBase __originalMethod, long __state) { if (!Names.TryGetValue(__originalMethod, out var value)) { value = (Names[__originalMethod] = __originalMethod.DeclaringType.Name + "." + __originalMethod.Name); } Record(value, __state); } } private static readonly Dictionary<string, Stat> Stats = new Dictionary<string, Stat>(); private static readonly double TickToMs = 1000.0 / (double)Stopwatch.Frequency; private static float _windowStart; private static int _windowFrames; private static int _windowStartGc; private static readonly Dictionary<string, Stat> FrameStats = new Dictionary<string, Stat>(); private static long _lastFrameTs; private static int _lastGcCount; private static int _hitchesThisWindow; private static int _hitchesSuppressed; private const int MaxHitchLogsPerWindow = 15; private static readonly FrameTiming[] Timings = (FrameTiming[])(object)new FrameTiming[1]; private static double _cpuMain; private static double _cpuRender; private static double _gpu; private static int _timingSamples; private static Marker[] _markers; private static readonly (ProfilerCategory cat, string name)[] MarkerNames = new(ProfilerCategory, string)[13] { (ProfilerCategory.Internal, "Main Thread"), (ProfilerCategory.Internal, "PlayerLoop"), (ProfilerCategory.Render, "Gfx.WaitForPresentOnGfxThread"), (ProfilerCategory.Render, "Gfx.WaitForRenderThread"), (ProfilerCategory.Render, "Camera.Render"), (ProfilerCategory.Render, "Shadows.RenderShadowMap"), (ProfilerCategory.Physics, "Physics.Simulate"), (ProfilerCategory.Animation, "Animators.Update"), (ProfilerCategory.Animation, "Animators.WriteJob"), (ProfilerCategory.Scripts, "BehaviourUpdate"), (ProfilerCategory.Scripts, "FixedBehaviourUpdate"), (ProfilerCategory.Scripts, "LateBehaviourUpdate"), (ProfilerCategory.Internal, "GC.Collect") }; internal static void Install(Harmony harmony) { harmony.CreateClassProcessor(typeof(MonoUpdaterScopes)).Patch(); harmony.CreateClassProcessor(typeof(NamedScopes)).Patch(); Plugin.Log.LogInfo((object)"Enabled module ProfilerPatches"); } internal static void Record(string key, long startTimestamp) { long num = Stopwatch.GetTimestamp() - startTimestamp; if (!Stats.TryGetValue(key, out var value)) { value = (Stats[key] = new Stat()); } value.Ticks += num; value.Calls++; if (num > value.MaxTicks) { value.MaxTicks = num; } if (!FrameStats.TryGetValue(key, out var value2)) { value2 = (FrameStats[key] = new Stat()); } value2.Ticks += num; value2.Calls++; } private static void CheckHitch() { long timestamp = Stopwatch.GetTimestamp(); int num = GC.CollectionCount(0); double num2 = ((_lastFrameTs == 0L) ? 0.0 : ((double)(timestamp - _lastFrameTs) * TickToMs)); int gcDelta = num - _lastGcCount; _lastFrameTs = timestamp; _lastGcCount = num; float value = Settings.HitchThresholdMs.Value; if (value > 0f && num2 >= (double)value && (Object)(object)Player.m_localPlayer != (Object)null) { if (_hitchesThisWindow++ < 15) { LogHitch(num2, gcDelta); } else { _hitchesSuppressed++; } } foreach (Stat value2 in FrameStats.Values) { value2.Ticks = 0L; value2.Calls = 0; } } private static void LogHitch(double frameMs, int gcDelta) { List<KeyValuePair<string, Stat>> list = new List<KeyValuePair<string, Stat>>(); foreach (KeyValuePair<string, Stat> frameStat in FrameStats) { if (frameStat.Value.Calls > 0) { list.Add(frameStat); } } list.Sort((KeyValuePair<string, Stat> a, KeyValuePair<string, Stat> b) => b.Value.Ticks.CompareTo(a.Value.Ticks)); FrameStats.TryGetValue("MonoUpdaters.FixedUpdate.Character", out var value); string text = ((!((Object)(object)EnvMan.instance != (Object)null)) ? "?" : EnvMan.instance.GetCurrentEnvironment()?.m_name); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(string.Format("[LeanHeim hitch] {0:0} ms frame | physics steps {1} | GC {2} | heap {3} MB | env {4} | chars {5} | smoke {6} | top:", frameMs, value?.Calls ?? 0, (gcDelta > 0) ? ("YES x" + gcDelta) : "no", GC.GetTotalMemory(forceFullCollection: false) / 1048576, text, Character.GetAllCharacters().Count, Smoke.GetTotalSmoke())); int num = 0; foreach (KeyValuePair<string, Stat> item in list) { if (num++ >= 6) { break; } stringBuilder.Append($" {item.Key}={(double)item.Value.Ticks * TickToMs:0.0}ms"); if (item.Value.Calls > 1) { stringBuilder.Append($"(x{item.Value.Calls})"); } } Plugin.Log.LogWarning((object)stringBuilder.ToString()); } private static void SampleFrameTimings() { try { FrameTimingManager.CaptureFrameTimings(); if (FrameTimingManager.GetLatestTimings(1u, Timings) != 0) { _cpuMain += Timings[0].cpuMainThreadFrameTime; _cpuRender += Timings[0].cpuRenderThreadFrameTime; _gpu += Timings[0].gpuFrameTime; _timingSamples++; } } catch { } } private static void SampleMarkers() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (_markers == null) { _markers = new Marker[MarkerNames.Length]; for (int i = 0; i < MarkerNames.Length; i++) { Marker marker = new Marker { Name = MarkerNames[i].name }; try { marker.Recorder = ProfilerRecorder.StartNew(MarkerNames[i].cat, MarkerNames[i].name, 1, (ProfilerRecorderOptions)24); } catch { } _markers[i] = marker; } } Marker[] markers = _markers; foreach (Marker marker2 in markers) { if (((ProfilerRecorder)(ref marker2.Recorder)).Valid) { marker2.TotalNs += ((ProfilerRecorder)(ref marker2.Recorder)).LastValue; } } } private static void AppendMarkers(StringBuilder sb, int frames) { if (_markers == null) { return; } sb.Append(" Unity markers (ms/frame):"); Marker[] markers = _markers; foreach (Marker marker in markers) { if (((ProfilerRecorder)(ref marker.Recorder)).Valid) { sb.Append($" {marker.Name}={marker.TotalNs / 1000000.0 / (double)Math.Max(1, frames):0.0}"); } else { sb.Append(" " + marker.Name + "=n/a"); } marker.TotalNs = 0.0; } sb.AppendLine(); } internal static void Tick() { _windowFrames++; CheckHitch(); BuildingStats.Tick(); SampleFrameTimings(); SampleMarkers(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (_windowStart == 0f) { _windowStart = realtimeSinceStartup; return; } float num = realtimeSinceStartup - _windowStart; if (num < Settings.ProfilerInterval.Value) { return; } List<KeyValuePair<string, Stat>> list = new List<KeyValuePair<string, Stat>>(Stats); list.Sort((KeyValuePair<string, Stat> a, KeyValuePair<string, Stat> b) => b.Value.Ticks.CompareTo(a.Value.Ticks)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"[LeanHeim profiler] {num:0.#}s, {_windowFrames} frames, avg {1000f * num / (float)Math.Max(1, _windowFrames):0.0} ms/frame ({(float)_windowFrames / num:0} fps)"); if (_timingSamples > 0) { stringBuilder.AppendLine($" Unity frame timings (avg): cpuMain {_cpuMain / (double)_timingSamples:0.0} ms, cpuRender {_cpuRender / (double)_timingSamples:0.0} ms, gpu {_gpu / (double)_timingSamples:0.0} ms"); } else { stringBuilder.AppendLine(" Unity frame timings: not available"); } stringBuilder.AppendLine($" GC: gen0 collections {GC.CollectionCount(0) - _windowStartGc}, mono heap {GC.GetTotalMemory(forceFullCollection: false) / 1048576} MB"); _windowStartGc = GC.CollectionCount(0); AppendMarkers(stringBuilder, _windowFrames); stringBuilder.AppendLine($" Objects: characters {Character.GetAllCharacters().Count}, wearNTear pieces {WearNTear.GetAllInstances().Count}, smoke {Smoke.GetTotalSmoke()}, fixedDelta {Time.fixedDeltaTime * 1000f:0.#} ms, maxDelta {Time.maximumDeltaTime * 1000f:0} ms, vsync {QualitySettings.vSyncCount}, lodBias {QualitySettings.lodBias:0.##}, shadowDist {QualitySettings.shadowDistance:0}, cascades {QualitySettings.shadowCascades}, texLimit {QualitySettings.globalTextureMipmapLimit}, screen {Screen.width}x{Screen.height}"); stringBuilder.AppendLine(string.Format(" Hitches (>{0:0} ms) this window: {1}{2}", Settings.HitchThresholdMs.Value, _hitchesThisWindow, (_hitchesSuppressed > 0) ? $" ({_hitchesSuppressed} not logged)" : "")); string text = BuildingStats.Report(); if (text != null) { stringBuilder.AppendLine(text); } _hitchesThisWindow = 0; _hitchesSuppressed = 0; stringBuilder.AppendLine(" ms/frame max ms calls/frame scope"); int num2 = 0; foreach (KeyValuePair<string, Stat> item in list) { if (num2++ >= 20) { break; } Stat value = item.Value; double num3 = (double)value.Ticks * TickToMs / (double)_windowFrames; stringBuilder.AppendLine($" {num3,8:0.000} {(double)value.MaxTicks * TickToMs,8:0.00} {(double)value.Calls / (double)_windowFrames,12:0.0} {item.Key}"); } Plugin.Log.LogInfo((object)stringBuilder.ToString()); Stats.Clear(); _cpuMain = (_cpuRender = (_gpu = 0.0)); _timingSamples = 0; _windowFrames = 0; _windowStart = realtimeSinceStartup; } } [HarmonyPatch] internal static class RenderingPatches { [HarmonyPatch(typeof(GraphicsSettingsManager), "ApplyQualitySettings")] [HarmonyPostfix] private static void ApplyQualitySettings_Postfix() { ApplyQuality(); } [HarmonyPatch(typeof(GameCamera), "Awake")] [HarmonyPostfix] private static void GameCamera_Awake_Postfix() { ApplyCullDistances(); } internal static void ApplyQuality() { int value = Settings.ParticleRaycastBudget.Value; if (value > 0) { QualitySettings.particleRaycastBudget = value; } float value2 = Settings.MaxShadowDistance.Value; if (value2 > 0f && QualitySettings.shadowDistance > value2) { QualitySettings.shadowDistance = value2; } } internal static void ApplyCullDistances() { Camera mainCamera = Utils.GetMainCamera(); if (!((Object)(object)mainCamera == (Object)null)) { int num = LayerMask.NameToLayer("Default_small"); if (num >= 0) { float[] layerCullDistances = mainCamera.layerCullDistances; layerCullDistances[num] = Settings.SmallObjectDistance.Value; mainCamera.layerCullDistances = layerCullDistances; mainCamera.layerCullSpherical = true; } } } } [HarmonyPatch] internal static class WearNTearPatches { private sealed class SnowState { public float LastUpdateTime = -1f; } private const float SnowWriteStep = 0.02f; [ThreadStatic] private static WearNTear _current; [ThreadStatic] private static bool _snowWriteSkipped; [ThreadStatic] private static float _snowSkippedValue; private static readonly ConditionalWeakTable<WearNTear, SnowState> SnowStates = new ConditionalWeakTable<WearNTear, SnowState>(); private static readonly FieldRef<WearNTearUpdater, float> SleepUntil = AccessTools.FieldRefAccess<WearNTearUpdater, float>("m_sleepUntil"); private static readonly FieldRef<WearNTearUpdater, float> SleepUntilNext = AccessTools.FieldRefAccess<WearNTearUpdater, float>("m_sleepUntilNext"); private static readonly int AshlandsWearTextureId = Shader.PropertyToID("_AshlandsWearTexture"); private static int _passIndex = -1; [HarmonyPatch(typeof(WearNTear), "UpdateWear")] [HarmonyPrefix] private static void UpdateWear_Prefix(WearNTear __instance) { _current = __instance; _snowWriteSkipped = false; } [HarmonyPatch(typeof(WearNTear), "UpdateWear")] [HarmonyPostfix] private static void UpdateWear_Postfix(WearNTear __instance) { if (_snowWriteSkipped) { __instance.m_snowBuildup = _snowSkippedValue; } _snowWriteSkipped = false; _current = null; } [HarmonyPatch(typeof(WearNTear), "UpdateWear")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> UpdateWear_Transpiler(IEnumerable<CodeInstruction> instructions) { MethodInfo getDeltaTime = AccessTools.PropertyGetter(typeof(Time), "deltaTime"); MethodInfo zdoSetFloat = AccessTools.Method(typeof(ZDO), "Set", new Type[2] { typeof(int), typeof(float) }, (Type[])null); MethodInfo ourDelta = AccessTools.Method(typeof(WearNTearPatches), "SnowDeltaTime", (Type[])null, (Type[])null); MethodInfo ourSet = AccessTools.Method(typeof(WearNTearPatches), "SetFloat", (Type[])null, (Type[])null); int replacedDelta = 0; int replacedSet = 0; foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, getDeltaTime)) { yield return CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Call, (object)ourDelta), instruction); replacedDelta++; } else if (CodeInstructionExtensions.Calls(instruction, zdoSetFloat)) { yield return CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Call, (object)ourSet), instruction); replacedSet++; } else { yield return instruction; } } if (replacedDelta == 0 || replacedSet == 0) { Plugin.Log.LogWarning((object)$"WearNTear.UpdateWear changed in this game version (deltaTime {replacedDelta}, ZDO.Set {replacedSet}); snow fix partially inactive."); } } private static float SnowDeltaTime() { WearNTear current = _current; if ((Object)(object)current == (Object)null || !Settings.SnowFixFps.Value) { return Time.deltaTime; } SnowState orCreateValue = SnowStates.GetOrCreateValue(current); float time = Time.time; float num = ((orCreateValue.LastUpdateTime < 0f) ? 1f : Mathf.Clamp(time - orCreateValue.LastUpdateTime, 0f, 5f)); orCreateValue.LastUpdateTime = time; return num / Settings.SnowReferenceFps.Value; } private static void SetFloat(ZDO zdo, int hash, float value) { if ((Object)(object)_current != (Object)null && Settings.SnowQuantizeWrites.Value && hash == ZDOVars.s_snow && value < 1f) { float num = zdo.GetFloat(hash, 0f); if (Mathf.Abs(value - num) < 0.02f) { _snowWriteSkipped = true; _snowSkippedValue = value; return; } } zdo.Set(hash, value); } [HarmonyPatch(typeof(WearNTearUpdater), "UpdateWearNTear")] [HarmonyPrefix] private static bool UpdateWearNTear_Prefix(WearNTearUpdater __instance, float deltaTime, float time) { if (!Settings.SpreadCoverPass.Value) { return true; } if (_passIndex >= 0) { ContinuePass(deltaTime); } if (!SleepUntilNext.Invoke(__instance).Equals(SleepUntil.Invoke(__instance))) { return true; } SleepUntilNext.Invoke(__instance) = time + 1f; Shader.SetGlobalTexture(AshlandsWearTextureId, (Texture)(object)__instance.m_ashlandsWearTexture); if (_passIndex >= 0) { FinishPass(deltaTime); } _passIndex = 0; ContinuePass(deltaTime); return false; } private static void ContinuePass(float deltaTime) { List<WearNTear> allInstances = WearNTear.GetAllInstances(); int num = Mathf.Max(Settings.CoverPassPiecesPerFrame.Value, 1); int num2 = Mathf.Min(_passIndex + num, allInstances.Count); float time = Time.time; for (int i = _passIndex; i < num2; i++) { WearNTear val = allInstances[i]; if (!((Object)(object)val == (Object)null)) { if (((Behaviour)val).enabled) { val.UpdateCover(deltaTime); } val.UpdateAshlandsMaterialValues(time); } } _passIndex = ((num2 >= allInstances.Count) ? (-1) : num2); } private static void FinishPass(float deltaTime) { while (_passIndex >= 0) { ContinuePass(deltaTime); } } } [HarmonyPatch] internal static class WeatherPatches { private sealed class Original { public float RateOverTime; public float RateOverDistance; public int MaxParticles; public bool Collision; public bool Lights; public bool Trails; } private static readonly ConditionalWeakTable<ParticleSystem, Original> Originals = new ConditionalWeakTable<ParticleSystem, Original>(); [HarmonyPatch(typeof(EnvMan), "SetParticleArrayEnabled")] [HarmonyPrefix] private static void SetParticleArrayEnabled_Prefix(GameObject[] psystems) { if (psystems == null) { return; } foreach (GameObject val in psystems) { if (!((Object)(object)val == (Object)null)) { ParticleSystem[] componentsInChildren = val.GetComponentsInChildren<ParticleSystem>(true); for (int j = 0; j < componentsInChildren.Length; j++) { Apply(componentsInChildren[j]); } } } } internal static void ReapplyAll() { EnvMan instance = EnvMan.instance; if (!((Object)(object)instance == (Object)null)) { ParticleSystem[] componentsInChildren = ((Component)instance).GetComponentsInChildren<ParticleSystem>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Apply(componentsInChildren[i]); } } } private static void Apply(ParticleSystem ps) { //IL_00a6: 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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) if (!Originals.TryGetValue(ps, out var value)) { EmissionModule emission = ps.emission; Original obj = new Original { RateOverTime = ((EmissionModule)(ref emission)).rateOverTimeMultiplier, RateOverDistance = ((EmissionModule)(ref emission)).rateOverDistanceMultiplier }; MainModule main = ps.main; obj.MaxParticles = ((MainModule)(ref main)).maxParticles; CollisionModule collision = ps.collision; obj.Collision = ((CollisionModule)(ref collision)).enabled; LightsModule lights = ps.lights; obj.Lights = ((LightsModule)(ref lights)).enabled; TrailModule trails = ps.trails; obj.Trails = ((TrailModule)(ref trails)).enabled; value = obj; Originals.Add(ps, value); } float value2 = Settings.WeatherDensity.Value; EmissionModule emission2 = ps.emission; ((EmissionModule)(ref emission2)).rateOverTimeMultiplier = value.RateOverTime * value2; ((EmissionModule)(ref emission2)).rateOverDistanceMultiplier = value.RateOverDistance * value2; MainModule main2 = ps.main; ((MainModule)(ref main2)).maxParticles = Mathf.Max(8, Mathf.RoundToInt((float)value.MaxParticles * value2)); CollisionModule collision2 = ps.collision; ((CollisionModule)(ref collision2)).enabled = value.Collision && !Settings.WeatherNoCollision.Value; LightsModule lights2 = ps.lights; ((LightsModule)(ref lights2)).enabled = value.Lights && !Settings.WeatherNoLights.Value; TrailModule trails2 = ps.trails; ((TrailModule)(ref trails2)).enabled = value.Trails && !Settings.WeatherNoTrails.Value; } } }