Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValheimBuildOptimization v0.5.5
plugins/ValheimBuildOptimization.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
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<Light, bool> OriginalLightEnabled = new Dictionary<Light, bool>(); public readonly Dictionary<Light, int> OriginalLightCullingMask = new Dictionary<Light, int>(); public readonly Dictionary<ParticleSystem, bool> OriginalParticlePlaying = new Dictionary<ParticleSystem, bool>(); public readonly Dictionary<ParticleSystem, bool> OriginalParticleEmissionEnabled = new Dictionary<ParticleSystem, bool>(); public readonly Dictionary<MeshRenderer, ShadowCastingMode> OriginalShadowModes = new Dictionary<MeshRenderer, ShadowCastingMode>(); public readonly Dictionary<Behaviour, bool> OriginalBehaviourEnabled = new Dictionary<Behaviour, bool>(); public readonly HashSet<ParticleSystem> ForcedStoppedParticles = new HashSet<ParticleSystem>(); public GameObject ProxyLightObject; public Light ProxyLight; } private struct ClusterProxyKey : IEquatable<ClusterProxyKey> { 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<RaycastHit> { 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<LightOffenderSnapshot> TopLightOffenders; } private enum ColliderClusterPieceEligibility { Eligible, Ineligible, Interactive, NameFilter } private struct ColliderClusterCellKey : IEquatable<ColliderClusterCellKey> { 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<ColliderClusterGroupKey> { 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<ColliderClusterSource> Sources = new List<ColliderClusterSource>(); } private class ColliderClusterOutput { public GameObject GameObject; public BoxCollider Collider; public List<ColliderClusterSource> Sources; public Bounds WorldBounds; } private class ColliderClusterCell { public ColliderClusterCellKey Key; public readonly HashSet<Piece> Pieces = new HashSet<Piece>(); public readonly List<ColliderClusterOutput> Outputs = new List<ColliderClusterOutput>(); 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<MethodBase> 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<MethodBase> 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<Piece>() : 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<Piece>() : 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<Piece>() : 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<Piece>() : null); instance?.NotifyRendererBatchVisualChange(piece, 0.35f); instance?.NotifyStaticSleepPieceChanged(piece); } } } private struct RendererBatchCellKey : IEquatable<RendererBatchCellKey> { 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<RendererBatchGroupKey> { 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<RendererShadowBatchGroupKey> { 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<RendererBatchSource> Sources; } private class RendererShadowBatchOutput { public GameObject GameObject; public Mesh Mesh; public int VertexCount; } private class RendererBatchCell { public RendererBatchCellKey Key; public readonly HashSet<Piece> Pieces = new HashSet<Piece>(); public readonly Dictionary<MeshRenderer, bool> OriginalRendererEnabled = new Dictionary<MeshRenderer, bool>(); public readonly HashSet<LODGroup> ForcedLodGroups = new HashSet<LODGroup>(); public readonly List<RendererBatchOutput> Outputs = new List<RendererBatchOutput>(); public readonly List<RendererShadowBatchOutput> ShadowOutputs = new List<RendererShadowBatchOutput>(); public readonly Dictionary<Piece, List<MeshRenderer>> SourceRenderersByPiece = new Dictionary<Piece, List<MeshRenderer>>(); 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<StaticSleepComponentState> 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<Piece> _loadedPieceRegistry = new HashSet<Piece>(); 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<string> _expandedProfilerSections = new HashSet<string>(); 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<WearNTear, float> WearNTearSupportRef = AccessTools.FieldRefAccess<WearNTear, float>("m_support"); private static readonly FieldRef<WearNTear, bool> WearNTearRainWetRef = AccessTools.FieldRefAccess<WearNTear, bool>("m_rainWet"); private static readonly FieldRef<WearNTear, bool> WearNTearHaveRoofRef = AccessTools.FieldRefAccess<WearNTear, bool>("m_haveRoof"); private static readonly FieldRef<WearNTear, bool> WearNTearHaveAshRoofRef = AccessTools.FieldRefAccess<WearNTear, bool>("m_haveAshRoof"); private static readonly FieldRef<WearNTear, GameObject> WearNTearWetObjectRef = AccessTools.FieldRefAccess<WearNTear, GameObject>("m_wet"); private Counts _counts = default(Counts); private ConfigEntry<bool> _enableProfiler; private ConfigEntry<bool> _enableConsoleLogging; private ConfigEntry<bool> _showProfilerOnStart; private ConfigEntry<bool> _enableAutomaticProfilerPolling; private ConfigEntry<float> _profilerPollInterval; private ConfigEntry<KeyCode> _toggleProfilerKey; private ConfigEntry<bool> _enableFireOptimizations; private ConfigEntry<bool> _enableWearNTearOptimizations; private ConfigEntry<float> _wearNTearBypassSupportValue; private ConfigEntry<string> _knownFirePieceNameTokens; private readonly Dictionary<Piece, FireOptimizationState> _fireStates = new Dictionary<Piece, FireOptimizationState>(); private readonly List<FireCandidate> _fireCandidates = new List<FireCandidate>(); private readonly HashSet<Piece> _fireCandidatePieces = new HashSet<Piece>(); private readonly Stack<GameObject> _proxyLightPool = new Stack<GameObject>(); private readonly HashSet<GameObject> _availableProxyLightObjects = new HashSet<GameObject>(); private readonly HashSet<GameObject> _allProxyLightObjects = new HashSet<GameObject>(); private readonly Dictionary<ClusterProxyKey, ClusterProxyLightState> _clusterProxyLights = new Dictionary<ClusterProxyKey, ClusterProxyLightState>(); private readonly List<ClusterProxyKey> _inactiveClusterProxyKeys = new List<ClusterProxyKey>(); 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<float> _optimizerUpdateInterval; private ConfigEntry<float> _fireCandidateRefreshInterval; private ConfigEntry<bool> _enablePeriodicFireCandidateRefresh; private ConfigEntry<FireOptimizationMode> _fireOptimizationMode; private ConfigEntry<bool> _useFireVisibilityCulling; private ConfigEntry<float> _fireVisibilityGraceSeconds; private ConfigEntry<float> _staticLightIntensityMultiplier; private ConfigEntry<float> _staticLightRangeMultiplier; private ConfigEntry<string> _fireLightUpdateComponentNameTokens; private ConfigEntry<bool> _useClusteredFireProxyLights; private ConfigEntry<float> _clusteredFireProxyCellSize; private ConfigEntry<bool> _useFireOcclusionCulling; private ConfigEntry<bool> _ignoreKnownFirePiecesInFireOcclusion; private ConfigEntry<float> _fireOcclusionRayRadius; private ConfigEntry<float> _fireOcclusionCacheSeconds; private ConfigEntry<int> _maxFireOcclusionChecksPerUpdate; private ConfigEntry<bool> _debugFireOcclusion; private ConfigEntry<float> _fireRestoreGraceSeconds; private ConfigEntry<int> _topLightOffenderCount; private Texture2D _opaqueBackground; private const string ColliderClusterRootName = "BuildPieceProfiler_ColliderClusters"; private const string ColliderClusterCellName = "ColliderClusterCell"; private static readonly HashSet<string> ColliderClusterDeniedTypeNames = new HashSet<string>(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<ColliderClusterCellKey, ColliderClusterCell> _colliderClusterCells = new Dictionary<ColliderClusterCellKey, ColliderClusterCell>(); private readonly Dictionary<Piece, ColliderClusterCellKey> _colliderClusterPieceCells = new Dictionary<Piece, ColliderClusterCellKey>(); private readonly Queue<ColliderClusterCellKey> _colliderClusterDirtyQueue = new Queue<ColliderClusterCellKey>(); private ConfigEntry<bool> _enableColliderClusterSystem; private ConfigEntry<bool> _enableColliderClusterProfiling; private ConfigEntry<bool> _enableColliderClustering; private ConfigEntry<float> _colliderClusterCellSize; private ConfigEntry<int> _colliderClusterMinimumBoxes; private ConfigEntry<int> _colliderClusterMaximumBoxesPerCollider; private ConfigEntry<float> _colliderClusterRestoreDistance; private ConfigEntry<float> _colliderClusterActivationDistance; private ConfigEntry<float> _colliderClusterUpdateInterval; private ConfigEntry<int> _colliderClusterCellsRebuiltPerUpdate; private ConfigEntry<int> _colliderClusterDiscoveryPiecesPerUpdate; private ConfigEntry<string> _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<ZDOID, NetworkPieceState> _networkPieceStatesByZdo = new Dictionary<ZDOID, NetworkPieceState>(); private readonly Dictionary<ZNetView, NetworkPieceState> _networkPieceStatesByView = new Dictionary<ZNetView, NetworkPieceState>(); private readonly Dictionary<string, int> _networkRpcMethodCounts = new Dictionary<string, int>(StringComparer.Ordinal); private readonly List<NetworkPieceState> _networkPieceStateList = new List<NetworkPieceState>(); private readonly List<NetworkWriterSnapshot> _networkTopWriters = new List<NetworkWriterSnapshot>(); private readonly List<NetworkRpcSnapshot> _networkTopRpcMethods = new List<NetworkRpcSnapshot>(); private readonly List<NetworkWriterSnapshot> _networkWriterAggregation = new List<NetworkWriterSnapshot>(); private ConfigEntry<bool> _enableNetworkIdleSystem; private ConfigEntry<bool> _enableNetworkIdleProfiling; private ConfigEntry<int> _networkDiscoveryPiecesPerUpdate; private ConfigEntry<int> _networkRevisionSamplesPerUpdate; private ConfigEntry<float> _networkMetricsInterval; private ConfigEntry<float> _networkActiveWriterSeconds; private ConfigEntry<int> _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<RendererBatchCellKey, RendererBatchCell> _rendererBatchCells = new Dictionary<RendererBatchCellKey, RendererBatchCell>(); private readonly Dictionary<Piece, RendererBatchCellKey> _rendererBatchPieceCells = new Dictionary<Piece, RendererBatchCellKey>(); private readonly Queue<RendererBatchCellKey> _rendererBatchDirtyQueue = new Queue<RendererBatchCellKey>(); private readonly Dictionary<Piece, float> _rendererBatchPieceExcludedUntil = new Dictionary<Piece, float>(); private readonly Dictionary<Mesh, RendererShadowSourceGeometry> _rendererShadowGeometryCache = new Dictionary<Mesh, RendererShadowSourceGeometry>(); private readonly Dictionary<Piece, RendererBatchPieceEligibility> _rendererBatchEligibilityCache = new Dictionary<Piece, RendererBatchPieceEligibility>(); private readonly Dictionary<Piece, MeshRenderer[]> _rendererBatchRendererCache = new Dictionary<Piece, MeshRenderer[]>(); private readonly Dictionary<MeshRenderer, RendererLodCacheEntry> _rendererLodCache = new Dictionary<MeshRenderer, RendererLodCacheEntry>(); private readonly Dictionary<Piece, float> _rendererBatchHighlightedUntil = new Dictionary<Piece, float>(); private ConfigEntry<bool> _enableRendererBatching; private ConfigEntry<bool> _enableShadowOptimizationSystem; private ConfigEntry<float> _rendererBatchCellSize; private ConfigEntry<int> _rendererBatchMinimumRenderers; private ConfigEntry<int> _rendererBatchMaximumVertices; private ConfigEntry<int> _rendererBatchCellsPerUpdate; private ConfigEntry<int> _rendererBatchDiscoveryPiecesPerUpdate; private ConfigEntry<float> _rendererBatchRebuildDelay; private ConfigEntry<string> _rendererBatchExcludedNameTokens; private ConfigEntry<bool> _enableShadowCasterOptimization; private ConfigEntry<int> _shadowClusterMinimumVisibleBatches; private ConfigEntry<int> _shadowClusterMinimumDrawsSaved; private ConfigEntry<int> _shadowClusterMaximumVertices; private ConfigEntry<int> _shadowClusterMaximumVerticesPerDrawSaved; private ConfigEntry<float> _shadowClusterMaximumBoundsDiagonal; private ConfigEntry<bool> _enableSimplifiedStructuralShadowCasters; private ConfigEntry<string> _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<string> StaticSleepGameplayTypeDenylist = new HashSet<string>(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<Piece, StaticSleepPieceState> _staticSleepPieceStates = new Dictionary<Piece, StaticSleepPieceState>(); private readonly HashSet<Piece> _staticSleepProfiledPieces = new HashSet<Piece>(); private readonly Dictionary<Piece, List<StaticSleepProfileRecord>> _staticSleepPieceProfiles = new Dictionary<Piece, List<StaticSleepProfileRecord>>(); private readonly Dictionary<Type, StaticSleepTypeMetrics> _staticSleepTypeMetrics = new Dictionary<Type, StaticSleepTypeMetrics>(); private readonly Dictionary<Type, bool> _staticSleepUpdateLoopCache = new Dictionary<Type, bool>(); private readonly List<StaticSleepScheduleEntry> _staticSleepScheduleHeap = new List<StaticSleepScheduleEntry>(); private readonly List<StaticSleepTypeSnapshot> _staticSleepTopTypeSnapshot = new List<StaticSleepTypeSnapshot>(); private ConfigEntry<bool> _enableStaticComponentSystem; private ConfigEntry<bool> _enableStaticComponentProfiling; private ConfigEntry<bool> _enableStaticComponentSleeping; private ConfigEntry<string> _staticSleepComponentTypeNames; private ConfigEntry<int> _staticSleepDiscoveryPiecesPerUpdate; private ConfigEntry<float> _staticSleepWakeGraceSeconds; private ConfigEntry<int> _staticSleepTopTypeCount; private Piece[] _staticSleepDiscoveryPieces; private int _staticSleepDiscoveryIndex; private bool _staticSleepDiscoveryComplete; private bool _staticSleepWorldActive; private int _staticSleepSettingsSignature; private string _cachedStaticSleepTypeConfig; private HashSet<string> _cachedStaticSleepTypeNames = new HashSet<string>(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<string> list = new List<string>(); 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<Light, bool> 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<Behaviour, bool> 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<MeshRenderer, ShadowCastingMode> originalShadowMode in value.OriginalShadowModes) { if ((Object)(object)originalShadowMode.Key != (Object)null) { ((Renderer)originalShadowMode.Key).shadowCastingMode = originalShadowMode.Value; } } foreach (KeyValuePair<Behaviour, bool> 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<Light, bool> 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<ParticleSystem, bool> 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<MonoBehaviour>(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<MonoBehaviour>(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<KeyValuePair<Piece, FireOptimizationState>> list = new List<KeyValuePair<Piece, FireOptimizationState>>(_fireStates); foreach (KeyValuePair<Piece, FireOptimizationState> 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<KeyValuePair<Piece, FireOptimizationState>> list = null; foreach (KeyValuePair<Piece, FireOptimizationState> fireState in _fireStates) { Piece key = fireState.Key; if (!((Object)(object)key != (Object)null)) { if (list == null) { list = new List<KeyValuePair<Piece, FireOptimizationState>>(); } list.Add(fireState); } } if (list == null) { return; } foreach (KeyValuePair<Piece, FireOptimizationState> item in list) { if (item.Value != null) { ReleaseProxyLightObject(item.Value.ProxyLightObject); } _fireStates.Remove(item.Key); } } private void RefreshFireCandidateCache() { _hasRefreshedFireCandidates = true; _fireCandidateRefreshRequested = false; Dictionary<Piece, FireCandidate> dictionary = new Dictionary<Piece, FireCandidate>(); 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<Fireplace>((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<Piece>(); if ((Object)(object)componentInParent == (Object)null || !((Component)componentInParent).gameObject.activeInHierarchy || _fireCandidatePieces.Contains(componentInParent) || !IsKnownFirePiece(componentInParent, cachedKnownFireNameTokens)) { continue; } Light[] originalLights = GetOriginalLights(((Component)componentInParent).GetComponentsInChildren<Light>(true)); if (originalLights.Length == 0) { continue; } ParticleSystem[] componentsInChildren = ((Component)componentInParent).GetComponentsInChildren<ParticleSystem>(true); if (IsFireCandidate(originalLights, componentsInChildren)) { FireCandidate fireCandidate = new FireCandidate { Piece = componentInParent, Source = val, Renderers = ((Component)componentInParent).GetComponentsInChildren<MeshRenderer>(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<Piece> list = null; foreach (KeyValuePair<Piece, FireOptimizationState> 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<Piece>(); } 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<Piece> list = ((PieceAllPiecesField != null) ? (PieceAllPiecesField.GetValue(null) as List<Piece>) : null); array = ((list != null) ? list.ToArray() : Object.FindObjectsByType<Piece>((FindObjectsSortMode)0)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Piece registry snapshot unavailable: " + ex.Message)); array = Object.FindObjectsByType<Piece>((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<Light>(); _allProxyLightObjects.Add(val); } else { proxyLight = val.GetComponent<Light>(); if ((Object)(object)proxyLight == (Object)null) { proxyLight = val.AddComponent<Light>(); } } ((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<Light>(); 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<ClusterProxyKey, ClusterProxyLightState> 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<Light> list = new List<Light>(); 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<Piece>() != (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<ParticleSystem>() != (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<Piece>(); 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<Light, bool> item in value.OriginalLightEnabled) { if ((Object)(object)item.Key != (Object)null && !IsOurProxyLight(item.Key) && !((Behaviour)item.Key).enabled && item.Value) { metrics.FireOriginalLightsDisabled++; } } foreach (KeyValuePair<ParticleSystem, bool> item2 in value.OriginalParticlePlaying) { if ((Object)(object)item2.Key != (Object)null && item2.Value && !item2.Key.isPlaying) { metrics.FireParticlesStopped++; } } foreach (KeyValuePair<MeshRenderer, ShadowCastingMode> 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<bool>("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<bool>("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<bool>("Profiler", "ShowProfilerOnStart", false, "Opens the profiler window automatically after entering a world. The profiler must also be enabled."); _enableAutomaticProfilerPolling = ((BaseUnityPlugin)this).Config.Bind<bool>("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<float>("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<KeyCode>("Profiler", "ToggleProfilerKey", (KeyCode)288, "Keyboard key that opens or closes the in-game profiler window."); _topLightOffenderCount = ((BaseUnityPlugin)this).Config.Bind<int>("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<bool>("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<FireOptimizationMode>("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<float>("Fire", "UpdateIntervalSeconds", 0.5f, "Seconds between fire visibility checks. Lower values react faster; higher values reduce optimizer CPU work."); _fireVisibilityGraceSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("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<float>("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<bool>("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<float>("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<string>("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<float>("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<bool>("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<string>("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<bool>("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<bool>("Fire Visibility", "CullBehindGeometry", true, "Allows fires behind solid walls, floors, or roofs to use their hidden optimized state."); _ignoreKnow