using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("ValheimBuildOptimization")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ValheimBuildOptimization")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("be15940e-381c-4623-a8d4-222869a01afc")] [assembly: AssemblyFileVersion("0.5.3.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.5.3.0")] namespace BuildPieceProfiler; [BepInPlugin("valheim.buildpieceprofiler", "Valheim Build Optimization", "0.5.3")] public class BuildPieceProfilerPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(Piece), "Awake")] private static class PieceAwakeOptimizationLifecyclePatch { private static void Postfix(Piece __instance) { NotifyPieceLoadedOrChanged(__instance, forceRefresh: false); } } [HarmonyPatch(typeof(Piece), "OnPlaced")] private static class PieceOnPlacedOptimizationLifecyclePatch { private static void Postfix(Piece __instance) { NotifyPieceLoadedOrChanged(__instance, forceRefresh: true); } } [HarmonyPatch(typeof(Piece), "OnDestroy")] private static class PieceOnDestroyOptimizationLifecyclePatch { private static void Prefix(Piece __instance) { NotifyPieceDestroyed(__instance); } } [HarmonyPatch(typeof(Fireplace), "Awake")] private static class FireplaceAwakeOptimizationLifecyclePatch { private static void Postfix() { if ((Object)(object)_instance != (Object)null && _instance._enableFireOptimizations != null && _instance._enableFireOptimizations.Value) { _instance._fireCandidateRefreshRequested = true; } } } [HarmonyPatch(typeof(WearNTear), "UpdateSupport")] private static class WearNTearUpdateSupportPatch { private static bool Prefix(WearNTear __instance) { if (!IsWearNTearOptimizationEnabled()) { return true; } ApplyBypassedSupport(__instance); return false; } } [HarmonyPatch(typeof(WearNTear), "GetSupport")] private static class WearNTearGetSupportPatch { private static bool Prefix(WearNTear __instance, ref float __result) { if (!IsWearNTearOptimizationEnabled()) { return true; } __result = GetBypassedSupportValue(); return false; } } [HarmonyPatch(typeof(WearNTear), "HaveSupport")] private static class WearNTearHaveSupportPatch { private static bool Prefix(ref bool __result) { if (!IsWearNTearOptimizationEnabled()) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(WearNTear), "UpdateCover")] private static class WearNTearUpdateCoverPatch { private static bool Prefix(WearNTear __instance) { if (!IsWearNTearOptimizationEnabled()) { return true; } ApplyDryRoofedState(__instance); return false; } } [HarmonyPatch(typeof(WearNTear), "HaveRoof")] private static class WearNTearHaveRoofPatch { private static bool Prefix(WearNTear __instance, ref bool __result) { if (!IsWearNTearOptimizationEnabled()) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(WearNTear), "HaveAshRoof")] private static class WearNTearHaveAshRoofPatch { private static bool Prefix(WearNTear __instance, ref bool __result) { if (!IsWearNTearOptimizationEnabled()) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(WearNTear), "IsWet")] private static class WearNTearIsWetPatch { private static bool Prefix(WearNTear __instance, ref bool __result) { if (!IsWearNTearOptimizationEnabled()) { return true; } __result = false; return false; } } private enum AppliedFireMode { StaticLight, FullCull } private class FireCandidate { public Piece Piece; public Fireplace Source; public MeshRenderer[] Renderers; public Light[] OriginalLights; public ParticleSystem[] Particles; public bool HasOcclusionResult; public bool CachedOccluded; public float LastOcclusionCheckTime; public int OcclusionCheckGeneration; public float LastRelevantTime; public float LastIrrelevantTime; } private class FireOptimizationState { public Piece Piece; public Fireplace Source; public AppliedFireMode AppliedMode; public readonly Dictionary OriginalLightEnabled = new Dictionary(); public readonly Dictionary OriginalLightCullingMask = new Dictionary(); public readonly Dictionary OriginalParticlePlaying = new Dictionary(); public readonly Dictionary OriginalParticleEmissionEnabled = new Dictionary(); public readonly Dictionary OriginalShadowModes = new Dictionary(); public readonly Dictionary OriginalBehaviourEnabled = new Dictionary(); public readonly HashSet ForcedStoppedParticles = new HashSet(); public GameObject ProxyLightObject; public Light ProxyLight; } private struct ClusterProxyKey : IEquatable { public int X; public int Y; public int Z; public bool Equals(ClusterProxyKey other) { return X == other.X && Y == other.Y && Z == other.Z; } public override bool Equals(object obj) { return obj is ClusterProxyKey other && Equals(other); } public override int GetHashCode() { int num = 17; num = num * 31 + X; num = num * 31 + Y; return num * 31 + Z; } } private class RaycastHitDistanceComparer : IComparer { public int Compare(RaycastHit x, RaycastHit y) { return ((RaycastHit)(ref x)).distance.CompareTo(((RaycastHit)(ref y)).distance); } } private class ClusterProxyLightState { public GameObject ProxyLightObject; public Light ProxyLight; public bool ActiveThisUpdate; public int MemberCount; public Vector3 PositionSum; public Color ColorSum; public float IntensitySum; public float RangeMax; public Quaternion Rotation; public bool HasRotation; } private enum FireOptimizationMode { StaticLight, FullCull } private struct FireMetrics { public int FireCandidates; public int RendererVisibleFireCandidates; public int OccludedFireCandidates; public int RelevantFireCandidates; public int HiddenOrIrrelevantFireCandidates; public int OptimizedFirePieces; public int StaticLightFirePieces; public int FullCullFirePieces; public int FireProxyLightsActive; public int FireOriginalLightsDisabled; public int FireParticlesStopped; public int FireShadowsDisabled; } private struct LightOffenderSnapshot { public string Name; public float Distance; public int EnabledLights; public int ShadowLights; public int ActiveParticles; public int LightUpdateBehaviours; public float MaxRange; public float TotalIntensity; public float Score; } private struct Counts { public int Pieces; public int WearNTear; public int ZNetView; public int MeshRenderer; public int EnabledMeshRenderer; public int VisibleMeshRenderer; public int Collider; public int EnabledCollider; public int LODGroup; public int Light; public int EnabledLight; public int ParticleSystem; public int ActiveParticleSystem; public int AudioSource; public int ActiveRigidbody; public int PieceMeshRenderer; public int PieceEnabledMeshRenderer; public int PieceVisibleMeshRenderer; public int PieceCollider; public int PieceEnabledCollider; public int PieceLODGroup; public int PieceLight; public int PieceEnabledLight; public int PieceParticleSystem; public int PieceActiveParticleSystem; public int PieceAudioSource; public int PieceRigidbody; public int PieceActiveRigidbody; public int FireCandidates; public int RendererVisibleFireCandidates; public int OccludedFireCandidates; public int RelevantFireCandidates; public int HiddenOrIrrelevantFireCandidates; public int OptimizedFirePieces; public int StaticLightFirePieces; public int FullCullFirePieces; public int FireProxyLightsActive; public int FireOriginalLightsDisabled; public int FireParticlesStopped; public int FireShadowsDisabled; public List TopLightOffenders; } private enum ColliderClusterPieceEligibility { Eligible, Ineligible, Interactive, NameFilter } private struct ColliderClusterCellKey : IEquatable { public int X; public int Y; public int Z; public bool Equals(ColliderClusterCellKey other) { return X == other.X && Y == other.Y && Z == other.Z; } public override bool Equals(object obj) { return obj is ColliderClusterCellKey other && Equals(other); } public override int GetHashCode() { int num = 17; num = num * 31 + X; num = num * 31 + Y; return num * 31 + Z; } public override string ToString() { return $"{X},{Y},{Z}"; } } private struct ColliderClusterGroupKey : IEquatable { public int Layer; public PhysicsMaterial Material; public bool Equals(ColliderClusterGroupKey other) { return Layer == other.Layer && (Object)(object)Material == (Object)(object)other.Material; } public override bool Equals(object obj) { return obj is ColliderClusterGroupKey other && Equals(other); } public override int GetHashCode() { int layer = Layer; return (layer * 397) ^ (((Object)(object)Material != (Object)null) ? ((Object)Material).GetInstanceID() : 0); } } internal class ColliderClusterSource { public BoxCollider Collider; public Piece Piece; public WearNTear WearNTear; public bool OriginalEnabled; public Bounds WorldBounds; } private class ColliderMergeRun { public Bounds WorldBounds; public readonly List Sources = new List(); } private class ColliderClusterOutput { public GameObject GameObject; public BoxCollider Collider; public List Sources; public Bounds WorldBounds; } private class ColliderClusterCell { public ColliderClusterCellKey Key; public readonly HashSet Pieces = new HashSet(); public readonly List Outputs = new List(); public GameObject Root; public bool Dirty; public bool Queued; public bool ClusterActive; public int PiecesConsidered; public int PiecesEligible; public int BoxCollidersConsidered; public int EligibleBoxColliders; public int ExcludedTriggers; public int ExcludedRigidbodies; public int ExcludedInteractivePieces; public int ExcludedNamePieces; public int ExcludedNonBoxColliders; public int GroupsBelowMinimum; public int PotentialClusterColliders; public int PotentialClusteredBoxes; public Bounds ClusterBounds; public bool HasClusterBounds; } private struct ColliderClusterMetrics { public bool ProfilingEnabled; public bool ClusteringRequested; public bool ClusteringActive; public bool WearNTearRequirementMet; public int TrackedPieces; public int Cells; public int DirtyCells; public int DiscoveryRemaining; public int ActiveClusterCells; public int PiecesConsidered; public int PiecesEligible; public int BoxCollidersConsidered; public int EligibleBoxColliders; public int ClusterColliders; public int ClusteredSourceBoxes; public int OriginalCollidersDisabled; public int EstimatedColliderReduction; public int PotentialClusterColliders; public int PotentialClusteredBoxes; public int PotentialColliderReduction; public int ExcludedTriggers; public int ExcludedRigidbodies; public int ExcludedInteractivePieces; public int ExcludedNamePieces; public int ExcludedNonBoxColliders; public int GroupsBelowMinimum; } private static class ZdoExtraDataSetNetworkProfilerPatch { internal static IEnumerable TargetMethods() { Type type = AccessTools.TypeByName("ZDOExtraData"); if (type == null) { yield break; } foreach (MethodInfo method in AccessTools.GetDeclaredMethods(type)) { ParameterInfo[] parameters = method.GetParameters(); if (method.Name == "Set" && method.ReturnType == typeof(bool) && parameters.Length >= 3 && parameters[0].ParameterType == typeof(ZDOID)) { yield return method; } } } internal static void Postfix(ZDOID zid, bool __result) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (_networkProfilerHooksActive) { _instance?.RecordNetworkZdoWrite(zid, __result); } } } private static class ZNetViewInvokeRpcNetworkProfilerPatch { internal static IEnumerable TargetMethods() { foreach (MethodInfo method in AccessTools.GetDeclaredMethods(typeof(ZNetView))) { if (method.Name == "InvokeRPC") { yield return method; } } } internal static void Prefix(ZNetView __instance, string method) { if (_networkProfilerHooksActive) { _instance?.RecordNetworkRpc(__instance, method); } } } private class NetworkPieceState { public Piece Piece; public ZNetView View; public ZDO Zdo; public ZDOID ZdoId; public string Name; public uint LastDataRevision; public ushort LastOwnerRevision; public long LastOwner; public long WriteAttempts; public long ChangedWrites; public long RpcCalls; public long ObservedRevisionChanges; public long OwnerChanges; public float LastActivityTime = float.MinValue; public bool IsStaticStructure; } private struct NetworkWriterSnapshot { public string Name; public long WriteAttempts; public long ChangedWrites; public long RevisionChanges; public long RpcCalls; public bool IsStaticStructure; } private struct NetworkRpcSnapshot { public string Method; public int Count; } private struct NetworkIdleMetrics { public int TrackedPieces; public int DiscoveryRemaining; public int OwnedByLocal; public int OwnedByOther; public int Unowned; public int ActiveWriters; public int StaticActiveWriters; public int DynamicActiveWriters; public long StaticWriteAttempts; public long StaticChangedWrites; public long DynamicWriteAttempts; public long DynamicChangedWrites; public long WriteAttempts; public long ChangedWrites; public long RedundantWrites; public long RpcCalls; public long ObservedRevisionChanges; public long ObservedOwnerChanges; public int SentZdos; public int ReceivedZdos; public int ClientChangeQueue; } private struct NetworkMetricsAggregate { public int OwnedByLocal; public int OwnedByOther; public int Unowned; public int ActiveWriters; public int StaticActiveWriters; public int DynamicActiveWriters; public long StaticWriteAttempts; public long StaticChangedWrites; public long DynamicWriteAttempts; public long DynamicChangedWrites; } [HarmonyPatch(typeof(WearNTear), "Highlight")] private static class WearNTearHighlightOptimizationPatch { private static void Prefix(WearNTear __instance) { BuildPieceProfilerPlugin instance = _instance; if (!((Object)(object)instance == (Object)null) && instance.ShouldHandleRendererOrStaticSleepEvents()) { Piece piece = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponentInParent() : null); instance?.NotifyRendererBatchHighlight(piece, 0.15f); instance?.NotifyStaticSleepPieceChanged(piece); } } } [HarmonyPatch(typeof(WearNTear), "Damage")] private static class WearNTearDamageOptimizationPatch { private static void Postfix(WearNTear __instance) { BuildPieceProfilerPlugin instance = _instance; if (!((Object)(object)instance == (Object)null) && instance.ShouldHandleRendererOrStaticSleepEvents()) { Piece piece = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponentInParent() : null); instance?.NotifyRendererBatchVisualChange(piece, 0.35f); instance?.NotifyStaticSleepPieceChanged(piece); } } } [HarmonyPatch(typeof(WearNTear), "Repair")] private static class WearNTearRepairOptimizationPatch { private static void Postfix(WearNTear __instance) { BuildPieceProfilerPlugin instance = _instance; if (!((Object)(object)instance == (Object)null) && instance.ShouldHandleRendererOrStaticSleepEvents()) { Piece piece = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponentInParent() : null); instance?.NotifyRendererBatchVisualChange(piece, 0.35f); instance?.NotifyStaticSleepPieceChanged(piece); } } } [HarmonyPatch(typeof(WearNTear), "RPC_HealthChanged")] private static class WearNTearHealthChangedOptimizationPatch { private static void Postfix(WearNTear __instance) { BuildPieceProfilerPlugin instance = _instance; if (!((Object)(object)instance == (Object)null) && instance.ShouldHandleRendererOrStaticSleepEvents()) { Piece piece = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponentInParent() : null); instance?.NotifyRendererBatchVisualChange(piece, 0.35f); instance?.NotifyStaticSleepPieceChanged(piece); } } } private struct RendererBatchCellKey : IEquatable { public int X; public int Y; public int Z; public bool Equals(RendererBatchCellKey other) { return X == other.X && Y == other.Y && Z == other.Z; } public override bool Equals(object obj) { return obj is RendererBatchCellKey other && Equals(other); } public override int GetHashCode() { int num = 17; num = num * 31 + X; num = num * 31 + Y; return num * 31 + Z; } public override string ToString() { return $"{X},{Y},{Z}"; } } private enum RendererBatchPieceEligibility { Eligible, TemporarilyExcluded, NonStructural, Interactive, Animated, Effects, ExcludedByName } private enum RendererBatchRendererEligibility { Eligible, Inactive, StaticBatch, Lod, PropertyBlock, MeshLayout, Lightmap, Material } private struct RendererBatchGroupKey : IEquatable { public Material Material; public ShadowCastingMode ShadowCastingMode; public bool ReceiveShadows; public LightProbeUsage LightProbeUsage; public ReflectionProbeUsage ReflectionProbeUsage; public Transform ProbeAnchor; public int Layer; public MotionVectorGenerationMode MotionVectorGenerationMode; public bool AllowOcclusionWhenDynamic; public int SortingLayerId; public int SortingOrder; public bool Equals(RendererBatchGroupKey other) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_007a: Unknown result type (might be due to invalid IL or missing references) return (Object)(object)Material == (Object)(object)other.Material && ShadowCastingMode == other.ShadowCastingMode && ReceiveShadows == other.ReceiveShadows && LightProbeUsage == other.LightProbeUsage && ReflectionProbeUsage == other.ReflectionProbeUsage && (Object)(object)ProbeAnchor == (Object)(object)other.ProbeAnchor && Layer == other.Layer && MotionVectorGenerationMode == other.MotionVectorGenerationMode && AllowOcclusionWhenDynamic == other.AllowOcclusionWhenDynamic && SortingLayerId == other.SortingLayerId && SortingOrder == other.SortingOrder; } public override bool Equals(object obj) { return obj is RendererBatchGroupKey other && Equals(other); } public override int GetHashCode() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected I4, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected I4, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected I4, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected I4, but got Unknown int num = (((Object)(object)Material != (Object)null) ? ((Object)Material).GetInstanceID() : 0); num = (num * 397) ^ ShadowCastingMode; num = (num * 397) ^ ReceiveShadows.GetHashCode(); num = (num * 397) ^ LightProbeUsage; num = (num * 397) ^ ReflectionProbeUsage; num = (num * 397) ^ (((Object)(object)ProbeAnchor != (Object)null) ? ((Object)ProbeAnchor).GetInstanceID() : 0); num = (num * 397) ^ Layer; num = (num * 397) ^ MotionVectorGenerationMode; num = (num * 397) ^ AllowOcclusionWhenDynamic.GetHashCode(); num = (num * 397) ^ SortingLayerId; return (num * 397) ^ SortingOrder; } } private struct RendererShadowBatchGroupKey : IEquatable { public Shader Shader; public int Layer; public int CullMode; public bool Equals(RendererShadowBatchGroupKey other) { return (Object)(object)Shader == (Object)(object)other.Shader && Layer == other.Layer && CullMode == other.CullMode; } public override bool Equals(object obj) { return obj is RendererShadowBatchGroupKey other && Equals(other); } public override int GetHashCode() { int num = (((Object)(object)Shader != (Object)null) ? ((Object)Shader).GetInstanceID() : 0); num = (num * 397) ^ Layer; return (num * 397) ^ CullMode; } } private class RendererBatchSource { public MeshRenderer Renderer; public Mesh Mesh; public int VertexCount; public RendererBatchGroupKey GroupKey; public LODGroup LodGroup; public Piece Piece; } private class RendererShadowSourceGeometry { public Vector3[] Vertices; public int[] Triangles; } private class RendererBatchOutput { public GameObject GameObject; public MeshRenderer Renderer; public Mesh Mesh; public int VertexCount; public RendererBatchGroupKey GroupKey; public List Sources; } private class RendererShadowBatchOutput { public GameObject GameObject; public Mesh Mesh; public int VertexCount; } private class RendererBatchCell { public RendererBatchCellKey Key; public readonly HashSet Pieces = new HashSet(); public readonly Dictionary OriginalRendererEnabled = new Dictionary(); public readonly HashSet ForcedLodGroups = new HashSet(); public readonly List Outputs = new List(); public readonly List ShadowOutputs = new List(); public readonly Dictionary> SourceRenderersByPiece = new Dictionary>(); public GameObject Root; public bool Dirty; public bool Queued; public float RebuildAfter; public int EligibleRendererCount; public int UnreadableMeshesSkipped; public int PropertyBlocksSkipped; public int PiecesConsidered; public int PiecesEligible; public int PiecesExcludedNonStructural; public int PiecesExcludedInteractive; public int PiecesExcludedAnimated; public int PiecesExcludedEffects; public int PiecesExcludedByName; public int RenderersExcludedStaticBatch; public int RenderersExcludedLod; public int RenderersExcludedMeshLayout; public int RenderersExcludedLightmap; public int RenderersExcludedMaterial; public int GroupsBelowMinimum; public int ShadowCastingVisibleBatches; public int ShadowEligibleVisibleBatches; public int ShadowSourceRenderersConsolidated; public int ShadowDrawCallsAvoided; public int ShadowExcludedCastingModeBatches; public int ShadowExcludedMaterialBatches; public int ShadowGroupsBelowMinimum; public int ShadowRejectedVisibleBatchCount; public int ShadowRejectedDrawBenefit; public int ShadowRejectedVertexBenefit; public int ShadowRejectedBounds; public int ShadowOriginalVerticesReplaced; public int ShadowSimplifiedSources; } private struct RendererLodCacheEntry { public LODGroup LodGroup; public int LodIndex; } private struct RendererBatchMetrics { public int TrackedPieces; public int Cells; public int DirtyCells; public int DiscoveryRemaining; public int SourceRenderersDisabled; public int CombinedRenderers; public int CombinedVertices; public int EligibleSourceRenderers; public int UnreadableMeshesSkipped; public int PropertyBlocksSkipped; public int PiecesConsidered; public int PiecesEligible; public int PiecesExcludedNonStructural; public int PiecesExcludedInteractive; public int PiecesExcludedAnimated; public int PiecesExcludedEffects; public int PiecesExcludedByName; public int RenderersExcludedStaticBatch; public int RenderersExcludedLod; public int RenderersExcludedMeshLayout; public int RenderersExcludedLightmap; public int RenderersExcludedMaterial; public int GroupsBelowMinimum; public int ShadowCastingVisibleBatches; public int ShadowEligibleVisibleBatches; public int ShadowClusterRenderers; public int ShadowClusterVertices; public int ShadowSourceRenderersConsolidated; public int ShadowDrawCallsAvoided; public int ShadowExcludedCastingModeBatches; public int ShadowExcludedMaterialBatches; public int ShadowGroupsBelowMinimum; public int ShadowRejectedVisibleBatchCount; public int ShadowRejectedDrawBenefit; public int ShadowRejectedVertexBenefit; public int ShadowRejectedBounds; public int ShadowOriginalVerticesReplaced; public int ShadowSimplifiedSources; } private class StaticSleepPieceState { public Piece Piece; public List Components; public int Generation; public float SleepAfter; public bool IsSleeping; public bool SchedulePending; } private class StaticSleepComponentState { public MonoBehaviour Behaviour; public StaticSleepTypeMetrics TypeMetrics; public bool OriginalEnabled; public bool DisabledByUs; } private class StaticSleepTypeMetrics { public Type Type; public int Total; public int EnabledAtDiscovery; public int Whitelisted; public int Candidates; public int Sleeping; } private struct StaticSleepProfileRecord { public StaticSleepTypeMetrics TypeMetrics; public bool EnabledAtDiscovery; public bool Whitelisted; public bool Candidate; } private struct StaticSleepScheduleEntry { public StaticSleepPieceState State; public int Generation; public float DueTime; } private struct StaticSleepTypeSnapshot { public string Name; public int Total; public int EnabledAtDiscovery; public int Candidates; public int Sleeping; } private struct StaticSleepMetrics { public int ProfiledPieces; public int DiscoveryRemaining; public int UpdateLoopComponents; public int EnabledAtDiscovery; public int CandidatePieces; public int CandidateComponents; public int SleepingComponents; public int WakeEvents; } public const string PluginGuid = "valheim.buildpieceprofiler"; public const string PluginName = "Valheim Build Optimization"; public const string PluginVersion = "0.5.3"; private const string StaticFireLightProxyName = "BuildPieceProfiler_StaticFireLightProxy"; private const string ProxyLightPoolRootName = "BuildPieceProfiler_ProxyLightPool"; private const float DefaultBypassedSupportValue = 1000000f; private static BuildPieceProfilerPlugin _instance; private static readonly FieldInfo PieceAllPiecesField = AccessTools.Field(typeof(Piece), "s_allPieces"); private static bool _wearNTearOptimizationActive; private static float _cachedBypassedSupportValue = 1000000f; private Harmony _harmony; private readonly HashSet _loadedPieceRegistry = new HashSet(); private Piece[] _loadedPieceSnapshot; private bool _loadedPieceSnapshotDirty = true; private bool _loadedPieceRegistryInitialized; private readonly Rect _windowRectDefault = new Rect(20f, 40f, 520f, 980f); private Rect _windowRect; private Vector2 _scrollPosition = Vector2.zero; private readonly HashSet _expandedProfilerSections = new HashSet(); private GUIStyle _profilerSectionStyle; private GUIStyle _profilerStatStyle; private GUIStyle _profilerTooltipStyle; private static readonly string[] ProfilerSectionIds = new string[15] { "overview", "global-scene", "global-rendering", "global-physics", "global-effects", "piece-rendering", "piece-physics", "piece-effects", "fire", "renderer-batching", "shadow", "static-sleep", "collider", "network", "light-offenders" }; private bool _showOverlay; private float _nextPollTime; private bool _automaticProfilerLimitWarningLogged; private const int MaximumAutomaticProfilerPieces = 5000; private static readonly FieldRef WearNTearSupportRef = AccessTools.FieldRefAccess("m_support"); private static readonly FieldRef WearNTearRainWetRef = AccessTools.FieldRefAccess("m_rainWet"); private static readonly FieldRef WearNTearHaveRoofRef = AccessTools.FieldRefAccess("m_haveRoof"); private static readonly FieldRef WearNTearHaveAshRoofRef = AccessTools.FieldRefAccess("m_haveAshRoof"); private static readonly FieldRef WearNTearWetObjectRef = AccessTools.FieldRefAccess("m_wet"); private Counts _counts = default(Counts); private ConfigEntry _enableProfiler; private ConfigEntry _enableConsoleLogging; private ConfigEntry _showProfilerOnStart; private ConfigEntry _enableAutomaticProfilerPolling; private ConfigEntry _profilerPollInterval; private ConfigEntry _toggleProfilerKey; private ConfigEntry _enableFireOptimizations; private ConfigEntry _enableWearNTearOptimizations; private ConfigEntry _wearNTearBypassSupportValue; private ConfigEntry _knownFirePieceNameTokens; private readonly Dictionary _fireStates = new Dictionary(); private readonly List _fireCandidates = new List(); private readonly HashSet _fireCandidatePieces = new HashSet(); private readonly Stack _proxyLightPool = new Stack(); private readonly HashSet _availableProxyLightObjects = new HashSet(); private readonly HashSet _allProxyLightObjects = new HashSet(); private readonly Dictionary _clusterProxyLights = new Dictionary(); private readonly List _inactiveClusterProxyKeys = new List(); private readonly RaycastHit[] _fireOcclusionHits = (RaycastHit[])(object)new RaycastHit[64]; private readonly Plane[] _fireFrustumPlanes = (Plane[])(object)new Plane[6]; private static readonly RaycastHitDistanceComparer FireOcclusionHitComparer = new RaycastHitDistanceComparer(); private FireMetrics _fireMetrics = default(FireMetrics); private float _nextOptimizerUpdateTime; private float _nextFireCandidateRefreshTime; private int _nextFireOcclusionStartIndex; private int _fireOcclusionBudgetGeneration; private bool _hasRefreshedFireCandidates; private bool _fireCandidateRefreshRequested; private bool _fireOptimizationsWereActive; private GameObject _proxyLightPoolRoot; private string _cachedFireLightUpdateTokenConfig; private string[] _cachedFireLightUpdateTokens = new string[0]; private string _cachedKnownFireNameTokenConfig; private string[] _cachedKnownFireNameTokens = new string[0]; private ConfigEntry _optimizerUpdateInterval; private ConfigEntry _fireCandidateRefreshInterval; private ConfigEntry _enablePeriodicFireCandidateRefresh; private ConfigEntry _fireOptimizationMode; private ConfigEntry _useFireVisibilityCulling; private ConfigEntry _fireVisibilityGraceSeconds; private ConfigEntry _staticLightIntensityMultiplier; private ConfigEntry _staticLightRangeMultiplier; private ConfigEntry _fireLightUpdateComponentNameTokens; private ConfigEntry _useClusteredFireProxyLights; private ConfigEntry _clusteredFireProxyCellSize; private ConfigEntry _useFireOcclusionCulling; private ConfigEntry _ignoreKnownFirePiecesInFireOcclusion; private ConfigEntry _fireOcclusionRayRadius; private ConfigEntry _fireOcclusionCacheSeconds; private ConfigEntry _maxFireOcclusionChecksPerUpdate; private ConfigEntry _debugFireOcclusion; private ConfigEntry _fireRestoreGraceSeconds; private ConfigEntry _topLightOffenderCount; private Texture2D _opaqueBackground; private const string ColliderClusterRootName = "BuildPieceProfiler_ColliderClusters"; private const string ColliderClusterCellName = "ColliderClusterCell"; private static readonly HashSet ColliderClusterDeniedTypeNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "ArmorStand", "Aoe", "Container", "CookingStation", "CraftingStation", "Door", "EffectFade", "Fermenter", "Fireplace", "ItemStand", "ShieldGenerator", "Ship", "Sign", "Smelter", "SpinningWheel", "TeleportWorld", "Trap", "Turret", "Vagon", "Vine", "VortexParticles", "Windmill" }; private readonly Dictionary _colliderClusterCells = new Dictionary(); private readonly Dictionary _colliderClusterPieceCells = new Dictionary(); private readonly Queue _colliderClusterDirtyQueue = new Queue(); private ConfigEntry _enableColliderClusterSystem; private ConfigEntry _enableColliderClusterProfiling; private ConfigEntry _enableColliderClustering; private ConfigEntry _colliderClusterCellSize; private ConfigEntry _colliderClusterMinimumBoxes; private ConfigEntry _colliderClusterMaximumBoxesPerCollider; private ConfigEntry _colliderClusterRestoreDistance; private ConfigEntry _colliderClusterActivationDistance; private ConfigEntry _colliderClusterUpdateInterval; private ConfigEntry _colliderClusterCellsRebuiltPerUpdate; private ConfigEntry _colliderClusterDiscoveryPiecesPerUpdate; private ConfigEntry _colliderClusterExcludedNameTokens; private GameObject _colliderClusterRoot; private Piece[] _colliderClusterDiscoveryPieces; private int _colliderClusterDiscoveryIndex; private bool _colliderClusterDiscoveryComplete; private bool _colliderClusteringWasActive; private int _colliderClusterSettingsSignature; private string _cachedColliderClusterExcludedTokenConfig; private string[] _cachedColliderClusterExcludedTokens = new string[0]; private float _nextColliderClusterSettingsCheck; private float _nextColliderClusterDistanceUpdate; private float _nextColliderClusterMetricsRefresh; private ColliderClusterMetrics _colliderClusterMetrics; private static bool _networkProfilerHooksActive; private Harmony _networkProfilerHarmony; private bool _networkProfilerHooksInstalled; private readonly Dictionary _networkPieceStatesByZdo = new Dictionary(); private readonly Dictionary _networkPieceStatesByView = new Dictionary(); private readonly Dictionary _networkRpcMethodCounts = new Dictionary(StringComparer.Ordinal); private readonly List _networkPieceStateList = new List(); private readonly List _networkTopWriters = new List(); private readonly List _networkTopRpcMethods = new List(); private readonly List _networkWriterAggregation = new List(); private ConfigEntry _enableNetworkIdleSystem; private ConfigEntry _enableNetworkIdleProfiling; private ConfigEntry _networkDiscoveryPiecesPerUpdate; private ConfigEntry _networkRevisionSamplesPerUpdate; private ConfigEntry _networkMetricsInterval; private ConfigEntry _networkActiveWriterSeconds; private ConfigEntry _networkTopWriterCount; private Piece[] _networkDiscoveryPieces; private int _networkDiscoveryIndex; private bool _networkDiscoveryComplete; private bool _networkProfilerWorldActive; private int _networkRevisionSampleCursor; private float _nextNetworkMetricsRefresh; private int _networkMetricsAggregationCursor; private NetworkMetricsAggregate _networkMetricsAggregate; private long _networkWriteAttempts; private long _networkChangedWrites; private long _networkRpcCalls; private long _networkObservedRevisionChanges; private long _networkObservedOwnerChanges; private NetworkIdleMetrics _networkIdleMetrics; private const string RendererBatchRootName = "BuildPieceProfiler_RendererBatches"; private const string RendererBatchCellName = "RendererBatchCell"; private const string RendererBatchObjectName = "RendererBatch"; private const string RendererShadowBatchObjectName = "RendererShadowBatch"; private readonly Dictionary _rendererBatchCells = new Dictionary(); private readonly Dictionary _rendererBatchPieceCells = new Dictionary(); private readonly Queue _rendererBatchDirtyQueue = new Queue(); private readonly Dictionary _rendererBatchPieceExcludedUntil = new Dictionary(); private readonly Dictionary _rendererShadowGeometryCache = new Dictionary(); private readonly Dictionary _rendererBatchEligibilityCache = new Dictionary(); private readonly Dictionary _rendererBatchRendererCache = new Dictionary(); private readonly Dictionary _rendererLodCache = new Dictionary(); private readonly Dictionary _rendererBatchHighlightedUntil = new Dictionary(); private ConfigEntry _enableRendererBatching; private ConfigEntry _enableShadowOptimizationSystem; private ConfigEntry _rendererBatchCellSize; private ConfigEntry _rendererBatchMinimumRenderers; private ConfigEntry _rendererBatchMaximumVertices; private ConfigEntry _rendererBatchCellsPerUpdate; private ConfigEntry _rendererBatchDiscoveryPiecesPerUpdate; private ConfigEntry _rendererBatchRebuildDelay; private ConfigEntry _rendererBatchExcludedNameTokens; private ConfigEntry _enableShadowCasterOptimization; private ConfigEntry _shadowClusterMinimumVisibleBatches; private ConfigEntry _shadowClusterMinimumDrawsSaved; private ConfigEntry _shadowClusterMaximumVertices; private ConfigEntry _shadowClusterMaximumVerticesPerDrawSaved; private ConfigEntry _shadowClusterMaximumBoundsDiagonal; private ConfigEntry _enableSimplifiedStructuralShadowCasters; private ConfigEntry _simplifiedShadowPieceNameTokens; private GameObject _rendererBatchRoot; private Piece[] _rendererBatchDiscoveryPieces; private int _rendererBatchDiscoveryIndex; private bool _rendererBatchDiscoveryComplete; private bool _rendererBatchingWasActive; private int _rendererBatchSettingsSignature; private string _cachedRendererBatchExcludedTokenConfig; private string[] _cachedRendererBatchExcludedTokens = new string[0]; private string _cachedSimplifiedShadowTokenConfig; private string[] _cachedSimplifiedShadowTokens = new string[0]; private float _nextRendererBatchSettingsCheck; private float _nextRendererBatchMetricsRefresh; private RendererBatchMetrics _rendererBatchMetrics; private static readonly HashSet StaticSleepGameplayTypeDenylist = new HashSet(StringComparer.OrdinalIgnoreCase) { "ArmorStand", "Catapult", "ConditionalObject", "Container", "CookingStation", "CraftingStation", "Door", "Fermenter", "Fireplace", "ItemStand", "Piece", "RandomAnimation", "ShieldGenerator", "Ship", "SiegeMachine", "Sign", "Smelter", "SpinningWheel", "TeleportWorld", "Trap", "Turret", "Vagon", "Vine", "WearNTear", "Windmill", "ZNetView", "ZSyncTransform" }; private static readonly string[] StaticSleepUpdateMethodNames = new string[3] { "Update", "LateUpdate", "FixedUpdate" }; private readonly Dictionary _staticSleepPieceStates = new Dictionary(); private readonly HashSet _staticSleepProfiledPieces = new HashSet(); private readonly Dictionary> _staticSleepPieceProfiles = new Dictionary>(); private readonly Dictionary _staticSleepTypeMetrics = new Dictionary(); private readonly Dictionary _staticSleepUpdateLoopCache = new Dictionary(); private readonly List _staticSleepScheduleHeap = new List(); private readonly List _staticSleepTopTypeSnapshot = new List(); private ConfigEntry _enableStaticComponentSystem; private ConfigEntry _enableStaticComponentProfiling; private ConfigEntry _enableStaticComponentSleeping; private ConfigEntry _staticSleepComponentTypeNames; private ConfigEntry _staticSleepDiscoveryPiecesPerUpdate; private ConfigEntry _staticSleepWakeGraceSeconds; private ConfigEntry _staticSleepTopTypeCount; private Piece[] _staticSleepDiscoveryPieces; private int _staticSleepDiscoveryIndex; private bool _staticSleepDiscoveryComplete; private bool _staticSleepWorldActive; private int _staticSleepSettingsSignature; private string _cachedStaticSleepTypeConfig; private HashSet _cachedStaticSleepTypeNames = new HashSet(StringComparer.OrdinalIgnoreCase); private float _nextStaticSleepSettingsCheck; private float _nextStaticSleepMetricsRefresh; private int _staticSleepCandidatePieceCount; private StaticSleepMetrics _staticSleepMetrics; private bool IsFireCandidate(Light[] lights, ParticleSystem[] particles) { if (lights == null || particles == null || lights.Length == 0 || particles.Length == 0) { return false; } bool flag = false; bool flag2 = false; foreach (Light val in lights) { if ((Object)(object)val != (Object)null) { flag = true; break; } } foreach (ParticleSystem val2 in particles) { if ((Object)(object)val2 != (Object)null) { flag2 = true; break; } } return flag && flag2; } private bool HasActiveFireVisuals(Light[] lights, ParticleSystem[] particles) { if (lights != null) { foreach (Light val in lights) { if ((Object)(object)val != (Object)null && ((Behaviour)val).enabled && ((Component)val).gameObject.activeInHierarchy) { return true; } } } if (particles != null) { foreach (ParticleSystem val2 in particles) { if ((Object)(object)val2 != (Object)null && ((Component)val2).gameObject.activeInHierarchy && (val2.isPlaying || val2.IsAlive(true))) { return true; } } } return false; } private string NormalizeNameToken(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } return value.ToLowerInvariant().Replace("(clone)", string.Empty).Trim(); } private string[] GetNameTokens(string configValue) { if (string.IsNullOrEmpty(configValue)) { return new string[0]; } string[] array = configValue.Split(new char[3] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries); List list = new List(); string[] array2 = array; foreach (string value in array2) { string text = NormalizeNameToken(value); if (text.Length > 0) { list.Add(text); } } return list.ToArray(); } private string[] GetCachedFireLightUpdateNameTokens() { string text = ((_fireLightUpdateComponentNameTokens != null) ? _fireLightUpdateComponentNameTokens.Value : string.Empty); if (_cachedFireLightUpdateTokenConfig != text) { _cachedFireLightUpdateTokenConfig = text; _cachedFireLightUpdateTokens = GetNameTokens(text); } return _cachedFireLightUpdateTokens; } private string[] GetCachedKnownFireNameTokens() { string text = ((_knownFirePieceNameTokens != null) ? _knownFirePieceNameTokens.Value : string.Empty); if (_cachedKnownFireNameTokenConfig != text) { _cachedKnownFireNameTokenConfig = text; _cachedKnownFireNameTokens = GetNameTokens(text); } return _cachedKnownFireNameTokens; } private bool IsKnownFirePiece(Piece piece, string[] knownFireNameTokens) { if ((Object)(object)piece == (Object)null || knownFireNameTokens == null || knownFireNameTokens.Length == 0) { return false; } string text = NormalizeNameToken(((Object)(object)((Component)piece).gameObject != (Object)null) ? ((Object)((Component)piece).gameObject).name : ((Object)piece).name); string text2 = NormalizeNameToken(((Object)piece).name); foreach (string value in knownFireNameTokens) { if (text.Contains(value) || text2.Contains(value)) { return true; } } return false; } private bool IsPieceVisible(MeshRenderer[] renderers, Plane[] frustumPlanes) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (renderers == null) { return false; } foreach (MeshRenderer val in renderers) { if (!((Object)(object)val == (Object)null) && ((Renderer)val).enabled && ((Component)val).gameObject.activeInHierarchy && ((frustumPlanes != null) ? GeometryUtility.TestPlanesAABB(frustumPlanes, ((Renderer)val).bounds) : ((Renderer)val).isVisible)) { return true; } } return false; } private bool ShouldRefreshProfilerMetrics() { return _showOverlay || (_enableConsoleLogging != null && _enableConsoleLogging.Value); } private void EnforceOptimizedFireState() { //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) foreach (FireOptimizationState value in _fireStates.Values) { if (value == null || (Object)(object)value.Piece == (Object)null) { continue; } foreach (KeyValuePair item in value.OriginalLightEnabled) { Light key = item.Key; if (!((Object)(object)key == (Object)null) && !IsOurProxyLight(key)) { if (((Behaviour)key).enabled) { ((Behaviour)key).enabled = false; } if (key.cullingMask != 0) { key.cullingMask = 0; } } } if (value.AppliedMode == AppliedFireMode.FullCull) { DisableProxyLight(value); } foreach (KeyValuePair item2 in value.OriginalBehaviourEnabled) { if ((Object)(object)item2.Key != (Object)null && item2.Key.enabled) { item2.Key.enabled = false; } } foreach (ParticleSystem forcedStoppedParticle in value.ForcedStoppedParticles) { if (!((Object)(object)forcedStoppedParticle == (Object)null)) { EmissionModule emission = forcedStoppedParticle.emission; if (((EmissionModule)(ref emission)).enabled) { ((EmissionModule)(ref emission)).enabled = false; } if (forcedStoppedParticle.isPlaying) { forcedStoppedParticle.Stop(true, (ParticleSystemStopBehavior)0); } } } } } private void RestoreFire(Piece piece) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece == (Object)null || !_fireStates.TryGetValue(piece, out var value)) { return; } foreach (KeyValuePair originalShadowMode in value.OriginalShadowModes) { if ((Object)(object)originalShadowMode.Key != (Object)null) { ((Renderer)originalShadowMode.Key).shadowCastingMode = originalShadowMode.Value; } } foreach (KeyValuePair item in value.OriginalBehaviourEnabled) { if ((Object)(object)item.Key != (Object)null) { item.Key.enabled = item.Value; } } bool flag = (Object)(object)value.Source == (Object)null || value.Source.IsBurning(); foreach (KeyValuePair item2 in value.OriginalLightEnabled) { if ((Object)(object)item2.Key != (Object)null) { if (value.OriginalLightCullingMask.TryGetValue(item2.Key, out var value2)) { item2.Key.cullingMask = value2; } ((Behaviour)item2.Key).enabled = flag && item2.Value; } } foreach (KeyValuePair item3 in value.OriginalParticlePlaying) { ParticleSystem key = item3.Key; if (!((Object)(object)key == (Object)null)) { if (value.OriginalParticleEmissionEnabled.TryGetValue(key, out var value3)) { EmissionModule emission = key.emission; ((EmissionModule)(ref emission)).enabled = value3; } if (flag && item3.Value) { PlayParticle(key); } else if (key.isPlaying) { key.Stop(true, (ParticleSystemStopBehavior)0); } } } DisableProxyLight(value); _fireStates.Remove(piece); } private void DisableFireLightUpdateBehaviours(Piece piece, FireOptimizationState state) { if ((Object)(object)piece == (Object)null || state == null || _fireLightUpdateComponentNameTokens == null) { return; } string[] cachedFireLightUpdateNameTokens = GetCachedFireLightUpdateNameTokens(); if (cachedFireLightUpdateNameTokens.Length == 0) { return; } MonoBehaviour[] componentsInChildren = ((Component)piece).GetComponentsInChildren(true); MonoBehaviour[] array = componentsInChildren; foreach (MonoBehaviour val in array) { if (!((Object)(object)val == (Object)null) && !state.OriginalBehaviourEnabled.ContainsKey((Behaviour)(object)val) && IsFireLightUpdateBehaviour(val, cachedFireLightUpdateNameTokens)) { state.OriginalBehaviourEnabled[(Behaviour)(object)val] = ((Behaviour)val).enabled; if (((Behaviour)val).enabled) { ((Behaviour)val).enabled = false; } } } } private bool IsFireLightUpdateBehaviour(MonoBehaviour behaviour, string[] tokens) { if ((Object)(object)behaviour == (Object)null || tokens == null || tokens.Length == 0) { return false; } Type type = ((object)behaviour).GetType(); string text = NormalizeNameToken(type.Name); string text2 = NormalizeNameToken(type.FullName); foreach (string value in tokens) { if (text.Contains(value) || text2.Contains(value)) { return true; } } return false; } private int CountFireLightUpdateBehaviours(Piece piece) { if ((Object)(object)piece == (Object)null || _fireLightUpdateComponentNameTokens == null) { return 0; } string[] cachedFireLightUpdateNameTokens = GetCachedFireLightUpdateNameTokens(); if (cachedFireLightUpdateNameTokens.Length == 0) { return 0; } int num = 0; MonoBehaviour[] componentsInChildren = ((Component)piece).GetComponentsInChildren(true); MonoBehaviour[] array = componentsInChildren; foreach (MonoBehaviour val in array) { if ((Object)(object)val != (Object)null && ((Behaviour)val).enabled && IsFireLightUpdateBehaviour(val, cachedFireLightUpdateNameTokens)) { num++; } } return num; } private void RestoreAllFireOptimizations() { List> list = new List>(_fireStates); foreach (KeyValuePair item in list) { if ((Object)(object)item.Key != (Object)null) { RestoreFire(item.Key); continue; } if (item.Value != null) { ReleaseProxyLightObject(item.Value.ProxyLightObject); } _fireStates.Remove(item.Key); } ReleaseAllClusterProxyLights(); } private bool HasActiveFireOptimizations() { return _fireStates.Count > 0; } private void ResetFireCandidateCache() { _fireCandidates.Clear(); _fireCandidatePieces.Clear(); _nextFireCandidateRefreshTime = 0f; _nextFireOcclusionStartIndex = 0; _hasRefreshedFireCandidates = false; _fireCandidateRefreshRequested = false; _fireMetrics = default(FireMetrics); } private void CleanupDestroyedFireStates() { List> list = null; foreach (KeyValuePair fireState in _fireStates) { Piece key = fireState.Key; if (!((Object)(object)key != (Object)null)) { if (list == null) { list = new List>(); } list.Add(fireState); } } if (list == null) { return; } foreach (KeyValuePair item in list) { if (item.Value != null) { ReleaseProxyLightObject(item.Value.ProxyLightObject); } _fireStates.Remove(item.Key); } } private void RefreshFireCandidateCache() { _hasRefreshedFireCandidates = true; _fireCandidateRefreshRequested = false; Dictionary dictionary = new Dictionary(); foreach (FireCandidate fireCandidate2 in _fireCandidates) { if (fireCandidate2 != null && (Object)(object)fireCandidate2.Piece != (Object)null) { dictionary[fireCandidate2.Piece] = fireCandidate2; } } _fireCandidates.Clear(); _fireCandidatePieces.Clear(); Fireplace[] array = Object.FindObjectsByType((FindObjectsSortMode)0); string[] cachedKnownFireNameTokens = GetCachedKnownFireNameTokens(); float time = Time.time; Fireplace[] array2 = array; foreach (Fireplace val in array2) { if ((Object)(object)val == (Object)null) { continue; } Piece componentInParent = ((Component)val).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null || !((Component)componentInParent).gameObject.activeInHierarchy || _fireCandidatePieces.Contains(componentInParent) || !IsKnownFirePiece(componentInParent, cachedKnownFireNameTokens)) { continue; } Light[] originalLights = GetOriginalLights(((Component)componentInParent).GetComponentsInChildren(true)); if (originalLights.Length == 0) { continue; } ParticleSystem[] componentsInChildren = ((Component)componentInParent).GetComponentsInChildren(true); if (IsFireCandidate(originalLights, componentsInChildren)) { FireCandidate fireCandidate = new FireCandidate { Piece = componentInParent, Source = val, Renderers = ((Component)componentInParent).GetComponentsInChildren(true), OriginalLights = originalLights, Particles = componentsInChildren, LastRelevantTime = time, LastIrrelevantTime = time }; if (dictionary.TryGetValue(componentInParent, out var value)) { fireCandidate.HasOcclusionResult = value.HasOcclusionResult; fireCandidate.CachedOccluded = value.CachedOccluded; fireCandidate.LastOcclusionCheckTime = value.LastOcclusionCheckTime; fireCandidate.LastRelevantTime = value.LastRelevantTime; fireCandidate.LastIrrelevantTime = value.LastIrrelevantTime; } _fireCandidates.Add(fireCandidate); _fireCandidatePieces.Add(componentInParent); } } List list = null; foreach (KeyValuePair fireState in _fireStates) { Piece key = fireState.Key; FireOptimizationState value2 = fireState.Value; if (!((Object)(object)key == (Object)null) && value2 != null && !_fireCandidatePieces.Contains(key)) { if (list == null) { list = new List(); } list.Add(key); } } if (list != null) { foreach (Piece item in list) { RestoreFire(item); } } _nextFireCandidateRefreshTime = Time.time + Mathf.Max(1f, _fireCandidateRefreshInterval.Value); } private void OnDestroy() { ResetNetworkIdleProfiling(); RestoreAllColliderClusters(); ResetStaticComponentSleeping(clearProfile: true); RestoreAllRendererBatches(); RestoreAllFireOptimizations(); DestroyProxyLightPool(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _loadedPieceRegistry.Clear(); _loadedPieceSnapshot = null; _loadedPieceRegistryInitialized = false; if ((Object)(object)_opaqueBackground != (Object)null) { Object.Destroy((Object)(object)_opaqueBackground); _opaqueBackground = null; } if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } _wearNTearOptimizationActive = false; _cachedBypassedSupportValue = 1000000f; } private void EnsureLoadedPieceRegistry() { if (_loadedPieceRegistryInitialized) { return; } Piece[] array; try { List list = ((PieceAllPiecesField != null) ? (PieceAllPiecesField.GetValue(null) as List) : null); array = ((list != null) ? list.ToArray() : Object.FindObjectsByType((FindObjectsSortMode)0)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Piece registry snapshot unavailable: " + ex.Message)); array = Object.FindObjectsByType((FindObjectsSortMode)0); } Piece[] array2 = array; foreach (Piece val in array2) { if ((Object)(object)val != (Object)null) { _loadedPieceRegistry.Add(val); } } _loadedPieceRegistryInitialized = true; _loadedPieceSnapshotDirty = true; } private Piece[] GetSharedLoadedPiecesSnapshot() { EnsureLoadedPieceRegistry(); if (_loadedPieceSnapshotDirty || _loadedPieceSnapshot == null) { _loadedPieceRegistry.RemoveWhere((Piece piece) => (Object)(object)piece == (Object)null); _loadedPieceSnapshot = (Piece[])(object)new Piece[_loadedPieceRegistry.Count]; _loadedPieceRegistry.CopyTo(_loadedPieceSnapshot); _loadedPieceSnapshotDirty = false; } return _loadedPieceSnapshot; } private void RegisterLoadedPiece(Piece piece) { if ((Object)(object)piece != (Object)null && _loadedPieceRegistry.Add(piece)) { _loadedPieceSnapshotDirty = true; } } private void UnregisterLoadedPiece(Piece piece) { if (piece != null && _loadedPieceRegistry.Remove(piece)) { _loadedPieceSnapshotDirty = true; } } private static void NotifyPieceLoadedOrChanged(Piece piece, bool forceRefresh) { BuildPieceProfilerPlugin instance = _instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)piece == (Object)null)) { instance.RegisterLoadedPiece(piece); instance.NotifyRendererBatchPieceChanged(piece, forceRefresh); instance.NotifyColliderClusterPieceChanged(piece, forceRefresh); instance.NotifyStaticSleepPieceChanged(piece); instance.NotifyNetworkPieceChanged(piece); } } private static void NotifyPieceDestroyed(Piece piece) { BuildPieceProfilerPlugin instance = _instance; if (!((Object)(object)instance == (Object)null)) { instance.NotifyRendererBatchPieceDestroyed(piece); instance.UntrackColliderClusterPiece(piece); instance.UntrackStaticSleepPiece(piece); instance.UntrackNetworkPiece(piece); instance.UnregisterLoadedPiece(piece); } } private static bool IsWearNTearOptimizationEnabled() { return _wearNTearOptimizationActive; } private static float GetBypassedSupportValue() { return _cachedBypassedSupportValue; } private static void ApplyBypassedSupport(WearNTear wearNTear) { if (!((Object)(object)wearNTear == (Object)null)) { WearNTearSupportRef.Invoke(wearNTear) = GetBypassedSupportValue(); } } private static void ApplyDryRoofedState(WearNTear wearNTear) { if (!((Object)(object)wearNTear == (Object)null)) { WearNTearRainWetRef.Invoke(wearNTear) = false; WearNTearHaveRoofRef.Invoke(wearNTear) = true; WearNTearHaveAshRoofRef.Invoke(wearNTear) = true; GameObject val = WearNTearWetObjectRef.Invoke(wearNTear); if ((Object)(object)val != (Object)null && val.activeSelf) { val.SetActive(false); } } } private void ApplyStaticLight(Piece piece, Fireplace source, MeshRenderer[] renderers, Light[] lights, ParticleSystem[] particles) { FireOptimizationState fireOptimizationState = PrepareFireState(piece, source, AppliedFireMode.StaticLight, renderers, lights, particles); fireOptimizationState.ForcedStoppedParticles.Clear(); Light firstOriginallyEnabledLight = GetFirstOriginallyEnabledLight(fireOptimizationState, lights); if ((Object)(object)firstOriginallyEnabledLight == (Object)null) { DisableProxyLight(fireOptimizationState); } else if (_useClusteredFireProxyLights.Value) { DisableProxyLight(fireOptimizationState); EnsureClusterProxyLight(piece, firstOriginallyEnabledLight); } else { EnsureProxyLight(piece, fireOptimizationState, firstOriginallyEnabledLight); } DisableOriginalLights(lights); AddForcedStoppedParticles(fireOptimizationState, particles); DisableParticleEmission(particles); StopParticles(particles); DisableShadows(renderers); } private void AddForcedStoppedParticles(FireOptimizationState state, ParticleSystem[] particles) { if (state == null || particles == null) { return; } foreach (ParticleSystem val in particles) { if ((Object)(object)val != (Object)null) { state.ForcedStoppedParticles.Add(val); } } } private void ApplyFullCull(Piece piece, Fireplace source, MeshRenderer[] renderers, Light[] lights, ParticleSystem[] particles) { FireOptimizationState fireOptimizationState = PrepareFireState(piece, source, AppliedFireMode.FullCull, renderers, lights, particles); fireOptimizationState.ForcedStoppedParticles.Clear(); DisableProxyLight(fireOptimizationState); DisableOriginalLights(lights); AddForcedStoppedParticles(fireOptimizationState, particles); DisableParticleEmission(particles); StopParticles(particles); DisableShadows(renderers); } private FireOptimizationState PrepareFireState(Piece piece, Fireplace source, AppliedFireMode mode, MeshRenderer[] renderers, Light[] lights, ParticleSystem[] particles) { if (_fireStates.TryGetValue(piece, out var value) && value.AppliedMode != mode) { RestoreFire(piece); value = null; } if (value == null) { value = new FireOptimizationState { Piece = piece, Source = source, AppliedMode = mode }; _fireStates[piece] = value; } else { value.Source = source; } StoreOriginalStates(value, renderers, lights, particles); DisableFireLightUpdateBehaviours(piece, value); return value; } private void StoreOriginalStates(FireOptimizationState state, MeshRenderer[] renderers, Light[] lights, ParticleSystem[] particles) { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) if (state == null) { return; } lights = (Light[])(((object)lights) ?? ((object)new Light[0])); particles = (ParticleSystem[])(((object)particles) ?? ((object)new ParticleSystem[0])); renderers = (MeshRenderer[])(((object)renderers) ?? ((object)new MeshRenderer[0])); Light[] array = lights; foreach (Light val in array) { if (!((Object)(object)val == (Object)null) && !state.OriginalLightEnabled.ContainsKey(val)) { state.OriginalLightEnabled[val] = ((Behaviour)val).enabled; state.OriginalLightCullingMask[val] = val.cullingMask; } } ParticleSystem[] array2 = particles; foreach (ParticleSystem val2 in array2) { if (!((Object)(object)val2 == (Object)null) && !state.OriginalParticlePlaying.ContainsKey(val2)) { state.OriginalParticlePlaying[val2] = val2.isPlaying; EmissionModule emission = val2.emission; state.OriginalParticleEmissionEnabled[val2] = ((EmissionModule)(ref emission)).enabled; } } MeshRenderer[] array3 = renderers; foreach (MeshRenderer val3 in array3) { if (!((Object)(object)val3 == (Object)null) && !state.OriginalShadowModes.ContainsKey(val3)) { state.OriginalShadowModes[val3] = ((Renderer)val3).shadowCastingMode; } } } private void DisableOriginalLights(Light[] lights) { if (lights == null) { return; } foreach (Light val in lights) { if (!((Object)(object)val == (Object)null)) { ((Behaviour)val).enabled = false; val.cullingMask = 0; } } } private void DestroyProxyLightPool() { foreach (FireOptimizationState value in _fireStates.Values) { if (value != null && (Object)(object)value.ProxyLightObject != (Object)null) { value.ProxyLightObject = null; value.ProxyLight = null; } } foreach (ClusterProxyLightState value2 in _clusterProxyLights.Values) { if (value2 != null && (Object)(object)value2.ProxyLightObject != (Object)null) { value2.ProxyLightObject = null; value2.ProxyLight = null; } } _clusterProxyLights.Clear(); foreach (GameObject allProxyLightObject in _allProxyLightObjects) { if ((Object)(object)allProxyLightObject != (Object)null) { Object.Destroy((Object)(object)allProxyLightObject); } } _proxyLightPool.Clear(); _availableProxyLightObjects.Clear(); _allProxyLightObjects.Clear(); if ((Object)(object)_proxyLightPoolRoot != (Object)null) { Object.Destroy((Object)(object)_proxyLightPoolRoot); _proxyLightPoolRoot = null; } } private void PlayParticle(ParticleSystem particle) { if (!((Object)(object)particle == (Object)null) && ((Component)particle).gameObject.activeInHierarchy && !particle.isPlaying) { particle.Play(true); } } private void StopParticles(ParticleSystem[] particles) { if (particles == null) { return; } foreach (ParticleSystem val in particles) { if (!((Object)(object)val == (Object)null)) { val.Stop(true, (ParticleSystemStopBehavior)0); } } } private void DisableParticleEmission(ParticleSystem[] particles) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (particles == null) { return; } foreach (ParticleSystem val in particles) { if (!((Object)(object)val == (Object)null)) { EmissionModule emission = val.emission; ((EmissionModule)(ref emission)).enabled = false; } } } private void DisableShadows(MeshRenderer[] renderers) { if (renderers == null) { return; } foreach (MeshRenderer val in renderers) { if (!((Object)(object)val == (Object)null)) { ((Renderer)val).shadowCastingMode = (ShadowCastingMode)0; } } } private Light GetFirstOriginallyEnabledLight(FireOptimizationState state, Light[] lights) { if (state == null || lights == null) { return null; } bool value = default(bool); foreach (Light val in lights) { if ((Object)(object)val != (Object)null && state.OriginalLightEnabled.TryGetValue(val, out value) && value) { return val; } } return null; } private GameObject GetProxyLightPoolRoot() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)_proxyLightPoolRoot == (Object)null) { _proxyLightPoolRoot = new GameObject("BuildPieceProfiler_ProxyLightPool"); _proxyLightPoolRoot.SetActive(true); } return _proxyLightPoolRoot; } private GameObject AcquireProxyLight(out Light proxyLight) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown GameObject val = null; while (_proxyLightPool.Count > 0 && (Object)(object)val == (Object)null) { val = _proxyLightPool.Pop(); _availableProxyLightObjects.Remove(val); } if ((Object)(object)val == (Object)null) { val = new GameObject("BuildPieceProfiler_StaticFireLightProxy"); proxyLight = val.AddComponent(); _allProxyLightObjects.Add(val); } else { proxyLight = val.GetComponent(); if ((Object)(object)proxyLight == (Object)null) { proxyLight = val.AddComponent(); } } ((Object)val).name = "BuildPieceProfiler_StaticFireLightProxy"; val.transform.SetParent((Transform)null, false); val.SetActive(true); return val; } private void ReleaseProxyLightObject(GameObject proxyObject) { if (!((Object)(object)proxyObject == (Object)null)) { Light component = proxyObject.GetComponent(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = false; } proxyObject.SetActive(false); proxyObject.transform.SetParent(GetProxyLightPoolRoot().transform, false); if (!_availableProxyLightObjects.Contains(proxyObject)) { _availableProxyLightObjects.Add(proxyObject); _allProxyLightObjects.Add(proxyObject); _proxyLightPool.Push(proxyObject); } } } private void BeginClusterProxyLightUpdate() { //IL_0057: 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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (!_useClusteredFireProxyLights.Value) { ReleaseAllClusterProxyLights(); return; } foreach (ClusterProxyLightState value in _clusterProxyLights.Values) { if (value != null) { value.ActiveThisUpdate = false; value.MemberCount = 0; value.PositionSum = Vector3.zero; value.ColorSum = Color.black; value.IntensitySum = 0f; value.RangeMax = 0f; value.HasRotation = false; } } } private void FinalizeClusterProxyLightUpdate() { if (_clusterProxyLights.Count == 0) { return; } _inactiveClusterProxyKeys.Clear(); foreach (KeyValuePair clusterProxyLight in _clusterProxyLights) { ClusterProxyLightState value = clusterProxyLight.Value; if (value != null) { if (value.ActiveThisUpdate) { UpdateClusterProxyLight(value); } else { _inactiveClusterProxyKeys.Add(clusterProxyLight.Key); } } } if (_inactiveClusterProxyKeys.Count == 0) { return; } foreach (ClusterProxyKey inactiveClusterProxyKey in _inactiveClusterProxyKeys) { if (_clusterProxyLights.TryGetValue(inactiveClusterProxyKey, out var value2)) { ReleaseProxyLightObject(value2.ProxyLightObject); _clusterProxyLights.Remove(inactiveClusterProxyKey); } } } private void ReleaseAllClusterProxyLights() { if (_clusterProxyLights.Count == 0) { return; } foreach (ClusterProxyLightState value in _clusterProxyLights.Values) { if (value != null) { ReleaseProxyLightObject(value.ProxyLightObject); } } _clusterProxyLights.Clear(); } private void EnsureClusterProxyLight(Piece piece, Light sourceLight) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)piece == (Object)null) && !((Object)(object)sourceLight == (Object)null)) { float cellSize = Mathf.Max(1f, _clusteredFireProxyCellSize.Value); Vector3 position = ((Component)sourceLight).transform.position; ClusterProxyKey clusterProxyKey = GetClusterProxyKey(position, cellSize); if (!_clusterProxyLights.TryGetValue(clusterProxyKey, out var value)) { value = new ClusterProxyLightState(); value.ProxyLightObject = AcquireProxyLight(out var proxyLight); value.ProxyLight = proxyLight; _clusterProxyLights[clusterProxyKey] = value; } else if ((Object)(object)value.ProxyLightObject == (Object)null || (Object)(object)value.ProxyLight == (Object)null) { value.ProxyLightObject = AcquireProxyLight(out var proxyLight2); value.ProxyLight = proxyLight2; } value.ActiveThisUpdate = true; value.MemberCount++; ClusterProxyLightState clusterProxyLightState = value; clusterProxyLightState.PositionSum += position; ClusterProxyLightState clusterProxyLightState2 = value; clusterProxyLightState2.ColorSum += sourceLight.color; value.IntensitySum += sourceLight.intensity; value.RangeMax = Mathf.Max(value.RangeMax, sourceLight.range); if (!value.HasRotation) { value.Rotation = ((Component)sourceLight).transform.rotation; value.HasRotation = true; } _inactiveClusterProxyKeys.Clear(); } } private ClusterProxyKey GetClusterProxyKey(Vector3 position, float cellSize) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) return new ClusterProxyKey { X = Mathf.FloorToInt(position.x / cellSize), Y = Mathf.FloorToInt(position.y / cellSize), Z = Mathf.FloorToInt(position.z / cellSize) }; } private void UpdateClusterProxyLight(ClusterProxyLightState cluster) { //IL_0064: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (cluster != null && !((Object)(object)cluster.ProxyLightObject == (Object)null) && !((Object)(object)cluster.ProxyLight == (Object)null) && cluster.MemberCount > 0) { float num = Mathf.Max(1, cluster.MemberCount); float num2 = Mathf.Min(1.75f, Mathf.Sqrt(num)); cluster.ProxyLightObject.transform.position = cluster.PositionSum / num; cluster.ProxyLightObject.transform.rotation = cluster.Rotation; cluster.ProxyLightObject.SetActive(true); cluster.ProxyLight.type = (LightType)2; cluster.ProxyLight.color = cluster.ColorSum / num; cluster.ProxyLight.intensity = cluster.IntensitySum / num * Mathf.Max(0f, _staticLightIntensityMultiplier.Value) * num2; cluster.ProxyLight.range = cluster.RangeMax * Mathf.Max(0f, _staticLightRangeMultiplier.Value); cluster.ProxyLight.shadows = (LightShadows)0; ((Behaviour)cluster.ProxyLight).enabled = true; } } private void EnsureProxyLight(Piece piece, FireOptimizationState state, Light sourceLight) { //IL_006d: 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_00b5: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)piece == (Object)null) && !((Object)(object)sourceLight == (Object)null) && state != null) { if ((Object)(object)state.ProxyLightObject == (Object)null || (Object)(object)state.ProxyLight == (Object)null) { state.ProxyLightObject = AcquireProxyLight(out var proxyLight); state.ProxyLight = proxyLight; } state.ProxyLightObject.transform.position = ((Component)sourceLight).transform.position; state.ProxyLightObject.transform.rotation = ((Component)sourceLight).transform.rotation; state.ProxyLightObject.SetActive(true); state.ProxyLight.type = (LightType)2; state.ProxyLight.color = sourceLight.color; state.ProxyLight.intensity = sourceLight.intensity * Mathf.Max(0f, _staticLightIntensityMultiplier.Value); state.ProxyLight.range = sourceLight.range * Mathf.Max(0f, _staticLightRangeMultiplier.Value); state.ProxyLight.shadows = (LightShadows)0; ((Behaviour)state.ProxyLight).enabled = true; } } private void MaintainFireOptimization(FireOptimizationState state, Light[] originalLights) { if (state != null) { Light firstOriginallyEnabledLight = GetFirstOriginallyEnabledLight(state, originalLights); if (state.AppliedMode == AppliedFireMode.FullCull || (Object)(object)firstOriginallyEnabledLight == (Object)null) { DisableProxyLight(state); } else if (_useClusteredFireProxyLights.Value) { DisableProxyLight(state); EnsureClusterProxyLight(state.Piece, firstOriginallyEnabledLight); } else { EnsureProxyLight(state.Piece, state, firstOriginallyEnabledLight); } } } private void DisableProxyLight(FireOptimizationState state) { if (state != null) { ReleaseProxyLightObject(state.ProxyLightObject); state.ProxyLightObject = null; state.ProxyLight = null; } } private Vector3 GetFireTargetPosition(Piece piece, Light[] lights, MeshRenderer[] renderers) { //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_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) if (lights != null && lights.Length != 0) { Vector3 val = Vector3.zero; int num = 0; foreach (Light val2 in lights) { if (!((Object)(object)val2 == (Object)null)) { val += ((Component)val2).transform.position; num++; } } if (num > 0) { return val / (float)num; } } if (renderers != null && renderers.Length != 0) { Bounds bounds = default(Bounds); ((Bounds)(ref bounds))..ctor(((Component)piece).transform.position, Vector3.zero); bool flag = false; foreach (MeshRenderer val3 in renderers) { if (!((Object)(object)val3 == (Object)null)) { if (!flag) { bounds = ((Renderer)val3).bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(((Renderer)val3).bounds); } } } if (flag) { return ((Bounds)(ref bounds)).center; } } return ((Component)piece).transform.position; } private bool IsOurProxyLight(Light light) { if ((Object)(object)light == (Object)null) { return false; } if ((Object)(object)((Component)light).gameObject == (Object)null) { return false; } return ((Object)((Component)light).gameObject).name == "BuildPieceProfiler_StaticFireLightProxy"; } private Light[] GetOriginalLights(Light[] lights) { if (lights == null || lights.Length == 0) { return (Light[])(object)new Light[0]; } List list = new List(); foreach (Light val in lights) { if (!((Object)(object)val == (Object)null) && !IsOurProxyLight(val)) { list.Add(val); } } return list.ToArray(); } private bool HitBelongsToPiece(RaycastHit hit, Piece piece) { if ((Object)(object)piece == (Object)null || (Object)(object)((RaycastHit)(ref hit)).collider == (Object)null) { return false; } Transform val = ((Component)((RaycastHit)(ref hit)).collider).transform; while ((Object)(object)val != (Object)null) { if ((Object)(object)val == (Object)(object)((Component)piece).transform) { return true; } val = val.parent; } return false; } private bool HitIsNonStructuralEffect(RaycastHit hit) { if ((Object)(object)((RaycastHit)(ref hit)).collider == (Object)null) { return true; } if ((Object)(object)((Component)((RaycastHit)(ref hit)).collider).GetComponentInParent() != (Object)null) { return false; } if ((Object)(object)((RaycastHit)(ref hit)).collider.attachedRigidbody != (Object)null) { return true; } return (Object)(object)((Component)((RaycastHit)(ref hit)).collider).GetComponentInParent() != (Object)null; } private bool HitIsKnownFirePiece(RaycastHit hit, Piece targetPiece) { if (_ignoreKnownFirePiecesInFireOcclusion == null || !_ignoreKnownFirePiecesInFireOcclusion.Value || (Object)(object)((RaycastHit)(ref hit)).collider == (Object)null) { return false; } Piece componentInParent = ((Component)((RaycastHit)(ref hit)).collider).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null || (Object)(object)componentInParent == (Object)(object)targetPiece) { return false; } return _fireCandidatePieces.Contains(componentInParent) || IsKnownFirePiece(componentInParent, GetCachedKnownFireNameTokens()); } private bool FireOcclusionHitsSolidBlocker(RaycastHit[] hits, int hitCount, Piece piece) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (hitCount <= 0) { return false; } Array.Sort(hits, 0, hitCount, FireOcclusionHitComparer); for (int i = 0; i < hitCount; i++) { RaycastHit hit = hits[i]; if (!((Object)(object)((RaycastHit)(ref hit)).collider == (Object)null)) { if (HitBelongsToPiece(hit, piece)) { return false; } if (!HitIsNonStructuralEffect(hit) && !HitIsKnownFirePiece(hit, piece)) { return true; } } } return false; } private bool IsFireOccludedFromCamera(Piece piece, Light[] lights, MeshRenderer[] renderers, Camera mainCamera) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) if (!_useFireOcclusionCulling.Value) { return false; } if ((Object)(object)piece == (Object)null || (Object)(object)mainCamera == (Object)null) { return false; } Vector3 position = ((Component)mainCamera).transform.position; Vector3 fireTargetPosition = GetFireTargetPosition(piece, lights, renderers); Vector3 val = fireTargetPosition - position; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude <= 0.1f) { return false; } val /= magnitude; float num = Mathf.Max(0f, _fireOcclusionRayRadius.Value); bool flag = FireOcclusionHitsSolidBlocker(hitCount: (!(num > 0f)) ? Physics.RaycastNonAlloc(position, val, _fireOcclusionHits, magnitude, -5, (QueryTriggerInteraction)1) : Physics.SphereCastNonAlloc(position, num, val, _fireOcclusionHits, magnitude, -5, (QueryTriggerInteraction)1), hits: _fireOcclusionHits, piece: piece); if (_debugFireOcclusion.Value) { Debug.DrawLine(position, fireTargetPosition, flag ? Color.red : Color.green, (_optimizerUpdateInterval != null) ? Mathf.Max(0.1f, _optimizerUpdateInterval.Value) : 0.5f); } return flag; } private void MarkFireOcclusionCheckBudget() { _fireOcclusionBudgetGeneration++; if (_fireOcclusionBudgetGeneration == 0) { _fireOcclusionBudgetGeneration = 1; } if (!_useFireOcclusionCulling.Value || _fireCandidates.Count == 0) { return; } int num = Mathf.Min(_fireCandidates.Count, Mathf.Max(0, _maxFireOcclusionChecksPerUpdate.Value)); if (num == 0) { return; } if (_nextFireOcclusionStartIndex < 0 || _nextFireOcclusionStartIndex >= _fireCandidates.Count) { _nextFireOcclusionStartIndex = 0; } for (int i = 0; i < num; i++) { int index = (_nextFireOcclusionStartIndex + i) % _fireCandidates.Count; FireCandidate fireCandidate = _fireCandidates[index]; if (fireCandidate != null) { fireCandidate.OcclusionCheckGeneration = _fireOcclusionBudgetGeneration; } } _nextFireOcclusionStartIndex = (_nextFireOcclusionStartIndex + num) % _fireCandidates.Count; } private bool GetBudgetedFireOcclusion(FireCandidate candidate, Piece piece, Light[] lights, MeshRenderer[] renderers, Camera mainCamera) { if (!_useFireOcclusionCulling.Value) { return false; } if (candidate == null) { return false; } float num = Mathf.Max(0.1f, _fireOcclusionCacheSeconds.Value); if (candidate.HasOcclusionResult && Time.time - candidate.LastOcclusionCheckTime < num) { return candidate.CachedOccluded; } if (candidate.OcclusionCheckGeneration != _fireOcclusionBudgetGeneration) { return candidate.HasOcclusionResult && candidate.CachedOccluded; } candidate.CachedOccluded = IsFireOccludedFromCamera(piece, lights, renderers, mainCamera); candidate.HasOcclusionResult = true; candidate.LastOcclusionCheckTime = Time.time; return candidate.CachedOccluded; } private void AddFireOptimizationStateMetrics(ref FireMetrics metrics) { //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Invalid comparison between Unknown and I4 foreach (FireOptimizationState value in _fireStates.Values) { if (value == null) { continue; } metrics.OptimizedFirePieces++; if (value.AppliedMode == AppliedFireMode.StaticLight) { metrics.StaticLightFirePieces++; } if (value.AppliedMode == AppliedFireMode.FullCull) { metrics.FullCullFirePieces++; } if ((Object)(object)value.ProxyLight != (Object)null && ((Behaviour)value.ProxyLight).enabled && (Object)(object)value.ProxyLightObject != (Object)null && value.ProxyLightObject.activeInHierarchy) { metrics.FireProxyLightsActive++; } foreach (KeyValuePair item in value.OriginalLightEnabled) { if ((Object)(object)item.Key != (Object)null && !IsOurProxyLight(item.Key) && !((Behaviour)item.Key).enabled && item.Value) { metrics.FireOriginalLightsDisabled++; } } foreach (KeyValuePair item2 in value.OriginalParticlePlaying) { if ((Object)(object)item2.Key != (Object)null && item2.Value && !item2.Key.isPlaying) { metrics.FireParticlesStopped++; } } foreach (KeyValuePair originalShadowMode in value.OriginalShadowModes) { if ((Object)(object)originalShadowMode.Key != (Object)null && (int)((Renderer)originalShadowMode.Key).shadowCastingMode == 0 && (int)originalShadowMode.Value > 0) { metrics.FireShadowsDisabled++; } } } foreach (ClusterProxyLightState value2 in _clusterProxyLights.Values) { if (value2 != null && (Object)(object)value2.ProxyLight != (Object)null && ((Behaviour)value2.ProxyLight).enabled && (Object)(object)value2.ProxyLightObject != (Object)null && value2.ProxyLightObject.activeInHierarchy) { metrics.FireProxyLightsActive++; } } } private void UpdateFireOptimizations() { if ((Object)(object)Player.m_localPlayer == (Object)null) { if (HasActiveFireOptimizations()) { RestoreAllFireOptimizations(); } ResetFireCandidateCache(); return; } Camera main = Camera.main; Plane[] frustumPlanes = null; if ((Object)(object)main != (Object)null) { GeometryUtility.CalculateFrustumPlanes(main, _fireFrustumPlanes); frustumPlanes = _fireFrustumPlanes; } float time = Time.time; if (!_hasRefreshedFireCandidates || _fireCandidateRefreshRequested || (_enablePeriodicFireCandidateRefresh.Value && time >= _nextFireCandidateRefreshTime)) { RefreshFireCandidateCache(); } FireMetrics metrics = default(FireMetrics); MarkFireOcclusionCheckBudget(); BeginClusterProxyLightUpdate(); for (int num = _fireCandidates.Count - 1; num >= 0; num--) { FireCandidate fireCandidate = _fireCandidates[num]; if (fireCandidate == null || (Object)(object)fireCandidate.Piece == (Object)null) { if (fireCandidate != null) { _fireCandidatePieces.Remove(fireCandidate.Piece); } _fireCandidates.RemoveAt(num); continue; } Piece piece = fireCandidate.Piece; if ((Object)(object)piece == (Object)null) { _fireCandidates.RemoveAt(num); continue; } MeshRenderer[] renderers = fireCandidate.Renderers; Light[] originalLights = fireCandidate.OriginalLights; ParticleSystem[] particles = fireCandidate.Particles; if (!IsFireCandidate(originalLights, particles)) { RestoreFire(piece); _fireCandidatePieces.Remove(piece); _fireCandidates.RemoveAt(num); continue; } bool flag = IsPieceVisible(renderers, frustumPlanes); bool flag2 = flag && GetBudgetedFireOcclusion(fireCandidate, piece, originalLights, renderers, main); bool flag3 = _useFireVisibilityCulling.Value && !flag; bool flag4 = _useFireOcclusionCulling.Value && flag && flag2; bool flag5 = flag3 || flag4; bool flag6 = !flag5; metrics.FireCandidates++; if (flag) { metrics.RendererVisibleFireCandidates++; } if (flag2) { metrics.OccludedFireCandidates++; } if (flag6) { metrics.RelevantFireCandidates++; } else { metrics.HiddenOrIrrelevantFireCandidates++; } if (flag6) { fireCandidate.LastRelevantTime = time; } else { fireCandidate.LastIrrelevantTime = time; } bool flag7 = flag5 && time - fireCandidate.LastRelevantTime >= Mathf.Max(0f, _fireVisibilityGraceSeconds.Value); bool flag8 = flag6 && time - fireCandidate.LastIrrelevantTime >= Mathf.Max(0f, _fireRestoreGraceSeconds.Value); bool flag9 = _fireStates.ContainsKey(piece); AppliedFireMode appliedFireMode = ((_fireOptimizationMode.Value != FireOptimizationMode.StaticLight) ? AppliedFireMode.FullCull : AppliedFireMode.StaticLight); if (flag9 && (Object)(object)fireCandidate.Source != (Object)null && !fireCandidate.Source.IsBurning()) { RestoreFire(piece); continue; } if (flag9) { if (flag8) { RestoreFire(piece); continue; } FireOptimizationState fireOptimizationState = _fireStates[piece]; if (fireOptimizationState.AppliedMode == appliedFireMode) { MaintainFireOptimization(fireOptimizationState, originalLights); continue; } } else { bool flag10 = (((Object)(object)fireCandidate.Source != (Object)null) ? fireCandidate.Source.IsBurning() : HasActiveFireVisuals(originalLights, particles)); if (!flag7 || !flag10) { continue; } } if (_fireOptimizationMode.Value == FireOptimizationMode.StaticLight) { ApplyStaticLight(piece, fireCandidate.Source, renderers, originalLights, particles); } else { ApplyFullCull(piece, fireCandidate.Source, renderers, originalLights, particles); } } FinalizeClusterProxyLightUpdate(); EnforceOptimizedFireState(); AddFireOptimizationStateMetrics(ref metrics); _fireMetrics = metrics; CleanupDestroyedFireStates(); } private void Awake() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0455: Unknown result type (might be due to invalid IL or missing references) //IL_046a: Unknown result type (might be due to invalid IL or missing references) //IL_0474: Expected O, but got Unknown _instance = this; _windowRect = _windowRectDefault; _enableProfiler = ((BaseUnityPlugin)this).Config.Bind("Profiler", "EnableProfiler", false, "Allows the F7 profiler window and manual snapshots. Leave this off during normal play for zero profiler UI or snapshot overhead."); _enableConsoleLogging = ((BaseUnityPlugin)this).Config.Bind("Profiler", "EnableConsoleLogging", false, "Writes each completed profiler snapshot to the BepInEx log. Useful for comparisons, but repeated logging can create large files."); _showProfilerOnStart = ((BaseUnityPlugin)this).Config.Bind("Profiler", "ShowProfilerOnStart", false, "Opens the profiler window automatically after entering a world. The profiler must also be enabled."); _enableAutomaticProfilerPolling = ((BaseUnityPlugin)this).Config.Bind("Profiler", "EnableAutomaticPolling", false, "Refreshes profiler snapshots automatically while the window or logging is active. Leave this off in large worlds and use Poll now when needed."); _profilerPollInterval = ((BaseUnityPlugin)this).Config.Bind("Profiler", "PollIntervalSeconds", 5f, "Seconds between automatic snapshots. Short intervals feel more live but make expensive scene scans happen more often."); _toggleProfilerKey = ((BaseUnityPlugin)this).Config.Bind("Profiler", "ToggleProfilerKey", (KeyCode)288, "Keyboard key that opens or closes the in-game profiler window."); _topLightOffenderCount = ((BaseUnityPlugin)this).Config.Bind("Profiler", "TopLightOffenderCount", 8, "Maximum number of likely light-heavy build pieces shown after a snapshot. Set to zero to skip this ranking."); _enableFireOptimizations = ((BaseUnityPlugin)this).Config.Bind("Fire", "EnableFireOptimizations", false, "Optimizes known fire pieces when they are outside the camera view or safely hidden behind solid building geometry."); _fireOptimizationMode = ((BaseUnityPlugin)this).Config.Bind("Fire", "Mode", FireOptimizationMode.StaticLight, "StaticLight keeps hidden areas lit with cheap shared lights. FullCull removes hidden fire lights and visual effects completely."); _optimizerUpdateInterval = ((BaseUnityPlugin)this).Config.Bind("Fire", "UpdateIntervalSeconds", 0.5f, "Seconds between fire visibility checks. Lower values react faster; higher values reduce optimizer CPU work."); _fireVisibilityGraceSeconds = ((BaseUnityPlugin)this).Config.Bind("Fire", "HiddenGraceSeconds", 1.5f, "Seconds a fire must stay hidden before its expensive light and effects are optimized. This prevents rapid camera movement from causing visual popping."); _fireRestoreGraceSeconds = ((BaseUnityPlugin)this).Config.Bind("Fire", "RestoreGraceSeconds", 1f, "Seconds a fire must stay visible or unblocked before its original light and effects return. This prevents rapid on/off cycling."); _enableWearNTearOptimizations = ((BaseUnityPlugin)this).Config.Bind("WearNTear", "EnableWearNTearOptimizations", false, "Stops structural-support and weather-wear calculations. Pieces still keep health, damage, repair, destruction, and saved network state."); _wearNTearBypassSupportValue = ((BaseUnityPlugin)this).Config.Bind("WearNTear", "BypassedSupportValue", 1000000f, "Support strength reported while support calculations are bypassed. The large default makes every affected structure fully supported."); _knownFirePieceNameTokens = ((BaseUnityPlugin)this).Config.Bind("Fire Discovery", "KnownFirePieceNameTokens", "piece_firepit,fire_pit,firepit,piece_hearth,hearth,piece_bonfire,bonfire,brazier,piece_brazier,standing_brazier,groundtorch,walltorch,piece_groundtorch,piece_walltorch,sconce", "Comma-separated internal name fragments used to recognize safe fire pieces. Only matching Fireplace pieces can enter the optimizer."); _fireCandidateRefreshInterval = ((BaseUnityPlugin)this).Config.Bind("Fire Discovery", "RefreshIntervalSeconds", 15f, "Seconds between optional fallback scans for Fireplace components. This setting matters only when the periodic safety refresh is enabled."); _enablePeriodicFireCandidateRefresh = ((BaseUnityPlugin)this).Config.Bind("Fire Discovery", "EnablePeriodicSafetyRefresh", false, "Runs an occasional full Fireplace scan as a discovery fallback. Normal discovery is event-driven, so leave this off unless a modded fire is missed."); _fireLightUpdateComponentNameTokens = ((BaseUnityPlugin)this).Config.Bind("Fire Discovery", "LightUpdateComponentNameTokens", "lightflicker,lightlod", "Comma-separated script-name fragments for known fire light animation scripts. Matching scripts pause while their fire is hidden."); _useFireVisibilityCulling = ((BaseUnityPlugin)this).Config.Bind("Fire Visibility", "CullOutsideCamera", true, "Allows fires outside the camera's current field of view to use their hidden optimized state."); _useFireOcclusionCulling = ((BaseUnityPlugin)this).Config.Bind("Fire Visibility", "CullBehindGeometry", true, "Allows fires behind solid walls, floors, or roofs to use their hidden optimized state."); _ignoreKnownFirePiecesInFireOcclusion = ((BaseUnityPlugin)this).Config.Bind("Fire Visibility", "IgnoreOtherFirePieces", true, "Makes fire-piece colliders transparent to fire visibility checks so nearby braziers and torches do not incorrectly hide each other."); _fireOcclusionRayRadius = ((BaseUnityPlugin)this).Config.Bind("Fire Visibility", "OcclusionRayRadius", 0.05f, "Thickness of the visibility test from camera to fire. Zero uses a thin ray; a small radius is more stable around wall edges."); _fireOcclusionCacheSeconds = ((BaseUnityPlugin)this).Config.Bind("Fire Visibility", "OcclusionCacheSeconds", 1.5f, "Seconds an occlusion result is reused before another physics check is needed. Longer caching costs less CPU but reacts more slowly to changes."); _maxFireOcclusionChecksPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Fire Visibility", "MaxOcclusionChecksPerUpdate", 16, "Maximum fresh wall-blocking physics checks allowed during one fire update. Lower values spread the work across more updates."); _debugFireOcclusion = ((BaseUnityPlugin)this).Config.Bind("Fire Visibility", "DebugOcclusionRays", false, "Draws green and red camera-to-fire lines for troubleshooting visibility checks. Intended only for testing."); _staticLightIntensityMultiplier = ((BaseUnityPlugin)this).Config.Bind("Fire Proxy Lights", "IntensityMultiplier", 1f, "Brightness of cheap replacement lights compared with the original hidden fire lights. 1.0 keeps the measured original brightness."); _staticLightRangeMultiplier = ((BaseUnityPlugin)this).Config.Bind("Fire Proxy Lights", "RangeMultiplier", 1f, "Reach of cheap replacement lights compared with the original hidden fire lights. 1.0 keeps the measured original range."); _useClusteredFireProxyLights = ((BaseUnityPlugin)this).Config.Bind("Fire Proxy Lights", "UseClusteredLights", true, "Lets nearby hidden fires share one cheap replacement light, reducing active light count while keeping rooms illuminated."); _clusteredFireProxyCellSize = ((BaseUnityPlugin)this).Config.Bind("Fire Proxy Lights", "ClusterCellSize", 8f, "Size in meters of the invisible cubes used to group hidden fires into shared replacement lights. Larger values share lights more aggressively."); BindRendererBatchingConfig(); BindStaticComponentSleepingConfig(); BindColliderClusteringConfig(); BindNetworkIdleConfig(); RefreshRuntimeConfigFlags(); _showOverlay = _showProfilerOnStart.Value; _opaqueBackground = MakeTexture(2, 2, new Color(0.05f, 0.05f, 0.05f, 1f)); _harmony = new Harmony("valheim.buildpieceprofiler"); _harmony.PatchAll(typeof(BuildPieceProfilerPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Valheim Build Optimization 0.5.3 loaded."); } private void Update() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) RefreshRuntimeConfigFlags(); if (_enableProfiler.Value) { if (Input.GetKeyDown(_toggleProfilerKey.Value)) { _showOverlay = !_showOverlay; } bool flag = _enableAutomaticProfilerPolling.Value && (_showOverlay || _enableConsoleLogging.Value); if (flag) { int num = GetSharedLoadedPiecesSnapshot().Length; if (num > 5000) { flag = false; if (!_automaticProfilerLimitWarningLogged) { _automaticProfilerLimitWarningLogged = true; ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Automatic profiler polling was suppressed because {num} loaded pieces exceeds the safe limit of {5000}. Use Poll now for an explicit snapshot."); } } } float num2 = Mathf.Max(0.1f, _profilerPollInterval.Value); if (flag && Time.time >= _nextPollTime) { _nextPollTime = Time.time + num2; _counts = PollCounts(); if (_enableConsoleLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Pieces={_counts.Pieces}, " + $"WearNTear={_counts.WearNTear}, " + $"ZNetView={_counts.ZNetView}, " + $"MeshRenderer={_counts.MeshRenderer}, " + $"EnabledMeshRenderer={_counts.EnabledMeshRenderer}, " + $"VisibleMeshRenderer={_counts.VisibleMeshRenderer}, " + $"Collider={_counts.Collider}, " + $"EnabledCollider={_counts.EnabledCollider}, " + $"LODGroup={_counts.LODGroup}, " + $"Light={_counts.Light}, " + $"EnabledLight={_counts.EnabledLight}, " + $"ParticleSystem={_counts.ParticleSystem}, " + $"ActiveParticleSystem={_counts.ActiveParticleSystem}, " + $"AudioSource={_counts.AudioSource}, " + $"ActiveRigidbody={_counts.ActiveRigidbody}, " + $"PieceMeshRenderer={_counts.PieceMeshRenderer}, " + $"PieceEnabledMeshRenderer={_counts.PieceEnabledMeshRenderer}, " + $"PieceVisibleMeshRenderer={_counts.PieceVisibleMeshRenderer}, " + $"PieceCollider={_counts.PieceCollider}, " + $"PieceEnabledCollider={_counts.PieceEnabledCollider}")); } } } if (!_enableFireOptimizations.Value || (!_useFireVisibilityCulling.Value && !_useFireOcclusionCulling.Value)) { if (_fireOptimizationsWereActive || HasActiveFireOptimizations()) { RestoreAllFireOptimizations(); ResetFireCandidateCache(); } _fireOptimizationsWereActive = false; } else { if (!_fireOptimizationsWereActive) { _nextOptimizerUpdateTime = 0f; _nextFireCandidateRefreshTime = 0f; _fireOptimizationsWereActive = true; } float num3 = Mathf.Max(0.1f, _optimizerUpdateInterval.Value); if (Time.time >= _nextOptimizerUpdateTime) { _nextOptimizerUpdateTime = Time.time + num3; UpdateFireOptimizations(); } } UpdateRendererBatching(); UpdateStaticComponentSleeping(); UpdateColliderClustering(); UpdateNetworkIdleProfiling(); } private void RefreshRuntimeConfigFlags() { _wearNTearOptimizationActive = _enableWearNTearOptimizations != null && _enableWearNTearOptimizations.Value; _cachedBypassedSupportValue = ((_wearNTearBypassSupportValue != null) ? Mathf.Max(1f, _wearNTearBypassSupportValue.Value) : 1000000f); } private void OnGUI() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (_enableProfiler.Value && _showOverlay) { _windowRect = GUILayout.Window(872391, _windowRect, new WindowFunction(DrawWindow), "Valheim Build Optimization - Profiler", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(520f), GUILayout.Height(940f) }); } } private void DrawWindow(int windowId) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) EnsureProfilerStyles(); GUI.DrawTexture(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width, ((Rect)(ref _windowRect)).height), (Texture)(object)_opaqueBackground, (ScaleMode)0); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(new GUIContent("Poll now", "Collect a fresh snapshot. In very large worlds this can pause the game briefly."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f) })) { _counts = PollCounts(); } if (GUILayout.Button(new GUIContent("Expand all", "Open every profiler section."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f) })) { string[] profilerSectionIds = ProfilerSectionIds; foreach (string item in profilerSectionIds) { _expandedProfilerSections.Add(item); } } if (GUILayout.Button(new GUIContent("Collapse all", "Close every profiler section."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f) })) { _expandedProfilerSections.Clear(); } GUILayout.EndHorizontal(); _scrollPosition = GUILayout.BeginScrollView(_scrollPosition, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(500f), GUILayout.Height(Mathf.Max(200f, ((Rect)(ref _windowRect)).height - 68f)) }); DrawProfilerSection("overview", "Profiler Status", "Current profiler controls and the main optimization switches.", delegate { //IL_000d: Unknown result type (might be due to invalid IL or missing references) DrawProfilerStat("Overlay key", _toggleProfilerKey.Value, "Press this key in game to show or hide this window."); DrawProfilerStat("Poll interval", $"{_profilerPollInterval.Value:0.0} seconds", "How long automatic polling waits between snapshots."); DrawProfilerStat("Automatic polling", _enableAutomaticProfilerPolling.Value, "When enabled, snapshots update automatically while the profiler is visible or logging."); DrawProfilerStat("Fire optimization", _enableFireOptimizations.Value, "Whether hidden fire effects and lights are currently eligible for optimization."); DrawProfilerStat("Wear and tear optimization", IsWearNTearOptimizationEnabled(), "Whether structural support and weather wear calculations are currently bypassed."); }); DrawProfilerSection("global-scene", "Global Scene", "Counts important objects across the entire loaded game scene, not only player-built structures.", delegate { DrawProfilerStat("Pieces", _counts.Pieces, "All loaded objects using Valheim's Piece component. This includes build pieces and some placeable objects."); DrawProfilerStat("WearNTear components", _counts.WearNTear, "Objects that can normally track structural support, weather exposure, health, damage, and repairs."); DrawProfilerStat("Network views", _counts.ZNetView, "Objects connected to Valheim's network and save-state system."); }); DrawProfilerSection("global-rendering", "Global Rendering", "Rendering objects loaded anywhere in the current scene.", delegate { DrawProfilerStat("Mesh renderers", _counts.MeshRenderer, "Objects capable of drawing a normal 3D mesh."); DrawProfilerStat("Enabled mesh renderers", _counts.EnabledMeshRenderer, "Mesh renderers currently switched on and able to draw."); DrawProfilerStat("Visible mesh renderers", _counts.VisibleMeshRenderer, "Enabled mesh renderers that Unity currently considers visible to at least one camera."); DrawProfilerStat("LOD groups", _counts.LODGroup, "Objects that can swap between detailed and simplified models depending on viewing distance."); }); DrawProfilerSection("global-physics", "Global Physics", "Physics objects loaded across the full scene.", delegate { DrawProfilerStat("Colliders", _counts.Collider, "Shapes used for walking, hits, placement checks, and other physical contact."); DrawProfilerStat("Enabled colliders", _counts.EnabledCollider, "Collider shapes currently participating in physics queries."); DrawProfilerStat("Active rigidbodies", _counts.ActiveRigidbody, "Physics bodies that are awake and may currently require simulation work."); }); DrawProfilerSection("global-effects", "Global Effects", "Lights, particles, and sound sources loaded across the full scene.", delegate { DrawProfilerStat("Lights", _counts.Light, "All loaded Unity lights, including disabled lights."); DrawProfilerStat("Enabled lights", _counts.EnabledLight, "Lights currently switched on. Shadow-casting lights are usually much more expensive."); DrawProfilerStat("Particle systems", _counts.ParticleSystem, "All loaded particle emitters, such as flames, sparks, mist, and smoke-like effects."); DrawProfilerStat("Active particle systems", _counts.ActiveParticleSystem, "Particle systems that still contain or emit live particles."); DrawProfilerStat("Audio sources", _counts.AudioSource, "Objects capable of playing positional or ambient sounds."); }); DrawProfilerSection("piece-rendering", "Build-Piece Rendering", "Rendering statistics counted only under loaded Piece objects.", delegate { DrawProfilerStat("Mesh renderers", _counts.PieceMeshRenderer, "Mesh renderers attached to build pieces and other Piece objects."); DrawProfilerStat("Enabled mesh renderers", _counts.PieceEnabledMeshRenderer, "Build-piece mesh renderers currently switched on."); DrawProfilerStat("Visible mesh renderers", _counts.PieceVisibleMeshRenderer, "Build-piece mesh renderers Unity currently considers visible."); DrawProfilerStat("LOD groups", _counts.PieceLODGroup, "Build pieces that use distance-based model detail levels."); }); DrawProfilerSection("piece-physics", "Build-Piece Physics", "Physics statistics counted only under loaded Piece objects.", delegate { DrawProfilerStat("Colliders", _counts.PieceCollider, "Physics shapes attached to build pieces."); DrawProfilerStat("Enabled colliders", _counts.PieceEnabledCollider, "Build-piece physics shapes currently active."); DrawProfilerStat("Rigidbodies", _counts.PieceRigidbody, "Build-piece objects that have a physics body."); DrawProfilerStat("Active rigidbodies", _counts.PieceActiveRigidbody, "Build-piece physics bodies that are awake and being simulated."); }); DrawProfilerSection("piece-effects", "Build-Piece Effects", "Lights, particles, and sounds attached specifically to build pieces.", delegate { DrawProfilerStat("Lights", _counts.PieceLight, "Lights attached to build pieces such as torches, braziers, and hearths."); DrawProfilerStat("Enabled lights", _counts.PieceEnabledLight, "Build-piece lights currently switched on."); DrawProfilerStat("Particle systems", _counts.PieceParticleSystem, "Particle emitters attached to build pieces."); DrawProfilerStat("Active particle systems", _counts.PieceActiveParticleSystem, "Build-piece particle systems that still contain or emit particles."); DrawProfilerStat("Audio sources", _counts.PieceAudioSource, "Sound emitters attached to build pieces."); }); DrawProfilerSection("fire", "Fire Optimization", "What the fire optimizer currently sees and what it has disabled or replaced.", delegate { DrawProfilerStat("Candidates", _counts.FireCandidates, "Known loaded fire pieces that are safe for the optimizer to inspect."); DrawProfilerStat("Visible candidates", _counts.RendererVisibleFireCandidates, "Fire candidates whose mesh is currently inside the camera view."); DrawProfilerStat("Occluded candidates", _counts.OccludedFireCandidates, "Fire candidates hidden behind solid structure geometry."); DrawProfilerStat("Relevant candidates", _counts.RelevantFireCandidates, "Fire candidates that should currently look and behave normally."); DrawProfilerStat("Hidden candidates", _counts.HiddenOrIrrelevantFireCandidates, "Fire candidates currently outside view or blocked by geometry."); DrawProfilerStat("Mode", _fireOptimizationMode.Value, "StaticLight keeps cheap shared lighting. FullCull removes hidden fire lighting as well."); DrawProfilerStat("Outside-camera culling", _useFireVisibilityCulling.Value, "Whether fires outside the camera view can be optimized."); DrawProfilerStat("Occlusion culling", _useFireOcclusionCulling.Value, "Whether fires blocked by walls and roofs can be optimized."); DrawProfilerStat("Ignore fire blockers", _ignoreKnownFirePiecesInFireOcclusion.Value, "Prevents one fire piece from incorrectly hiding another fire piece."); DrawProfilerStat("Safety refresh", _enablePeriodicFireCandidateRefresh.Value, "Whether the optional periodic fallback scan for fire pieces is enabled."); DrawProfilerStat("Clustered proxy lights", _useClusteredFireProxyLights.Value, "Whether nearby hidden fires can share one cheap replacement light."); DrawProfilerStat("Active light clusters", _clusterProxyLights.Count, "The number of shared replacement lights currently in use."); DrawProfilerStat("Light cluster size", $"{_clusteredFireProxyCellSize.Value:0.0} m", "The world-space cube size used to decide which hidden fires can share a light."); DrawProfilerStat("Optimized fires", _counts.OptimizedFirePieces, "Fire pieces currently using an optimized hidden state."); DrawProfilerStat("Static-light fires", _counts.StaticLightFirePieces, "Optimized fires represented by cheap shadowless lighting."); DrawProfilerStat("Fully culled fires", _counts.FullCullFirePieces, "Optimized fires with their hidden lighting and effects completely disabled."); DrawProfilerStat("Active proxy lights", _counts.FireProxyLightsActive, "Cheap replacement lights currently standing in for hidden original fire lights."); DrawProfilerStat("Original lights disabled", _counts.FireOriginalLightsDisabled, "Original fire lights currently switched off by the optimizer."); DrawProfilerStat("Particle systems stopped", _counts.FireParticlesStopped, "Fire particle systems currently prevented from emitting or animating."); DrawProfilerStat("Shadows disabled", _counts.FireShadowsDisabled, "Fire mesh shadow casters currently switched off while hidden."); }); DrawRendererBatchingProfiler(); DrawStaticComponentSleepingProfiler(); DrawColliderClusteringProfiler(); DrawNetworkIdleProfiler(); DrawProfilerSection("light-offenders", "Top Light Offenders", "Build pieces ranked by a rough combination of lights, shadows, particles, update scripts, range, and intensity.", delegate { if (_counts.TopLightOffenders == null || _counts.TopLightOffenders.Count == 0) { DrawProfilerStat("Results", "No snapshot data", "Press Poll now to calculate the current light offenders."); return; } foreach (LightOffenderSnapshot topLightOffender in _counts.TopLightOffenders) { DrawProfilerStat(topLightOffender.Name, $"lights {topLightOffender.EnabledLights}, shadows {topLightOffender.ShadowLights}, particles {topLightOffender.ActiveParticles}, scripts {topLightOffender.LightUpdateBehaviours}, range {topLightOffender.MaxRange:0.0}, intensity {topLightOffender.TotalIntensity:0.0}, distance {topLightOffender.Distance:0.0} m", "A higher combination of active lights, real-time shadows, particles, update scripts, range, and brightness makes this piece a more likely performance offender."); } }); GUILayout.EndScrollView(); DrawProfilerTooltip(); GUI.DragWindow(); } private void EnsureProfilerStyles() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_003a: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0067: Expected O, but got Unknown //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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_0096: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if (_profilerSectionStyle == null) { _profilerSectionStyle = new GUIStyle(GUI.skin.button) { padding = new RectOffset(10, 8, 5, 5) }; _profilerStatStyle = new GUIStyle(GUI.skin.label) { wordWrap = true, padding = new RectOffset(8, 8, 2, 2) }; _profilerTooltipStyle = new GUIStyle(GUI.skin.box) { wordWrap = true, padding = new RectOffset(10, 10, 7, 7) }; _profilerTooltipStyle.normal.textColor = Color.white; } } private void DrawProfilerSection(string id, string title, string tooltip, Action drawContent) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown bool flag = _expandedProfilerSections.Contains(id); string text = (flag ? "[-] " : "[+] "); if (GUILayout.Button(new GUIContent(text + title, tooltip), _profilerSectionStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { if (flag) { _expandedProfilerSections.Remove(id); } else { _expandedProfilerSections.Add(id); } flag = !flag; } if (flag) { GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); drawContent?.Invoke(); GUILayout.EndVertical(); GUILayout.Space(3f); } } private void DrawProfilerStat(string label, object value, string tooltip) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown GUILayout.Label(new GUIContent($"{label}: {value}", tooltip), _profilerStatStyle, Array.Empty()); } private void DrawProfilerTooltip() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) string tooltip = GUI.tooltip; if (!string.IsNullOrEmpty(tooltip)) { float num = _profilerTooltipStyle.CalcHeight(new GUIContent(tooltip), 410f); Vector2 mousePosition = Event.current.mousePosition; float num2 = Mathf.Clamp(mousePosition.x + 18f, 8f, Mathf.Max(8f, ((Rect)(ref _windowRect)).width - 410f - 8f)); float num3 = mousePosition.y + 20f; if (num3 + num > ((Rect)(ref _windowRect)).height - 8f) { num3 = Mathf.Max(8f, mousePosition.y - num - 12f); } GUI.Box(new Rect(num2, num3, 410f, num), tooltip, _profilerTooltipStyle); } } private Texture2D MakeTexture(int width, int height, Color color) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = color; } Texture2D val = new Texture2D(width, height); val.SetPixels(array); val.Apply(); return val; } private Counts PollCounts() { //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_0691: Unknown result type (might be due to invalid IL or missing references) //IL_0697: Invalid comparison between Unknown and I4 Piece[] sharedLoadedPiecesSnapshot = GetSharedLoadedPiecesSnapshot(); WearNTear[] array = Object.FindObjectsByType((FindObjectsSortMode)0); ZNetView[] array2 = Object.FindObjectsByType((FindObjectsSortMode)0); MeshRenderer[] array3 = Object.FindObjectsByType((FindObjectsSortMode)0); Collider[] array4 = Object.FindObjectsByType((FindObjectsSortMode)0); LODGroup[] array5 = Object.FindObjectsByType((FindObjectsSortMode)0); Light[] array6 = Object.FindObjectsByType((FindObjectsSortMode)0); ParticleSystem[] array7 = Object.FindObjectsByType((FindObjectsSortMode)0); AudioSource[] array8 = Object.FindObjectsByType((FindObjectsSortMode)0); Rigidbody[] array9 = Object.FindObjectsByType((FindObjectsSortMode)0); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; MeshRenderer[] array10 = array3; foreach (MeshRenderer val in array10) { if (!((Object)(object)val == (Object)null)) { if (((Renderer)val).enabled && ((Component)val).gameObject.activeInHierarchy) { num++; } if (((Renderer)val).enabled && ((Component)val).gameObject.activeInHierarchy && ((Renderer)val).isVisible) { num2++; } } } Collider[] array11 = array4; foreach (Collider val2 in array11) { if ((Object)(object)val2 != (Object)null && val2.enabled && ((Component)val2).gameObject.activeInHierarchy) { num3++; } } Light[] array12 = array6; foreach (Light val3 in array12) { if ((Object)(object)val3 != (Object)null && ((Behaviour)val3).enabled && ((Component)val3).gameObject.activeInHierarchy) { num4++; } } ParticleSystem[] array13 = array7; foreach (ParticleSystem val4 in array13) { if ((Object)(object)val4 != (Object)null && ((Component)val4).gameObject.activeInHierarchy && val4.IsAlive(true)) { num5++; } } Rigidbody[] array14 = array9; foreach (Rigidbody val5 in array14) { if ((Object)(object)val5 != (Object)null && ((Component)val5).gameObject.activeInHierarchy && !val5.IsSleeping()) { num6++; } } int num7 = 0; int num8 = 0; int num9 = 0; int num10 = 0; int num11 = 0; int num12 = 0; int num13 = 0; int num14 = 0; int num15 = 0; int num16 = 0; int num17 = 0; int num18 = 0; int num19 = 0; Vector3 val6 = Vector3.zero; bool flag = (Object)(object)Player.m_localPlayer != (Object)null; if (flag) { val6 = ((Component)Player.m_localPlayer).transform.position; } int num20 = 0; int num21 = 0; int num22 = 0; int num23 = 0; int num24 = 0; bool flag2 = _enableFireOptimizations.Value && (_useFireVisibilityCulling.Value || _useFireOcclusionCulling.Value); Camera val7 = (flag2 ? null : Camera.main); Plane[] frustumPlanes = (((Object)(object)val7 != (Object)null) ? GeometryUtility.CalculateFrustumPlanes(val7) : null); string[] cachedKnownFireNameTokens = GetCachedKnownFireNameTokens(); HashSet hashSet = null; if (!flag2) { hashSet = new HashSet(); Fireplace[] array15 = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Fireplace val8 in array15) { if (!((Object)(object)val8 == (Object)null)) { Piece componentInParent = ((Component)val8).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && IsKnownFirePiece(componentInParent, cachedKnownFireNameTokens)) { hashSet.Add(componentInParent); } } } } int num25 = Mathf.Max(0, _topLightOffenderCount.Value); List list = ((num25 > 0) ? new List() : null); Piece[] array16 = sharedLoadedPiecesSnapshot; foreach (Piece val9 in array16) { if ((Object)(object)val9 == (Object)null) { continue; } float distance = 0f; if (flag) { distance = Vector3.Distance(val6, ((Component)val9).transform.position); } MeshRenderer[] componentsInChildren = ((Component)val9).GetComponentsInChildren(true); Collider[] componentsInChildren2 = ((Component)val9).GetComponentsInChildren(true); LODGroup[] componentsInChildren3 = ((Component)val9).GetComponentsInChildren(true); Light[] componentsInChildren4 = ((Component)val9).GetComponentsInChildren(true); ParticleSystem[] componentsInChildren5 = ((Component)val9).GetComponentsInChildren(true); if (!flag2 && hashSet.Contains(val9)) { Light[] originalLights = GetOriginalLights(componentsInChildren4); if (IsFireCandidate(originalLights, componentsInChildren5)) { num20++; bool flag3 = IsPieceVisible(componentsInChildren, frustumPlanes); bool flag4 = flag3 && _useFireOcclusionCulling.Value && IsFireOccludedFromCamera(val9, originalLights, componentsInChildren, val7); bool flag5 = _useFireVisibilityCulling.Value && !flag3; bool flag6 = _useFireOcclusionCulling.Value && flag3 && flag4; bool flag7 = !(flag5 || flag6); if (flag3) { num21++; } if (flag4) { num22++; } if (flag7) { num23++; } else { num24++; } } } AudioSource[] componentsInChildren6 = ((Component)val9).GetComponentsInChildren(true); Rigidbody[] componentsInChildren7 = ((Component)val9).GetComponentsInChildren(true); num7 += componentsInChildren.Length; num10 += componentsInChildren2.Length; num12 += componentsInChildren3.Length; num13 += componentsInChildren4.Length; num15 += componentsInChildren5.Length; num17 += componentsInChildren6.Length; num18 += componentsInChildren7.Length; int num27 = 0; int num28 = 0; int num29 = 0; float num30 = 0f; float num31 = 0f; MeshRenderer[] array17 = componentsInChildren; foreach (MeshRenderer val10 in array17) { if (!((Object)(object)val10 == (Object)null)) { if (((Renderer)val10).enabled && ((Component)val10).gameObject.activeInHierarchy) { num8++; } if (((Renderer)val10).enabled && ((Component)val10).gameObject.activeInHierarchy && ((Renderer)val10).isVisible) { num9++; } } } Collider[] array18 = componentsInChildren2; foreach (Collider val11 in array18) { if ((Object)(object)val11 != (Object)null && val11.enabled && ((Component)val11).gameObject.activeInHierarchy) { num11++; } } Light[] array19 = componentsInChildren4; foreach (Light val12 in array19) { if ((Object)(object)val12 != (Object)null && ((Behaviour)val12).enabled && ((Component)val12).gameObject.activeInHierarchy) { num14++; num27++; num30 = Mathf.Max(num30, val12.range); num31 += val12.intensity; if ((int)val12.shadows > 0) { num28++; } } } ParticleSystem[] array20 = componentsInChildren5; foreach (ParticleSystem val13 in array20) { if ((Object)(object)val13 != (Object)null && ((Component)val13).gameObject.activeInHierarchy && val13.IsAlive(true)) { num16++; num29++; } } if (list != null && num27 > 0) { int num36 = CountFireLightUpdateBehaviours(val9); float score = (float)num28 * 100f + (float)num27 * 30f + (float)num29 * 12f + (float)num36 * 8f + num31 + num30; AddTopLightOffender(list, num25, new LightOffenderSnapshot { Name = (((Object)(object)((Component)val9).gameObject != (Object)null) ? ((Object)((Component)val9).gameObject).name : ((Object)val9).name), Distance = distance, EnabledLights = num27, ShadowLights = num28, ActiveParticles = num29, LightUpdateBehaviours = num36, MaxRange = num30, TotalIntensity = num31, Score = score }); } Rigidbody[] array21 = componentsInChildren7; foreach (Rigidbody val14 in array21) { if ((Object)(object)val14 != (Object)null && ((Component)val14).gameObject.activeInHierarchy && !val14.IsSleeping()) { num19++; } } } FireMetrics metrics = (flag2 ? _fireMetrics : new FireMetrics { FireCandidates = num20, RendererVisibleFireCandidates = num21, OccludedFireCandidates = num22, RelevantFireCandidates = num23, HiddenOrIrrelevantFireCandidates = num24 }); if (!flag2) { AddFireOptimizationStateMetrics(ref metrics); } return new Counts { Pieces = sharedLoadedPiecesSnapshot.Length, WearNTear = array.Length, ZNetView = array2.Length, MeshRenderer = array3.Length, EnabledMeshRenderer = num, VisibleMeshRenderer = num2, Collider = array4.Length, EnabledCollider = num3, LODGroup = array5.Length, Light = array6.Length, EnabledLight = num4, ParticleSystem = array7.Length, ActiveParticleSystem = num5, AudioSource = array8.Length, ActiveRigidbody = num6, PieceMeshRenderer = num7, PieceEnabledMeshRenderer = num8, PieceVisibleMeshRenderer = num9, PieceCollider = num10, PieceEnabledCollider = num11, PieceLODGroup = num12, PieceLight = num13, PieceEnabledLight = num14, PieceParticleSystem = num15, PieceActiveParticleSystem = num16, PieceAudioSource = num17, PieceRigidbody = num18, PieceActiveRigidbody = num19, FireCandidates = metrics.FireCandidates, RendererVisibleFireCandidates = metrics.RendererVisibleFireCandidates, OccludedFireCandidates = metrics.OccludedFireCandidates, RelevantFireCandidates = metrics.RelevantFireCandidates, HiddenOrIrrelevantFireCandidates = metrics.HiddenOrIrrelevantFireCandidates, OptimizedFirePieces = metrics.OptimizedFirePieces, StaticLightFirePieces = metrics.StaticLightFirePieces, FullCullFirePieces = metrics.FullCullFirePieces, FireProxyLightsActive = metrics.FireProxyLightsActive, FireOriginalLightsDisabled = metrics.FireOriginalLightsDisabled, FireParticlesStopped = metrics.FireParticlesStopped, FireShadowsDisabled = metrics.FireShadowsDisabled, TopLightOffenders = list }; } private void AddTopLightOffender(List snapshots, int maxCount, LightOffenderSnapshot snapshot) { if (snapshots == null || maxCount <= 0) { return; } int num = snapshots.Count; for (int i = 0; i < snapshots.Count; i++) { if (snapshot.Score > snapshots[i].Score) { num = i; break; } } if (num < maxCount) { snapshots.Insert(num, snapshot); if (snapshots.Count > maxCount) { snapshots.RemoveAt(snapshots.Count - 1); } } } private void BindColliderClusteringConfig() { _enableColliderClusterSystem = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "EnableSystem", false, "Master switch for this experimental system. When off, it performs no collider scanning, profiling, rebuilding, distance checks, or event work."); _enableColliderClusterProfiling = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "EnableColliderProfiling", false, "Counts simple static building boxes and estimates how many colliders could potentially be removed."); _enableColliderClustering = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "EnableColliderClustering", false, "Experimental. Replaces distant rows of matching building boxes with fewer larger boxes. Requires wear-and-tear optimization."); _colliderClusterCellSize = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "CellSize", 10f, "Size in meters of each local collider region. Smaller regions limit how far one cluster can spread."); _colliderClusterMinimumBoxes = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "MinimumBoxesPerCluster", 12, "Minimum number of matching box colliders required before merging is considered worthwhile."); _colliderClusterMaximumBoxesPerCollider = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "MaximumBoxesPerMergedCollider", 128, "Maximum original boxes represented by one generated merged box."); _colliderClusterRestoreDistance = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "RestoreOriginalsWithinDistance", 30f, "Distance in meters where original piece colliders are always used for accurate building, selection, and combat."); _colliderClusterActivationDistance = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "ActivateClustersBeyondDistance", 40f, "Distance in meters where merged colliders may replace originals. Keep this above the restore distance to prevent rapid switching."); _colliderClusterUpdateInterval = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "DistanceUpdateIntervalSeconds", 0.25f, "Seconds between player-distance checks for collider regions. Longer intervals cost less CPU but switch less quickly."); _colliderClusterCellsRebuiltPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "CellsRebuiltPerUpdate", 1, "Maximum changed collider regions rebuilt in one frame. Lower values reduce spikes during construction."); _colliderClusterDiscoveryPiecesPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "DiscoveryPiecesPerUpdate", 200, "Maximum loaded pieces inspected per frame when collider profiling starts."); _colliderClusterExcludedNameTokens = ((BaseUnityPlugin)this).Config.Bind("Collider Clustering", "ExcludedPieceNameTokens", "door,gate,portal,teleport,crafting,workbench,forge,stonecutter,cauldron,fermenter,windmill,spinningwheel,smelter,blastfurnace,kiln,oven,chest,container,itemstand,armorstand,sign,cart,ship,ladder", "Comma-separated internal name fragments that must keep their own colliders for interaction, movement, or safety."); } private void UpdateColliderClustering() { if (_enableColliderClusterSystem == null || !_enableColliderClusterSystem.Value) { if (_colliderClusteringWasActive) { RestoreAllColliderClusters(); } _colliderClusteringWasActive = false; return; } bool flag = _enableColliderClusterProfiling != null && _enableColliderClusterProfiling.Value; bool flag2 = _enableColliderClustering != null && _enableColliderClustering.Value && IsWearNTearOptimizationEnabled(); if ((Object)(object)Player.m_localPlayer == (Object)null || (!flag && !flag2)) { if (_colliderClusteringWasActive) { RestoreAllColliderClusters(); } _colliderClusteringWasActive = false; return; } if (!_colliderClusteringWasActive) { _colliderClusteringWasActive = true; _colliderClusterSettingsSignature = GetColliderClusterSettingsSignature(); _nextColliderClusterSettingsCheck = Time.unscaledTime + 1f; BeginColliderClusterDiscovery(); } if (Time.unscaledTime >= _nextColliderClusterSettingsCheck) { _nextColliderClusterSettingsCheck = Time.unscaledTime + 1f; int colliderClusterSettingsSignature = GetColliderClusterSettingsSignature(); if (colliderClusterSettingsSignature != _colliderClusterSettingsSignature) { RestoreAllColliderClusters(); _colliderClusteringWasActive = true; _colliderClusterSettingsSignature = colliderClusterSettingsSignature; BeginColliderClusterDiscovery(); } } ProcessColliderClusterDiscovery(); if (_colliderClusterDiscoveryComplete) { ProcessDirtyColliderClusterCells(flag2); if (flag2 && Time.unscaledTime >= _nextColliderClusterDistanceUpdate) { _nextColliderClusterDistanceUpdate = Time.unscaledTime + Mathf.Max(0.05f, _colliderClusterUpdateInterval.Value); UpdateColliderClusterDistances(); } else if (!flag2) { RestoreActiveColliderClusters(); } } if (ShouldRefreshProfilerMetrics() && Time.unscaledTime >= _nextColliderClusterMetricsRefresh) { _nextColliderClusterMetricsRefresh = Time.unscaledTime + 0.5f; RefreshColliderClusterMetrics(flag2); } } private int GetColliderClusterSettingsSignature() { int num = 17; num = (num * 31) ^ _enableColliderClusterSystem.Value.GetHashCode(); num = (num * 31) ^ _enableColliderClusterProfiling.Value.GetHashCode(); num = (num * 31) ^ _enableColliderClustering.Value.GetHashCode(); num = (num * 31) ^ IsWearNTearOptimizationEnabled().GetHashCode(); num = (num * 31) ^ Mathf.Max(4f, _colliderClusterCellSize.Value).GetHashCode(); num = (num * 31) ^ Mathf.Max(2, _colliderClusterMinimumBoxes.Value); num = (num * 31) ^ Mathf.Max(8, _colliderClusterMaximumBoxesPerCollider.Value); return (num * 31) ^ (_colliderClusterExcludedNameTokens.Value ?? string.Empty).GetHashCode(); } private string[] GetCachedColliderClusterExcludedTokens() { string text = ((_colliderClusterExcludedNameTokens != null) ? _colliderClusterExcludedNameTokens.Value : string.Empty); if (!string.Equals(_cachedColliderClusterExcludedTokenConfig, text, StringComparison.Ordinal)) { _cachedColliderClusterExcludedTokenConfig = text; _cachedColliderClusterExcludedTokens = GetNameTokens(text); } return _cachedColliderClusterExcludedTokens; } private void BeginColliderClusterDiscovery() { _colliderClusterDiscoveryPieces = GetLoadedPiecesSnapshot(); _colliderClusterDiscoveryIndex = 0; _colliderClusterDiscoveryComplete = _colliderClusterDiscoveryPieces == null || _colliderClusterDiscoveryPieces.Length == 0; } private void ProcessColliderClusterDiscovery() { if (!_colliderClusterDiscoveryComplete && _colliderClusterDiscoveryPieces != null) { int num = Mathf.Max(1, _colliderClusterDiscoveryPiecesPerUpdate.Value); int num2 = Mathf.Min(_colliderClusterDiscoveryPieces.Length, _colliderClusterDiscoveryIndex + num); while (_colliderClusterDiscoveryIndex < num2) { TrackColliderClusterPiece(_colliderClusterDiscoveryPieces[_colliderClusterDiscoveryIndex]); _colliderClusterDiscoveryIndex++; } if (_colliderClusterDiscoveryIndex >= _colliderClusterDiscoveryPieces.Length) { _colliderClusterDiscoveryComplete = true; _colliderClusterDiscoveryPieces = null; } } } private void TrackColliderClusterPiece(Piece piece, bool forceRefresh = false) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).gameObject == (Object)null) { return; } ColliderClusterCellKey colliderClusterCellKey = GetColliderClusterCellKey(((Component)piece).transform.position); if (_colliderClusterPieceCells.TryGetValue(piece, out var value)) { if (value.Equals(colliderClusterCellKey)) { ColliderClusterCell orCreateColliderClusterCell = GetOrCreateColliderClusterCell(colliderClusterCellKey); bool flag = orCreateColliderClusterCell.Pieces.Add(piece); if (flag || forceRefresh) { MarkColliderClusterCellDirty(orCreateColliderClusterCell); } return; } if (_colliderClusterCells.TryGetValue(value, out var value2)) { value2.Pieces.Remove(piece); RestoreColliderClusterCell(value2); MarkColliderClusterCellDirty(value2); } } _colliderClusterPieceCells[piece] = colliderClusterCellKey; ColliderClusterCell orCreateColliderClusterCell2 = GetOrCreateColliderClusterCell(colliderClusterCellKey); orCreateColliderClusterCell2.Pieces.Add(piece); MarkColliderClusterCellDirty(orCreateColliderClusterCell2); } private void UntrackColliderClusterPiece(Piece piece) { if (piece != null && _colliderClusterPieceCells.TryGetValue(piece, out var value)) { if (_colliderClusterCells.TryGetValue(value, out var value2)) { RestoreColliderClusterCell(value2); value2.Pieces.Remove(piece); MarkColliderClusterCellDirty(value2); } _colliderClusterPieceCells.Remove(piece); } } private ColliderClusterCellKey GetColliderClusterCellKey(Vector3 position) { //IL_0021: 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) //IL_0049: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(4f, _colliderClusterCellSize.Value); return new ColliderClusterCellKey { X = Mathf.FloorToInt(position.x / num), Y = Mathf.FloorToInt(position.y / num), Z = Mathf.FloorToInt(position.z / num) }; } private Vector3 GetColliderClusterCellOrigin(ColliderClusterCellKey key) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) float num = Mathf.Max(4f, _colliderClusterCellSize.Value); return new Vector3((float)key.X * num, (float)key.Y * num, (float)key.Z * num); } private ColliderClusterCell GetOrCreateColliderClusterCell(ColliderClusterCellKey key) { if (_colliderClusterCells.TryGetValue(key, out var value)) { return value; } value = new ColliderClusterCell { Key = key }; _colliderClusterCells[key] = value; return value; } private void MarkColliderClusterCellDirty(ColliderClusterCell cell) { if (cell != null) { cell.Dirty = true; if (!cell.Queued) { cell.Queued = true; _colliderClusterDirtyQueue.Enqueue(cell.Key); } } } private void ProcessDirtyColliderClusterCells(bool buildClusters) { int num = Mathf.Max(1, _colliderClusterCellsRebuiltPerUpdate.Value); while (num-- > 0 && _colliderClusterDirtyQueue.Count > 0) { ColliderClusterCellKey key = _colliderClusterDirtyQueue.Dequeue(); if (_colliderClusterCells.TryGetValue(key, out var value)) { value.Queued = false; if (value.Dirty) { value.Dirty = false; RebuildColliderClusterCell(value, buildClusters); } } } } private void RebuildColliderClusterCell(ColliderClusterCell cell, bool buildClusters) { //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) RestoreColliderClusterCell(cell); RemoveDestroyedColliderClusterPieces(cell); if (cell.Pieces.Count == 0) { DestroyColliderClusterCell(cell); _colliderClusterCells.Remove(cell.Key); return; } cell.PiecesConsidered = 0; cell.PiecesEligible = 0; cell.BoxCollidersConsidered = 0; cell.EligibleBoxColliders = 0; cell.ExcludedTriggers = 0; cell.ExcludedRigidbodies = 0; cell.ExcludedInteractivePieces = 0; cell.ExcludedNamePieces = 0; cell.ExcludedNonBoxColliders = 0; cell.GroupsBelowMinimum = 0; cell.PotentialClusterColliders = 0; cell.PotentialClusteredBoxes = 0; cell.HasClusterBounds = false; Dictionary> dictionary = new Dictionary>(); foreach (Piece piece in cell.Pieces) { cell.PiecesConsidered++; switch (GetColliderClusterPieceEligibility(piece)) { case ColliderClusterPieceEligibility.Interactive: cell.ExcludedInteractivePieces++; break; case ColliderClusterPieceEligibility.NameFilter: cell.ExcludedNamePieces++; break; case ColliderClusterPieceEligibility.Eligible: { cell.PiecesEligible++; WearNTear componentInChildren = ((Component)piece).GetComponentInChildren(true); Collider[] componentsInChildren = ((Component)piece).GetComponentsInChildren(true); Collider[] array = componentsInChildren; foreach (Collider val in array) { if ((Object)(object)val == (Object)null || !val.enabled || !((Component)val).gameObject.activeInHierarchy) { continue; } cell.BoxCollidersConsidered++; BoxCollider val2 = (BoxCollider)(object)((val is BoxCollider) ? val : null); if (val2 == null) { cell.ExcludedNonBoxColliders++; continue; } if (((Collider)val2).isTrigger) { cell.ExcludedTriggers++; continue; } if ((Object)(object)((Collider)val2).attachedRigidbody != (Object)null) { cell.ExcludedRigidbodies++; continue; } ColliderClusterGroupKey key = new ColliderClusterGroupKey { Layer = ((Component)val2).gameObject.layer, Material = ((Collider)val2).sharedMaterial }; if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List()); } value.Add(new ColliderClusterSource { Collider = val2, Piece = piece, WearNTear = componentInChildren, OriginalEnabled = ((Collider)val2).enabled, WorldBounds = ((Collider)val2).bounds }); cell.EligibleBoxColliders++; } break; } } } foreach (KeyValuePair> item in dictionary) { List runs = BuildColliderMergeRuns(item.Value); CountPotentialColliderClusterGroup(cell, item.Value, runs); if (buildClusters) { BuildColliderClusterGroup(cell, item.Key, runs); } } } private void CountPotentialColliderClusterGroup(ColliderClusterCell cell, List sources, List runs) { int num = Mathf.Max(2, _colliderClusterMinimumBoxes.Value); bool flag = false; foreach (ColliderMergeRun run in runs) { if (run.Sources.Count >= num) { flag = true; cell.PotentialClusterColliders++; cell.PotentialClusteredBoxes += run.Sources.Count; } } if (!flag && sources != null && sources.Count > 0) { cell.GroupsBelowMinimum++; } } private ColliderClusterPieceEligibility GetColliderClusterPieceEligibility(Piece piece) { if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).gameObject == (Object)null || !((Component)piece).gameObject.activeInHierarchy || (Object)(object)((Component)piece).GetComponentInChildren(true) == (Object)null) { return ColliderClusterPieceEligibility.Ineligible; } Rigidbody componentInChildren = ((Component)piece).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null && !componentInChildren.isKinematic) { return ColliderClusterPieceEligibility.Interactive; } MonoBehaviour[] componentsInChildren = ((Component)piece).GetComponentsInChildren(true); MonoBehaviour[] array = componentsInChildren; foreach (MonoBehaviour val in array) { if (!((Object)(object)val == (Object)null) && (ColliderClusterDeniedTypeNames.Contains(((object)val).GetType().Name) || val is Hoverable || val is Interactable || (val is IDestructible && !(val is WearNTear)))) { return ColliderClusterPieceEligibility.Interactive; } } if ((Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null) { return ColliderClusterPieceEligibility.Interactive; } string text = NormalizeNameToken(((Object)(object)((Component)piece).gameObject != (Object)null) ? ((Object)((Component)piece).gameObject).name : ((Object)piece).name); string[] cachedColliderClusterExcludedTokens = GetCachedColliderClusterExcludedTokens(); foreach (string value in cachedColliderClusterExcludedTokens) { if (text.Contains(value)) { return ColliderClusterPieceEligibility.NameFilter; } } return ColliderClusterPieceEligibility.Eligible; } private void BuildColliderClusterGroup(ColliderClusterCell cell, ColliderClusterGroupKey groupKey, List runs) { int num = Mathf.Max(2, _colliderClusterMinimumBoxes.Value); foreach (ColliderMergeRun run in runs) { if (run.Sources.Count >= num) { TryBuildColliderClusterRun(cell, groupKey, run); } } } private List BuildColliderMergeRuns(List sources) { //IL_0098: 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_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) List list = new List(); int num = Mathf.Max(2, _colliderClusterMaximumBoxesPerCollider.Value); if (sources == null) { return list; } foreach (ColliderClusterSource source in sources) { if (source == null || (Object)(object)source.Collider == (Object)null || !IsWorldAxisAlignedBox(source.Collider)) { continue; } bool flag = false; for (int i = 0; i < list.Count; i++) { ColliderMergeRun colliderMergeRun = list[i]; if (colliderMergeRun.Sources.Count < num && TryMergeCollinearBounds(colliderMergeRun.WorldBounds, source.WorldBounds, out var merged)) { colliderMergeRun.WorldBounds = merged; colliderMergeRun.Sources.Add(source); flag = true; break; } } if (!flag) { ColliderMergeRun colliderMergeRun2 = new ColliderMergeRun { WorldBounds = source.WorldBounds }; colliderMergeRun2.Sources.Add(source); list.Add(colliderMergeRun2); } } return list; } private bool IsWorldAxisAlignedBox(BoxCollider box) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)box == (Object)null) { return false; } Matrix4x4 localToWorldMatrix = ((Component)box).transform.localToWorldMatrix; Vector3[] array = new Vector3[3]; Vector3 val = ((Matrix4x4)(ref localToWorldMatrix)).MultiplyVector(Vector3.right); array[0] = ((Vector3)(ref val)).normalized; val = ((Matrix4x4)(ref localToWorldMatrix)).MultiplyVector(Vector3.up); array[1] = ((Vector3)(ref val)).normalized; val = ((Matrix4x4)(ref localToWorldMatrix)).MultiplyVector(Vector3.forward); array[2] = ((Vector3)(ref val)).normalized; Vector3[] array2 = (Vector3[])(object)array; Vector3[] array3 = array2; foreach (Vector3 val2 in array3) { float num = Mathf.Max(new float[3] { Mathf.Abs(Vector3.Dot(val2, Vector3.right)), Mathf.Abs(Vector3.Dot(val2, Vector3.up)), Mathf.Abs(Vector3.Dot(val2, Vector3.forward)) }); if (num < 0.999f) { return false; } } return true; } private bool TryMergeCollinearBounds(Bounds left, Bounds right, out Bounds merged) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_006b: 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_00c9: 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_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 3; i++) { int axis = (i + 1) % 3; int axis2 = (i + 2) % 3; if (Approximately(GetVectorAxis(((Bounds)(ref left)).min, axis), GetVectorAxis(((Bounds)(ref right)).min, axis), 0.005f) && Approximately(GetVectorAxis(((Bounds)(ref left)).max, axis), GetVectorAxis(((Bounds)(ref right)).max, axis), 0.005f) && Approximately(GetVectorAxis(((Bounds)(ref left)).min, axis2), GetVectorAxis(((Bounds)(ref right)).min, axis2), 0.005f) && Approximately(GetVectorAxis(((Bounds)(ref left)).max, axis2), GetVectorAxis(((Bounds)(ref right)).max, axis2), 0.005f)) { float vectorAxis = GetVectorAxis(((Bounds)(ref left)).min, i); float vectorAxis2 = GetVectorAxis(((Bounds)(ref left)).max, i); float vectorAxis3 = GetVectorAxis(((Bounds)(ref right)).min, i); float vectorAxis4 = GetVectorAxis(((Bounds)(ref right)).max, i); if (!(vectorAxis3 > vectorAxis2 + 0.01f) && !(vectorAxis > vectorAxis4 + 0.01f)) { merged = left; ((Bounds)(ref merged)).Encapsulate(right); return true; } } } merged = default(Bounds); return false; } private float GetVectorAxis(Vector3 value, int axis) { //IL_0018: 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_0008: Unknown result type (might be due to invalid IL or missing references) return axis switch { 1 => value.y, 0 => value.x, _ => value.z, }; } private bool Approximately(float left, float right, float tolerance) { return Mathf.Abs(left - right) <= tolerance; } private bool TryBuildColliderClusterRun(ColliderClusterCell cell, ColliderClusterGroupKey groupKey, ColliderMergeRun run) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_0058: 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) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) GameObject colliderClusterCellRoot = GetColliderClusterCellRoot(cell); GameObject val = null; try { val = new GameObject("ColliderClusterCell"); val.transform.SetParent(colliderClusterCellRoot.transform, false); val.layer = groupKey.Layer; BoxCollider val2 = val.AddComponent(); ((Collider)val2).sharedMaterial = groupKey.Material; val2.center = colliderClusterCellRoot.transform.InverseTransformPoint(((Bounds)(ref run.WorldBounds)).center); val2.size = ((Bounds)(ref run.WorldBounds)).size; ColliderClusterDamageProxy colliderClusterDamageProxy = val.AddComponent(); colliderClusterDamageProxy.Initialize(run.Sources); cell.Outputs.Add(new ColliderClusterOutput { GameObject = val, Collider = val2, Sources = run.Sources, WorldBounds = run.WorldBounds }); if (!cell.HasClusterBounds) { cell.ClusterBounds = run.WorldBounds; cell.HasClusterBounds = true; } else { ((Bounds)(ref cell.ClusterBounds)).Encapsulate(run.WorldBounds); } val.SetActive(false); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Collider cluster build failed for cell {cell.Key}: {ex.Message}"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } return false; } } private GameObject GetColliderClusterCellRoot(ColliderClusterCell cell) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_colliderClusterRoot == (Object)null) { _colliderClusterRoot = new GameObject("BuildPieceProfiler_ColliderClusters"); } if ((Object)(object)cell.Root == (Object)null) { cell.Root = new GameObject(string.Format("{0}_{1}_{2}_{3}", "ColliderClusterCell", cell.Key.X, cell.Key.Y, cell.Key.Z)); cell.Root.transform.SetParent(_colliderClusterRoot.transform, false); cell.Root.transform.position = GetColliderClusterCellOrigin(cell.Key); } return cell.Root; } private void UpdateColliderClusterDistances() { //IL_0030: 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) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (_colliderClusterCells.Count == 0 || (Object)(object)Player.m_localPlayer == (Object)null) { return; } Vector3 position = ((Component)Player.m_localPlayer).transform.position; float num = Mathf.Max(5f, _colliderClusterRestoreDistance.Value); float num2 = Mathf.Max(num + 1f, _colliderClusterActivationDistance.Value); foreach (ColliderClusterCell value in _colliderClusterCells.Values) { if (value != null && value.Outputs.Count != 0) { float colliderClusterCellDistance = GetColliderClusterCellDistance(value, position); if (colliderClusterCellDistance <= num) { SetColliderClusterCellActive(value, active: false); } else if (colliderClusterCellDistance >= num2) { SetColliderClusterCellActive(value, active: true); } } } } private float GetColliderClusterCellDistance(ColliderClusterCell cell, Vector3 point) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) return cell.HasClusterBounds ? Mathf.Sqrt(((Bounds)(ref cell.ClusterBounds)).SqrDistance(point)) : float.MaxValue; } private void SetColliderClusterCellActive(ColliderClusterCell cell, bool active) { if (cell == null || cell.ClusterActive == active) { return; } if (active) { foreach (ColliderClusterOutput output in cell.Outputs) { if (output == null || (Object)(object)output.GameObject == (Object)null) { continue; } output.GameObject.SetActive(true); foreach (ColliderClusterSource source in output.Sources) { if ((Object)(object)source.Collider != (Object)null) { ((Collider)source.Collider).enabled = false; } } } } else { foreach (ColliderClusterOutput output2 in cell.Outputs) { if (output2 == null) { continue; } if ((Object)(object)output2.GameObject != (Object)null) { output2.GameObject.SetActive(false); } foreach (ColliderClusterSource source2 in output2.Sources) { if ((Object)(object)source2.Collider != (Object)null) { ((Collider)source2.Collider).enabled = source2.OriginalEnabled; } } } } cell.ClusterActive = active; } private void RestoreActiveColliderClusters() { foreach (ColliderClusterCell value in _colliderClusterCells.Values) { SetColliderClusterCellActive(value, active: false); } } private void RestoreColliderClusterCell(ColliderClusterCell cell) { if (cell == null) { return; } SetColliderClusterCellActive(cell, active: false); foreach (ColliderClusterOutput output in cell.Outputs) { if (output != null && (Object)(object)output.GameObject != (Object)null) { Object.Destroy((Object)(object)output.GameObject); } } cell.Outputs.Clear(); cell.HasClusterBounds = false; } private void DestroyColliderClusterCell(ColliderClusterCell cell) { RestoreColliderClusterCell(cell); if (cell != null && (Object)(object)cell.Root != (Object)null) { Object.Destroy((Object)(object)cell.Root); cell.Root = null; } } private void RemoveDestroyedColliderClusterPieces(ColliderClusterCell cell) { List list = null; foreach (Piece piece in cell.Pieces) { if (!((Object)(object)piece != (Object)null)) { if (list == null) { list = new List(); } list.Add(piece); } } if (list == null) { return; } foreach (Piece item in list) { cell.Pieces.Remove(item); _colliderClusterPieceCells.Remove(item); } } private void RestoreAllColliderClusters() { foreach (ColliderClusterCell value in _colliderClusterCells.Values) { DestroyColliderClusterCell(value); } _colliderClusterCells.Clear(); _colliderClusterPieceCells.Clear(); _colliderClusterDirtyQueue.Clear(); _colliderClusterDiscoveryPieces = null; _colliderClusterDiscoveryIndex = 0; _colliderClusterDiscoveryComplete = false; _colliderClusterMetrics = default(ColliderClusterMetrics); if ((Object)(object)_colliderClusterRoot != (Object)null) { Object.Destroy((Object)(object)_colliderClusterRoot); _colliderClusterRoot = null; } } private void RefreshColliderClusterMetrics(bool clusteringActive) { ColliderClusterMetrics colliderClusterMetrics = new ColliderClusterMetrics { ProfilingEnabled = _enableColliderClusterProfiling.Value, ClusteringRequested = _enableColliderClustering.Value, ClusteringActive = clusteringActive, WearNTearRequirementMet = IsWearNTearOptimizationEnabled(), TrackedPieces = _colliderClusterPieceCells.Count, Cells = _colliderClusterCells.Count, DiscoveryRemaining = ((_colliderClusterDiscoveryPieces != null) ? Mathf.Max(0, _colliderClusterDiscoveryPieces.Length - _colliderClusterDiscoveryIndex) : 0) }; foreach (ColliderClusterCell value in _colliderClusterCells.Values) { if (value == null) { continue; } if (value.Dirty) { colliderClusterMetrics.DirtyCells++; } if (value.ClusterActive) { colliderClusterMetrics.ActiveClusterCells++; } colliderClusterMetrics.PiecesConsidered += value.PiecesConsidered; colliderClusterMetrics.PiecesEligible += value.PiecesEligible; colliderClusterMetrics.BoxCollidersConsidered += value.BoxCollidersConsidered; colliderClusterMetrics.EligibleBoxColliders += value.EligibleBoxColliders; colliderClusterMetrics.ClusterColliders += value.Outputs.Count; colliderClusterMetrics.ExcludedTriggers += value.ExcludedTriggers; colliderClusterMetrics.ExcludedRigidbodies += value.ExcludedRigidbodies; colliderClusterMetrics.ExcludedInteractivePieces += value.ExcludedInteractivePieces; colliderClusterMetrics.ExcludedNamePieces += value.ExcludedNamePieces; colliderClusterMetrics.ExcludedNonBoxColliders += value.ExcludedNonBoxColliders; colliderClusterMetrics.GroupsBelowMinimum += value.GroupsBelowMinimum; colliderClusterMetrics.PotentialClusterColliders += value.PotentialClusterColliders; colliderClusterMetrics.PotentialClusteredBoxes += value.PotentialClusteredBoxes; foreach (ColliderClusterOutput output in value.Outputs) { if (output != null) { colliderClusterMetrics.ClusteredSourceBoxes += output.Sources.Count; if (value.ClusterActive) { colliderClusterMetrics.OriginalCollidersDisabled += output.Sources.Count; } } } } colliderClusterMetrics.EstimatedColliderReduction = Mathf.Max(0, colliderClusterMetrics.ClusteredSourceBoxes - colliderClusterMetrics.ClusterColliders); colliderClusterMetrics.PotentialColliderReduction = Mathf.Max(0, colliderClusterMetrics.PotentialClusteredBoxes - colliderClusterMetrics.PotentialClusterColliders); _colliderClusterMetrics = colliderClusterMetrics; } private void DrawColliderClusteringProfiler() { DrawProfilerSection("collider", "Compound Collider Clustering", "Experimental system that replaces groups of distant simple box colliders with fewer merged boxes.", delegate { DrawProfilerStat("System enabled", _enableColliderClusterSystem.Value, "Master switch for collider discovery, profiling, building, and distance checks."); DrawProfilerStat("Profiling enabled", _colliderClusterMetrics.ProfilingEnabled, "Whether potential collider reductions are being measured."); DrawProfilerStat("Clustering requested", _colliderClusterMetrics.ClusteringRequested, "Whether the configuration asks the system to generate cluster colliders."); DrawProfilerStat("Clustering active", _colliderClusterMetrics.ClusteringActive, "Whether clustering currently meets all requirements and is running."); DrawProfilerStat("WearNTear requirement met", _colliderClusterMetrics.WearNTearRequirementMet, "Collider clustering currently requires the wear-and-tear optimization to be enabled."); DrawProfilerStat("Tracked pieces", _colliderClusterMetrics.TrackedPieces, "Loaded pieces registered with the collider system."); DrawProfilerStat("Cells / dirty", $"{_colliderClusterMetrics.Cells} / {_colliderClusterMetrics.DirtyCells}", "Total collider regions compared with regions waiting to be rebuilt."); DrawProfilerStat("Discovery remaining", _colliderClusterMetrics.DiscoveryRemaining, "Pieces still waiting for collider inspection."); DrawProfilerStat("Active cluster cells", _colliderClusterMetrics.ActiveClusterCells, "Distant regions currently using merged colliders instead of original piece colliders."); DrawProfilerStat("Pieces considered / eligible", $"{_colliderClusterMetrics.PiecesConsidered} / {_colliderClusterMetrics.PiecesEligible}", "Inspected pieces compared with pieces considered safe for clustering."); DrawProfilerStat("Box colliders considered", _colliderClusterMetrics.BoxCollidersConsidered, "Active colliders examined during cluster building."); DrawProfilerStat("Eligible source boxes", _colliderClusterMetrics.EligibleBoxColliders, "Simple static box colliders that passed the safety checks."); DrawProfilerStat("Merged colliders", _colliderClusterMetrics.ClusterColliders, "Generated larger box colliders representing several originals."); DrawProfilerStat("Clustered source boxes", _colliderClusterMetrics.ClusteredSourceBoxes, "Original box colliders represented by generated merged colliders."); DrawProfilerStat("Original colliders disabled", _colliderClusterMetrics.OriginalCollidersDisabled, "Original piece colliders currently replaced in distant active cells."); DrawProfilerStat("Estimated reduction", _colliderClusterMetrics.EstimatedColliderReduction, "Approximate active collider count removed by current clusters."); DrawProfilerStat("Potential cluster colliders", _colliderClusterMetrics.PotentialClusterColliders, "Merged colliders that could be created from all currently eligible groups."); DrawProfilerStat("Potential source boxes", _colliderClusterMetrics.PotentialClusteredBoxes, "Original boxes that could be represented by potential clusters."); DrawProfilerStat("Potential reduction", _colliderClusterMetrics.PotentialColliderReduction, "Approximate collider-count reduction if every eligible group were active."); DrawProfilerStat("Trigger exclusions", _colliderClusterMetrics.ExcludedTriggers, "Trigger volumes skipped because they represent events or detection areas rather than solid structure."); DrawProfilerStat("Rigidbody exclusions", _colliderClusterMetrics.ExcludedRigidbodies, "Colliders skipped because they belong to a moving physics body."); DrawProfilerStat("Interactive exclusions", _colliderClusterMetrics.ExcludedInteractivePieces, "Doors, containers, machines, and other pieces whose physical identity must remain separate."); DrawProfilerStat("Name-filter exclusions", _colliderClusterMetrics.ExcludedNamePieces, "Pieces skipped by the configured safety name list."); DrawProfilerStat("Non-box exclusions", _colliderClusterMetrics.ExcludedNonBoxColliders, "Sphere, capsule, mesh, and other collider shapes the merger does not support."); DrawProfilerStat("Groups below minimum", _colliderClusterMetrics.GroupsBelowMinimum, "Compatible groups too small to justify a cluster."); }); } private void NotifyColliderClusterPieceChanged(Piece piece, bool forceRefresh) { if (_enableColliderClusterSystem != null && _enableColliderClusterSystem.Value && _enableColliderClusterProfiling != null && (_enableColliderClusterProfiling.Value || _enableColliderClustering.Value)) { TrackColliderClusterPiece(piece, forceRefresh); } } private void BindNetworkIdleConfig() { _enableNetworkIdleSystem = ((BaseUnityPlugin)this).Config.Bind("Network Idle", "EnableSystem", false, "Master switch for network diagnostics. When off, no network hooks, piece mapping, revision sampling, or statistic aggregation occurs."); _enableNetworkIdleProfiling = ((BaseUnityPlugin)this).Config.Bind("Network Idle", "EnableNetworkIdleProfiling", false, "Measures build-piece save-state writes, revision changes, ownership changes, remote calls, and global ZDO traffic. Diagnostic only."); _networkDiscoveryPiecesPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Network Idle", "DiscoveryPiecesPerUpdate", 250, "Maximum loaded pieces connected to their save/network records in one frame during startup."); _networkRevisionSamplesPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Network Idle", "RevisionSamplesPerUpdate", 256, "Maximum tracked records checked for data or ownership changes in one frame."); _networkMetricsInterval = ((BaseUnityPlugin)this).Config.Bind("Network Idle", "MetricsIntervalSeconds", 1f, "Seconds between published network summaries while the profiler window or logging is active."); _networkActiveWriterSeconds = ((BaseUnityPlugin)this).Config.Bind("Network Idle", "ActiveWriterWindowSeconds", 10f, "Seconds a build-piece record remains classified as recently active after a successful write or revision change."); _networkTopWriterCount = ((BaseUnityPlugin)this).Config.Bind("Network Idle", "TopWriterCount", 10, "Maximum number of busiest build-piece records and remote-call names shown in the profiler."); } private void UpdateNetworkIdleProfiling() { bool flag = _enableNetworkIdleSystem != null && _enableNetworkIdleSystem.Value && _enableNetworkIdleProfiling != null && _enableNetworkIdleProfiling.Value; if ((Object)(object)Player.m_localPlayer == (Object)null || !flag) { SetNetworkProfilerHooksActive(active: false); if (_networkProfilerWorldActive) { ResetNetworkIdleProfiling(); } _networkProfilerWorldActive = false; return; } if (!_networkProfilerWorldActive) { _networkProfilerWorldActive = true; SetNetworkProfilerHooksActive(active: true); BeginNetworkPieceDiscovery(); } ProcessNetworkPieceDiscovery(); SampleNetworkPieceRevisions(); if (ShouldRefreshProfilerMetrics()) { ProcessNetworkMetricsAggregation(); } } private void BeginNetworkPieceDiscovery() { _networkDiscoveryPieces = GetLoadedPiecesSnapshot(); _networkDiscoveryIndex = 0; _networkDiscoveryComplete = _networkDiscoveryPieces == null || _networkDiscoveryPieces.Length == 0; } private void ProcessNetworkPieceDiscovery() { if (!_networkDiscoveryComplete && _networkDiscoveryPieces != null) { int num = Mathf.Max(1, _networkDiscoveryPiecesPerUpdate.Value); int num2 = Mathf.Min(_networkDiscoveryPieces.Length, _networkDiscoveryIndex + num); while (_networkDiscoveryIndex < num2) { TrackNetworkPiece(_networkDiscoveryPieces[_networkDiscoveryIndex]); _networkDiscoveryIndex++; } if (_networkDiscoveryIndex >= _networkDiscoveryPieces.Length) { _networkDiscoveryComplete = true; _networkDiscoveryPieces = null; } } } private void TrackNetworkPiece(Piece piece) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).gameObject == (Object)null) { return; } ZNetView componentInChildren = ((Component)piece).GetComponentInChildren(true); if (!((Object)(object)componentInChildren == (Object)null) && !_networkPieceStatesByView.ContainsKey(componentInChildren)) { ZDO zDO = componentInChildren.GetZDO(); if (zDO != null && zDO.IsValid()) { NetworkPieceState networkPieceState = new NetworkPieceState { Piece = piece, View = componentInChildren, Zdo = zDO, ZdoId = zDO.m_uid, LastDataRevision = zDO.DataRevision, LastOwnerRevision = zDO.OwnerRevision, LastOwner = zDO.GetOwner(), Name = ((Object)((Component)piece).gameObject).name, IsStaticStructure = IsLikelyStaticNetworkStructure(piece) }; _networkPieceStatesByView[componentInChildren] = networkPieceState; _networkPieceStatesByZdo[networkPieceState.ZdoId] = networkPieceState; _networkPieceStateList.Add(networkPieceState); } } } private bool IsLikelyStaticNetworkStructure(Piece piece) { if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) == (Object)null) { return false; } Rigidbody componentInChildren = ((Component)piece).GetComponentInChildren(true); return (Object)(object)componentInChildren == (Object)null || componentInChildren.isKinematic; } private void UntrackNetworkPiece(Piece piece) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (piece != null && _networkPieceStatesByView.Count != 0) { ZNetView val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponentInChildren(true) : null); if (!((Object)(object)val == (Object)null) && _networkPieceStatesByView.TryGetValue(val, out var value)) { _networkPieceStatesByView.Remove(val); _networkPieceStatesByZdo.Remove(value.ZdoId); _networkPieceStateList.Remove(value); } } } private void SampleNetworkPieceRevisions() { int count = _networkPieceStateList.Count; if (count == 0) { return; } int num = Mathf.Min(count, Mathf.Max(1, _networkRevisionSamplesPerUpdate.Value)); for (int i = 0; i < num; i++) { if (_networkRevisionSampleCursor >= _networkPieceStateList.Count) { _networkRevisionSampleCursor = 0; } if (_networkPieceStateList.Count == 0) { break; } NetworkPieceState networkPieceState = _networkPieceStateList[_networkRevisionSampleCursor++]; if (networkPieceState != null && networkPieceState.Zdo != null && networkPieceState.Zdo.IsValid()) { uint dataRevision = networkPieceState.Zdo.DataRevision; if (dataRevision != networkPieceState.LastDataRevision) { uint unsignedRevisionDelta = GetUnsignedRevisionDelta(networkPieceState.LastDataRevision, dataRevision); networkPieceState.ObservedRevisionChanges += unsignedRevisionDelta; networkPieceState.LastDataRevision = dataRevision; networkPieceState.LastActivityTime = Time.time; _networkObservedRevisionChanges += unsignedRevisionDelta; } ushort ownerRevision = networkPieceState.Zdo.OwnerRevision; long owner = networkPieceState.Zdo.GetOwner(); if (ownerRevision != networkPieceState.LastOwnerRevision || owner != networkPieceState.LastOwner) { networkPieceState.OwnerChanges++; networkPieceState.LastOwnerRevision = ownerRevision; networkPieceState.LastOwner = owner; networkPieceState.LastActivityTime = Time.time; _networkObservedOwnerChanges++; } } } } private uint GetUnsignedRevisionDelta(uint previous, uint current) { return (current >= previous) ? (current - previous) : current; } private void RecordNetworkZdoWrite(ZDOID zdoId, bool changed) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (_networkProfilerWorldActive && _networkPieceStatesByZdo.TryGetValue(zdoId, out var value)) { _networkWriteAttempts++; value.WriteAttempts++; if (changed) { _networkChangedWrites++; value.ChangedWrites++; value.LastActivityTime = Time.time; } } } private void RecordNetworkRpc(ZNetView view, string method) { if (_networkProfilerWorldActive && !((Object)(object)view == (Object)null) && _networkPieceStatesByView.TryGetValue(view, out var value)) { _networkRpcCalls++; value.RpcCalls++; value.LastActivityTime = Time.time; string key = (string.IsNullOrEmpty(method) ? "" : method); _networkRpcMethodCounts.TryGetValue(key, out var value2); _networkRpcMethodCounts[key] = value2 + 1; } } private void ProcessNetworkMetricsAggregation() { if (_networkPieceStateList.Count == 0) { PublishNetworkIdleMetrics(default(NetworkMetricsAggregate)); return; } if (_networkMetricsAggregationCursor == 0) { _networkMetricsAggregate = default(NetworkMetricsAggregate); _networkWriterAggregation.Clear(); } float num = Mathf.Max(1f, _networkActiveWriterSeconds.Value); int num2 = Mathf.Min(_networkPieceStateList.Count, Mathf.Max(1, _networkRevisionSamplesPerUpdate.Value)); int num3 = Mathf.Min(_networkPieceStateList.Count, _networkMetricsAggregationCursor + num2); while (_networkMetricsAggregationCursor < num3) { NetworkPieceState networkPieceState = _networkPieceStateList[_networkMetricsAggregationCursor++]; if (networkPieceState == null || networkPieceState.Zdo == null) { continue; } if (!networkPieceState.Zdo.HasOwner()) { _networkMetricsAggregate.Unowned++; } else if (networkPieceState.Zdo.IsOwner()) { _networkMetricsAggregate.OwnedByLocal++; } else { _networkMetricsAggregate.OwnedByOther++; } if (Time.time - networkPieceState.LastActivityTime <= num) { _networkMetricsAggregate.ActiveWriters++; if (networkPieceState.IsStaticStructure) { _networkMetricsAggregate.StaticActiveWriters++; } else { _networkMetricsAggregate.DynamicActiveWriters++; } } if (networkPieceState.ChangedWrites != 0L || networkPieceState.RpcCalls != 0L || networkPieceState.ObservedRevisionChanges != 0) { if (networkPieceState.IsStaticStructure) { _networkMetricsAggregate.StaticWriteAttempts += networkPieceState.WriteAttempts; _networkMetricsAggregate.StaticChangedWrites += networkPieceState.ChangedWrites; } else { _networkMetricsAggregate.DynamicWriteAttempts += networkPieceState.WriteAttempts; _networkMetricsAggregate.DynamicChangedWrites += networkPieceState.ChangedWrites; } _networkWriterAggregation.Add(new NetworkWriterSnapshot { Name = networkPieceState.Name, WriteAttempts = networkPieceState.WriteAttempts, ChangedWrites = networkPieceState.ChangedWrites, RevisionChanges = networkPieceState.ObservedRevisionChanges, RpcCalls = networkPieceState.RpcCalls, IsStaticStructure = networkPieceState.IsStaticStructure }); } } if (_networkMetricsAggregationCursor >= _networkPieceStateList.Count) { if (Time.unscaledTime < _nextNetworkMetricsRefresh) { _networkMetricsAggregationCursor = 0; return; } _nextNetworkMetricsRefresh = Time.unscaledTime + Mathf.Max(0.25f, _networkMetricsInterval.Value); _networkMetricsAggregationCursor = 0; PublishNetworkIdleMetrics(_networkMetricsAggregate); } } private void PublishNetworkIdleMetrics(NetworkMetricsAggregate aggregate) { _networkTopWriters.Clear(); _networkTopWriters.AddRange(_networkWriterAggregation); _networkTopWriters.Sort(delegate(NetworkWriterSnapshot left, NetworkWriterSnapshot right) { long value = left.ChangedWrites + left.RevisionChanges + left.RpcCalls; return (right.ChangedWrites + right.RevisionChanges + right.RpcCalls).CompareTo(value); }); int num = Mathf.Max(0, _networkTopWriterCount.Value); if (_networkTopWriters.Count > num) { _networkTopWriters.RemoveRange(num, _networkTopWriters.Count - num); } _networkTopRpcMethods.Clear(); foreach (KeyValuePair networkRpcMethodCount in _networkRpcMethodCounts) { _networkTopRpcMethods.Add(new NetworkRpcSnapshot { Method = networkRpcMethodCount.Key, Count = networkRpcMethodCount.Value }); } _networkTopRpcMethods.Sort((NetworkRpcSnapshot left, NetworkRpcSnapshot right) => right.Count.CompareTo(left.Count)); if (_networkTopRpcMethods.Count > num) { _networkTopRpcMethods.RemoveRange(num, _networkTopRpcMethods.Count - num); } ZDOMan instance = ZDOMan.instance; _networkIdleMetrics = new NetworkIdleMetrics { TrackedPieces = _networkPieceStateList.Count, DiscoveryRemaining = ((_networkDiscoveryPieces != null) ? Mathf.Max(0, _networkDiscoveryPieces.Length - _networkDiscoveryIndex) : 0), OwnedByLocal = aggregate.OwnedByLocal, OwnedByOther = aggregate.OwnedByOther, Unowned = aggregate.Unowned, ActiveWriters = aggregate.ActiveWriters, StaticActiveWriters = aggregate.StaticActiveWriters, DynamicActiveWriters = aggregate.DynamicActiveWriters, StaticWriteAttempts = aggregate.StaticWriteAttempts, StaticChangedWrites = aggregate.StaticChangedWrites, DynamicWriteAttempts = aggregate.DynamicWriteAttempts, DynamicChangedWrites = aggregate.DynamicChangedWrites, WriteAttempts = _networkWriteAttempts, ChangedWrites = _networkChangedWrites, RedundantWrites = Math.Max(0L, _networkWriteAttempts - _networkChangedWrites), RpcCalls = _networkRpcCalls, ObservedRevisionChanges = _networkObservedRevisionChanges, ObservedOwnerChanges = _networkObservedOwnerChanges, SentZdos = ((instance != null) ? instance.GetSentZDOs() : 0), ReceivedZdos = ((instance != null) ? instance.GetRecvZDOs() : 0), ClientChangeQueue = ((instance != null) ? instance.GetClientChangeQueue() : 0) }; } private void ResetNetworkIdleProfiling() { SetNetworkProfilerHooksActive(active: false); _networkPieceStatesByZdo.Clear(); _networkProfilerHooksActive = false; _networkPieceStatesByView.Clear(); _networkPieceStateList.Clear(); _networkRpcMethodCounts.Clear(); _networkTopWriters.Clear(); _networkTopRpcMethods.Clear(); _networkWriterAggregation.Clear(); _networkDiscoveryPieces = null; _networkDiscoveryIndex = 0; _networkDiscoveryComplete = false; _networkRevisionSampleCursor = 0; _networkMetricsAggregationCursor = 0; _networkMetricsAggregate = default(NetworkMetricsAggregate); _networkWriteAttempts = 0L; _networkChangedWrites = 0L; _networkRpcCalls = 0L; _networkObservedRevisionChanges = 0L; _networkObservedOwnerChanges = 0L; _networkIdleMetrics = default(NetworkIdleMetrics); } private void SetNetworkProfilerHooksActive(bool active) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown if (active) { if (!_networkProfilerHooksInstalled) { _networkProfilerHarmony = new Harmony("valheim.buildpieceprofiler.network-profiler"); HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(ZdoExtraDataSetNetworkProfilerPatch), "Postfix", (Type[])null, (Type[])null)); foreach (MethodBase item in ZdoExtraDataSetNetworkProfilerPatch.TargetMethods()) { _networkProfilerHarmony.Patch(item, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(ZNetViewInvokeRpcNetworkProfilerPatch), "Prefix", (Type[])null, (Type[])null)); foreach (MethodBase item2 in ZNetViewInvokeRpcNetworkProfilerPatch.TargetMethods()) { _networkProfilerHarmony.Patch(item2, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } _networkProfilerHooksInstalled = true; } _networkProfilerHooksActive = true; return; } _networkProfilerHooksActive = false; if (_networkProfilerHooksInstalled) { Harmony networkProfilerHarmony = _networkProfilerHarmony; if (networkProfilerHarmony != null) { networkProfilerHarmony.UnpatchSelf(); } _networkProfilerHarmony = null; _networkProfilerHooksInstalled = false; } } private void DrawNetworkIdleProfiler() { DrawProfilerSection("network", "ZDO / Network Activity", "Diagnostic view of build-piece save-state writes, ownership changes, and network calls.", delegate { DrawProfilerStat("System enabled", _enableNetworkIdleSystem.Value, "Master switch for all network discovery, hooks, sampling, and aggregation."); DrawProfilerStat("Profiling enabled", _enableNetworkIdleProfiling.Value, "Whether build-piece network activity is currently being measured."); DrawProfilerStat("Runtime hooks installed", _networkProfilerHooksInstalled, "Whether the temporary diagnostic hooks are attached to Valheim's network write methods."); DrawProfilerStat("Tracked build-piece ZDOs", _networkIdleMetrics.TrackedPieces, "Build-piece save/network records currently mapped by the profiler."); DrawProfilerStat("Discovery remaining", _networkIdleMetrics.DiscoveryRemaining, "Loaded pieces still waiting to be mapped to a network record."); DrawProfilerStat("Owned local / other / unowned", $"{_networkIdleMetrics.OwnedByLocal} / {_networkIdleMetrics.OwnedByOther} / {_networkIdleMetrics.Unowned}", "Tracked records controlled by this computer, another peer, or currently nobody."); DrawProfilerStat("Active writers", _networkIdleMetrics.ActiveWriters, "Tracked records that changed recently."); DrawProfilerStat("Static / dynamic writers", $"{_networkIdleMetrics.StaticActiveWriters} / {_networkIdleMetrics.DynamicActiveWriters}", "Recently changing structure records compared with machines, ships, and other dynamic pieces."); DrawProfilerStat("Static changed writes", $"{_networkIdleMetrics.StaticChangedWrites}/{_networkIdleMetrics.StaticWriteAttempts}", "Successful value changes compared with all attempted writes on likely static structures."); DrawProfilerStat("Dynamic changed writes", $"{_networkIdleMetrics.DynamicChangedWrites}/{_networkIdleMetrics.DynamicWriteAttempts}", "Successful value changes compared with all attempted writes on likely dynamic pieces."); DrawProfilerStat("ZDO write attempts", _networkIdleMetrics.WriteAttempts, "Calls attempting to write build-piece save or network data."); DrawProfilerStat("Changed ZDO writes", _networkIdleMetrics.ChangedWrites, "Write attempts that actually changed stored data."); DrawProfilerStat("Deduplicated writes", _networkIdleMetrics.RedundantWrites, "Write attempts where the requested value already matched the stored value."); DrawProfilerStat("Build-piece RPC calls", _networkIdleMetrics.RpcCalls, "Remote procedure calls sent through tracked build-piece network views."); DrawProfilerStat("Revision changes", _networkIdleMetrics.ObservedRevisionChanges, "Observed increases to tracked records' data revision numbers."); DrawProfilerStat("Ownership changes", _networkIdleMetrics.ObservedOwnerChanges, "Times control of a tracked record moved between peers or ownership states."); DrawProfilerStat("ZDOs sent / received", $"{_networkIdleMetrics.SentZdos} / {_networkIdleMetrics.ReceivedZdos}", "Valheim's current global counters for network records sent and received."); DrawProfilerStat("Client change queue", _networkIdleMetrics.ClientChangeQueue, "Pending local record changes waiting for network processing."); foreach (NetworkWriterSnapshot networkTopWriter in _networkTopWriters) { DrawProfilerStat(networkTopWriter.Name, string.Format("{0}, changed {1}/{2}, revisions {3}, RPCs {4}", networkTopWriter.IsStaticStructure ? "static" : "dynamic", networkTopWriter.ChangedWrites, networkTopWriter.WriteAttempts, networkTopWriter.RevisionChanges, networkTopWriter.RpcCalls), "A tracked piece with comparatively frequent save-state changes or network calls."); } foreach (NetworkRpcSnapshot networkTopRpcMethod in _networkTopRpcMethods) { DrawProfilerStat(networkTopRpcMethod.Method, networkTopRpcMethod.Count, "How many times this named build-piece network method was called during the current profiling session."); } }); } private void NotifyNetworkPieceChanged(Piece piece) { if (_enableNetworkIdleSystem != null && _enableNetworkIdleSystem.Value && _enableNetworkIdleProfiling != null && _enableNetworkIdleProfiling.Value) { TrackNetworkPiece(piece); } } private void BindRendererBatchingConfig() { _enableRendererBatching = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "EnableRendererBatching", false, "Combines compatible static building meshes into fewer local renderers. Original colliders and gameplay components remain untouched."); _rendererBatchCellSize = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "CellSize", 10f, "Size in meters of each local batching region. Smaller cells hide more precisely; larger cells can combine more renderers."); _rendererBatchMinimumRenderers = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "MinimumRenderersPerBatch", 4, "Smallest compatible group worth combining. Higher values avoid creating batches that save very little rendering work."); _rendererBatchMaximumVertices = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "MaximumVerticesPerMesh", 200000, "Maximum detail stored in one generated mesh. Groups above this limit are split into multiple combined renderers."); _rendererBatchCellsPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "CellsRebuiltPerUpdate", 1, "Maximum changed batching regions rebuilt in one frame. Lower values reduce construction spikes but take longer to finish updates."); _rendererBatchDiscoveryPiecesPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "DiscoveryPiecesPerUpdate", 250, "Maximum loaded pieces inspected per frame when batching starts. Lower values make startup smoother but slower."); _rendererBatchRebuildDelay = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "RebuildDelaySeconds", 0.35f, "Wait time before rebuilding a changed region. This groups rapid building actions into fewer rebuilds."); _rendererBatchExcludedNameTokens = ((BaseUnityPlugin)this).Config.Bind("Renderer Batching", "ExcludedPieceNameTokens", "door,gate,portal,teleport,crafting,workbench,forge,stonecutter,cauldron,fermenter,windmill,spinningwheel,smelter,blastfurnace,kiln,oven,chest,container,itemstand,armorstand,sign,cart,ship", "Comma-separated internal name fragments that must remain separate for interaction, animation, or safety."); _enableShadowCasterOptimization = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "EnableShadowCasterOptimization", false, "Experimental. Replaces groups of compatible batch shadows with fewer shadow-only meshes. Requires both this option and the shadow master switch."); _enableShadowOptimizationSystem = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "EnableSystem", false, "Master switch for all experimental shadow work. When off, no shadow grouping, mesh reading, caching, or cluster generation occurs."); _shadowClusterMinimumVisibleBatches = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "MinimumVisibleBatchesPerCluster", 4, "Minimum number of compatible shadow-casting batches required before one replacement caster is considered."); _shadowClusterMinimumDrawsSaved = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "MinimumShadowDrawsSaved", 3, "Minimum estimated shadow draw calls that must be saved before a replacement caster is accepted."); _shadowClusterMaximumVertices = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "MaximumVerticesPerShadowMesh", 150000, "Maximum detail allowed in one generated shadow-only mesh."); _shadowClusterMaximumVerticesPerDrawSaved = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "MaximumVerticesPerDrawSaved", 25000, "Rejects a shadow cluster when it adds too much geometry for each estimated shadow draw call saved."); _shadowClusterMaximumBoundsDiagonal = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "MaximumBoundsDiagonal", 14f, "Largest allowed physical span of one shadow cluster. Smaller limits help Unity hide off-screen shadow casters more precisely."); _enableSimplifiedStructuralShadowCasters = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "EnableSimplifiedStructuralCasters", false, "Experimental. Represents matching walls and roofs with simple box-like shadow geometry instead of their full mesh."); _simplifiedShadowPieceNameTokens = ((BaseUnityPlugin)this).Config.Bind("Shadow Optimization", "SimplifiedStructuralPieceNameTokens", "wall,roof", "Comma-separated internal name fragments allowed to use simplified wall or roof shadow geometry."); } private void UpdateRendererBatching() { if (_enableRendererBatching == null || !_enableRendererBatching.Value) { if (_rendererBatchingWasActive) { RestoreAllRendererBatches(); } _rendererBatchingWasActive = false; return; } if ((Object)(object)Player.m_localPlayer == (Object)null) { if (_rendererBatchingWasActive) { RestoreAllRendererBatches(); } _rendererBatchingWasActive = false; return; } if (!_rendererBatchingWasActive || Time.unscaledTime >= _nextRendererBatchSettingsCheck) { _nextRendererBatchSettingsCheck = Time.unscaledTime + 1f; int rendererBatchSettingsSignature = GetRendererBatchSettingsSignature(); if (!_rendererBatchingWasActive || _rendererBatchSettingsSignature != rendererBatchSettingsSignature) { RestoreAllRendererBatches(); _rendererBatchSettingsSignature = rendererBatchSettingsSignature; _rendererBatchingWasActive = true; BeginRendererBatchDiscovery(); } } ProcessRendererBatchDiscovery(); if (_rendererBatchDiscoveryComplete) { ProcessDirtyRendererBatchCells(); } UpdateRendererBatchHighlights(); if (ShouldRefreshProfilerMetrics() && Time.unscaledTime >= _nextRendererBatchMetricsRefresh) { _nextRendererBatchMetricsRefresh = Time.unscaledTime + 0.5f; RefreshRendererBatchMetrics(); } } private int GetRendererBatchSettingsSignature() { int num = 17; num = (num * 31) ^ Mathf.Max(4f, _rendererBatchCellSize.Value).GetHashCode(); num = (num * 31) ^ Mathf.Max(2, _rendererBatchMinimumRenderers.Value); num = (num * 31) ^ Mathf.Max(1000, _rendererBatchMaximumVertices.Value); num = (num * 31) ^ _enableShadowCasterOptimization.Value.GetHashCode(); num = (num * 31) ^ _enableShadowOptimizationSystem.Value.GetHashCode(); num = (num * 31) ^ Mathf.Max(2, _shadowClusterMinimumVisibleBatches.Value); num = (num * 31) ^ Mathf.Max(1, _shadowClusterMinimumDrawsSaved.Value); num = (num * 31) ^ Mathf.Max(1000, _shadowClusterMaximumVertices.Value); num = (num * 31) ^ Mathf.Max(1000, _shadowClusterMaximumVerticesPerDrawSaved.Value); num = (num * 31) ^ Mathf.Max(1f, _shadowClusterMaximumBoundsDiagonal.Value).GetHashCode(); num = (num * 31) ^ _enableSimplifiedStructuralShadowCasters.Value.GetHashCode(); num = (num * 31) ^ (_simplifiedShadowPieceNameTokens.Value ?? string.Empty).GetHashCode(); return (num * 31) ^ (_rendererBatchExcludedNameTokens.Value ?? string.Empty).GetHashCode(); } private string[] GetCachedRendererBatchExcludedTokens() { string text = ((_rendererBatchExcludedNameTokens != null) ? _rendererBatchExcludedNameTokens.Value : string.Empty); if (_cachedRendererBatchExcludedTokenConfig != text) { _cachedRendererBatchExcludedTokenConfig = text; _cachedRendererBatchExcludedTokens = GetNameTokens(text); } return _cachedRendererBatchExcludedTokens; } private string[] GetCachedSimplifiedShadowTokens() { string text = ((_simplifiedShadowPieceNameTokens != null) ? _simplifiedShadowPieceNameTokens.Value : string.Empty); if (_cachedSimplifiedShadowTokenConfig != text) { _cachedSimplifiedShadowTokenConfig = text; _cachedSimplifiedShadowTokens = GetNameTokens(text); } return _cachedSimplifiedShadowTokens; } private void BeginRendererBatchDiscovery() { _rendererBatchDiscoveryPieces = GetLoadedPiecesSnapshot(); _rendererBatchDiscoveryIndex = 0; _rendererBatchDiscoveryComplete = _rendererBatchDiscoveryPieces == null || _rendererBatchDiscoveryPieces.Length == 0; } private Piece[] GetLoadedPiecesSnapshot() { return GetSharedLoadedPiecesSnapshot(); } private void ProcessRendererBatchDiscovery() { if (!_rendererBatchDiscoveryComplete && _rendererBatchDiscoveryPieces != null) { int num = Mathf.Max(1, _rendererBatchDiscoveryPiecesPerUpdate.Value); int num2 = Mathf.Min(_rendererBatchDiscoveryPieces.Length, _rendererBatchDiscoveryIndex + num); while (_rendererBatchDiscoveryIndex < num2) { TrackRendererBatchPiece(_rendererBatchDiscoveryPieces[_rendererBatchDiscoveryIndex]); _rendererBatchDiscoveryIndex++; } if (_rendererBatchDiscoveryIndex >= _rendererBatchDiscoveryPieces.Length) { _rendererBatchDiscoveryComplete = true; _rendererBatchDiscoveryPieces = null; } } } private void TrackRendererBatchPiece(Piece piece, bool forceRefresh = false) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece == (Object)null) { return; } RendererBatchCellKey rendererBatchCellKey = GetRendererBatchCellKey(((Component)piece).transform.position); if (_rendererBatchPieceCells.TryGetValue(piece, out var value)) { if (value.Equals(rendererBatchCellKey)) { RendererBatchCell orCreateRendererBatchCell = GetOrCreateRendererBatchCell(rendererBatchCellKey); bool flag = orCreateRendererBatchCell.Pieces.Add(piece); if (flag || forceRefresh) { MarkRendererBatchCellDirty(orCreateRendererBatchCell); } return; } if (_rendererBatchCells.TryGetValue(value, out var value2)) { value2.Pieces.Remove(piece); MarkRendererBatchCellDirty(value2); } } _rendererBatchPieceCells[piece] = rendererBatchCellKey; RendererBatchCell orCreateRendererBatchCell2 = GetOrCreateRendererBatchCell(rendererBatchCellKey); orCreateRendererBatchCell2.Pieces.Add(piece); MarkRendererBatchCellDirty(orCreateRendererBatchCell2); } private void UntrackRendererBatchPiece(Piece piece) { if (piece == null) { return; } if (_rendererBatchPieceCells.TryGetValue(piece, out var value)) { if (_rendererBatchCells.TryGetValue(value, out var value2)) { if (value2.SourceRenderersByPiece.TryGetValue(piece, out var value3)) { foreach (MeshRenderer item in value3) { if ((Object)(object)item != (Object)null) { _rendererLodCache.Remove(item); } } } value2.Pieces.Remove(piece); RestoreRendererBatchCell(value2); MarkRendererBatchCellDirty(value2); } _rendererBatchPieceCells.Remove(piece); } _rendererBatchPieceExcludedUntil.Remove(piece); _rendererBatchEligibilityCache.Remove(piece); _rendererBatchRendererCache.Remove(piece); _rendererBatchHighlightedUntil.Remove(piece); } private RendererBatchCell GetOrCreateRendererBatchCell(RendererBatchCellKey key) { if (_rendererBatchCells.TryGetValue(key, out var value)) { return value; } value = new RendererBatchCell { Key = key }; _rendererBatchCells[key] = value; return value; } private RendererBatchCellKey GetRendererBatchCellKey(Vector3 position) { //IL_0021: 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) //IL_0049: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(4f, _rendererBatchCellSize.Value); return new RendererBatchCellKey { X = Mathf.FloorToInt(position.x / num), Y = Mathf.FloorToInt(position.y / num), Z = Mathf.FloorToInt(position.z / num) }; } private Vector3 GetRendererBatchCellOrigin(RendererBatchCellKey key) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) float num = Mathf.Max(4f, _rendererBatchCellSize.Value); return new Vector3((float)key.X * num, (float)key.Y * num, (float)key.Z * num); } private void MarkRendererBatchCellDirty(RendererBatchCell cell) { if (cell != null) { cell.Dirty = true; cell.RebuildAfter = Time.time + Mathf.Max(0f, _rendererBatchRebuildDelay.Value); if (!cell.Queued) { cell.Queued = true; _rendererBatchDirtyQueue.Enqueue(cell.Key); } } } private void ProcessDirtyRendererBatchCells() { int num = Mathf.Max(1, _rendererBatchCellsPerUpdate.Value); int num2 = 0; int num3 = Mathf.Min(_rendererBatchDirtyQueue.Count, Mathf.Max(8, num * 8)); while (num3-- > 0 && num2 < num && _rendererBatchDirtyQueue.Count > 0) { RendererBatchCellKey rendererBatchCellKey = _rendererBatchDirtyQueue.Dequeue(); if (_rendererBatchCells.TryGetValue(rendererBatchCellKey, out var value)) { if (!value.Dirty) { value.Queued = false; continue; } if (Time.time < value.RebuildAfter) { _rendererBatchDirtyQueue.Enqueue(rendererBatchCellKey); continue; } value.Queued = false; value.Dirty = false; RebuildRendererBatchCell(value); num2++; } } } private void RebuildRendererBatchCell(RendererBatchCell cell) { if (cell == null) { return; } RestoreRendererBatchCell(cell); RemoveDestroyedRendererBatchPieces(cell); if (cell.Pieces.Count == 0) { DestroyRendererBatchCell(cell); _rendererBatchCells.Remove(cell.Key); return; } Dictionary> dictionary = new Dictionary>(); cell.EligibleRendererCount = 0; cell.UnreadableMeshesSkipped = 0; cell.PropertyBlocksSkipped = 0; cell.PiecesConsidered = 0; cell.PiecesEligible = 0; cell.PiecesExcludedNonStructural = 0; cell.PiecesExcludedInteractive = 0; cell.PiecesExcludedAnimated = 0; cell.PiecesExcludedEffects = 0; cell.PiecesExcludedByName = 0; cell.RenderersExcludedStaticBatch = 0; cell.RenderersExcludedLod = 0; cell.RenderersExcludedMeshLayout = 0; cell.RenderersExcludedLightmap = 0; cell.RenderersExcludedMaterial = 0; cell.GroupsBelowMinimum = 0; cell.ShadowCastingVisibleBatches = 0; cell.ShadowEligibleVisibleBatches = 0; cell.ShadowSourceRenderersConsolidated = 0; cell.ShadowDrawCallsAvoided = 0; cell.ShadowExcludedCastingModeBatches = 0; cell.ShadowExcludedMaterialBatches = 0; cell.ShadowGroupsBelowMinimum = 0; cell.ShadowRejectedVisibleBatchCount = 0; cell.ShadowRejectedDrawBenefit = 0; cell.ShadowRejectedVertexBenefit = 0; cell.ShadowRejectedBounds = 0; cell.ShadowOriginalVerticesReplaced = 0; cell.ShadowSimplifiedSources = 0; foreach (Piece piece in cell.Pieces) { cell.PiecesConsidered++; RendererBatchPieceEligibility rendererBatchPieceEligibility = GetRendererBatchPieceEligibility(piece); if (rendererBatchPieceEligibility != RendererBatchPieceEligibility.Eligible) { CountRendererBatchPieceExclusion(cell, rendererBatchPieceEligibility); continue; } cell.PiecesEligible++; MeshRenderer[] cachedRendererBatchRenderers = GetCachedRendererBatchRenderers(piece); MeshRenderer[] array = cachedRendererBatchRenderers; foreach (MeshRenderer val in array) { if ((Object)(object)val != (Object)null && ((Renderer)val).HasPropertyBlock()) { cell.PropertyBlocksSkipped++; continue; } MeshFilter val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); Mesh val3 = (((Object)(object)val2 != (Object)null) ? val2.sharedMesh : null); if ((Object)(object)val3 != (Object)null && !val3.isReadable) { cell.UnreadableMeshesSkipped++; continue; } RendererBatchSource source; RendererBatchRendererEligibility rendererBatchRendererEligibility = TryCreateRendererBatchSource(piece, val, out source); if (rendererBatchRendererEligibility != RendererBatchRendererEligibility.Eligible) { CountRendererBatchRendererExclusion(cell, rendererBatchRendererEligibility); continue; } cell.EligibleRendererCount++; if (!dictionary.TryGetValue(source.GroupKey, out var value)) { value = new List(); dictionary[source.GroupKey] = value; } value.Add(source); } } foreach (KeyValuePair> item in dictionary) { if (item.Value.Count < Mathf.Max(2, _rendererBatchMinimumRenderers.Value)) { cell.GroupsBelowMinimum++; } BuildRendererBatchGroup(cell, item.Key, item.Value); } BuildRendererShadowClusters(cell); foreach (RendererBatchOutput output in cell.Outputs) { if (output != null) { output.Sources = null; } } } private RendererBatchPieceEligibility GetRendererBatchPieceEligibility(Piece piece) { if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).gameObject == (Object)null || !((Component)piece).gameObject.activeInHierarchy) { return RendererBatchPieceEligibility.NonStructural; } if (_rendererBatchPieceExcludedUntil.TryGetValue(piece, out var value)) { if (Time.time < value) { return RendererBatchPieceEligibility.TemporarilyExcluded; } _rendererBatchPieceExcludedUntil.Remove(piece); } if (_rendererBatchEligibilityCache.TryGetValue(piece, out var value2)) { return value2; } if ((Object)(object)((Component)piece).GetComponentInChildren(true) == (Object)null) { return CacheRendererBatchEligibility(piece, RendererBatchPieceEligibility.NonStructural); } if (IsKnownFirePiece(piece, GetCachedKnownFireNameTokens()) || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null) { return CacheRendererBatchEligibility(piece, RendererBatchPieceEligibility.Interactive); } if ((Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null) { return CacheRendererBatchEligibility(piece, RendererBatchPieceEligibility.Animated); } if ((Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) != (Object)null) { return CacheRendererBatchEligibility(piece, RendererBatchPieceEligibility.Effects); } Rigidbody componentInChildren = ((Component)piece).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null && !componentInChildren.isKinematic) { return CacheRendererBatchEligibility(piece, RendererBatchPieceEligibility.Interactive); } string text = NormalizeNameToken(((Object)(object)((Component)piece).gameObject != (Object)null) ? ((Object)((Component)piece).gameObject).name : ((Object)piece).name); string[] cachedRendererBatchExcludedTokens = GetCachedRendererBatchExcludedTokens(); foreach (string value3 in cachedRendererBatchExcludedTokens) { if (text.Contains(value3)) { return CacheRendererBatchEligibility(piece, RendererBatchPieceEligibility.ExcludedByName); } } return CacheRendererBatchEligibility(piece, RendererBatchPieceEligibility.Eligible); } private RendererBatchPieceEligibility CacheRendererBatchEligibility(Piece piece, RendererBatchPieceEligibility eligibility) { if ((Object)(object)piece != (Object)null) { _rendererBatchEligibilityCache[piece] = eligibility; } return eligibility; } private MeshRenderer[] GetCachedRendererBatchRenderers(Piece piece) { if ((Object)(object)piece == (Object)null) { return (MeshRenderer[])(object)new MeshRenderer[0]; } if (!_rendererBatchRendererCache.TryGetValue(piece, out var value)) { value = ((Component)piece).GetComponentsInChildren(true); _rendererBatchRendererCache[piece] = value; } return value; } private RendererBatchRendererEligibility TryCreateRendererBatchSource(Piece piece, MeshRenderer renderer, out RendererBatchSource source) { //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) source = null; if ((Object)(object)renderer == (Object)null || !((Renderer)renderer).enabled || !((Component)renderer).gameObject.activeInHierarchy) { return RendererBatchRendererEligibility.Inactive; } if (((Renderer)renderer).isPartOfStaticBatch) { return RendererBatchRendererEligibility.StaticBatch; } int lodIndex; LODGroup rendererLodGroup = GetRendererLodGroup(renderer, out lodIndex); if ((Object)(object)rendererLodGroup != (Object)null && lodIndex != 0) { return RendererBatchRendererEligibility.Lod; } if (((Renderer)renderer).HasPropertyBlock()) { return RendererBatchRendererEligibility.PropertyBlock; } MeshFilter component = ((Component)renderer).GetComponent(); Mesh val = (((Object)(object)component != (Object)null) ? component.sharedMesh : null); Material[] sharedMaterials = ((Renderer)renderer).sharedMaterials; if ((Object)(object)val == (Object)null || val.vertexCount == 0 || !val.isReadable) { return RendererBatchRendererEligibility.MeshLayout; } if (val.subMeshCount != 1 || sharedMaterials == null || sharedMaterials.Length != 1 || (Object)(object)sharedMaterials[0] == (Object)null) { return RendererBatchRendererEligibility.MeshLayout; } if (!IsOpaqueRendererBatchMaterial(sharedMaterials[0])) { return RendererBatchRendererEligibility.Material; } if (((Renderer)renderer).lightmapIndex >= 0 || ((Renderer)renderer).realtimeLightmapIndex >= 0) { return RendererBatchRendererEligibility.Lightmap; } RendererBatchGroupKey groupKey = new RendererBatchGroupKey { Material = sharedMaterials[0], ShadowCastingMode = ((Renderer)renderer).shadowCastingMode, ReceiveShadows = ((Renderer)renderer).receiveShadows, LightProbeUsage = ((Renderer)renderer).lightProbeUsage, ReflectionProbeUsage = ((Renderer)renderer).reflectionProbeUsage, ProbeAnchor = ((Renderer)renderer).probeAnchor, Layer = ((Component)renderer).gameObject.layer, MotionVectorGenerationMode = ((Renderer)renderer).motionVectorGenerationMode, AllowOcclusionWhenDynamic = ((Renderer)renderer).allowOcclusionWhenDynamic, SortingLayerId = ((Renderer)renderer).sortingLayerID, SortingOrder = ((Renderer)renderer).sortingOrder }; source = new RendererBatchSource { Renderer = renderer, Mesh = val, VertexCount = val.vertexCount, GroupKey = groupKey, LodGroup = rendererLodGroup, Piece = piece }; return RendererBatchRendererEligibility.Eligible; } private LODGroup GetRendererLodGroup(MeshRenderer renderer, out int lodIndex) { lodIndex = -1; if ((Object)(object)renderer == (Object)null) { return null; } if (_rendererLodCache.TryGetValue(renderer, out var value)) { lodIndex = value.LodIndex; return value.LodGroup; } LODGroup componentInParent = ((Component)renderer).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { _rendererLodCache[renderer] = new RendererLodCacheEntry { LodGroup = null, LodIndex = -1 }; return null; } LOD[] lODs = componentInParent.GetLODs(); for (int i = 0; i < lODs.Length; i++) { Renderer[] renderers = lODs[i].renderers; foreach (Renderer val in renderers) { if ((Object)(object)val == (Object)(object)renderer) { lodIndex = i; _rendererLodCache[renderer] = new RendererLodCacheEntry { LodGroup = componentInParent, LodIndex = i }; return componentInParent; } } } _rendererLodCache[renderer] = new RendererLodCacheEntry { LodGroup = null, LodIndex = -1 }; return null; } private void CountRendererBatchPieceExclusion(RendererBatchCell cell, RendererBatchPieceEligibility eligibility) { switch (eligibility) { case RendererBatchPieceEligibility.NonStructural: cell.PiecesExcludedNonStructural++; break; case RendererBatchPieceEligibility.Interactive: cell.PiecesExcludedInteractive++; break; case RendererBatchPieceEligibility.Animated: cell.PiecesExcludedAnimated++; break; case RendererBatchPieceEligibility.Effects: cell.PiecesExcludedEffects++; break; case RendererBatchPieceEligibility.ExcludedByName: cell.PiecesExcludedByName++; break; } } private void CountRendererBatchRendererExclusion(RendererBatchCell cell, RendererBatchRendererEligibility eligibility) { switch (eligibility) { case RendererBatchRendererEligibility.StaticBatch: cell.RenderersExcludedStaticBatch++; break; case RendererBatchRendererEligibility.Lod: cell.RenderersExcludedLod++; break; case RendererBatchRendererEligibility.MeshLayout: cell.RenderersExcludedMeshLayout++; break; case RendererBatchRendererEligibility.Lightmap: cell.RenderersExcludedLightmap++; break; case RendererBatchRendererEligibility.Material: cell.RenderersExcludedMaterial++; break; case RendererBatchRendererEligibility.PropertyBlock: break; } } private bool IsOpaqueRendererBatchMaterial(Material material) { if ((Object)(object)material == (Object)null || (Object)(object)material.shader == (Object)null) { return false; } if (material.renderQueue > 2450) { return false; } string tag = material.GetTag("RenderType", false, string.Empty); if (!string.IsNullOrEmpty(tag) && tag.IndexOf("transparent", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } string text = ((Object)material.shader).name ?? string.Empty; if (text.IndexOf("transparent", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("particle", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("water", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("glass", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } if (material.HasProperty("_Mode") && material.GetFloat("_Mode") > 0.01f) { return false; } if (material.HasProperty("_Surface") && material.GetFloat("_Surface") > 0.01f) { return false; } return true; } private void BuildRendererBatchGroup(RendererBatchCell cell, RendererBatchGroupKey groupKey, List sources) { if (cell == null || sources == null) { return; } int minimumRenderers = Mathf.Max(2, _rendererBatchMinimumRenderers.Value); int num = Mathf.Max(1000, _rendererBatchMaximumVertices.Value); List list = new List(); int num2 = 0; foreach (RendererBatchSource source in sources) { if (source != null && !((Object)(object)source.Renderer == (Object)null) && !((Object)(object)source.Mesh == (Object)null)) { if (list.Count > 0 && num2 + source.VertexCount > num) { TryBuildRendererBatchChunk(cell, groupKey, list, minimumRenderers); list.Clear(); num2 = 0; } list.Add(source); num2 += source.VertexCount; } } TryBuildRendererBatchChunk(cell, groupKey, list, minimumRenderers); } private bool TryBuildRendererBatchChunk(RendererBatchCell cell, RendererBatchGroupKey groupKey, List sources, int minimumRenderers) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: 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_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Expected O, but got Unknown //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) if (cell == null || sources == null || sources.Count < minimumRenderers) { return false; } GameObject rendererBatchCellRoot = GetRendererBatchCellRoot(cell); GameObject val = null; Mesh val2 = null; try { CombineInstance[] array = (CombineInstance[])(object)new CombineInstance[sources.Count]; Matrix4x4 worldToLocalMatrix = rendererBatchCellRoot.transform.worldToLocalMatrix; int num = 0; for (int i = 0; i < sources.Count; i++) { RendererBatchSource rendererBatchSource = sources[i]; int num2 = i; CombineInstance val3 = default(CombineInstance); ((CombineInstance)(ref val3)).mesh = rendererBatchSource.Mesh; ((CombineInstance)(ref val3)).subMeshIndex = 0; ((CombineInstance)(ref val3)).transform = worldToLocalMatrix * ((Component)rendererBatchSource.Renderer).transform.localToWorldMatrix; array[num2] = val3; num += rendererBatchSource.VertexCount; } Mesh val4 = new Mesh(); ((Object)val4).name = string.Format("{0}_{1}_{2}_{3}", "RendererBatch", cell.Key.X, cell.Key.Y, cell.Key.Z); val4.indexFormat = (IndexFormat)(num > 65535); val2 = val4; val2.CombineMeshes(array, true, true, false); val2.RecalculateBounds(); val2.UploadMeshData(true); val = new GameObject("RendererBatch"); val.transform.SetParent(rendererBatchCellRoot.transform, false); val.layer = groupKey.Layer; MeshFilter val5 = val.AddComponent(); val5.sharedMesh = val2; MeshRenderer val6 = val.AddComponent(); ((Renderer)val6).sharedMaterial = groupKey.Material; ((Renderer)val6).shadowCastingMode = groupKey.ShadowCastingMode; ((Renderer)val6).receiveShadows = groupKey.ReceiveShadows; ((Renderer)val6).lightProbeUsage = groupKey.LightProbeUsage; ((Renderer)val6).reflectionProbeUsage = groupKey.ReflectionProbeUsage; ((Renderer)val6).probeAnchor = groupKey.ProbeAnchor; ((Renderer)val6).motionVectorGenerationMode = groupKey.MotionVectorGenerationMode; ((Renderer)val6).allowOcclusionWhenDynamic = groupKey.AllowOcclusionWhenDynamic; ((Renderer)val6).sortingLayerID = groupKey.SortingLayerId; ((Renderer)val6).sortingOrder = groupKey.SortingOrder; RendererBatchOutput item = new RendererBatchOutput { GameObject = val, Renderer = val6, Mesh = val2, VertexCount = num, GroupKey = groupKey, Sources = new List(sources) }; cell.Outputs.Add(item); foreach (RendererBatchSource source in sources) { if (!((Object)(object)source.Renderer == (Object)null)) { if (!cell.OriginalRendererEnabled.ContainsKey(source.Renderer)) { cell.OriginalRendererEnabled[source.Renderer] = ((Renderer)source.Renderer).enabled; } if (!cell.SourceRenderersByPiece.TryGetValue(source.Piece, out var value)) { value = new List(); cell.SourceRenderersByPiece[source.Piece] = value; } value.Add(source.Renderer); if ((Object)(object)source.LodGroup != (Object)null && cell.ForcedLodGroups.Add(source.LodGroup)) { source.LodGroup.ForceLOD(0); } ((Renderer)source.Renderer).enabled = false; } } return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Renderer batch build failed for cell {cell.Key}: {ex.Message}"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } return false; } } private void BuildRendererShadowClusters(RendererBatchCell cell) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Invalid comparison between Unknown and I4 //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Invalid comparison between Unknown and I4 if (cell == null || _enableShadowOptimizationSystem == null || !_enableShadowOptimizationSystem.Value || _enableShadowCasterOptimization == null || !_enableShadowCasterOptimization.Value) { return; } Dictionary> dictionary = new Dictionary>(); foreach (RendererBatchOutput output in cell.Outputs) { if (output == null || (Object)(object)output.Renderer == (Object)null || output.Sources == null || output.Sources.Count == 0 || (int)output.GroupKey.ShadowCastingMode == 0) { continue; } cell.ShadowCastingVisibleBatches++; if ((int)output.GroupKey.ShadowCastingMode != 1) { cell.ShadowExcludedCastingModeBatches++; continue; } Material material = output.GroupKey.Material; if (!IsStrictOpaqueShadowMaterial(material)) { cell.ShadowExcludedMaterialBatches++; continue; } RendererShadowBatchGroupKey key = new RendererShadowBatchGroupKey { Layer = output.GroupKey.Layer, Shader = material.shader, CullMode = (material.HasProperty("_Cull") ? Mathf.RoundToInt(material.GetFloat("_Cull")) : int.MinValue) }; if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List()); } cell.ShadowEligibleVisibleBatches++; value.Add(output); } foreach (KeyValuePair> item in dictionary) { BuildRendererShadowClusterGroup(cell, item.Key, item.Value); } } private bool IsStrictOpaqueShadowMaterial(Material material) { if ((Object)(object)material == (Object)null || (Object)(object)material.shader == (Object)null || !IsOpaqueRendererBatchMaterial(material) || material.renderQueue > 2000) { return false; } string tag = material.GetTag("RenderType", false, string.Empty); return string.IsNullOrEmpty(tag) || string.Equals(tag, "Opaque", StringComparison.OrdinalIgnoreCase); } private void BuildRendererShadowClusterGroup(RendererBatchCell cell, RendererShadowBatchGroupKey groupKey, List visibleOutputs) { if (cell == null || visibleOutputs == null || visibleOutputs.Count == 0) { return; } visibleOutputs.Sort(CompareRendererShadowBatchPosition); int num = Mathf.Max(1000, _shadowClusterMaximumVertices.Value); List list = new List(); int num2 = 0; foreach (RendererBatchOutput visibleOutput in visibleOutputs) { if (visibleOutput != null && visibleOutput.Sources != null && visibleOutput.Sources.Count != 0) { int estimatedShadowVertexCount = GetEstimatedShadowVertexCount(visibleOutput); if (list.Count > 0 && num2 + estimatedShadowVertexCount > num) { TryBuildRendererShadowClusterChunk(cell, groupKey, list); list.Clear(); num2 = 0; } list.Add(visibleOutput); num2 += estimatedShadowVertexCount; } } TryBuildRendererShadowClusterChunk(cell, groupKey, list); } private bool TryBuildRendererShadowClusterChunk(RendererBatchCell cell, RendererShadowBatchGroupKey groupKey, List visibleOutputs) { //IL_05b9: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Expected O, but got Unknown //IL_03dd: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Expected O, but got Unknown if (cell == null || visibleOutputs == null || visibleOutputs.Count == 0) { return false; } int num = Mathf.Max(2, _shadowClusterMinimumVisibleBatches.Value); int num2 = visibleOutputs.Count - 1; if (visibleOutputs.Count < num) { cell.ShadowGroupsBelowMinimum++; cell.ShadowRejectedVisibleBatchCount++; return false; } if (num2 < Mathf.Max(1, _shadowClusterMinimumDrawsSaved.Value)) { cell.ShadowRejectedDrawBenefit++; return false; } List list = new List(); int num3 = 0; foreach (RendererBatchOutput visibleOutput in visibleOutputs) { if (visibleOutput != null && visibleOutput.Sources != null) { list.AddRange(visibleOutput.Sources); num3 += GetEstimatedShadowVertexCount(visibleOutput); } } if (list.Count == 0) { return false; } int num4 = Mathf.Max(1000, _shadowClusterMaximumVertices.Value); int num5 = Mathf.Max(1000, _shadowClusterMaximumVerticesPerDrawSaved.Value); if (num3 > num4 || num3 / Mathf.Max(1, num2) > num5) { cell.ShadowRejectedVertexBenefit++; return false; } if (TryGetShadowClusterBounds(visibleOutputs, out var bounds)) { Vector3 size = ((Bounds)(ref bounds)).size; if (!(((Vector3)(ref size)).magnitude > Mathf.Max(1f, _shadowClusterMaximumBoundsDiagonal.Value))) { GameObject rendererBatchCellRoot = GetRendererBatchCellRoot(cell); GameObject val = null; Mesh val2 = null; RendererShadowBatchOutput rendererShadowBatchOutput = null; List list2 = new List(visibleOutputs.Count); try { Matrix4x4 worldToLocalMatrix = rendererBatchCellRoot.transform.worldToLocalMatrix; List list3 = new List(num3); List list4 = new List(); int num6 = 0; int num7 = 0; foreach (RendererBatchSource item in list) { if (item != null && !((Object)(object)item.Renderer == (Object)null) && !((Object)(object)item.Mesh == (Object)null)) { num7 += item.VertexCount; Matrix4x4 transform = worldToLocalMatrix * ((Component)item.Renderer).transform.localToWorldMatrix; if (ShouldUseSimplifiedShadowGeometry(item)) { AppendShadowBoundsBox(item.Mesh.bounds, transform, list3, list4); num6++; } else if (!TryAppendExactShadowGeometry(item.Mesh, transform, list3, list4)) { throw new InvalidOperationException("Could not read shadow geometry for " + ((Object)item.Mesh).name); } } } if (list3.Count == 0 || list4.Count == 0) { return false; } Mesh val3 = new Mesh(); ((Object)val3).name = string.Format("{0}_{1}_{2}_{3}", "RendererShadowBatch", cell.Key.X, cell.Key.Y, cell.Key.Z); val3.indexFormat = (IndexFormat)(list3.Count > 65535); val2 = val3; val2.SetVertices(list3); val2.SetTriangles(list4, 0, false); val2.RecalculateBounds(); val2.UploadMeshData(true); val = new GameObject("RendererShadowBatch"); val.transform.SetParent(rendererBatchCellRoot.transform, false); val.layer = groupKey.Layer; MeshFilter val4 = val.AddComponent(); val4.sharedMesh = val2; MeshRenderer val5 = val.AddComponent(); ((Renderer)val5).sharedMaterial = visibleOutputs[0].GroupKey.Material; ((Renderer)val5).shadowCastingMode = (ShadowCastingMode)3; ((Renderer)val5).receiveShadows = false; ((Renderer)val5).lightProbeUsage = (LightProbeUsage)0; ((Renderer)val5).reflectionProbeUsage = (ReflectionProbeUsage)0; ((Renderer)val5).motionVectorGenerationMode = (MotionVectorGenerationMode)2; ((Renderer)val5).allowOcclusionWhenDynamic = true; rendererShadowBatchOutput = new RendererShadowBatchOutput { GameObject = val, Mesh = val2, VertexCount = list3.Count }; foreach (RendererBatchOutput visibleOutput2 in visibleOutputs) { if (visibleOutput2 != null && (Object)(object)visibleOutput2.Renderer != (Object)null) { ((Renderer)visibleOutput2.Renderer).shadowCastingMode = (ShadowCastingMode)0; list2.Add(visibleOutput2); } } cell.ShadowOutputs.Add(rendererShadowBatchOutput); cell.ShadowSourceRenderersConsolidated += list.Count; cell.ShadowDrawCallsAvoided += num2; cell.ShadowOriginalVerticesReplaced += num7; cell.ShadowSimplifiedSources += num6; return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Renderer shadow batch build failed for cell {cell.Key}: {ex.Message}"); foreach (RendererBatchOutput item2 in list2) { if (item2 != null && (Object)(object)item2.Renderer != (Object)null) { ((Renderer)item2.Renderer).shadowCastingMode = item2.GroupKey.ShadowCastingMode; } } if (rendererShadowBatchOutput != null) { cell.ShadowOutputs.Remove(rendererShadowBatchOutput); } if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } return false; } } } cell.ShadowRejectedBounds++; return false; } private int CompareRendererShadowBatchPosition(RendererBatchOutput left, RendererBatchOutput right) { //IL_0057: 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_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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) if (left == right) { return 0; } if (left == null || (Object)(object)left.Renderer == (Object)null) { return 1; } if (right == null || (Object)(object)right.Renderer == (Object)null) { return -1; } Bounds bounds = ((Renderer)left.Renderer).bounds; Vector3 center = ((Bounds)(ref bounds)).center; bounds = ((Renderer)right.Renderer).bounds; Vector3 center2 = ((Bounds)(ref bounds)).center; int num = center.x.CompareTo(center2.x); if (num != 0) { return num; } num = center.z.CompareTo(center2.z); return (num != 0) ? num : center.y.CompareTo(center2.y); } private int GetEstimatedShadowVertexCount(RendererBatchOutput output) { if (output == null || output.Sources == null) { return 0; } int num = 0; foreach (RendererBatchSource source in output.Sources) { if (source != null) { num += (ShouldUseSimplifiedShadowGeometry(source) ? 24 : source.VertexCount); } } return num; } private bool ShouldUseSimplifiedShadowGeometry(RendererBatchSource source) { if (_enableSimplifiedStructuralShadowCasters == null || !_enableSimplifiedStructuralShadowCasters.Value || source == null || (Object)(object)source.Piece == (Object)null) { return false; } string text = NormalizeNameToken(((Object)(object)((Component)source.Piece).gameObject != (Object)null) ? ((Object)((Component)source.Piece).gameObject).name : ((Object)source.Piece).name); string[] cachedSimplifiedShadowTokens = GetCachedSimplifiedShadowTokens(); foreach (string value in cachedSimplifiedShadowTokens) { if (text.Contains(value)) { return true; } } return false; } private bool TryGetShadowClusterBounds(List visibleOutputs, out Bounds bounds) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); bool flag = false; foreach (RendererBatchOutput visibleOutput in visibleOutputs) { if (visibleOutput != null && !((Object)(object)visibleOutput.Renderer == (Object)null)) { if (!flag) { bounds = ((Renderer)visibleOutput.Renderer).bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(((Renderer)visibleOutput.Renderer).bounds); } } } return flag; } private bool TryAppendExactShadowGeometry(Mesh mesh, Matrix4x4 transform, List vertices, List triangles) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)mesh == (Object)null || vertices == null || triangles == null) { return false; } if (!_rendererShadowGeometryCache.TryGetValue(mesh, out var value)) { if (!mesh.isReadable) { return false; } Vector3[] vertices2 = mesh.vertices; value = new RendererShadowSourceGeometry { Vertices = vertices2, Triangles = mesh.triangles }; _rendererShadowGeometryCache[mesh] = value; } int count = vertices.Count; for (int i = 0; i < value.Vertices.Length; i++) { vertices.Add(((Matrix4x4)(ref transform)).MultiplyPoint3x4(value.Vertices[i])); } int[] triangles2 = value.Triangles; foreach (int num in triangles2) { triangles.Add(count + num); } return true; } private void AppendShadowBoundsBox(Bounds localBounds, Matrix4x4 transform, List vertices, List triangles) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_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_00c1: 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_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: 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_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) Vector3 min = ((Bounds)(ref localBounds)).min; Vector3 max = ((Bounds)(ref localBounds)).max; Vector3[] array = (Vector3[])(object)new Vector3[8] { new Vector3(min.x, min.y, min.z), new Vector3(max.x, min.y, min.z), new Vector3(max.x, max.y, min.z), new Vector3(min.x, max.y, min.z), new Vector3(min.x, min.y, max.z), new Vector3(max.x, min.y, max.z), new Vector3(max.x, max.y, max.z), new Vector3(min.x, max.y, max.z) }; int[,] array2 = new int[6, 4] { { 0, 3, 2, 1 }, { 4, 5, 6, 7 }, { 0, 4, 7, 3 }, { 1, 2, 6, 5 }, { 0, 1, 5, 4 }, { 3, 7, 6, 2 } }; for (int i = 0; i < 6; i++) { int count = vertices.Count; Vector3 item = ((Matrix4x4)(ref transform)).MultiplyPoint3x4(array[array2[i, 0]]); Vector3 item2 = ((Matrix4x4)(ref transform)).MultiplyPoint3x4(array[array2[i, 1]]); Vector3 item3 = ((Matrix4x4)(ref transform)).MultiplyPoint3x4(array[array2[i, 2]]); Vector3 item4 = ((Matrix4x4)(ref transform)).MultiplyPoint3x4(array[array2[i, 3]]); vertices.Add(item); vertices.Add(item2); vertices.Add(item3); vertices.Add(item4); triangles.Add(count); triangles.Add(count + 1); triangles.Add(count + 2); triangles.Add(count); triangles.Add(count + 2); triangles.Add(count + 3); } } private GameObject GetRendererBatchRoot() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)_rendererBatchRoot == (Object)null) { _rendererBatchRoot = new GameObject("BuildPieceProfiler_RendererBatches"); } return _rendererBatchRoot; } private GameObject GetRendererBatchCellRoot(RendererBatchCell cell) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)cell.Root != (Object)null) { return cell.Root; } cell.Root = new GameObject(string.Format("{0}_{1}_{2}_{3}", "RendererBatchCell", cell.Key.X, cell.Key.Y, cell.Key.Z)); cell.Root.transform.SetParent(GetRendererBatchRoot().transform, false); cell.Root.transform.position = GetRendererBatchCellOrigin(cell.Key); return cell.Root; } private void RestoreRendererBatchCell(RendererBatchCell cell) { if (cell == null) { return; } foreach (KeyValuePair item in cell.OriginalRendererEnabled) { if ((Object)(object)item.Key != (Object)null) { ((Renderer)item.Key).enabled = item.Value; } } cell.OriginalRendererEnabled.Clear(); foreach (LODGroup forcedLodGroup in cell.ForcedLodGroups) { if ((Object)(object)forcedLodGroup != (Object)null) { forcedLodGroup.ForceLOD(-1); } } cell.ForcedLodGroups.Clear(); foreach (Piece key in cell.SourceRenderersByPiece.Keys) { _rendererBatchHighlightedUntil.Remove(key); } cell.SourceRenderersByPiece.Clear(); foreach (RendererShadowBatchOutput shadowOutput in cell.ShadowOutputs) { if (shadowOutput != null) { if ((Object)(object)shadowOutput.GameObject != (Object)null) { shadowOutput.GameObject.SetActive(false); Object.Destroy((Object)(object)shadowOutput.GameObject); } if ((Object)(object)shadowOutput.Mesh != (Object)null) { Object.Destroy((Object)(object)shadowOutput.Mesh); } } } cell.ShadowOutputs.Clear(); foreach (RendererBatchOutput output in cell.Outputs) { if (output != null) { if ((Object)(object)output.GameObject != (Object)null) { output.GameObject.SetActive(false); Object.Destroy((Object)(object)output.GameObject); } if ((Object)(object)output.Mesh != (Object)null) { Object.Destroy((Object)(object)output.Mesh); } } } cell.Outputs.Clear(); } private void DestroyRendererBatchCell(RendererBatchCell cell) { if (cell != null) { RestoreRendererBatchCell(cell); if ((Object)(object)cell.Root != (Object)null) { Object.Destroy((Object)(object)cell.Root); cell.Root = null; } } } private void RemoveDestroyedRendererBatchPieces(RendererBatchCell cell) { if (cell == null || cell.Pieces.Count == 0) { return; } List list = null; foreach (Piece piece in cell.Pieces) { if (!((Object)(object)piece != (Object)null)) { if (list == null) { list = new List(); } list.Add(piece); } } if (list == null) { return; } foreach (Piece item in list) { cell.Pieces.Remove(item); _rendererBatchPieceCells.Remove(item); _rendererBatchPieceExcludedUntil.Remove(item); } } private void RestoreAllRendererBatches() { foreach (RendererBatchCell value in _rendererBatchCells.Values) { DestroyRendererBatchCell(value); } _rendererBatchCells.Clear(); _rendererBatchPieceCells.Clear(); _rendererBatchPieceExcludedUntil.Clear(); _rendererBatchEligibilityCache.Clear(); _rendererBatchRendererCache.Clear(); _rendererLodCache.Clear(); _rendererBatchHighlightedUntil.Clear(); _rendererShadowGeometryCache.Clear(); _rendererBatchDirtyQueue.Clear(); _rendererBatchDiscoveryPieces = null; _rendererBatchDiscoveryIndex = 0; _rendererBatchDiscoveryComplete = false; _rendererBatchMetrics = default(RendererBatchMetrics); if ((Object)(object)_rendererBatchRoot != (Object)null) { Object.Destroy((Object)(object)_rendererBatchRoot); _rendererBatchRoot = null; } } private void NotifyRendererBatchPieceChanged(Piece piece, bool forceRefresh) { if (_enableRendererBatching != null && _enableRendererBatching.Value) { if (forceRefresh) { _rendererBatchEligibilityCache.Remove(piece); _rendererBatchRendererCache.Remove(piece); } TrackRendererBatchPiece(piece, forceRefresh); } } private void NotifyRendererBatchPieceDestroyed(Piece piece) { UntrackRendererBatchPiece(piece); } private void NotifyRendererBatchVisualChange(Piece piece, float exclusionSeconds) { if ((Object)(object)piece == (Object)null || _enableRendererBatching == null || !_enableRendererBatching.Value) { return; } float time = Time.time; float num = time + Mathf.Max(0.1f, exclusionSeconds); float value; bool flag = _rendererBatchPieceExcludedUntil.TryGetValue(piece, out value) && value > time; _rendererBatchPieceExcludedUntil[piece] = Mathf.Max(value, num); if (_rendererBatchPieceCells.TryGetValue(piece, out var value2) && _rendererBatchCells.TryGetValue(value2, out var value3)) { if (!flag) { RestoreRendererBatchCell(value3); } MarkRendererBatchCellDirty(value3); value3.RebuildAfter = Mathf.Max(value3.RebuildAfter, _rendererBatchPieceExcludedUntil[piece]); } } private void NotifyRendererBatchHighlight(Piece piece, float seconds) { if ((Object)(object)piece == (Object)null || _enableRendererBatching == null || !_enableRendererBatching.Value || !_rendererBatchPieceCells.TryGetValue(piece, out var value) || !_rendererBatchCells.TryGetValue(value, out var value2) || !value2.SourceRenderersByPiece.TryGetValue(piece, out var value3)) { return; } float num = Time.time + Mathf.Max(0.1f, seconds); if (_rendererBatchHighlightedUntil.TryGetValue(piece, out var value4) && value4 > Time.time) { _rendererBatchHighlightedUntil[piece] = Mathf.Max(value4, num); return; } bool value5 = default(bool); foreach (MeshRenderer item in value3) { if ((Object)(object)item != (Object)null && value2.OriginalRendererEnabled.TryGetValue(item, out value5) && value5) { ((Renderer)item).enabled = true; } } _rendererBatchHighlightedUntil[piece] = Mathf.Max(value4, num); } private void UpdateRendererBatchHighlights() { if (_rendererBatchHighlightedUntil.Count == 0) { return; } List list = null; float time = Time.time; foreach (KeyValuePair item in _rendererBatchHighlightedUntil) { if (!((Object)(object)item.Key != (Object)null) || !(time < item.Value)) { if (list == null) { list = new List(); } list.Add(item.Key); } } if (list == null) { return; } foreach (Piece item2 in list) { if ((Object)(object)item2 != (Object)null && _rendererBatchPieceCells.TryGetValue(item2, out var value) && _rendererBatchCells.TryGetValue(value, out var value2) && value2.SourceRenderersByPiece.TryGetValue(item2, out var value3)) { foreach (MeshRenderer item3 in value3) { if ((Object)(object)item3 != (Object)null && value2.OriginalRendererEnabled.ContainsKey(item3)) { ((Renderer)item3).enabled = false; } } } _rendererBatchHighlightedUntil.Remove(item2); } } private void RefreshRendererBatchMetrics() { RendererBatchMetrics rendererBatchMetrics = new RendererBatchMetrics { TrackedPieces = _rendererBatchPieceCells.Count, Cells = _rendererBatchCells.Count, DiscoveryRemaining = ((_rendererBatchDiscoveryPieces != null) ? Mathf.Max(0, _rendererBatchDiscoveryPieces.Length - _rendererBatchDiscoveryIndex) : 0) }; foreach (RendererBatchCell value in _rendererBatchCells.Values) { if (value == null) { continue; } if (value.Dirty) { rendererBatchMetrics.DirtyCells++; } rendererBatchMetrics.SourceRenderersDisabled += value.OriginalRendererEnabled.Count; rendererBatchMetrics.CombinedRenderers += value.Outputs.Count; rendererBatchMetrics.EligibleSourceRenderers += value.EligibleRendererCount; rendererBatchMetrics.UnreadableMeshesSkipped += value.UnreadableMeshesSkipped; rendererBatchMetrics.PropertyBlocksSkipped += value.PropertyBlocksSkipped; rendererBatchMetrics.PiecesConsidered += value.PiecesConsidered; rendererBatchMetrics.PiecesEligible += value.PiecesEligible; rendererBatchMetrics.PiecesExcludedNonStructural += value.PiecesExcludedNonStructural; rendererBatchMetrics.PiecesExcludedInteractive += value.PiecesExcludedInteractive; rendererBatchMetrics.PiecesExcludedAnimated += value.PiecesExcludedAnimated; rendererBatchMetrics.PiecesExcludedEffects += value.PiecesExcludedEffects; rendererBatchMetrics.PiecesExcludedByName += value.PiecesExcludedByName; rendererBatchMetrics.RenderersExcludedStaticBatch += value.RenderersExcludedStaticBatch; rendererBatchMetrics.RenderersExcludedLod += value.RenderersExcludedLod; rendererBatchMetrics.RenderersExcludedMeshLayout += value.RenderersExcludedMeshLayout; rendererBatchMetrics.RenderersExcludedLightmap += value.RenderersExcludedLightmap; rendererBatchMetrics.RenderersExcludedMaterial += value.RenderersExcludedMaterial; rendererBatchMetrics.GroupsBelowMinimum += value.GroupsBelowMinimum; rendererBatchMetrics.ShadowCastingVisibleBatches += value.ShadowCastingVisibleBatches; rendererBatchMetrics.ShadowEligibleVisibleBatches += value.ShadowEligibleVisibleBatches; rendererBatchMetrics.ShadowClusterRenderers += value.ShadowOutputs.Count; rendererBatchMetrics.ShadowSourceRenderersConsolidated += value.ShadowSourceRenderersConsolidated; rendererBatchMetrics.ShadowDrawCallsAvoided += value.ShadowDrawCallsAvoided; rendererBatchMetrics.ShadowExcludedCastingModeBatches += value.ShadowExcludedCastingModeBatches; rendererBatchMetrics.ShadowExcludedMaterialBatches += value.ShadowExcludedMaterialBatches; rendererBatchMetrics.ShadowGroupsBelowMinimum += value.ShadowGroupsBelowMinimum; rendererBatchMetrics.ShadowRejectedVisibleBatchCount += value.ShadowRejectedVisibleBatchCount; rendererBatchMetrics.ShadowRejectedDrawBenefit += value.ShadowRejectedDrawBenefit; rendererBatchMetrics.ShadowRejectedVertexBenefit += value.ShadowRejectedVertexBenefit; rendererBatchMetrics.ShadowRejectedBounds += value.ShadowRejectedBounds; rendererBatchMetrics.ShadowOriginalVerticesReplaced += value.ShadowOriginalVerticesReplaced; rendererBatchMetrics.ShadowSimplifiedSources += value.ShadowSimplifiedSources; foreach (RendererBatchOutput output in value.Outputs) { if (output != null) { rendererBatchMetrics.CombinedVertices += output.VertexCount; } } foreach (RendererShadowBatchOutput shadowOutput in value.ShadowOutputs) { if (shadowOutput != null) { rendererBatchMetrics.ShadowClusterVertices += shadowOutput.VertexCount; } } } _rendererBatchMetrics = rendererBatchMetrics; } private void DrawRendererBatchingProfiler() { DrawProfilerSection("renderer-batching", "Spatial Renderer Batching", "Combines many compatible static build-piece meshes into fewer renderers.", delegate { DrawProfilerStat("Enabled", _enableRendererBatching.Value, "Whether spatial renderer batching is enabled."); DrawProfilerStat("Tracked pieces", _rendererBatchMetrics.TrackedPieces, "Loaded pieces registered with the batching system."); DrawProfilerStat("Batch cells", _rendererBatchMetrics.Cells, "Small world-space regions used to keep combined meshes local and easy to rebuild."); DrawProfilerStat("Dirty cells", _rendererBatchMetrics.DirtyCells, "Cells waiting to be rebuilt because a nearby piece changed."); DrawProfilerStat("Discovery remaining", _rendererBatchMetrics.DiscoveryRemaining, "Loaded pieces still waiting for initial batching inspection."); DrawProfilerStat("Source renderers disabled", _rendererBatchMetrics.SourceRenderersDisabled, "Original renderers replaced by combined meshes."); DrawProfilerStat("Combined renderers", _rendererBatchMetrics.CombinedRenderers, "New renderers drawing groups of compatible source meshes."); DrawProfilerStat("Combined vertices", _rendererBatchMetrics.CombinedVertices, "Total vertex count stored in the generated combined meshes."); DrawProfilerStat("Eligible source renderers", _rendererBatchMetrics.EligibleSourceRenderers, "Original renderers that passed the safety and compatibility checks."); DrawProfilerStat("Unreadable meshes skipped", _rendererBatchMetrics.UnreadableMeshesSkipped, "Meshes Unity does not allow the mod to read and combine at runtime."); DrawProfilerStat("Property blocks skipped", _rendererBatchMetrics.PropertyBlocksSkipped, "Renderers with per-object material overrides that cannot safely share a batch."); DrawProfilerStat("Pieces considered / eligible", $"{_rendererBatchMetrics.PiecesConsidered} / {_rendererBatchMetrics.PiecesEligible}", "All inspected pieces compared with pieces safe enough for batching."); DrawProfilerStat("Non-structural exclusions", _rendererBatchMetrics.PiecesExcludedNonStructural, "Objects without the structural components expected on normal building pieces."); DrawProfilerStat("Interactive exclusions", _rendererBatchMetrics.PiecesExcludedInteractive, "Doors, stations, portals, machines, and other pieces that can change or be used."); DrawProfilerStat("Animated exclusions", _rendererBatchMetrics.PiecesExcludedAnimated, "Pieces containing animation components or skinned meshes."); DrawProfilerStat("Effect exclusions", _rendererBatchMetrics.PiecesExcludedEffects, "Pieces containing lights or particle effects."); DrawProfilerStat("Name-filter exclusions", _rendererBatchMetrics.PiecesExcludedByName, "Pieces excluded by the configured safety name list."); DrawProfilerStat("LOD exclusions", _rendererBatchMetrics.RenderersExcludedLod, "Renderers belonging to non-primary distance-detail levels."); DrawProfilerStat("Mesh-layout exclusions", _rendererBatchMetrics.RenderersExcludedMeshLayout, "Meshes with unsupported submeshes, materials, or runtime-readable data."); DrawProfilerStat("Lightmap exclusions", _rendererBatchMetrics.RenderersExcludedLightmap, "Renderers using baked or real-time lightmaps that cannot be safely merged."); DrawProfilerStat("Material exclusions", _rendererBatchMetrics.RenderersExcludedMaterial, "Transparent or otherwise incompatible materials."); DrawProfilerStat("Groups below minimum", _rendererBatchMetrics.GroupsBelowMinimum, "Compatible groups too small to justify creating a combined renderer."); }); DrawProfilerSection("shadow", "Shadow Optimization", "Experimental consolidation of compatible shadow casters. Disabled by default.", delegate { DrawProfilerStat("System enabled", _enableShadowOptimizationSystem.Value, "Master switch for all shadow-cluster work."); DrawProfilerStat("Caster optimization enabled", _enableShadowCasterOptimization.Value, "Whether compatible visible batches may share shadow-only meshes."); DrawProfilerStat("Shadow-casting batches", _rendererBatchMetrics.ShadowCastingVisibleBatches, "Combined renderers that would normally cast real-time shadows."); DrawProfilerStat("Eligible batches", _rendererBatchMetrics.ShadowEligibleVisibleBatches, "Shadow-casting batches that passed material and casting-mode checks."); DrawProfilerStat("Shadow cluster renderers", _rendererBatchMetrics.ShadowClusterRenderers, "Generated shadow-only renderers replacing groups of original shadow casters."); DrawProfilerStat("Shadow cluster vertices", _rendererBatchMetrics.ShadowClusterVertices, "Vertices stored in all generated shadow-only meshes."); DrawProfilerStat("Source renderers consolidated", _rendererBatchMetrics.ShadowSourceRenderersConsolidated, "Original shadow-casting sources represented by the clusters."); DrawProfilerStat("Estimated shadow draws avoided", _rendererBatchMetrics.ShadowDrawCallsAvoided, "Approximate shadow draw calls removed by clustering."); DrawProfilerStat("Estimated casters after", Mathf.Max(0, _rendererBatchMetrics.ShadowCastingVisibleBatches - _rendererBatchMetrics.ShadowDrawCallsAvoided), "Approximate remaining shadow-caster draw count after clustering."); float num = ((_rendererBatchMetrics.ShadowOriginalVerticesReplaced > 0) ? (100f * (float)_rendererBatchMetrics.ShadowClusterVertices / (float)_rendererBatchMetrics.ShadowOriginalVerticesReplaced) : 0f); DrawProfilerStat("Shadow vertex retention", $"{num:F1}%", "Generated shadow vertices as a percentage of the original geometry they replace. Lower is generally better."); DrawProfilerStat("Cached source meshes", _rendererShadowGeometryCache.Count, "Distinct readable meshes cached while building exact shadow geometry."); DrawProfilerStat("Casting-mode exclusions", _rendererBatchMetrics.ShadowExcludedCastingModeBatches, "Batches using a shadow mode that cannot be safely consolidated."); DrawProfilerStat("Material exclusions", _rendererBatchMetrics.ShadowExcludedMaterialBatches, "Batches whose material is not safe for strict opaque shadow clustering."); DrawProfilerStat("Groups below minimum", _rendererBatchMetrics.ShadowGroupsBelowMinimum, "Shadow groups too small to provide the configured minimum benefit."); DrawProfilerStat("Rejected: too few batches", _rendererBatchMetrics.ShadowRejectedVisibleBatchCount, "Groups rejected because they did not contain enough visible source batches."); DrawProfilerStat("Rejected: insufficient savings", _rendererBatchMetrics.ShadowRejectedDrawBenefit, "Groups rejected because too few shadow draw calls would be removed."); DrawProfilerStat("Rejected: vertex cost", _rendererBatchMetrics.ShadowRejectedVertexBenefit, "Groups rejected because the replacement mesh would be too detailed for its expected benefit."); DrawProfilerStat("Rejected: bounds size", _rendererBatchMetrics.ShadowRejectedBounds, "Groups rejected because one large caster would reduce shadow-culling precision too much."); DrawProfilerStat("Original vertices replaced", _rendererBatchMetrics.ShadowOriginalVerticesReplaced, "Original source vertices represented by generated shadow meshes."); DrawProfilerStat("Simplified structural sources", _rendererBatchMetrics.ShadowSimplifiedSources, "Wall or roof sources represented by simplified box-like caster geometry."); }); } private bool ShouldHandleRendererOrStaticSleepEvents() { bool flag = _enableRendererBatching != null && _enableRendererBatching.Value; bool flag2 = _enableStaticComponentSystem != null && _enableStaticComponentSystem.Value && ((_enableStaticComponentProfiling != null && _enableStaticComponentProfiling.Value) || (_enableStaticComponentSleeping != null && _enableStaticComponentSleeping.Value)); return flag || flag2; } private void BindStaticComponentSleepingConfig() { _enableStaticComponentSystem = ((BaseUnityPlugin)this).Config.Bind("Static Component Sleeping", "EnableSystem", false, "Master switch for this experimental system. When off, it performs no scanning, profiling, sleeping, scheduling, or wake-event work."); _enableStaticComponentProfiling = ((BaseUnityPlugin)this).Config.Bind("Static Component Sleeping", "EnableComponentTypeProfiling", false, "Lists build-piece scripts that run every frame or physics tick. This helps identify safe cosmetic scripts before adding them to the sleep list."); _enableStaticComponentSleeping = ((BaseUnityPlugin)this).Config.Bind("Static Component Sleeping", "EnableStaticComponentSleeping", false, "Experimental. Pauses only approved cosmetic scripts on unchanged, non-interactive building pieces."); _staticSleepComponentTypeNames = ((BaseUnityPlugin)this).Config.Bind("Static Component Sleeping", "CosmeticComponentTypeNames", "StaticRotation", "Comma-separated exact script type names allowed to sleep. Built-in safety checks still reject known gameplay and interactive scripts."); _staticSleepDiscoveryPiecesPerUpdate = ((BaseUnityPlugin)this).Config.Bind("Static Component Sleeping", "DiscoveryPiecesPerUpdate", 200, "Maximum build pieces inspected in one frame when the system starts. Lower values make discovery smoother but slower."); _staticSleepWakeGraceSeconds = ((BaseUnityPlugin)this).Config.Bind("Static Component Sleeping", "WakeGraceSeconds", 2f, "Seconds an approved cosmetic script stays awake after its piece is placed, damaged, repaired, changed, or highlighted."); _staticSleepTopTypeCount = ((BaseUnityPlugin)this).Config.Bind("Static Component Sleeping", "TopProfiledTypeCount", 12, "Maximum number of recurring build-piece script types listed in the profiler, ordered by how many enabled copies were found."); } private void UpdateStaticComponentSleeping() { if (_enableStaticComponentSystem == null || !_enableStaticComponentSystem.Value) { if (_staticSleepWorldActive) { ResetStaticComponentSleeping(clearProfile: true); } _staticSleepWorldActive = false; return; } if ((Object)(object)Player.m_localPlayer == (Object)null) { if (_staticSleepWorldActive) { ResetStaticComponentSleeping(clearProfile: true); } _staticSleepWorldActive = false; return; } bool flag = _enableStaticComponentProfiling != null && _enableStaticComponentProfiling.Value; bool flag2 = _enableStaticComponentSleeping != null && _enableStaticComponentSleeping.Value; if (!flag && !flag2) { if (_staticSleepWorldActive) { ResetStaticComponentSleeping(clearProfile: true); } _staticSleepWorldActive = false; return; } if (!_staticSleepWorldActive) { _staticSleepWorldActive = true; _staticSleepSettingsSignature = GetStaticSleepSettingsSignature(); _nextStaticSleepSettingsCheck = Time.unscaledTime + 1f; BeginStaticSleepDiscovery(); } if (Time.unscaledTime >= _nextStaticSleepSettingsCheck) { _nextStaticSleepSettingsCheck = Time.unscaledTime + 1f; int staticSleepSettingsSignature = GetStaticSleepSettingsSignature(); if (staticSleepSettingsSignature != _staticSleepSettingsSignature) { ResetStaticComponentSleeping(clearProfile: true); _staticSleepSettingsSignature = staticSleepSettingsSignature; _staticSleepWorldActive = true; BeginStaticSleepDiscovery(); } } ProcessStaticSleepDiscovery(); if (flag2) { ProcessStaticSleepSchedule(); } else if (_staticSleepPieceStates.Count > 0) { RestoreAllStaticSleepComponents(); } if (ShouldRefreshProfilerMetrics() && Time.unscaledTime >= _nextStaticSleepMetricsRefresh) { _nextStaticSleepMetricsRefresh = Time.unscaledTime + 0.5f; RefreshStaticSleepMetrics(); } } private int GetStaticSleepSettingsSignature() { int num = 17; num = (num * 31) ^ _enableStaticComponentSystem.Value.GetHashCode(); num = (num * 31) ^ _enableStaticComponentProfiling.Value.GetHashCode(); num = (num * 31) ^ _enableStaticComponentSleeping.Value.GetHashCode(); return (num * 31) ^ (_staticSleepComponentTypeNames.Value ?? string.Empty).GetHashCode(); } private HashSet GetCachedStaticSleepTypeNames() { string text = ((_staticSleepComponentTypeNames != null) ? _staticSleepComponentTypeNames.Value : string.Empty); if (!string.Equals(_cachedStaticSleepTypeConfig, text, StringComparison.Ordinal)) { _cachedStaticSleepTypeConfig = text; _cachedStaticSleepTypeNames = new HashSet(StringComparer.OrdinalIgnoreCase); string[] nameTokens = GetNameTokens(text); foreach (string item in nameTokens) { _cachedStaticSleepTypeNames.Add(item); } } return _cachedStaticSleepTypeNames; } private void BeginStaticSleepDiscovery() { _staticSleepDiscoveryPieces = GetLoadedPiecesSnapshot(); _staticSleepDiscoveryIndex = 0; _staticSleepDiscoveryComplete = _staticSleepDiscoveryPieces == null || _staticSleepDiscoveryPieces.Length == 0; } private void ProcessStaticSleepDiscovery() { if (!_staticSleepDiscoveryComplete && _staticSleepDiscoveryPieces != null) { int num = Mathf.Max(1, _staticSleepDiscoveryPiecesPerUpdate.Value); int num2 = Mathf.Min(_staticSleepDiscoveryPieces.Length, _staticSleepDiscoveryIndex + num); while (_staticSleepDiscoveryIndex < num2) { ProfileStaticSleepPiece(_staticSleepDiscoveryPieces[_staticSleepDiscoveryIndex]); _staticSleepDiscoveryIndex++; } if (_staticSleepDiscoveryIndex >= _staticSleepDiscoveryPieces.Length) { _staticSleepDiscoveryComplete = true; _staticSleepDiscoveryPieces = null; } } } private void ProfileStaticSleepPiece(Piece piece) { if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).gameObject == (Object)null || !_staticSleepProfiledPieces.Add(piece)) { return; } MonoBehaviour[] componentsInChildren = ((Component)piece).GetComponentsInChildren(true); bool flag = IsStaticSleepPieceEligible(piece, componentsInChildren); List list = null; List list2 = null; HashSet cachedStaticSleepTypeNames = GetCachedStaticSleepTypeNames(); bool flag2 = _enableStaticComponentSleeping != null && _enableStaticComponentSleeping.Value; bool flag3 = false; MonoBehaviour[] array = componentsInChildren; foreach (MonoBehaviour val in array) { if ((Object)(object)val == (Object)null) { continue; } Type type = ((object)val).GetType(); if (!HasStaticSleepUpdateLoop(type)) { continue; } if (!_staticSleepTypeMetrics.TryGetValue(type, out var value)) { value = new StaticSleepTypeMetrics { Type = type }; _staticSleepTypeMetrics[type] = value; } value.Total++; if (((Behaviour)val).enabled && ((Component)val).gameObject.activeInHierarchy) { value.EnabledAtDiscovery++; } bool flag4 = IsStaticSleepTypeConfigured(type, cachedStaticSleepTypeNames) && IsStaticSleepTypeSafe(type); if (flag4) { value.Whitelisted++; } bool flag5 = flag && flag4 && ((Behaviour)val).enabled; if (list2 == null) { list2 = new List(); } list2.Add(new StaticSleepProfileRecord { TypeMetrics = value, EnabledAtDiscovery = (((Behaviour)val).enabled && ((Component)val).gameObject.activeInHierarchy), Whitelisted = flag4, Candidate = flag5 }); if (!flag5) { continue; } value.Candidates++; flag3 = true; if (flag2) { if (list == null) { list = new List(); } list.Add(new StaticSleepComponentState { Behaviour = val, TypeMetrics = value, OriginalEnabled = true }); } } if (list2 != null && list2.Count > 0) { _staticSleepPieceProfiles[piece] = list2; } if (flag3) { _staticSleepCandidatePieceCount++; } if (list != null && list.Count != 0) { StaticSleepPieceState staticSleepPieceState = new StaticSleepPieceState { Piece = piece, Components = list }; _staticSleepPieceStates[piece] = staticSleepPieceState; ScheduleStaticSleep(staticSleepPieceState, Time.time + Mathf.Max(0.1f, _staticSleepWakeGraceSeconds.Value)); } } private bool IsStaticSleepPieceEligible(Piece piece, MonoBehaviour[] behaviours) { if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).GetComponentInChildren(true) == (Object)null) { return false; } Rigidbody componentInChildren = ((Component)piece).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null && !componentInChildren.isKinematic) { return false; } foreach (MonoBehaviour val in behaviours) { if (!((Object)(object)val == (Object)null)) { string name = ((object)val).GetType().Name; if (StaticSleepGameplayTypeDenylist.Contains(name) && name != "Piece" && name != "WearNTear" && name != "ZNetView") { return false; } } } return true; } private bool HasStaticSleepUpdateLoop(Type type) { if (type == null) { return false; } if (_staticSleepUpdateLoopCache.TryGetValue(type, out var value)) { return value; } Type type2 = type; BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; while (type2 != null && type2 != typeof(MonoBehaviour) && type2 != typeof(Behaviour)) { string[] staticSleepUpdateMethodNames = StaticSleepUpdateMethodNames; foreach (string name in staticSleepUpdateMethodNames) { MethodInfo method = type2.GetMethod(name, bindingAttr, null, Type.EmptyTypes, null); if (method != null && method.ReturnType == typeof(void)) { _staticSleepUpdateLoopCache[type] = true; return true; } } type2 = type2.BaseType; } _staticSleepUpdateLoopCache[type] = false; return false; } private bool IsStaticSleepTypeConfigured(Type type, HashSet configuredTypes) { if (type == null || configuredTypes == null) { return false; } return configuredTypes.Contains(NormalizeNameToken(type.Name)) || configuredTypes.Contains(NormalizeNameToken(type.FullName)); } private bool IsStaticSleepTypeSafe(Type type) { if (type == null || StaticSleepGameplayTypeDenylist.Contains(type.Name)) { return false; } return type != typeof(Piece) && type != typeof(WearNTear) && type != typeof(ZNetView); } private void ScheduleStaticSleep(StaticSleepPieceState state, float sleepAfter) { if (state != null) { state.SleepAfter = Mathf.Max(state.SleepAfter, sleepAfter); if (!state.SchedulePending) { state.Generation++; state.SchedulePending = true; PushStaticSleepSchedule(new StaticSleepScheduleEntry { State = state, Generation = state.Generation, DueTime = state.SleepAfter }); } } } private void ProcessStaticSleepSchedule() { int num = 128; while (num-- > 0 && _staticSleepScheduleHeap.Count > 0) { StaticSleepScheduleEntry staticSleepScheduleEntry = _staticSleepScheduleHeap[0]; if (staticSleepScheduleEntry.DueTime > Time.time) { break; } PopStaticSleepSchedule(); StaticSleepPieceState state = staticSleepScheduleEntry.State; if (state == null || state.Generation != staticSleepScheduleEntry.Generation || (Object)(object)state.Piece == (Object)null) { continue; } state.SchedulePending = false; if (!state.IsSleeping) { if (state.SleepAfter > Time.time) { ScheduleStaticSleep(state, state.SleepAfter); } else { SleepStaticComponents(state); } } } } private void SleepStaticComponents(StaticSleepPieceState state) { if (state == null || (Object)(object)state.Piece == (Object)null || !((Component)state.Piece).gameObject.activeInHierarchy) { return; } int num = 0; foreach (StaticSleepComponentState component in state.Components) { if (component != null && !((Object)(object)component.Behaviour == (Object)null) && ((Behaviour)component.Behaviour).enabled) { ((Behaviour)component.Behaviour).enabled = false; component.DisabledByUs = true; component.TypeMetrics.Sleeping++; num++; } } state.IsSleeping = num > 0; } private void WakeStaticSleepPiece(Piece piece) { if ((Object)(object)piece == (Object)null) { return; } if (!_staticSleepProfiledPieces.Contains(piece)) { ProfileStaticSleepPiece(piece); } if (!_staticSleepPieceStates.TryGetValue(piece, out var value)) { return; } bool flag = false; foreach (StaticSleepComponentState component in value.Components) { if (component != null && !((Object)(object)component.Behaviour == (Object)null) && component.DisabledByUs) { ((Behaviour)component.Behaviour).enabled = component.OriginalEnabled; component.DisabledByUs = false; component.TypeMetrics.Sleeping = Mathf.Max(0, component.TypeMetrics.Sleeping - 1); flag = true; } } value.IsSleeping = false; if (flag) { _staticSleepMetrics.WakeEvents++; } ScheduleStaticSleep(value, Time.time + Mathf.Max(0.1f, _staticSleepWakeGraceSeconds.Value)); } private void UntrackStaticSleepPiece(Piece piece) { if (piece == null) { return; } if (_staticSleepPieceStates.TryGetValue(piece, out var value)) { RestoreStaticSleepState(value); value.Generation++; value.SchedulePending = false; _staticSleepPieceStates.Remove(piece); } if (!_staticSleepProfiledPieces.Remove(piece) || !_staticSleepPieceProfiles.TryGetValue(piece, out var value2)) { return; } bool flag = false; foreach (StaticSleepProfileRecord item in value2) { if (item.TypeMetrics != null) { StaticSleepTypeMetrics typeMetrics = item.TypeMetrics; typeMetrics.Total = Mathf.Max(0, typeMetrics.Total - 1); if (item.EnabledAtDiscovery) { typeMetrics.EnabledAtDiscovery = Mathf.Max(0, typeMetrics.EnabledAtDiscovery - 1); } if (item.Whitelisted) { typeMetrics.Whitelisted = Mathf.Max(0, typeMetrics.Whitelisted - 1); } if (item.Candidate) { flag = true; typeMetrics.Candidates = Mathf.Max(0, typeMetrics.Candidates - 1); } } } if (flag) { _staticSleepCandidatePieceCount = Mathf.Max(0, _staticSleepCandidatePieceCount - 1); } _staticSleepPieceProfiles.Remove(piece); } private void RestoreStaticSleepState(StaticSleepPieceState state) { if (state == null) { return; } foreach (StaticSleepComponentState component in state.Components) { if (component != null && !((Object)(object)component.Behaviour == (Object)null) && component.DisabledByUs) { ((Behaviour)component.Behaviour).enabled = component.OriginalEnabled; component.DisabledByUs = false; component.TypeMetrics.Sleeping = Mathf.Max(0, component.TypeMetrics.Sleeping - 1); } } state.IsSleeping = false; } private void RestoreAllStaticSleepComponents() { foreach (StaticSleepPieceState value in _staticSleepPieceStates.Values) { RestoreStaticSleepState(value); value.Generation++; value.SchedulePending = false; } _staticSleepPieceStates.Clear(); _staticSleepScheduleHeap.Clear(); } private void ResetStaticComponentSleeping(bool clearProfile) { RestoreAllStaticSleepComponents(); _staticSleepDiscoveryPieces = null; _staticSleepDiscoveryIndex = 0; _staticSleepDiscoveryComplete = false; if (clearProfile) { _staticSleepProfiledPieces.Clear(); _staticSleepPieceProfiles.Clear(); _staticSleepTypeMetrics.Clear(); _staticSleepTopTypeSnapshot.Clear(); _staticSleepCandidatePieceCount = 0; _staticSleepMetrics = default(StaticSleepMetrics); } } private void RefreshStaticSleepMetrics() { int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; _staticSleepTopTypeSnapshot.Clear(); foreach (StaticSleepTypeMetrics value in _staticSleepTypeMetrics.Values) { num += value.Total; num2 += value.EnabledAtDiscovery; num3 += value.Candidates; num4 += value.Sleeping; _staticSleepTopTypeSnapshot.Add(new StaticSleepTypeSnapshot { Name = ((value.Type != null) ? value.Type.Name : "Unknown"), Total = value.Total, EnabledAtDiscovery = value.EnabledAtDiscovery, Candidates = value.Candidates, Sleeping = value.Sleeping }); } _staticSleepTopTypeSnapshot.Sort(delegate(StaticSleepTypeSnapshot left, StaticSleepTypeSnapshot right) { int num6 = right.EnabledAtDiscovery.CompareTo(left.EnabledAtDiscovery); return (num6 != 0) ? num6 : string.Compare(left.Name, right.Name, StringComparison.Ordinal); }); int num5 = Mathf.Max(0, _staticSleepTopTypeCount.Value); if (_staticSleepTopTypeSnapshot.Count > num5) { _staticSleepTopTypeSnapshot.RemoveRange(num5, _staticSleepTopTypeSnapshot.Count - num5); } int wakeEvents = _staticSleepMetrics.WakeEvents; _staticSleepMetrics = new StaticSleepMetrics { ProfiledPieces = _staticSleepProfiledPieces.Count, DiscoveryRemaining = ((_staticSleepDiscoveryPieces != null) ? Mathf.Max(0, _staticSleepDiscoveryPieces.Length - _staticSleepDiscoveryIndex) : 0), UpdateLoopComponents = num, EnabledAtDiscovery = num2, CandidateComponents = num3, SleepingComponents = num4, CandidatePieces = _staticSleepCandidatePieceCount, WakeEvents = wakeEvents }; } private void DrawStaticComponentSleepingProfiler() { DrawProfilerSection("static-sleep", "Static Component Sleeping", "Experimental system for pausing approved cosmetic scripts on unchanged building pieces.", delegate { DrawProfilerStat("System enabled", _enableStaticComponentSystem.Value, "Master switch for component discovery, profiling, sleeping, and wake events."); DrawProfilerStat("Type profiling", _enableStaticComponentProfiling.Value, "Whether the mod searches build pieces for scripts with Update, LateUpdate, or FixedUpdate methods."); DrawProfilerStat("Sleeping enabled", _enableStaticComponentSleeping.Value, "Whether approved cosmetic scripts may actually be disabled while their piece is unchanged."); DrawProfilerStat("Profiled pieces", _staticSleepMetrics.ProfiledPieces, "Build pieces already inspected for update-loop scripts."); DrawProfilerStat("Discovery remaining", _staticSleepMetrics.DiscoveryRemaining, "Build pieces still waiting to be inspected."); DrawProfilerStat("Update-loop components", _staticSleepMetrics.UpdateLoopComponents, "Scripts found with a recurring Unity update method."); DrawProfilerStat("Enabled at discovery", _staticSleepMetrics.EnabledAtDiscovery, "Update-loop scripts that were active when first inspected."); DrawProfilerStat("Candidate pieces", _staticSleepMetrics.CandidatePieces, "Pieces containing at least one script that passed all sleeping safety checks."); DrawProfilerStat("Candidate components", _staticSleepMetrics.CandidateComponents, "Individual scripts allowed to sleep by both the whitelist and safety checks."); DrawProfilerStat("Sleeping components", _staticSleepMetrics.SleepingComponents, "Approved scripts currently disabled by the system."); DrawProfilerStat("Wake events", _staticSleepMetrics.WakeEvents, "Times sleeping scripts were restored because a piece changed, took damage, was repaired, or was highlighted."); if (_staticSleepTopTypeSnapshot.Count == 0) { return; } foreach (StaticSleepTypeSnapshot item in _staticSleepTopTypeSnapshot) { DrawProfilerStat(item.Name, $"enabled {item.EnabledAtDiscovery}/{item.Total}, candidates {item.Candidates}, sleeping {item.Sleeping}", "Counts for this script type: total found, originally enabled, approved for sleeping, and currently sleeping."); } }); } private void PushStaticSleepSchedule(StaticSleepScheduleEntry entry) { _staticSleepScheduleHeap.Add(entry); int num = _staticSleepScheduleHeap.Count - 1; while (num > 0) { int num2 = (num - 1) / 2; if (_staticSleepScheduleHeap[num2].DueTime <= entry.DueTime) { break; } _staticSleepScheduleHeap[num] = _staticSleepScheduleHeap[num2]; num = num2; } _staticSleepScheduleHeap[num] = entry; } private StaticSleepScheduleEntry PopStaticSleepSchedule() { StaticSleepScheduleEntry result = _staticSleepScheduleHeap[0]; int index = _staticSleepScheduleHeap.Count - 1; StaticSleepScheduleEntry value = _staticSleepScheduleHeap[index]; _staticSleepScheduleHeap.RemoveAt(index); if (_staticSleepScheduleHeap.Count == 0) { return result; } int num = 0; while (true) { int num2 = num * 2 + 1; if (num2 >= _staticSleepScheduleHeap.Count) { break; } int num3 = num2 + 1; int num4 = ((num3 < _staticSleepScheduleHeap.Count && _staticSleepScheduleHeap[num3].DueTime < _staticSleepScheduleHeap[num2].DueTime) ? num3 : num2); if (_staticSleepScheduleHeap[num4].DueTime >= value.DueTime) { break; } _staticSleepScheduleHeap[num] = _staticSleepScheduleHeap[num4]; num = num4; } _staticSleepScheduleHeap[num] = value; return result; } private void NotifyStaticSleepPieceChanged(Piece piece) { if (_enableStaticComponentSystem != null && _enableStaticComponentSystem.Value && !((Object)(object)Player.m_localPlayer == (Object)null)) { WakeStaticSleepPiece(piece); } } } public class ColliderClusterDamageProxy : MonoBehaviour, IDestructible { private List _sources; internal void Initialize(List sources) { _sources = sources; } public void Damage(HitData hit) { //IL_0013: 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) BuildPieceProfilerPlugin.ColliderClusterSource colliderClusterSource = ResolveSource(hit?.m_point ?? ((Component)this).transform.position); if (colliderClusterSource != null && !((Object)(object)colliderClusterSource.WearNTear == (Object)null)) { if (hit != null) { hit.m_hitCollider = (Collider)(object)colliderClusterSource.Collider; } colliderClusterSource.WearNTear.Damage(hit); } } public DestructibleType GetDestructibleType() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) return (DestructibleType)1; } private BuildPieceProfilerPlugin.ColliderClusterSource ResolveSource(Vector3 point) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) BuildPieceProfilerPlugin.ColliderClusterSource result = null; float num = float.MaxValue; if (_sources == null) { return null; } foreach (BuildPieceProfilerPlugin.ColliderClusterSource source in _sources) { if (source != null && !((Object)(object)source.Collider == (Object)null) && !((Object)(object)source.WearNTear == (Object)null)) { float num2 = ((Bounds)(ref source.WorldBounds)).SqrDistance(point); if (num2 < num) { num = num2; result = source; } } } return result; } }