Decompiled source of ProjectEcho v1.0.6
plugins/ProjectEcho/ProjectEcho.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using ProjectEcho.Analysis; using ProjectEcho.Core; using ProjectEcho.Filters; using ProjectEcho.Hooks; using ProjectEcho.Profiles; using ProjectEcho.Propagation; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ProjectEcho")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+fb9301e81d0f6b5e95330dbec2cbc1d773867bb6")] [assembly: AssemblyProduct("ProjectEcho")] [assembly: AssemblyTitle("ProjectEcho")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ProjectEcho.Propagation { public struct PropagationPath { public bool IsDirectPath; public float Attenuation; public float DiffractionAmount; public PropagationPortal? PortalUsed; public Vector3 VirtualSourcePos; } public class PropagationEngine : MonoBehaviour { private readonly List<PropagationPortal> _portals = new List<PropagationPortal>(); private const float MAX_PORTAL_SEARCH_DIST = 30f; private static event Action<PropagationPortal>? OnPortalRegistered; private static event Action<PropagationPortal>? OnPortalUnregistered; public static void RaisePortalRegistered(PropagationPortal p) { PropagationEngine.OnPortalRegistered?.Invoke(p); } public static void RaisePortalUnregistered(PropagationPortal p) { PropagationEngine.OnPortalUnregistered?.Invoke(p); } private void Awake() { OnPortalRegistered += RegisterPortal; OnPortalUnregistered += UnregisterPortal; } private void OnDestroy() { OnPortalRegistered -= RegisterPortal; OnPortalUnregistered -= UnregisterPortal; } public PropagationPath GetPath(Vector3 sourcePos, Vector3 listenerPos) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: 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_0123: 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_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) PropagationPath result; if (!QualityConfig.EnablePropagation.Value || QualityConfig.Tier.Value == QualityTier.Low) { result = default(PropagationPath); result.IsDirectPath = true; result.Attenuation = 1f; return result; } SpatialAudioManager instance = SpatialAudioManager.Instance; if ((Object)(object)instance == (Object)null) { result = default(PropagationPath); result.IsDirectPath = true; result.Attenuation = 1f; return result; } if (HasDirectLOS(sourcePos, listenerPos, instance.WallLayer)) { result = default(PropagationPath); result.IsDirectPath = true; result.Attenuation = 1f; return result; } PropagationPortal propagationPortal = null; float num = float.MaxValue; for (int num2 = _portals.Count - 1; num2 >= 0; num2--) { if ((Object)(object)_portals[num2] == (Object)null) { _portals.RemoveAt(num2); } else { PropagationPortal propagationPortal2 = _portals[num2]; if (propagationPortal2.IsOpen) { float num3 = Vector3.Distance(sourcePos, propagationPortal2.Center); if (!(num3 > 30f)) { float num4 = Vector3.Distance(propagationPortal2.Center, listenerPos); float num5 = num3 + num4; if (num5 < num && HasDirectLOS(sourcePos, propagationPortal2.Center, instance.WallLayer) && HasDirectLOS(propagationPortal2.Center, listenerPos, instance.WallLayer)) { propagationPortal = propagationPortal2; num = num5; } } } } } if ((Object)(object)propagationPortal == (Object)null) { result = default(PropagationPath); result.IsDirectPath = false; result.Attenuation = 0.03f; result.DiffractionAmount = 0f; return result; } float num6 = Vector3.Distance(sourcePos, listenerPos); float num7 = Mathf.Max(num - num6, 0f); float num8 = Mathf.Clamp(-6f * num7 / 5f, -30f, 0f); float num9 = Mathf.Pow(10f, num8 / 20f) * Mathf.Clamp01(propagationPortal.OpenRatio); result = default(PropagationPath); result.IsDirectPath = false; result.PortalUsed = propagationPortal; result.Attenuation = Mathf.Clamp01(num9); result.DiffractionAmount = Mathf.Abs(num8) / 30f; result.VirtualSourcePos = propagationPortal.Center; return result; } private bool HasDirectLOS(Vector3 a, Vector3 b, LayerMask wall) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) Vector3 val = b - a; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.01f) { return true; } return !Physics.Raycast(a, val / magnitude, magnitude - 0.05f, LayerMask.op_Implicit(wall)); } private void RegisterPortal(PropagationPortal p) { if (!_portals.Contains(p)) { _portals.Add(p); } } private void UnregisterPortal(PropagationPortal p) { _portals.Remove(p); } } public class PropagationPortal : MonoBehaviour { [Header("포탈 설정")] [Tooltip("문이 열려 있는지 여부")] public bool IsOpen = true; [Tooltip("열림 비율 0=닫힘 1=완전 열림")] [Range(0f, 1f)] public float OpenRatio = 1f; public Vector3 Center => ((Component)this).transform.position; private void OnEnable() { PropagationEngine.RaisePortalRegistered(this); } private void OnDisable() { PropagationEngine.RaisePortalUnregistered(this); } public void SetOpen(bool open, float ratio = 1f) { IsOpen = open; OpenRatio = (open ? ratio : 0f); } } } namespace ProjectEcho.Profiles { public readonly struct ReverbParams { public readonly float DryLevel; public readonly float Room; public readonly float RoomHF; public readonly float DecayTime; public readonly float DecayHFRatio; public readonly float ReflectionsLevel; public readonly float ReflectionsDelay; public readonly float ReverbLevel; public readonly float ReverbDelay; public readonly float Diffusion; public readonly float Density; public readonly float LowPassCutoff; public readonly float LowPassResonance; public ReverbParams(float dryLevel, float room, float roomHF, float decayTime, float decayHFRatio, float reflLevel, float reflDelay, float reverbLevel, float reverbDelay, float diffusion, float density, float lpfCutoff, float lpfRes) { DryLevel = dryLevel; Room = room; RoomHF = roomHF; DecayTime = decayTime; DecayHFRatio = decayHFRatio; ReflectionsLevel = reflLevel; ReflectionsDelay = reflDelay; ReverbLevel = reverbLevel; ReverbDelay = reverbDelay; Diffusion = diffusion; Density = density; LowPassCutoff = lpfCutoff; LowPassResonance = lpfRes; } public void ApplyTo(AudioReverbFilter reverb, AudioLowPassFilter lowPass) { if ((Object)(object)reverb != (Object)null) { reverb.reverbPreset = (AudioReverbPreset)27; reverb.dryLevel = DryLevel; reverb.room = Mathf.Clamp(Room, -10000f, 0f); reverb.roomHF = Mathf.Clamp(RoomHF, -10000f, 0f); reverb.decayTime = Mathf.Clamp(DecayTime, 0.1f, 20f); reverb.decayHFRatio = Mathf.Clamp(DecayHFRatio, 0.1f, 2f); reverb.reflectionsLevel = Mathf.Clamp(ReflectionsLevel, -10000f, 1000f); reverb.reflectionsDelay = Mathf.Clamp(ReflectionsDelay, 0f, 0.3f); reverb.reverbLevel = Mathf.Clamp(ReverbLevel, -10000f, 2000f); reverb.reverbDelay = Mathf.Clamp(ReverbDelay, 0f, 0.1f); reverb.diffusion = Mathf.Clamp01(Diffusion); reverb.density = Mathf.Clamp01(Density); } if ((Object)(object)lowPass != (Object)null) { lowPass.cutoffFrequency = Mathf.Clamp(LowPassCutoff, 10f, 22000f); lowPass.lowpassResonanceQ = Mathf.Max(LowPassResonance, 0.1f); } } public ReverbParams WithDecayTime(float rt60) { return new ReverbParams(DryLevel, Room, RoomHF, Mathf.Clamp(rt60, 0.1f, 20f), DecayHFRatio, ReflectionsLevel, ReflectionsDelay, ReverbLevel, ReverbDelay, Diffusion, Density, LowPassCutoff, LowPassResonance); } } public readonly struct OcclusionParams { public readonly float LowPassCutoff; public readonly float LowPassResonance; public readonly float VolumeMultiplier; public OcclusionParams(float cutoff, float res, float volMult) { LowPassCutoff = cutoff; LowPassResonance = res; VolumeMultiplier = volMult; } public void ApplyTo(AudioLowPassFilter lowPass, AudioSource src) { if ((Object)(object)lowPass != (Object)null) { lowPass.cutoffFrequency = Mathf.Clamp(LowPassCutoff, 10f, 22000f); lowPass.lowpassResonanceQ = LowPassResonance; } if ((Object)(object)src != (Object)null) { src.volume = Mathf.Clamp01(src.volume * VolumeMultiplier); } } } public static class AudioParameterPresets { public static readonly ReverbParams SmallMetalRoom = new ReverbParams(0f, -300f, -100f, 0.45f, 1.8f, -200f, 0.007f, -500f, 0.011f, 0.7f, 0.75f, 18000f, 1f); public static readonly ReverbParams NarrowCorridor = new ReverbParams(0f, -200f, -300f, 0.75f, 0.8f, -200f, 0.012f, -400f, 0.022f, 0.55f, 0.85f, 17000f, 1f); public static readonly ReverbParams MediumWarehouse = new ReverbParams(0f, -100f, -600f, 1.8f, 0.5f, -400f, 0.02f, -300f, 0.03f, 0.8f, 1f, 16000f, 1f); public static readonly ReverbParams LargeCavern = new ReverbParams(0f, 0f, -1500f, 5f, 0.15f, -600f, 0.06f, -200f, 0.09f, 0.4f, 0.6f, 12000f, 1f); public static readonly ReverbParams Outdoor = new ReverbParams(0f, -10000f, 0f, 0.1f, 1f, -10000f, 0f, -10000f, 0f, 1f, 1f, 22000f, 1f); public static readonly ReverbParams ShipInterior = new ReverbParams(0f, -1200f, -400f, 0.45f, 1.5f, -700f, 0.009f, -1100f, 0.015f, 0.75f, 0.8f, 17500f, 1f); public static readonly OcclusionParams FullOcclusion = new OcclusionParams(400f, 0.8f, 0.2f); public static readonly OcclusionParams PartialOcclusion = new OcclusionParams(2500f, 1f, 0.55f); public static readonly OcclusionParams LightOcclusion = new OcclusionParams(7000f, 1f, 0.8f); public static ReverbParams GetPresetByVolume(float volumeM3) { if (volumeM3 < 30f) { return SmallMetalRoom; } if (volumeM3 < 80f) { return NarrowCorridor; } if (volumeM3 < 500f) { return MediumWarehouse; } if (volumeM3 < 10000f) { return LargeCavern; } return Outdoor; } } public enum QualityTier { Low, Medium, High } public static class QualityConfig { private struct TierSettings { public int RayCount; public float ScanInterval; public int MaxActiveFilters; public float OcclusionRayDist; public bool UseJobSystem; public float FrameBudgetMs; } public static ConfigEntry<QualityTier> Tier = null; public static ConfigEntry<bool> EnableHRTF = null; public static ConfigEntry<bool> EnablePropagation = null; public static ConfigEntry<bool> EnableOcclusion = null; public static ConfigEntry<float> MasterVolume = null; public static ConfigEntry<float> ReverbIntensity = null; public static ConfigEntry<float> OcclusionStrength = null; public static ConfigEntry<float> RoomTransitionSpeed = null; public static ConfigEntry<bool> DebugMode = null; private static readonly TierSettings[] _settings = new TierSettings[3] { new TierSettings { RayCount = 6, ScanInterval = 1f, MaxActiveFilters = 8, OcclusionRayDist = 20f, UseJobSystem = false, FrameBudgetMs = 0.3f }, new TierSettings { RayCount = 10, ScanInterval = 0.5f, MaxActiveFilters = 16, OcclusionRayDist = 40f, UseJobSystem = false, FrameBudgetMs = 0.5f }, new TierSettings { RayCount = 14, ScanInterval = 0.25f, MaxActiveFilters = 32, OcclusionRayDist = 50f, UseJobSystem = true, FrameBudgetMs = 1f } }; public static int RayCount => _settings[(int)Tier.Value].RayCount; public static float ScanInterval => _settings[(int)Tier.Value].ScanInterval; public static float LerpSpeed => RoomTransitionSpeed.Value; public static int MaxActiveFilters => _settings[(int)Tier.Value].MaxActiveFilters; public static float OcclusionRayDist => _settings[(int)Tier.Value].OcclusionRayDist; public static bool UseJobSystem => _settings[(int)Tier.Value].UseJobSystem; public static float FrameBudgetMs => _settings[(int)Tier.Value].FrameBudgetMs; public static void Bind(ConfigFile cfg) { Tier = cfg.Bind<QualityTier>("Quality", "QualityTier", QualityTier.Medium, "Low / Medium / High — Low is recommended for older hardware"); EnableHRTF = cfg.Bind<bool>("Features", "EnableHRTF", true, "Enable 3D HRTF spatialization (disable for performance)"); EnablePropagation = cfg.Bind<bool>("Features", "EnablePropagation", true, "Enable door/corridor diffraction"); EnableOcclusion = cfg.Bind<bool>("Features", "EnableOcclusion", true, "Enable wall occlusion low-pass filter"); MasterVolume = cfg.Bind<float>("Audio", "MasterVolume", 1f, "Overall volume multiplier (0.0 - 2.0)"); ReverbIntensity = cfg.Bind<float>("Audio", "ReverbIntensity", 1f, "Reverb strength multiplier. 1.0 = default, 2.0 = stronger, 0.5 = subtler"); OcclusionStrength = cfg.Bind<float>("Audio", "OcclusionStrength", 1f, "Occlusion LPF strength. 1.0 = default, 0.0 = disabled"); RoomTransitionSpeed = cfg.Bind<float>("Audio", "RoomTransitionSpeed", 3.5f, "How fast audio blends when moving between rooms. Higher = faster (1.0 - 10.0)"); DebugMode = cfg.Bind<bool>("Debug", "DebugMode", false, "Enable verbose logging (RT60, floor material) to BepInEx console"); if (SystemInfo.systemMemorySize < 4096 && Tier.Value == QualityTier.Medium) { Tier.Value = QualityTier.Low; ProjectEchoPlugin.Log.LogWarning((object)"[Project Echo] Low RAM detected — quality tier set to Low automatically"); } } } } namespace ProjectEcho.Hooks { internal static class SpatialHookHelper { private static readonly Dictionary<int, float> _cooldown = new Dictionary<int, float>(32); private const float COOLDOWN_SEC = 0.08f; private const float CLEANUP_INTERVAL = 30f; private static float _lastCleanup = 0f; private static readonly HashSet<int> EXCLUDED_LAYERS = new HashSet<int> { 5, 6 }; private static readonly string[] EXCLUDED_KEYWORDS = new string[6] { "bgm", "music", "menu", "ambient2d", "ui_sfx", "stinger" }; public static void Process(AudioSource src) { //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)src == (Object)null || (Object)(object)((Component)src).gameObject == (Object)null) { return; } SpatialAudioManager instance = SpatialAudioManager.Instance; if ((Object)(object)instance == (Object)null || EXCLUDED_LAYERS.Contains(((Component)src).gameObject.layer)) { return; } string text = ((Object)((Component)src).gameObject).name.ToLowerInvariant(); string[] eXCLUDED_KEYWORDS = EXCLUDED_KEYWORDS; foreach (string value in eXCLUDED_KEYWORDS) { if (text.Contains(value)) { return; } } if (src.spatialBlend < 0.01f) { src.spatialBlend = 1f; } int instanceID = ((Object)src).GetInstanceID(); float time = Time.time; if (_cooldown.TryGetValue(instanceID, out var value2) && time - value2 < 0.08f) { return; } _cooldown[instanceID] = time; if (time - _lastCleanup > 30f) { _lastCleanup = time; CleanupCooldown(time); } FilterPool component = ((Component)instance).GetComponent<FilterPool>(); if ((Object)(object)component == (Object)null) { return; } AudioFilterController orCreate = component.GetOrCreate(((Component)src).gameObject); if ((Object)(object)orCreate == (Object)null) { return; } RoomProfile currentRoom = instance.CurrentRoom; if (!currentRoom.IsValid) { return; } SoundCategory soundCategory = AudioSourceClassifier.Classify(src); orCreate.SetCategory(soundCategory); bool occluded = QualityConfig.EnableOcclusion.Value && OcclusionProcessor.CheckOcclusion(((Component)src).transform.position, instance.PlayerPosition); orCreate.ApplyRoomReverb(currentRoom); orCreate.ApplyOcclusion(occluded, ((Component)src).transform.position); if (soundCategory == SoundCategory.Walkie) { orCreate.ApplyWalkieBandLimit(); } if (QualityConfig.EnableHRTF.Value) { orCreate.ApplyHRTF(((Component)src).transform.position); } if (!QualityConfig.EnablePropagation.Value) { return; } PropagationEngine component2 = ((Component)instance).GetComponent<PropagationEngine>(); if (!((Object)(object)component2 != (Object)null)) { return; } PropagationPath path = component2.GetPath(((Component)src).transform.position, instance.PlayerPosition); if (!path.IsDirectPath && path.Attenuation < 0.99f) { if (QualityConfig.MasterVolume.Value < 0.99f) { src.volume = Mathf.Clamp01(src.volume * QualityConfig.MasterVolume.Value); } if (path.DiffractionAmount > 0.1f) { orCreate.ApplyOcclusion(occluded: true, ((Component)src).transform.position); } } } private static void CleanupCooldown(float now) { List<int> list = new List<int>(8); foreach (KeyValuePair<int, float> item in _cooldown) { if (now - item.Value > 60f) { list.Add(item.Key); } } foreach (int item2 in list) { _cooldown.Remove(item2); } } public static void ClearCache() { _cooldown.Clear(); AudioSourceClassifier.ClearCache(); } } [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { })] internal static class Patch_Play { [HarmonyPrefix] private static void Prefix(AudioSource __instance) { SpatialHookHelper.Process(__instance); } } [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { typeof(ulong) })] internal static class Patch_Play_Delay { [HarmonyPrefix] private static void Prefix(AudioSource __instance) { SpatialHookHelper.Process(__instance); } } [HarmonyPatch(typeof(AudioSource), "PlayOneShot", new Type[] { typeof(AudioClip) })] internal static class Patch_PlayOneShot_Clip { [HarmonyPrefix] private static void Prefix(AudioSource __instance, AudioClip clip) { if ((Object)(object)clip != (Object)null) { SpatialHookHelper.Process(__instance); } } } [HarmonyPatch(typeof(AudioSource), "PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] internal static class Patch_PlayOneShot_ClipVol { [HarmonyPrefix] private static void Prefix(AudioSource __instance, AudioClip clip) { if ((Object)(object)clip != (Object)null) { SpatialHookHelper.Process(__instance); } } } public static class EntranceTriggerHook { [HarmonyPatch] internal static class Patch_EntranceTeleport { [HarmonyTargetMethod] private static MethodBase? TargetMethod() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType("EntranceTeleport"); if (!(type == null)) { MethodInfo method = type.GetMethod("TeleportPlayer", BindingFlags.Instance | BindingFlags.Public); if (method != null) { return method; } } } return null; } [HarmonyPostfix] private static void Postfix(object __instance) { try { Type type = __instance.GetType(); FieldInfo fieldInfo = null; string[] array = new string[3] { "isEntranceToBuilding", "isEntrance", "entranceToBuilding" }; string[] array2 = array; foreach (string name in array2) { fieldInfo = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null) { break; } } if (fieldInfo == null) { ProjectEchoPlugin.Log.LogWarning((object)"[EntranceTriggerHook] Could not find 'isEntranceToBuilding' field. LC may have been updated. Entrance detection disabled."); } else if ((bool)(fieldInfo.GetValue(__instance) ?? ((object)false))) { EntranceTriggerHook.OnPlayerEnteredFacility?.Invoke(); } else { EntranceTriggerHook.OnPlayerExitedFacility?.Invoke(); } } catch (Exception ex) { ProjectEchoPlugin.Log.LogWarning((object)("[EntranceTriggerHook] Exception: " + ex.Message)); } } } public static event Action? OnPlayerEnteredFacility; public static event Action? OnPlayerExitedFacility; } public static class SceneHook { public static void Register() { SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; EntranceTriggerHook.OnPlayerEnteredFacility += OnEntranceTransition; EntranceTriggerHook.OnPlayerExitedFacility += OnExitTransition; } public static void Unregister() { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; EntranceTriggerHook.OnPlayerEnteredFacility -= OnEntranceTransition; EntranceTriggerHook.OnPlayerExitedFacility -= OnExitTransition; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown ProjectEchoPlugin.Log.LogInfo((object)("[SceneHook] Scene loaded: " + ((Scene)(ref scene)).name)); if (!SpatialAudioManager.IsAlive()) { ProjectEchoPlugin.Log.LogWarning((object)"[SceneHook] SpatialAudioManager missing — recreating"); GameObject val = new GameObject("[ProjectEcho]"); val.AddComponent<SpatialAudioManager>(); val.AddComponent<RoomAnalyzer>(); val.AddComponent<PropagationEngine>(); val.AddComponent<FilterPool>(); Object.DontDestroyOnLoad((Object)(object)val); } } private static void OnSceneUnloaded(Scene scene) { ProjectEchoPlugin.Log.LogInfo((object)("[SceneHook] Scene unloaded: " + ((Scene)(ref scene)).name)); OcclusionProcessor.ClearCache(); } private static void OnEntranceTransition() { ProjectEchoPlugin.Log.LogInfo((object)"[SceneHook] Entrance transition — forcing room scan"); Object.FindObjectOfType<RoomAnalyzer>()?.ForceScan(); } private static void OnExitTransition() { ProjectEchoPlugin.Log.LogInfo((object)"[SceneHook] Exit transition — forcing outdoor profile"); SpatialAudioManager.Instance?.ForceOutdoorProfile(); Object.FindObjectOfType<RoomAnalyzer>()?.ForceScan(); } } } namespace ProjectEcho.Filters { [RequireComponent(typeof(AudioSource))] public class AudioFilterController : MonoBehaviour { private const float LPF_MIN = 10f; private const float LPF_MAX = 22000f; private const float HPF_MIN = 10f; private const float CHANGE_THRESHOLD = 0.5f; private const float REVERB_THRESHOLD = 1f; private const float DECAY_THRESHOLD = 0.01f; private AudioSource? _source; private AudioReverbFilter? _reverb; private AudioLowPassFilter? _lowPass; private AudioHighPassFilter? _highPass; private float _targetLPF = 22000f; private float _targetHPF = 10f; private float _targetDecayTime = 0.1f; private float _targetDecayHF = 1f; private float _targetRoomGain = -10000f; private float _targetRoomHF; private float _targetReflLevel = -10000f; private float _targetDryLevel; private float _targetReverbLvl; private bool _initialized; private bool _isOccluded; private float _occlusionVolMult = 1f; private float _originalVolume = 1f; private bool _settled = true; private SoundCategory _category; private AudioSource? _reflectionSource; private AudioLowPassFilter? _reflectionLPF; private AudioHighPassFilter? _reflectionHPF; private GameObject? _reflectionGO; private float _reflectionTimer; private const float REFLECTION_INTERVAL = 0.3f; private void Awake() { _source = ((Component)this).GetComponent<AudioSource>(); if (!((Object)(object)_source == (Object)null)) { int layer = ((Component)this).gameObject.layer; if (layer == LayerMask.NameToLayer("UI") || layer == 5) { ((Behaviour)this).enabled = false; return; } _reverb = ((Component)this).GetComponent<AudioReverbFilter>() ?? ((Component)this).gameObject.AddComponent<AudioReverbFilter>(); _lowPass = ((Component)this).GetComponent<AudioLowPassFilter>() ?? ((Component)this).gameObject.AddComponent<AudioLowPassFilter>(); _highPass = ((Component)this).GetComponent<AudioHighPassFilter>() ?? ((Component)this).gameObject.AddComponent<AudioHighPassFilter>(); _reverb.reverbPreset = (AudioReverbPreset)27; _reverb.decayTime = 0.1f; _reverb.decayHFRatio = 1f; _reverb.room = -10000f; _reverb.roomHF = 0f; _reverb.reflectionsLevel = -10000f; _reverb.reverbLevel = 0f; _reverb.dryLevel = 0f; _reverb.diffusion = 1f; _reverb.density = 1f; _lowPass.cutoffFrequency = 22000f; _lowPass.lowpassResonanceQ = 1f; _highPass.cutoffFrequency = 10f; _highPass.highpassResonanceQ = 1f; _originalVolume = _source.volume; _initialized = true; } } public void SetCategory(SoundCategory cat) { _category = cat; } public void ApplyRoomReverb(RoomProfile room) { if (!_initialized || (Object)(object)_reverb == (Object)null || !room.IsValid) { return; } float num = Mathf.Clamp(QualityConfig.ReverbIntensity.Value, 0f, 3f); float categoryReverbMult = GetCategoryReverbMult(); float categoryDecayMult = GetCategoryDecayMult(); float num2 = Mathf.Lerp(room.RoomGain, 0f, Mathf.Clamp01(num - 1f)); float num3 = Mathf.Lerp(-10000f, room.RoomGain, Mathf.Clamp01(num)); float num4 = ((num >= 1f) ? num2 : num3) * categoryReverbMult; _targetDecayTime = Mathf.Clamp(room.RT60 * Mathf.Max(num, 0.1f) * categoryDecayMult, 0.1f, 20f); _targetRoomGain = Mathf.Clamp(num4, -10000f, 0f); _targetRoomHF = Mathf.Clamp(room.HighFreqDamping, -10000f, 0f); _targetDecayHF = Mathf.Clamp(room.DecayHFRatio, 0.1f, 2f); _targetReflLevel = ((_category == SoundCategory.Walkie) ? (-10000f) : Mathf.Clamp(room.ReflectionsLevel, -10000f, 1000f)); float num5 = Mathf.InverseLerp(10f, 2000f, room.Volume); _targetDryLevel = ((_category == SoundCategory.Walkie) ? 0f : Mathf.Lerp(0f, -500f, num5 * categoryReverbMult)); _targetReverbLvl = ((_category == SoundCategory.Walkie) ? (-10000f) : Mathf.Lerp(-3000f, 0f, num5 * categoryReverbMult)); _reverb.reflectionsDelay = Mathf.Clamp(room.EarlyReflectionDelay, 0f, 0.3f); _reverb.reverbDelay = Mathf.Clamp(room.LateReverbDelay, 0f, 0.1f); _reverb.diffusion = Mathf.Clamp01(room.Diffusion); _reverb.density = Mathf.Clamp01(room.Density); if ((Object)(object)_highPass != (Object)null) { if (_category != SoundCategory.Footstep) { float materialHardness = room.MaterialHardness; _targetHPF = Mathf.Lerp(10f, 400f, materialHardness * materialHardness); _highPass.highpassResonanceQ = Mathf.Lerp(1f, 3.5f, materialHardness); } else { _targetHPF = 10f; } } _reflectionTimer = 0.3f; _settled = false; } public void ApplyOcclusion(bool occluded, Vector3 sourcePos) { //IL_001e: 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_0028: Unknown result type (might be due to invalid IL or missing references) if (!_initialized || (Object)(object)_source == (Object)null) { return; } _isOccluded = occluded; float num = Vector3.Distance(sourcePos, SpatialAudioManager.Instance?.PlayerPosition ?? sourcePos); if (!QualityConfig.EnableOcclusion.Value) { ApplyAirAbsorption(num); _occlusionVolMult = 1f; _settled = false; return; } float num2 = Mathf.Clamp01(QualityConfig.OcclusionStrength.Value); if (occluded) { float num3 = ((_category == SoundCategory.Monster) ? 1.3f : 1f); float num4 = Mathf.Lerp(300f, 700f, Mathf.InverseLerp(30f, 2f, num)) / num3; _targetLPF = Mathf.Lerp(22000f, num4, num2); _targetHPF = 10f; _occlusionVolMult = Mathf.Lerp(1f, 0.2f / num3, num2); } else { ApplyAirAbsorption(num); _occlusionVolMult = 1f; } _settled = false; } private void ApplyAirAbsorption(float dist) { float num = _category switch { SoundCategory.Footstep => 1.5f, SoundCategory.Monster => 1.2f, SoundCategory.Machine => 0.6f, SoundCategory.Ambient => 0.4f, SoundCategory.Walkie => 0f, _ => 1f, }; if (!(num <= 0f)) { float num2 = Mathf.InverseLerp(2f, 35f, dist); _targetLPF = Mathf.Lerp(22000f, 3000f, num2 * num2 * num); } } public void ApplyHRTF(Vector3 srcPos) { //IL_006f: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (!_initialized || (Object)(object)_source == (Object)null || !QualityConfig.EnableHRTF.Value || _category == SoundCategory.Walkie || _category == SoundCategory.Ambient) { return; } _source.spatialBlend = 1f; _source.spatialize = true; _source.spatializePostEffects = false; SpatialAudioManager instance = SpatialAudioManager.Instance; if (!((Object)(object)instance == (Object)null)) { Vector3 val = srcPos - instance.PlayerPosition; float num = Mathf.Atan2(val.y, ((Vector3)(ref val)).magnitude) * 57.29578f; float num2 = Mathf.Lerp(-400f, 400f, Mathf.InverseLerp(-45f, 45f, num)); if ((Object)(object)_reverb != (Object)null) { _reverb.roomHF = Mathf.Clamp(_targetRoomHF + num2, -10000f, 0f); } _settled = false; } } public void ApplyWalkieBandLimit() { if (_category == SoundCategory.Walkie) { _targetLPF = 3400f; _targetHPF = 300f; if ((Object)(object)_highPass != (Object)null) { _highPass.highpassResonanceQ = 1.5f; } _settled = false; } } private void UpdateReflectionSource(RoomProfile room) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Expected O, but got Unknown if ((Object)(object)_source == (Object)null || (Object)(object)_source.clip == (Object)null || !_source.isPlaying || QualityConfig.Tier.Value == QualityTier.Low || _category == SoundCategory.Walkie || _category == SoundCategory.Footstep) { return; } SpatialAudioManager instance = SpatialAudioManager.Instance; if ((Object)(object)instance == (Object)null) { return; } Vector3 position = ((Component)this).transform.position; Vector3 position2 = position; float num = float.MaxValue; string tag = "Default"; Vector3[] array = (Vector3[])(object)new Vector3[4] { Vector3.right, Vector3.left, Vector3.forward, Vector3.back }; Vector3[] array2 = array; RaycastHit val2 = default(RaycastHit); foreach (Vector3 val in array2) { if (Physics.Raycast(position, val, ref val2, 20f, LayerMask.op_Implicit(instance.WallLayer)) && ((RaycastHit)(ref val2)).distance < num) { num = ((RaycastHit)(ref val2)).distance; position2 = ((RaycastHit)(ref val2)).point; Collider collider = ((RaycastHit)(ref val2)).collider; object obj; if (collider == null) { obj = null; } else { PhysicMaterial sharedMaterial = collider.sharedMaterial; obj = ((sharedMaterial != null) ? ((Object)sharedMaterial).name : null); } if (obj == null) { Collider collider2 = ((RaycastHit)(ref val2)).collider; obj = ((collider2 != null) ? ((Component)collider2).gameObject.tag : null) ?? "Default"; } tag = (string)obj; } } if (!(num > 19f) && !(num < 0.5f)) { if ((Object)(object)_reflectionGO == (Object)null) { _reflectionGO = new GameObject($"[Echo_Refl_{((Object)this).GetInstanceID()}]"); _reflectionSource = _reflectionGO.AddComponent<AudioSource>(); _reflectionLPF = _reflectionGO.AddComponent<AudioLowPassFilter>(); _reflectionHPF = _reflectionGO.AddComponent<AudioHighPassFilter>(); _reflectionSource.spatialBlend = 1f; _reflectionSource.rolloffMode = (AudioRolloffMode)1; _reflectionSource.minDistance = 0.5f; _reflectionSource.maxDistance = 30f; _reflectionSource.loop = false; _reflectionSource.playOnAwake = false; } _reflectionGO.transform.position = position2; float hardnessFromTag = GetHardnessFromTag(tag); if ((Object)(object)_reflectionLPF != (Object)null) { _reflectionLPF.cutoffFrequency = Mathf.Lerp(3000f, 22000f, hardnessFromTag); _reflectionLPF.lowpassResonanceQ = Mathf.Lerp(1f, 2.5f, hardnessFromTag); } if ((Object)(object)_reflectionHPF != (Object)null) { _reflectionHPF.cutoffFrequency = Mathf.Lerp(10f, 200f, hardnessFromTag * 0.5f); _reflectionHPF.highpassResonanceQ = 1f; } if ((Object)(object)_reflectionSource != (Object)null && !_reflectionSource.isPlaying) { float volume = Mathf.Lerp(0.08f, 0.28f, hardnessFromTag * Mathf.InverseLerp(5f, 500f, room.Volume)); _reflectionSource.clip = _source.clip; _reflectionSource.volume = volume; _reflectionSource.PlayDelayed(num / 343f); } } } private static float GetHardnessFromTag(string tag) { if (string.IsNullOrEmpty(tag)) { return 0.5f; } string text = tag.ToLowerInvariant(); if (text.Contains("metal") || text.Contains("steel")) { return 1f; } if (text.Contains("glass")) { return 0.95f; } if (text.Contains("concrete") || text.Contains("brick")) { return 0.8f; } if (text.Contains("wood")) { return 0.5f; } if (text.Contains("carpet")) { return 0.05f; } if (text.Contains("dirt") || text.Contains("grass")) { return 0.15f; } return 0.5f; } public void ResetToDefault() { if (!_initialized) { return; } _targetLPF = 22000f; _targetHPF = 10f; _targetDecayTime = 0.1f; _targetDecayHF = 1f; _targetRoomGain = -10000f; _targetRoomHF = 0f; _targetReflLevel = -10000f; _targetDryLevel = 0f; _targetReverbLvl = 0f; _occlusionVolMult = 1f; _settled = false; if ((Object)(object)_reverb != (Object)null) { _reverb.decayTime = 0.1f; _reverb.room = -10000f; } if ((Object)(object)_lowPass != (Object)null) { _lowPass.cutoffFrequency = 22000f; } if ((Object)(object)_highPass != (Object)null) { _highPass.cutoffFrequency = 10f; } if ((Object)(object)_source != (Object)null) { _source.volume = _originalVolume; } if ((Object)(object)_reflectionGO != (Object)null) { AudioSource? reflectionSource = _reflectionSource; if (reflectionSource != null) { reflectionSource.Stop(); } Object.Destroy((Object)(object)_reflectionGO); _reflectionGO = null; _reflectionSource = null; _reflectionLPF = null; _reflectionHPF = null; } } private void Update() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 if (!_initialized) { return; } if ((Object)(object)_source != (Object)null && !_source.isPlaying) { _reflectionTimer = 0f; } else { if (_settled) { return; } float num = Time.deltaTime * QualityConfig.LerpSpeed; bool flag = false; if ((Object)(object)_reverb != (Object)null && (int)_reverb.reverbPreset != 27) { _reverb.reverbPreset = (AudioReverbPreset)27; } if ((Object)(object)_lowPass != (Object)null) { float num2 = Mathf.Lerp(_lowPass.cutoffFrequency, _targetLPF, num); if (Mathf.Abs(num2 - _lowPass.cutoffFrequency) > 0.5f) { _lowPass.cutoffFrequency = Mathf.Clamp(num2, 10f, 22000f); flag = true; } } if ((Object)(object)_highPass != (Object)null) { float num3 = Mathf.Lerp(_highPass.cutoffFrequency, _targetHPF, num); if (Mathf.Abs(num3 - _highPass.cutoffFrequency) > 0.5f) { _highPass.cutoffFrequency = Mathf.Clamp(num3, 10f, 22000f); flag = true; } } if ((Object)(object)_reverb != (Object)null) { float num4 = Mathf.Lerp(_reverb.decayTime, _targetDecayTime, num); if (Mathf.Abs(num4 - _reverb.decayTime) > 0.01f) { _reverb.decayTime = Mathf.Clamp(num4, 0.1f, 20f); flag = true; } float num5 = Mathf.Lerp(_reverb.decayHFRatio, _targetDecayHF, num); if (Mathf.Abs(num5 - _reverb.decayHFRatio) > 0.01f) { _reverb.decayHFRatio = Mathf.Clamp(num5, 0.1f, 2f); flag = true; } float num6 = Mathf.Lerp(_reverb.room, _targetRoomGain, num); if (Mathf.Abs(num6 - _reverb.room) > 1f) { _reverb.room = Mathf.Clamp(num6, -10000f, 0f); flag = true; } float num7 = Mathf.Lerp(_reverb.reflectionsLevel, _targetReflLevel, num); if (Mathf.Abs(num7 - _reverb.reflectionsLevel) > 1f) { _reverb.reflectionsLevel = Mathf.Clamp(num7, -10000f, 1000f); flag = true; } float num8 = Mathf.Lerp(_reverb.dryLevel, _targetDryLevel, num); if (Mathf.Abs(num8 - _reverb.dryLevel) > 1f) { _reverb.dryLevel = Mathf.Clamp(num8, -10000f, 0f); flag = true; } float num9 = Mathf.Lerp(_reverb.reverbLevel, _targetReverbLvl, num); if (Mathf.Abs(num9 - _reverb.reverbLevel) > 1f) { _reverb.reverbLevel = Mathf.Clamp(num9, -10000f, 2000f); flag = true; } } if ((Object)(object)_source != (Object)null) { float num10 = (_isOccluded ? (_originalVolume * _occlusionVolMult) : _originalVolume); float num11 = Mathf.Lerp(_source.volume, num10, num); if (Mathf.Abs(num11 - _source.volume) > 0.001f) { _source.volume = Mathf.Clamp01(num11); flag = true; } } if (!flag) { _settled = true; } if (!(_reflectionTimer > 0f)) { return; } _reflectionTimer -= Time.deltaTime; if (_reflectionTimer <= 0f) { SpatialAudioManager instance = SpatialAudioManager.Instance; if ((Object)(object)instance != (Object)null) { UpdateReflectionSource(instance.CurrentRoom); } } } } private void OnDestroy() { _initialized = false; if ((Object)(object)_reflectionGO != (Object)null) { Object.Destroy((Object)(object)_reflectionGO); } } private float GetCategoryReverbMult() { return _category switch { SoundCategory.Machine => 1.4f, SoundCategory.Ambient => 1.6f, SoundCategory.Footstep => 0.8f, SoundCategory.Walkie => 0f, SoundCategory.Monster => 1.1f, _ => 1f, }; } private float GetCategoryDecayMult() { return _category switch { SoundCategory.Machine => 1.3f, SoundCategory.Ambient => 1.5f, SoundCategory.Footstep => 0.7f, SoundCategory.Walkie => 0f, _ => 1f, }; } } public enum SoundCategory { General, Footstep, Monster, Machine, Walkie, Player, Ambient } public static class AudioSourceClassifier { private static readonly Dictionary<int, SoundCategory> _cache = new Dictionary<int, SoundCategory>(128); private static readonly (string[] keys, SoundCategory cat)[] NAME_RULES = new(string[], SoundCategory)[6] { (new string[6] { "footstep", "step", "walk", "run", "land", "feet" }, SoundCategory.Footstep), (new string[17] { "enemy", "monster", "creature", "beast", "spider", "crawler", "bracken", "thumper", "hoarding", "baboon", "eyeless", "ghost", "nutcracker", "coilhead", "snare", "masked", "jester" }, SoundCategory.Monster), (new string[12] { "machine", "engine", "fan", "vent", "pipe", "steam", "factory", "generator", "electricity", "buzz", "hum", "drone" }, SoundCategory.Machine), (new string[5] { "walkie", "radio", "transmit", "static", "comms" }, SoundCategory.Walkie), (new string[10] { "player", "breathe", "breath", "heartbeat", "hurt", "damage", "grab", "drop", "interact", "helmet" }, SoundCategory.Player), (new string[6] { "ambient", "wind", "rain", "atmo", "atmosphere", "background" }, SoundCategory.Ambient) }; private static readonly (string typeName, SoundCategory cat)[] TYPE_RULES = new(string, SoundCategory)[5] { ("EnemyAI", SoundCategory.Monster), ("WalkieTalkie", SoundCategory.Walkie), ("PlayerControllerB", SoundCategory.Player), ("AnimatedObjectTrigger", SoundCategory.Machine), ("SoundManager", SoundCategory.Ambient) }; public static SoundCategory Classify(AudioSource src) { if ((Object)(object)src == (Object)null) { return SoundCategory.General; } int instanceID = ((Object)src).GetInstanceID(); if (_cache.TryGetValue(instanceID, out var value)) { return value; } SoundCategory soundCategory = ClassifyInternal(src); _cache[instanceID] = soundCategory; return soundCategory; } private static SoundCategory ClassifyInternal(AudioSource src) { string text = ((Object)((Component)src).gameObject).name.ToLowerInvariant(); string text2 = (((Object)(object)src.clip != (Object)null) ? ((Object)src.clip).name.ToLowerInvariant() : ""); (string[], SoundCategory)[] nAME_RULES = NAME_RULES; for (int i = 0; i < nAME_RULES.Length; i++) { (string[], SoundCategory) tuple = nAME_RULES[i]; string[] item = tuple.Item1; SoundCategory item2 = tuple.Item2; string[] array = item; foreach (string value in array) { if (text.Contains(value) || text2.Contains(value)) { return item2; } } } Transform parent = ((Component)src).transform.parent; for (int k = 0; k < 2; k++) { if (!((Object)(object)parent != (Object)null)) { break; } string text3 = ((Object)parent).name.ToLowerInvariant(); (string[], SoundCategory)[] nAME_RULES2 = NAME_RULES; for (int l = 0; l < nAME_RULES2.Length; l++) { (string[], SoundCategory) tuple2 = nAME_RULES2[l]; string[] item3 = tuple2.Item1; SoundCategory item4 = tuple2.Item2; string[] array2 = item3; foreach (string value2 in array2) { if (text3.Contains(value2)) { return item4; } } } parent = parent.parent; } GameObject gameObject = ((Component)((Component)src).transform.root).gameObject; MonoBehaviour[] componentsInChildren = gameObject.GetComponentsInChildren<MonoBehaviour>(true); MonoBehaviour[] array3 = componentsInChildren; foreach (MonoBehaviour val in array3) { if ((Object)(object)val == (Object)null) { continue; } string name = ((object)val).GetType().Name; (string, SoundCategory)[] tYPE_RULES = TYPE_RULES; for (int num = 0; num < tYPE_RULES.Length; num++) { var (text4, result) = tYPE_RULES[num]; if (name == text4) { return result; } } } return SoundCategory.General; } public static void ClearCache() { _cache.Clear(); } } public class FilterPool : MonoBehaviour { private struct PoolEntry { public AudioFilterController? Ctrl; public float LastAccess; } [CompilerGenerated] private sealed class <PeriodicCleanup>d__8 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public FilterPool <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <PeriodicCleanup>d__8(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown int num = <>1__state; FilterPool filterPool = <>4__this; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; filterPool._toRemove.Clear(); foreach (KeyValuePair<int, PoolEntry> item in filterPool._pool) { if ((Object)(object)item.Value.Ctrl == (Object)null) { filterPool._toRemove.Add(item.Key); } } foreach (int item2 in filterPool._toRemove) { filterPool._pool.Remove(item2); } if (filterPool._toRemove.Count > 0) { ProjectEchoPlugin.Log.LogDebug((object)$"[FilterPool] {filterPool._toRemove.Count} stale entries removed"); } } else { <>1__state = -1; } <>2__current = (object)new WaitForSeconds(30f); <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly Dictionary<int, PoolEntry> _pool = new Dictionary<int, PoolEntry>(32); private readonly List<int> _toRemove = new List<int>(8); private Coroutine? _cleanupCoroutine; private void Start() { _cleanupCoroutine = ((MonoBehaviour)this).StartCoroutine(PeriodicCleanup()); } private void OnDestroy() { if (_cleanupCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_cleanupCoroutine); } } public AudioFilterController? GetOrCreate(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } int instanceID = ((Object)go).GetInstanceID(); float time = Time.time; if (_pool.TryGetValue(instanceID, out var value)) { if ((Object)(object)value.Ctrl != (Object)null) { if (time - value.LastAccess > 0.5f) { _pool[instanceID] = new PoolEntry { Ctrl = value.Ctrl, LastAccess = time }; } return value.Ctrl; } _pool.Remove(instanceID); } if (_pool.Count >= QualityConfig.MaxActiveFilters) { EvictLRU(); } AudioFilterController audioFilterController = go.GetComponent<AudioFilterController>() ?? go.AddComponent<AudioFilterController>(); _pool[instanceID] = new PoolEntry { Ctrl = audioFilterController, LastAccess = time }; return audioFilterController; } public void ClearAll() { foreach (KeyValuePair<int, PoolEntry> item in _pool) { item.Value.Ctrl?.ResetToDefault(); } _pool.Clear(); } [IteratorStateMachine(typeof(<PeriodicCleanup>d__8))] private IEnumerator PeriodicCleanup() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <PeriodicCleanup>d__8(0) { <>4__this = this }; } private void EvictLRU() { int num = -1; float num2 = float.MaxValue; foreach (KeyValuePair<int, PoolEntry> item in _pool) { if (item.Value.LastAccess < num2) { num2 = item.Value.LastAccess; num = item.Key; } } if (num != -1) { _pool[num].Ctrl?.ResetToDefault(); _pool.Remove(num); } } } [RequireComponent(typeof(AudioSource))] public class HRTFController : MonoBehaviour { private const float AIR_ABSORPTION_COEFF = 0.0002f; private const float PROXIMITY_DIST = 1.5f; private const float PROXIMITY_DB_BOOST = 6f; private const float REAR_HF_ROLLOFF = -800f; private AudioSource? _source; private AudioReverbFilter? _reverb; private AudioLowPassFilter? _lowPass; private float _cachedElevation; private float _cachedAzimuth; private float _cachedDistance = 5f; private float _smoothedHFAdj; private float _smoothedLFAdj; private float _nextUpdateTime; private const float UPDATE_INTERVAL = 0.05f; private static Transform? _cachedListenerTransform; private void Awake() { _source = ((Component)this).GetComponent<AudioSource>(); _reverb = ((Component)this).GetComponent<AudioReverbFilter>(); _lowPass = ((Component)this).GetComponent<AudioLowPassFilter>(); } private void OnEnable() { if (QualityConfig.Tier.Value == QualityTier.Low || !QualityConfig.EnableHRTF.Value) { ((Behaviour)this).enabled = false; } else if ((Object)(object)_source != (Object)null) { _source.spatialBlend = 1f; _source.spatialize = true; _source.spatializePostEffects = false; _source.dopplerLevel = 0.3f; _source.rolloffMode = (AudioRolloffMode)2; _source.minDistance = 1f; _source.maxDistance = 50f; } } private void Update() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (!(Time.time < _nextUpdateTime)) { _nextUpdateTime = Time.time + 0.05f; SpatialAudioManager instance = SpatialAudioManager.Instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)_source == (Object)null)) { UpdateSpatialParams(instance.PlayerPosition); } } } private void UpdateSpatialParams(Vector3 listenerPos) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0099: 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_009c: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Component)this).transform.position - listenerPos; _cachedDistance = ((Vector3)(ref val)).magnitude; if (!(_cachedDistance < 0.01f)) { Vector3 val2 = val / _cachedDistance; Vector2 val3 = new Vector2(val2.x, val2.z); float magnitude = ((Vector2)(ref val3)).magnitude; _cachedElevation = Mathf.Atan2(val2.y, magnitude) * 57.29578f; float num = Mathf.Lerp(-600f, 600f, Mathf.InverseLerp(-45f, 45f, _cachedElevation)); Vector3 listenerForward = GetListenerForward(); float num2 = Mathf.InverseLerp(90f, 180f, Mathf.Abs(_cachedAzimuth = Vector3.SignedAngle(listenerForward, val2, Vector3.up))); float num3 = -800f * num2; float num4 = (0f - _cachedDistance) * 0.0002f * 10000f; num4 = Mathf.Clamp(num4, -2000f, 0f); float num5 = 0f; if (_cachedDistance < 1.5f) { float num6 = 1f - _cachedDistance / 1.5f; num5 = 6f * num6 * 100f; } float num7 = Mathf.Clamp(num + num3 + num4, -5000f, 600f); float num8 = num5; _smoothedHFAdj = Mathf.Lerp(_smoothedHFAdj, num7, Time.deltaTime * QualityConfig.LerpSpeed); _smoothedLFAdj = Mathf.Lerp(_smoothedLFAdj, num8, Time.deltaTime * QualityConfig.LerpSpeed); ApplyToFilters(); } } private void ApplyToFilters() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 if ((Object)(object)_reverb != (Object)null && (int)_reverb.reverbPreset == 27) { float num = _reverb.roomHF - _smoothedHFAdj; _reverb.roomHF = Mathf.Clamp(num + _smoothedHFAdj, -10000f, 0f); } if ((Object)(object)_lowPass != (Object)null) { float num2 = Mathf.Lerp(22000f, 8000f, Mathf.InverseLerp(1f, 50f, _cachedDistance)); float cutoffFrequency = _lowPass.cutoffFrequency; float num3 = Mathf.Min(cutoffFrequency, num2); _lowPass.cutoffFrequency = Mathf.Clamp(num3, 10f, 22000f); } } private static Vector3 GetListenerForward() { //IL_0042: 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) if ((Object)(object)_cachedListenerTransform == (Object)null) { AudioListener val = Object.FindObjectOfType<AudioListener>(); _cachedListenerTransform = (((Object)(object)val != (Object)null) ? ((Component)val).transform : null); } if (!((Object)(object)_cachedListenerTransform != (Object)null)) { return Vector3.forward; } return _cachedListenerTransform.forward; } public HRTFDebugInfo GetDebugInfo() { HRTFDebugInfo result = default(HRTFDebugInfo); result.Elevation = _cachedElevation; result.Azimuth = _cachedAzimuth; result.Distance = _cachedDistance; result.HFAdjust = _smoothedHFAdj; result.LFAdjust = _smoothedLFAdj; return result; } } public struct HRTFDebugInfo { public float Elevation; public float Azimuth; public float Distance; public float HFAdjust; public float LFAdjust; } public static class OcclusionProcessor { private struct CacheEntry { public bool Result; public float Time; public int Frame; } private static readonly Dictionary<int, CacheEntry> _cache = new Dictionary<int, CacheEntry>(64); private static readonly Dictionary<int, bool> _frameCache = new Dictionary<int, bool>(32); private static int _lastFrameCacheFrame = -1; private const float CACHE_TTL = 0.15f; private const float SKIP_DIST_SQR = 4f; private static int _frameSkip = 0; public static bool CheckOcclusion(Vector3 sourcePos, Vector3 listenerPos) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: 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_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) SpatialAudioManager instance = SpatialAudioManager.Instance; if ((Object)(object)instance == (Object)null) { return false; } if (!QualityConfig.EnableOcclusion.Value) { return false; } Vector3 val = sourcePos - listenerPos; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < 4f) { return false; } int hashCode = ((object)(Vector3)(ref sourcePos)).GetHashCode(); int frameCount = Time.frameCount; if (_lastFrameCacheFrame != frameCount) { _frameCache.Clear(); _lastFrameCacheFrame = frameCount; } if (_frameCache.TryGetValue(hashCode, out var value)) { return value; } if (QualityConfig.Tier.Value == QualityTier.Low) { _frameSkip++; if (_frameSkip % 2 != 0) { return GetCached(sourcePos, listenerPos); } } int key = ((object)(Vector3)(ref sourcePos)).GetHashCode() ^ (((object)(Vector3)(ref listenerPos)).GetHashCode() << 2); float time = Time.time; if (_cache.TryGetValue(key, out var value2) && time - value2.Time < 0.15f) { _frameCache[hashCode] = value2.Result; return value2.Result; } float num = Mathf.Sqrt(sqrMagnitude); if (num < 0.01f) { return false; } Vector3 val2 = (listenerPos - sourcePos) / num; bool flag = Physics.Raycast(sourcePos, val2, num - 0.1f, LayerMask.op_Implicit(instance.WallLayer)); _cache[key] = new CacheEntry { Result = flag, Time = time, Frame = frameCount }; _frameCache[hashCode] = flag; return flag; } private static bool GetCached(Vector3 sourcePos, Vector3 listenerPos) { int key = ((object)(Vector3)(ref sourcePos)).GetHashCode() ^ (((object)(Vector3)(ref listenerPos)).GetHashCode() << 2); if (_cache.TryGetValue(key, out var value)) { return value.Result; } return false; } public static void ClearCache() { _cache.Clear(); _frameCache.Clear(); } } } namespace ProjectEcho.Core { public class PlayerTracker : MonoBehaviour { [CompilerGenerated] private sealed class <FindPlayerLoop>d__18 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public PlayerTracker <>4__this; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FindPlayerLoop>d__18(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown int num = <>1__state; PlayerTracker playerTracker = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if ((Object)(object)playerTracker._playerTransform == (Object)null) { if (playerTracker._findAttempts >= 20) { ProjectEchoPlugin.Log.LogWarning((object)"[PlayerTracker] Could not find player after max attempts. Search stopped."); return false; } playerTracker._findAttempts++; playerTracker._playerTransform = FindLocalPlayer(); if ((Object)(object)playerTracker._playerTransform != (Object)null) { ProjectEchoPlugin.Log.LogInfo((object)("[PlayerTracker] Player found: " + ((Object)playerTracker._playerTransform).name)); } <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; } <>2__current = (object)new WaitForSeconds(2f); <>1__state = 2; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <FindShipLoop>d__20 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public PlayerTracker <>4__this; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FindShipLoop>d__20(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown int num = <>1__state; PlayerTracker playerTracker = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if ((Object)(object)playerTracker._shipCollider == (Object)null) { GameObject val = GameObject.Find("HangarShip"); if ((Object)(object)val != (Object)null) { playerTracker._shipCollider = val.GetComponentInChildren<Collider>(); if ((Object)(object)playerTracker._shipCollider != (Object)null) { ProjectEchoPlugin.Log.LogInfo((object)"[PlayerTracker] Ship collider found"); } } <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; } <>2__current = (object)new WaitForSeconds(5f); <>1__state = 2; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string LC_PLAYER_TYPE = "GameNetcodeStuff.PlayerControllerB"; private const string LC_SHIP_NAME = "HangarShip"; private const int MAX_FIND_ATTEMPTS = 20; private Transform? _playerTransform; private Coroutine? _searchCoroutine; private Coroutine? _shipCoroutine; private int _findAttempts; private Collider? _shipCollider; public Vector3 Position { get; private set; } public bool IsInsideShip { get; private set; } private void Start() { _searchCoroutine = ((MonoBehaviour)this).StartCoroutine(FindPlayerLoop()); _shipCoroutine = ((MonoBehaviour)this).StartCoroutine(FindShipLoop()); } private void Update() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_playerTransform == (Object)null) { Position = Vector3.zero; return; } Position = _playerTransform.position; if ((Object)(object)_shipCollider != (Object)null) { Bounds bounds = _shipCollider.bounds; IsInsideShip = ((Bounds)(ref bounds)).Contains(Position); } } [IteratorStateMachine(typeof(<FindPlayerLoop>d__18))] private IEnumerator FindPlayerLoop() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FindPlayerLoop>d__18(0) { <>4__this = this }; } private static Transform? FindLocalPlayer() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); Assembly[] array = assemblies; foreach (Assembly assembly in array) { Type type = assembly.GetType("GameNetcodeStuff.PlayerControllerB"); if (type == null) { continue; } Object[] array2 = Object.FindObjectsOfType(type); Object[] array3 = array2; foreach (Object val in array3) { MonoBehaviour val2 = (MonoBehaviour)(object)((val is MonoBehaviour) ? val : null); if (!((Object)(object)val2 == (Object)null)) { PropertyInfo propertyInfo = type.GetProperty("IsOwner") ?? type.GetProperty("isPlayerControlled"); if (!(propertyInfo != null)) { return ((Component)val2).transform; } if ((bool)(propertyInfo.GetValue(val) ?? ((object)false))) { return ((Component)val2).transform; } } } } GameObject val3 = GameObject.FindWithTag("Player"); if ((Object)(object)val3 != (Object)null) { return val3.transform; } if ((Object)(object)Camera.main != (Object)null) { return ((Component)Camera.main).transform; } return null; } [IteratorStateMachine(typeof(<FindShipLoop>d__20))] private IEnumerator FindShipLoop() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FindShipLoop>d__20(0) { <>4__this = this }; } private void OnDestroy() { if (_searchCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_searchCoroutine); } if (_shipCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_shipCoroutine); } } } [BepInPlugin("com.yourname.projectecho", "Project Echo", "1.0.6")] [BepInProcess("Lethal Company.exe")] public class ProjectEchoPlugin : BaseUnityPlugin { public const string GUID = "com.yourname.projectecho"; public const string NAME = "Project Echo"; public const string VERSION = "1.0.6"; private Harmony? _harmony; private GameObject? _systemRoot; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"[Project Echo] v1.0.6 initializing..."); QualityConfig.Bind(((BaseUnityPlugin)this).Config); Log.LogInfo((object)string.Format("[{0}] Quality tier: {1}", "Project Echo", QualityConfig.Tier.Value)); if (!SpatialAudioManager.IsAlive()) { _systemRoot = new GameObject("[ProjectEcho_Root]"); _systemRoot.AddComponent<SpatialAudioManager>(); _systemRoot.AddComponent<RoomAnalyzer>(); _systemRoot.AddComponent<PropagationEngine>(); _systemRoot.AddComponent<FilterPool>(); _systemRoot.AddComponent<HRTFController>(); Object.DontDestroyOnLoad((Object)(object)_systemRoot); Log.LogInfo((object)"[Project Echo] System root created"); } _harmony = new Harmony("com.yourname.projectecho"); _harmony.PatchAll(typeof(ProjectEchoPlugin).Assembly); SceneHook.Register(); Log.LogInfo((object)"[Project Echo] Initialization complete"); } private void OnDestroy() { SceneHook.Unregister(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)_systemRoot != (Object)null) { Object.Destroy((Object)(object)_systemRoot); } Log.LogInfo((object)"[Project Echo] Patches unloaded"); } } public class SpatialAudioManager : MonoBehaviour { [CompilerGenerated] private sealed class <FastTransitionCoroutine>d__33 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public SpatialAudioManager <>4__this; private float <elapsed>5__2; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FastTransitionCoroutine>d__33(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; SpatialAudioManager spatialAudioManager = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <elapsed>5__2 = 0f; break; case 1: <>1__state = -1; break; } if (<elapsed>5__2 < 0.5f) { spatialAudioManager._roomTransitionT = Mathf.MoveTowards(spatialAudioManager._roomTransitionT, 1f, Time.deltaTime * QualityConfig.LerpSpeed * 3f); <elapsed>5__2 += Time.deltaTime; <>2__current = null; <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static SpatialAudioManager? _instance; private Vector3 _lastScanPosition; private const float SCAN_MOVE_THRESHOLD = 0.5f; private RoomProfile _currentRoom = RoomProfile.Default; private RoomProfile _previousRoom = RoomProfile.Default; private float _roomTransitionT = 1f; private PlayerTracker? _playerTracker; private FilterPool? _filterPool; public static SpatialAudioManager? Instance { get { if ((Object)(object)_instance == (Object)null) { ManualLogSource log = ProjectEchoPlugin.Log; if (log != null) { log.LogWarning((object)"[SAM] Instance not yet initialized"); } } return _instance; } } public Vector3 PlayerPosition { get; private set; } public bool IsPlayerInShip { get; private set; } public LayerMask WallLayer { get; private set; } public RoomProfile CurrentRoom { get { if (!(_roomTransitionT >= 1f)) { return RoomProfile.Lerp(_previousRoom, _currentRoom, _roomTransitionT); } return _currentRoom; } } public event Action<RoomProfile>? OnRoomChanged; public static bool IsAlive() { return (Object)(object)_instance != (Object)null; } private void Awake() { if ((Object)(object)_instance != (Object)null && (Object)(object)_instance != (Object)(object)this) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } _instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); InitWallLayer(); _playerTracker = ((Component)this).gameObject.AddComponent<PlayerTracker>(); _filterPool = ((Component)this).gameObject.AddComponent<FilterPool>(); SceneManager.sceneUnloaded += OnSceneUnloaded; } private void InitWallLayer() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) string[] array = new string[6] { "Default", "Room", "Colliders", "NavigationSurface", "Terrain", "InteractableObject" }; int num = 0; List<string> list = new List<string>(); string[] array2 = array; foreach (string text in array2) { int num2 = LayerMask.NameToLayer(text); if (num2 != -1) { num |= 1 << num2; list.Add(text); } } if (num == 0) { WallLayer = LayerMask.op_Implicit(-1); ProjectEchoPlugin.Log.LogWarning((object)"[SAM] No matching layers found — using all layers as fallback"); } else { WallLayer = LayerMask.op_Implicit(num); ProjectEchoPlugin.Log.LogInfo((object)("[SAM] Wall layers: [" + string.Join(", ", list) + "]")); } } private void Update() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_playerTracker != (Object)null) { PlayerPosition = _playerTracker.Position; IsPlayerInShip = _playerTracker.IsInsideShip; } if (_roomTransitionT < 1f) { _roomTransitionT = Mathf.MoveTowards(_roomTransitionT, 1f, Time.deltaTime * QualityConfig.LerpSpeed); } } public bool HasPlayerMovedSignificantly() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) bool flag = Vector3.SqrMagnitude(PlayerPosition - _lastScanPosition) > 0.25f; if (flag) { _lastScanPosition = PlayerPosition; } return flag; } public void ForceOutdoorProfile() { _previousRoom = CurrentRoom; _currentRoom = RoomProfile.Default; _roomTransitionT = 0f; ((MonoBehaviour)this).StartCoroutine(FastTransitionCoroutine()); this.OnRoomChanged?.Invoke(_currentRoom); ProjectEchoPlugin.Log.LogInfo((object)"[SAM] Forced outdoor profile"); } [IteratorStateMachine(typeof(<FastTransitionCoroutine>d__33))] private IEnumerator FastTransitionCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FastTransitionCoroutine>d__33(0) { <>4__this = this }; } public void UpdateRoom(RoomProfile newProfile) { if (newProfile.IsValid) { if (IsPlayerInShip) { ReverbParams shipInterior = AudioParameterPresets.ShipInterior; newProfile.RT60 = shipInterior.DecayTime; newProfile.RoomGain = shipInterior.Room; newProfile.HighFreqDamping = shipInterior.RoomHF; newProfile.Diffusion = shipInterior.Diffusion; newProfile.Density = shipInterior.Density; } _previousRoom = CurrentRoom; _currentRoom = newProfile; _roomTransitionT = 0f; this.OnRoomChanged?.Invoke(newProfile); if (QualityConfig.DebugMode.Value) { ProjectEchoPlugin.Log.LogInfo((object)($"[SAM] Room updated | V={newProfile.Volume:F0}m³ " + $"RT60={newProfile.RT60:F2}s " + $"α={newProfile.AverageSurfaceAlpha:F2} " + $"Ship={IsPlayerInShip}")); } } } private void OnSceneUnloaded(Scene scene) { _currentRoom = RoomProfile.Default; _previousRoom = RoomProfile.Default; _roomTransitionT = 1f; _filterPool?.ClearAll(); } private void OnDestroy() { SceneManager.sceneUnloaded -= OnSceneUnloaded; ((MonoBehaviour)this).StopAllCoroutines(); if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } } } namespace ProjectEcho.Analysis { public class RoomAnalyzer : MonoBehaviour { [CompilerGenerated] private sealed class <>c__DisplayClass23_0 { public RoomProfile primary; public RoomProfile secondary; public bool hasSecondary; internal void <ScanCoroutine>b__0(RoomProfile p) { primary = p; } internal void <ScanCoroutine>b__1(RoomProfile p) { secondary = p; hasSecondary = true; } } [CompilerGenerated] private sealed class <DoScanInto>d__24 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int rayCount; public RoomAnalyzer <>4__this; public Vector3 origin; public LayerMask wallLayer; public Action<RoomProfile> onComplete; private float <budget>5__2; private float <totalAbsorp>5__3; private float <totalArea>5__4; private float <totalHFRatio>5__5; private float <totalHardness>5__6; private float <sumX>5__7; private float <sumY>5__8; private float <sumZ>5__9; private float <ceilHeight>5__10; private int <cntX>5__11; private int <cntY>5__12; private int <cntZ>5__13; private int <matSamples>5__14; private int <count>5__15; private int <i>5__16; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DoScanInto>d__24(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: 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_0101: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) int num = <>1__state; RoomAnalyzer roomAnalyzer = <>4__this; float realtimeSinceStartup; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; realtimeSinceStartup = Time.realtimeSinceStartup; goto IL_0296; } <>1__state = -1; <budget>5__2 = QualityConfig.FrameBudgetMs * 0.001f; realtimeSinceStartup = Time.realtimeSinceStartup; <totalAbsorp>5__3 = 0f; <totalArea>5__4 = 0f; <totalHFRatio>5__5 = 0f; <totalHardness>5__6 = 0f; <sumX>5__7 = 0f; <sumY>5__8 = 0f; <sumZ>5__9 = 0f; <ceilHeight>5__10 = 3f; <cntX>5__11 = 0; <cntY>5__12 = 0; <cntZ>5__13 = 0; <matSamples>5__14 = 0; <count>5__15 = Mathf.Clamp(rayCount, 6, ALL_DIRECTIONS.Length); <i>5__16 = 0; goto IL_02a8; IL_0296: <i>5__16++; goto IL_02a8; IL_02a8: if (<i>5__16 < <count>5__15) { if (roomAnalyzer._isDestroyed) { return false; } Vector3 val = ALL_DIRECTIONS[<i>5__16]; float num2 = QualityConfig.OcclusionRayDist; RaycastHit hit = default(RaycastHit); if (Physics.Raycast(origin, val, ref hit, num2, LayerMask.op_Implicit(wallLayer))) { num2 = ((RaycastHit)(ref hit)).distance; string materialTag = GetMaterialTag(hit); float num3 = num2 * num2 * 0.3f; <totalAbsorp>5__3 += num3 * GetAbsorption(materialTag); <totalArea>5__4 += num3; float num4 = 1f / Mathf.Max(num2, 0.5f); <totalHFRatio>5__5 += GetValue(DECAY_HF_RATIO, materialTag) * num4; <totalHardness>5__6 += GetValue(HARDNESS, materialTag) * num4; <matSamples>5__14++; if (val == Vector3.up) { <ceilHeight>5__10 = num2; } } if (Mathf.Abs(val.x) > 0.6f) { <sumX>5__7 += num2; <cntX>5__11++; } if (Mathf.Abs(val.y) > 0.6f) { <sumY>5__8 += num2; <cntY>5__12++; } if (Mathf.Abs(val.z) > 0.6f) { <sumZ>5__9 += num2; <cntZ>5__13++; } if (Time.realtimeSinceStartup - realtimeSinceStartup > <budget>5__2) { <>2__current = null; <>1__state = 1; return true; } goto IL_0296; } SpatialAudioManager instance = SpatialAudioManager.Instance; RaycastHit hit2 = default(RaycastHit); if ((Object)(object)instance != (Object)null && Physics.Raycast(origin, Vector3.down, ref hit2, 5f, LayerMask.op_Implicit(instance.WallLayer))) { string materialTag2 = GetMaterialTag(hit2); <totalAbsorp>5__3 += 40f * GetAbsorption(materialTag2); <totalArea>5__4 += 40f; float num5 = 2f / Mathf.Max(((RaycastHit)(ref hit2)).distance, 0.1f); <totalHFRatio>5__5 += GetValue(DECAY_HF_RATIO, materialTag2) * num5; <totalHardness>5__6 += GetValue(HARDNESS, materialTag2) * num5; <matSamples>5__14++; } float decayHFRatio = ((<matSamples>5__14 > 0) ? (<totalHFRatio>5__5 / (float)<matSamples>5__14) : 0.5f); float matHardness = ((<matSamples>5__14 > 0) ? (<totalHardness>5__6 / (float)<matSamples>5__14) : 0.5f); onComplete(BuildProfile(<sumX>5__7, <sumY>5__8, <sumZ>5__9, <cntX>5__11, <cntY>5__12, <cntZ>5__13, <totalAbsorp>5__3, <totalArea>5__4, <ceilHeight>5__10, decayHFRatio, matHardness, QualityConfig.OcclusionRayDist)); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <FirstSpawnScan>d__20 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public RoomAnalyzer <>4__this; private float <w>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FirstSpawnScan>d__20(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //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_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) int num = <>1__state; RoomAnalyzer roomAnalyzer = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <w>5__2 = 0f; goto IL_0089; case 1: <>1__state = -1; <w>5__2 += 0.2f; goto IL_0089; case 2: { <>1__state = -1; if (roomAnalyzer._isDestroyed) { return false; } SpatialAudioManager instance = SpatialAudioManager.Instance; if ((Object)(object)instance == (Object)null) { return false; } ProjectEchoPlugin.Log.LogInfo((object)"[RoomAnalyzer] First spawn scan"); if (roomAnalyzer._activeCoroutine != null) { ((MonoBehaviour)roomAnalyzer).StopCoroutine(roomAnalyzer._activeCoroutine); } roomAnalyzer._activeCoroutine = ((MonoBehaviour)roomAnalyzer).StartCoroutine(roomAnalyzer.ScanCoroutine(instance.PlayerPosition, instance.WallLayer, QualityConfig.RayCount, forced: true)); return false; } IL_0089: if (<w>5__2 < 10f) { SpatialAudioManager instance2 = SpatialAudioManager.Instance; if (!((Object)(object)instance2 != (Object)null) || !(instance2.PlayerPosition != Vector3.zero)) { <>2__current = (object)new WaitForSeconds(0.2f); <>1__state = 1; return true; } } <>2__current = (object)new WaitForSeconds(0.3f); <>1__state = 2; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <ScanCoroutine>d__23 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private <>c__DisplayClass23_0 <>8__1; public RoomAnalyzer <>4__this; public Vector3 origin; public LayerMask wallLayer; public int rayCount; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ScanCoroutine>d__23(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) int num = <>1__state; RoomAnalyzer roomAnalyzer = <>4__this; SpatialAudioManager instance; switch (num) { default: return false; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass23_0(); <>2__current = (object)new WaitForEndOfFrame(); <>1__state = 1; return true; case 1: <>1__state = -1; <>8__1.primary = RoomProfile.Default; <>8__1.secondary = RoomProfile.Default; <>8__1.hasSecondary = false; <>2__current = ((MonoBehaviour)roomAnalyzer).StartCoroutine(roomAnalyzer.DoScanInto(origin, wallLayer, rayCount, delegate(RoomProfile p) { <>8__1.primary = p; })); <>1__state = 2; return true; case 2: { <>1__state = -1; if (roomAnalyzer._isDestroyed) { return false; } instance = SpatialAudioManager.Instance; if ((Object)(object)instance == (Object)null) { return false; } if (QualityConfig.Tier.Value == QualityTier.Low) { break; } Vector3 val = Vector3.zero; float num2 = 0f; float num3 = float.MaxValue; Vector3[] hORIZ_DIRS = HORIZ_DIRS; RaycastHit val3 = default(RaycastHit); foreach (Vector3 val2 in hORIZ_DIRS) { float num4 = QualityConfig.OcclusionRayDist; if (Physics.Raycast(origin, val2, ref val3, num4, LayerMask.op_Implicit(wallLayer))) { num4 = ((RaycastHit)(ref val3)).distance; } if (num4 > num2) { num2 = num4; val = val2; } if (num4 < num3) { num3 = num4; } } if (num3 < 5f && num2 > 8f && val != Vector3.zero) { Vector3 val4 = origin + val * 1.5f; <>2__current = ((MonoBehaviour)roomAnalyzer).StartCoroutine(roomAnalyzer.DoScanInto(val4, wallLayer, 6, delegate(RoomProfile p) { <>8__1.secondary = p; <>8__1.hasSecondary = true; })); <>1__state = 3; return true; } break; } case 3: <>1__state = -1; break; } if (roomAnalyzer._isDestroyed) { return false; } instance = SpatialAudioManager.Instance; if ((Object)(object)instance == (Object)null) { return false; } RoomProfile newProfile = <>8__1.primary; if (<>8__1.hasSecondary) { newProfile = RoomProfile.Lerp(<>8__1.primary, <>8__1.secondary, 0.35f); if (QualityConfig.DebugMode.Value) { ProjectEchoPlugin.Log.LogInfo((object)($"[RoomAnalyzer] Multizone blend: {0.35f:F2} " + $"(primary RT60={<>8__1.primary.RT60:F2}s, " + $"secondary RT60={<>8__1.secondary.RT60:F2}s)")); } } instance.UpdateRoom(newProfile); roomAnalyzer._firstScanDone = true; roomAnalyzer._activeCoroutine = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const float SABINE_K = 0.161f; private const float MIN_VOLUME = 1f; private const float MIN_SABIN = 0.001f; private const float ZONE_NEAR_THRESHOLD = 5f; private const float ZONE_FAR_THRESHOLD = 8f; private const float ZONE_OFFSET = 1.5f; private static readonly Vector3[] ALL_DIRECTIONS; private static readonly Vector3[] HORIZ_DIRS; private static readonly Dictionary<string, float> ABSORPTION; private static readonly Dictionary<string, float> DECAY_HF_RATIO; private static readonly Dictionary<string, float> HARDNESS; private ScanScheduler? _scheduler; private Coroutine? _activeCoroutine; private bool _isDestroyed; private bool _firstScanDone; private void Awake() { _scheduler = ((Component)this).GetComponent<ScanScheduler>(); if ((Object)(object)_scheduler == (Object)null) { _scheduler = ((Component)this).gameObject.AddComponent<ScanScheduler>(); } } private void Start() { ((MonoBehaviour)this).StartCoroutine(FirstSpawnScan()); } private void OnEnable() { if ((Object)(object)_scheduler != (Object)null) { _scheduler.OnScanRequested += OnScanRequested; } } private void OnDisable() { if ((Object)(object)_scheduler != (Object)null) { _scheduler.OnScanRequested -= OnScanRequested; } } private void OnDestroy() { _isDestroyed = true; if (_activeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_activeCoroutine); } } [IteratorStateMachine(typeof(<FirstSpawnScan>d__20))] private IEnumerator FirstSpawnScan() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FirstSpawnScan>d__20(0) { <>4__this = this }; } private void OnScanRequested(int rayCount) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) SpatialAudioManager instance = SpatialAudioManager.Instance; if (!((Object)(object)instance == (Object)null) && (!_firstScanDone || instance.HasPlayerMovedSignificantly())) { if (_activeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_activeCoroutine); } _activeCoroutine = ((MonoBehaviour)this).StartCoroutine(ScanCoroutine(instance.PlayerPosition, instance.WallLayer, rayCount)); } } public void ForceScan() { //IL_0028: 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) SpatialAudioManager instance = SpatialAudioManager.Instance; if (!((Object)(object)instance == (Object)null)) { if (_activeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_activeCoroutine); } _activeCoroutine = ((MonoBehaviour)this).StartCoroutine(ScanCoroutine(instance.PlayerPosition, instance.WallLayer, QualityConfig.RayCount, forced: true)); } } [IteratorStateMachine(typeof(<ScanCoroutine>d__23))] private IEnumerator ScanCoroutine(Vector3 origin, LayerMask wallLayer, int rayCount, bool forced = false) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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) //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ScanCoroutine>d__23(0) { <>4__this = this, origin = origin, wallLayer = wallLayer, rayCount = rayCount }; } [IteratorStateMachine(typeof(<DoScanInto>d__24))] private IEnumerator DoScanInto(Vector3 origin, LayerMask wallLayer, int rayCount, Action<RoomProfile> onComplete) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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) //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DoScanInto>d__24(0) { <>4__this = this, origin = origin, wallLayer = wallLayer, rayCount = rayCount, onComplete = onComplete }; } private static RoomProfile BuildProfile(float sumX, float sumY, float sumZ, int cntX, int cntY, int cntZ, float totalAbsorp, float totalArea, float ceilHeight, float decayHFRatio, float matHardness, float maxDist) { float num = ((cntX > 0) ? (sumX / (float)cntX * 2f) : maxDist); float num2 = ((cntY > 0) ? (sumY / (float)cntY * 2f) : 3f); float num3 = ((cntZ > 0) ? (sumZ / (float)cntZ * 2f) : maxDist); float num4 = Mathf.Max(num * num2 * num3, 1f); float num5 = Mathf.Max(totalAbsorp, 0.001f); float num6 = Mathf.Clamp(0.161f * num4 / num5, 0.1f, 8f); float num7 = ((totalArea > 0f) ? (totalAbsorp / totalArea) : 0.06f); float num8 = Mathf.InverseLerp(2f, 10f, ceilHeight); float num9 = num6 * Mathf.Lerp(0.75f, 1.25f, num8); float num10 = Mathf.Clamp01(Mathf.Lerp(0.3f, 0.9f, num8) * (1f - num4 / 8000f)); float num11 = Mathf.Max(num, num3); float num12 = Mathf.Min(num, num3); bool flag = num12 > 0f && num11 / num12 > 3f; if (flag) { num9 *= 0.8f; num10 *= 0.6f; } float num13 = Mathf.Lerp(-2000f, -100f, matHardness * Mathf.InverseLerp(500f, 5f, num4)); RoomProfile result = default(RoomProfile); result.Volume = num4; result.RT60 = Mathf.Clamp(num9, 0.1f, 8f); result.AverageSurfaceAlpha = num7; result.RoomGain = Mathf.Lerp(-3000f, -100f, Mathf.InverseLerp(10f, 2000f, num4)); result.HighFreqDamping = Mathf.Lerp(-200f, -3000f, Mathf.InverseLerp(0.02f, 0.5f, num7)); result.EarlyReflectionDelay = Mathf.Clamp(flag ? (num12 / 686f) : (num / 686f), 0.005f, 0.3f); result.LateReverbDelay = Mathf.Clamp(num9 * 0.1f, 0f, 0.1f); result.Diffusion = Mathf.Clamp01(num10); result.Density = (flag ? 0.7f : 1f); result.DecayHFRatio = Mathf.Clamp(decayHFRatio, 0.1f, 2f); result.ReflectionsLevel = Mathf.Clamp(num13, -10000f, 1000f); result.MaterialHardness = Mathf.Clamp01(matHardness); result.IsValid = true; return result; } private static string GetMaterialTag(RaycastHit hit) { if ((Object)(object)((RaycastHit)(ref hit)).collider == (Object)null) { return "Default"; } if ((Object)(object)((RaycastHit)(ref hit)).collider.sharedM