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 AmbienceSoundConfig v2.7.8
AmbienceSoundConfig.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.ConfigurationManager; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using Valheim.SettingsGui; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("AmbienceSoundConfig")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Valheim_Ambience_Mute")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("f01ac134-75c0-45a2-be66-fe63019a4264")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace BepInEx.ConfigurationManager { public sealed class ConfigurationManagerAttributes { public Action<ConfigEntryBase> CustomDrawer; public bool? HideDefaultButton; } } namespace AmbienceSoundConfig { [BepInPlugin("com.draconicvelum.ambiencesoundconfig", "Ambience Sound Config", "2.7.8")] public class AmbienceSoundConfig : BaseUnityPlugin { private sealed class CustomSectionSnapshot { private readonly string _extraSfxSplit; private readonly string _extraClipsSplit; private CustomSectionSnapshot(string extraSfxSplit, string extraClipsSplit) { _extraSfxSplit = extraSfxSplit; _extraClipsSplit = extraClipsSplit; } internal static CustomSectionSnapshot Capture(string path) { if (string.IsNullOrEmpty(path) || !File.Exists(path)) { return new CustomSectionSnapshot(null, null); } string text = File.ReadAllText(path); return new CustomSectionSnapshot(ReadRawSection(text, "Extra SFX Split"), ReadRawSection(text, "Extra Clips Split")); } internal void Restore(string path) { if (!string.IsNullOrEmpty(path) && File.Exists(path)) { string text = File.ReadAllText(path); if (!string.IsNullOrWhiteSpace(_extraSfxSplit)) { text = RemoveSection(text, "Extra SFX Split"); } if (!string.IsNullOrWhiteSpace(_extraClipsSplit)) { text = RemoveSection(text, "Extra Clips Split"); } StringBuilder stringBuilder = new StringBuilder(text.TrimEnd(Array.Empty<char>())); if (!string.IsNullOrWhiteSpace(_extraSfxSplit)) { stringBuilder.AppendLine(); stringBuilder.AppendLine(); stringBuilder.Append(_extraSfxSplit.TrimEnd(Array.Empty<char>())); } if (!string.IsNullOrWhiteSpace(_extraClipsSplit)) { stringBuilder.AppendLine(); stringBuilder.AppendLine(); stringBuilder.Append(_extraClipsSplit.TrimEnd(Array.Empty<char>())); } File.WriteAllText(path, stringBuilder.ToString() + Environment.NewLine); } } } public const string PluginVersion = "2.7.8"; public const string ExtraSfxSplitSection = "Extra SFX Split"; public const string ExtraClipSplitSection = "Extra Clips Split"; public static ConfigEntry<string> ConfigVersion; public static ConfigEntry<bool> ReloadConfigButton; public static ConfigEntry<float> VolumeSliderMax; public static ConfigEntry<float> MasterVolume; public static ConfigEntry<float> WindVolume; public static ConfigEntry<float> OceanVolume; public static ConfigEntry<float> AmbientLoopVolume; public static ConfigEntry<float> ShieldHumVolume; public static ConfigEntry<string> ExtraSfxList; public static ConfigEntry<float> ExtraSfxVolume; public static ConfigEntry<string> ExtraClipList; public static ConfigEntry<float> ExtraClipVolume; public static ConfigEntry<bool> EnableSoundLogging; public static ConfigEntry<bool> LogOnlyUnique; public static string PluginConfigFilePath; private static readonly Harmony harmony = new Harmony("com.draconicvelum.ambiencesoundconfig"); private void Awake() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Expected O, but got Unknown //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Expected O, but got Unknown //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Expected O, but got Unknown //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Expected O, but got Unknown PluginConfigFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; CustomSectionSnapshot startupCustomSections = CustomSectionSnapshot.Capture(PluginConfigFilePath); bool configNeedsUpdate = IsOlderConfigVersion(ReadConfigVersion(), "2.7.8"); ConfigVersion = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Config Version", "2.7.8", "Internal config version. The mod updates older config files to the latest format while keeping your values."); ReloadConfigButton = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Reload Config", false, new ConfigDescription("Button for compatible config manager mods. Reloads this config file and reapplies split SFX/clip volumes.", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { CustomDrawer = DrawReloadConfigButton, HideDefaultButton = true } })); VolumeSliderMax = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Volume Slider Max", 1f, new ConfigDescription("Config-only maximum value used by this mod's volume sliders and volume config entries. Default is 1.0; valid range is 0.0 to 5.0.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); MasterVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Ambience", "Master Volume", 0.25f, new ConfigDescription("Controls overall ambience loudness multiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); WindVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Ambience", "Wind Volume", 1f, new ConfigDescription("Volume for wind ambience (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); OceanVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Ambience", "Ocean Volume", 1f, new ConfigDescription("Volume for ocean ambience (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); AmbientLoopVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Ambience", "Ambient Loop Volume", 1f, new ConfigDescription("Volume for background ambient loop (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); ShieldHumVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Ambience", "Shield Hum Volume", 1f, new ConfigDescription("Volume for shield dome hum (multiplied by Master Volume).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); ExtraSfxList = ((BaseUnityPlugin)this).Config.Bind<string>("Extra SFX", "Extra SFX Prefabs", "", "Legacy comma separated list of ZSFX prefab names affected by Extra SFX Volume. For per-sound volumes, add entries under [Extra SFX Split]."); ExtraSfxVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Extra SFX", "Extra SFX Volume", 1f, new ConfigDescription("Legacy shared volume multiplier for sound effects listed in Extra SFX Prefabs.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); ExtraClipList = ((BaseUnityPlugin)this).Config.Bind<string>("Extra Clips", "Extra Clip Names", "", "Legacy comma separated list of AudioClip names affected by Extra Clip Volume. For per-clip volumes, add entries under [Extra Clips Split]."); ExtraClipVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Extra Clips", "Extra Clip Volume", 1f, new ConfigDescription("Legacy shared volume multiplier for clip names listed in Extra Clip Names.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); EnableSoundLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "Enable Sound Logging", false, "Logs all played SFX and AudioClips to console and a file next to this config."); LogOnlyUnique = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "Log Only Unique", true, "If enabled, each sound name is logged only once per session."); VolumeSliderMax.SettingChanged += delegate { ClampAllVolumeConfigs(); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); ApplyAllVolumes(); SettingsInject.SyncUI(); SaveConfigPreservingCustomSections(((BaseUnityPlugin)this).Config); }; MasterVolume.SettingChanged += delegate { ClampVolumeConfig(MasterVolume); }; WindVolume.SettingChanged += delegate { ClampVolumeConfig(WindVolume); }; OceanVolume.SettingChanged += delegate { ClampVolumeConfig(OceanVolume); }; AmbientLoopVolume.SettingChanged += delegate { ClampVolumeConfig(AmbientLoopVolume); }; ShieldHumVolume.SettingChanged += delegate { ClampVolumeConfig(ShieldHumVolume); }; ExtraSfxList.SettingChanged += delegate { AudioSourceFilter.Refresh(); }; ExtraSfxVolume.SettingChanged += delegate { ClampVolumeConfig(ExtraSfxVolume); AudioSourceFilter.Refresh(); AudioSource[] array = Object.FindObjectsOfType<AudioSource>(); for (int i = 0; i < array.Length; i++) { AudioSourceFilter.Apply(array[i]); } }; ExtraClipList.SettingChanged += delegate { AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); }; ExtraClipVolume.SettingChanged += delegate { ClampVolumeConfig(ExtraClipVolume); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); }; ClampAllVolumeConfigs(); UpdateConfigFileIfNeeded(configNeedsUpdate, startupCustomSections); EnsureCustomVolumeSections(); AudioSourceFilter.Refresh(); SoundPlayLogger.Init(((BaseUnityPlugin)this).Config); harmony.PatchAll(typeof(AudioSource_DoAll_Patch)); harmony.PatchAll(typeof(AudioSource_PlayOneShotScale_Patch)); harmony.PatchAll(typeof(ZSFX_VolumeScale_Patch)); harmony.PatchAll(typeof(AudioMan_AmbienceVolume_Patch)); harmony.PatchAll(typeof(SettingsInject)); ((BaseUnityPlugin)this).Config.ConfigReloaded += OnReloaded; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Ambience Sound Config (v2.7.8) loaded."); } private void OnReloaded(object sender, EventArgs e) { ClampAllVolumeConfigs(); EnsureCustomVolumeSections(); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); ApplyAllVolumes(); SettingsInject.SyncUI(); } public static void ReloadConfigFromManager() { try { ReloadConfigPreservingCustomSections(((ConfigEntryBase)ConfigVersion).ConfigFile); AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); ApplyAllVolumes(); SettingsInject.SyncUI(); Debug.Log((object)"[AmbienceSoundConfig] Config reloaded from config manager button."); } catch (Exception ex) { Debug.LogWarning((object)("[AmbienceSoundConfig] Config manager reload failed: " + ex.Message)); } } private static void DrawReloadConfigButton(ConfigEntryBase entry) { if (GUILayout.Button("Reload Config", Array.Empty<GUILayoutOption>())) { ReloadConfigFromManager(); } } public static void ApplyAllVolumes() { AudioMan_AmbienceVolume_Patch.ApplyWindVolumeWrapper(); AudioMan_AmbienceVolume_Patch.ApplyOceanVolumeWrapper(); AudioMan_AmbienceVolume_Patch.ApplyAmbientLoopVolumeWrapper(); AudioMan_AmbienceVolume_Patch.ApplyShieldHumVolumeWrapper(); } public static float GetVolumeSliderMax() { return Mathf.Clamp(VolumeSliderMax.Value, 0f, 5f); } public static float ClampConfiguredVolume(float value) { return Mathf.Clamp(value, 0f, GetVolumeSliderMax()); } private static void ClampVolumeConfig(ConfigEntry<float> entry) { float num = ClampConfiguredVolume(entry.Value); if (!Mathf.Approximately(entry.Value, num)) { entry.Value = num; } } private static void ClampAllVolumeConfigs() { ClampVolumeConfig(MasterVolume); ClampVolumeConfig(WindVolume); ClampVolumeConfig(OceanVolume); ClampVolumeConfig(AmbientLoopVolume); ClampVolumeConfig(ShieldHumVolume); ClampVolumeConfig(ExtraSfxVolume); ClampVolumeConfig(ExtraClipVolume); } private static string ReadConfigVersion() { if (string.IsNullOrEmpty(PluginConfigFilePath) || !File.Exists(PluginConfigFilePath)) { return null; } try { string[] array = File.ReadAllLines(PluginConfigFilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.StartsWith("Config Version =", StringComparison.OrdinalIgnoreCase)) { int num = text.IndexOf('='); if (num >= 0) { return text.Substring(num + 1).Trim(); } } } } catch (Exception ex) { Debug.LogWarning((object)("[AmbienceSoundConfig] Could not read config version: " + ex.Message)); } return null; } private static bool IsOlderConfigVersion(string currentVersion, string targetVersion) { if (string.IsNullOrEmpty(currentVersion)) { return File.Exists(PluginConfigFilePath); } if (!Version.TryParse(currentVersion, out Version result)) { return true; } if (!Version.TryParse(targetVersion, out Version result2)) { return false; } return result < result2; } private void UpdateConfigFileIfNeeded(bool configNeedsUpdate, CustomSectionSnapshot startupCustomSections) { if (!configNeedsUpdate) { return; } try { ConfigVersion.Value = "2.7.8"; SaveConfigPreservingCustomSections(((BaseUnityPlugin)this).Config, startupCustomSections); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Updated config file to v2.7.8 while keeping existing values."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not update config file: " + ex.Message)); } } internal static void EnsureCustomVolumeSections() { if (string.IsNullOrEmpty(PluginConfigFilePath)) { return; } try { if (File.Exists(PluginConfigFilePath)) { string text = RemoveLegacyEntryLines(File.ReadAllText(PluginConfigFilePath)); bool flag = text != File.ReadAllText(PluginConfigFilePath); StringBuilder stringBuilder = new StringBuilder(text.TrimEnd(Array.Empty<char>())); if (!HasSection(text, "Extra SFX Split")) { AppendExtraSfxSplitSection(stringBuilder); flag = true; } if (!HasSection(text, "Extra Clips Split")) { AppendExtraClipsSplitSection(stringBuilder); flag = true; } if (flag) { File.WriteAllText(PluginConfigFilePath, stringBuilder.ToString() + Environment.NewLine); } } } catch (Exception ex) { Debug.LogWarning((object)("[AmbienceSoundConfig] Could not seed custom volume config sections: " + ex.Message)); } } internal static void SaveConfigPreservingCustomSections(ConfigFile config) { SaveConfigPreservingCustomSections(config, CustomSectionSnapshot.Capture(PluginConfigFilePath)); } private static void SaveConfigPreservingCustomSections(ConfigFile config, CustomSectionSnapshot snapshot) { config.Save(); snapshot.Restore(PluginConfigFilePath); EnsureCustomVolumeSections(); } private static void ReloadConfigPreservingCustomSections(ConfigFile config) { CustomSectionSnapshot customSectionSnapshot = CustomSectionSnapshot.Capture(PluginConfigFilePath); config.Reload(); customSectionSnapshot.Restore(PluginConfigFilePath); EnsureCustomVolumeSections(); } private static void AppendExtraSfxSplitSection(StringBuilder builder) { builder.AppendLine(); builder.AppendLine(); builder.AppendLine("[Extra SFX Split]"); builder.AppendLine("## Config-only per-sound SFX volumes. Add one ZSFX prefab per line."); builder.AppendLine("## Format: prefab_name = volume"); builder.AppendLine("## Example: sfx_boar_idle = 0.25"); } private static void AppendExtraClipsSplitSection(StringBuilder builder) { builder.AppendLine(); builder.AppendLine(); builder.AppendLine("[Extra Clips Split]"); builder.AppendLine("## Config-only per-clip volumes. Add one AudioClip name per line."); builder.AppendLine("## Format: clip_name = volume"); builder.AppendLine("## Example: MeadowAmbience = 0.25"); } private static string RemoveLegacyEntryLines(string text) { StringBuilder stringBuilder = new StringBuilder(); using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { string text3 = text2.TrimStart(Array.Empty<char>()); if (!text3.StartsWith("Extra SFX Entries =", StringComparison.OrdinalIgnoreCase) && !text3.StartsWith("Extra Clip Entries =", StringComparison.OrdinalIgnoreCase)) { stringBuilder.AppendLine(text2); } } } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()) + Environment.NewLine; } private static string ReadRawSection(string text, string sectionName) { StringBuilder stringBuilder = new StringBuilder(); bool flag = false; using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { string text3 = text2.Trim(); if (text3.StartsWith("[") && text3.EndsWith("]")) { if (flag) { break; } flag = text3.Substring(1, text3.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase); } if (flag) { stringBuilder.AppendLine(text2); } } } if (stringBuilder.Length <= 0) { return null; } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()) + Environment.NewLine; } private static string RemoveSection(string text, string sectionName) { StringBuilder stringBuilder = new StringBuilder(); bool flag = false; using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { string text3 = text2.Trim(); if (text3.StartsWith("[") && text3.EndsWith("]")) { flag = text3.Substring(1, text3.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase); } if (!flag) { stringBuilder.AppendLine(text2); } } } return stringBuilder.ToString(); } private static bool HasSection(string text, string sectionName) { using (StringReader stringReader = new StringReader(text)) { string text2; while ((text2 = stringReader.ReadLine()) != null) { text2 = text2.Trim(); if (text2.StartsWith("[") && text2.EndsWith("]") && text2.Substring(1, text2.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } } [HarmonyPatch(typeof(AudioMan))] public static class AudioMan_AmbienceVolume_Patch { private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic; private static readonly Dictionary<string, FieldInfo> CachedFields = new Dictionary<string, FieldInfo>(); private static AudioSource GetPrivateSource(string fieldName) { if ((Object)(object)AudioMan.instance == (Object)null) { return null; } if (!CachedFields.TryGetValue(fieldName, out var value)) { value = typeof(AudioMan).GetField(fieldName, Flags); if (value != null) { CachedFields[fieldName] = value; } } object? obj = value?.GetValue(AudioMan.instance); return (AudioSource)((obj is AudioSource) ? obj : null); } public static void ApplyWindVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_windLoopSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.WindVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } public static void ApplyOceanVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_oceanAmbientSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.OceanVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } public static void ApplyAmbientLoopVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_ambientLoopSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.AmbientLoopVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } public static void ApplyShieldHumVolumeWrapper() { AudioSource privateSource = GetPrivateSource("m_shieldHumSource"); if ((Object)(object)privateSource != (Object)null) { privateSource.volume = AmbienceSoundConfig.ShieldHumVolume.Value * AmbienceSoundConfig.MasterVolume.Value; } } [HarmonyPostfix] [HarmonyPatch("UpdateWindAmbience")] public static void ApplyWindVolume(ref AudioSource ___m_windLoopSource) { ApplyWindVolumeWrapper(); } [HarmonyPostfix] [HarmonyPatch("UpdateOceanAmbiance")] public static void ApplyOceanVolume(ref AudioSource ___m_oceanAmbientSource) { ApplyOceanVolumeWrapper(); } [HarmonyPostfix] [HarmonyPatch("UpdateAmbientLoop")] public static void ApplyAmbientLoopVolume(ref AudioSource ___m_ambientLoopSource) { ApplyAmbientLoopVolumeWrapper(); } [HarmonyPostfix] [HarmonyPatch("UpdateShieldHum")] public static void ApplyShieldHumVolume(ref AudioSource ___m_shieldHumSource) { ApplyShieldHumVolumeWrapper(); } } internal static class AudioSourceFilter { private static readonly Dictionary<string, float> _nameTargets = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, float> _clipTargets = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<AudioSource, float> _baseVolume = new Dictionary<AudioSource, float>(); internal static bool HasClipTargets => _clipTargets.Count > 0; internal static void Refresh() { _nameTargets.Clear(); _clipTargets.Clear(); ParseSharedList(AmbienceSoundConfig.ExtraSfxList.Value, AmbienceSoundConfig.ExtraSfxVolume.Value, _nameTargets); ParseConfigVolumeSection("Extra SFX Split", _nameTargets); ParseSharedList(AmbienceSoundConfig.ExtraClipList.Value, AmbienceSoundConfig.ExtraClipVolume.Value, _clipTargets); ParseConfigVolumeSection("Extra Clips Split", _clipTargets); } internal static void Apply(AudioSource src) { if (!((Object)(object)src == (Object)null) && _nameTargets.Count != 0 && TryGetSfxMultiplier(((Object)((Component)src).gameObject).name, out var multiplier)) { ApplyVolume(src, multiplier); } } internal static void Apply(AudioSource src, ref float volume) { if (!((Object)(object)src == (Object)null) && _nameTargets.Count != 0 && TryGetSfxMultiplier(((Object)((Component)src).gameObject).name, out var multiplier)) { volume = Scale(volume, multiplier); } } internal static bool ShouldMuteClip(AudioClip clip) { float multiplier; return TryGetClipMultiplier(clip, out multiplier); } internal static bool TryGetClipMultiplier(AudioClip clip, out float multiplier) { multiplier = 1f; if ((Object)(object)clip != (Object)null) { return TryMatch(_clipTargets, ((Object)clip).name, out multiplier); } return false; } internal static void ApplyToRunningSources() { List<AudioSource> list = new List<AudioSource>(); foreach (KeyValuePair<AudioSource, float> item in _baseVolume) { if ((Object)(object)item.Key == (Object)null) { list.Add(item.Key); } } foreach (AudioSource item2 in list) { _baseVolume.Remove(item2); } AudioSource[] array = Object.FindObjectsOfType<AudioSource>(); foreach (AudioSource val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.clip == (Object)null) { continue; } float multiplier = 1f; if (!HasClipTargets || !TryGetClipMultiplier(val.clip, out multiplier)) { if (_baseVolume.ContainsKey(val)) { _baseVolume.Remove(val); } val.mute = false; continue; } if (!_baseVolume.ContainsKey(val)) { _baseVolume[val] = val.volume; } float num = _baseVolume[val]; if (multiplier <= 0f) { val.mute = true; continue; } val.mute = false; val.volume = Mathf.Clamp01(num * multiplier); } } private static bool TryGetSfxMultiplier(string goName, out float multiplier) { return TryMatch(_nameTargets, goName, out multiplier); } private static void ApplyVolume(AudioSource src, float mult) { if (mult <= 0f) { src.mute = true; return; } src.mute = false; src.volume = Mathf.Clamp01(src.volume * mult); } private static float Scale(float v, float mult) { if (mult <= 0f) { return 0f; } return v * mult; } private static bool TryMatch(Dictionary<string, float> targets, string sourceName, out float multiplier) { multiplier = 1f; if (targets.Count == 0) { return false; } string text = Normalize(sourceName); if (targets.TryGetValue(text, out multiplier)) { return true; } foreach (KeyValuePair<string, float> target in targets) { if (text.StartsWith(target.Key, StringComparison.OrdinalIgnoreCase) || text.IndexOf(target.Key, StringComparison.OrdinalIgnoreCase) >= 0) { multiplier = target.Value; return true; } } return false; } private static void ParseSharedList(string raw, float sharedVolume, Dictionary<string, float> target) { if (string.IsNullOrEmpty(raw)) { return; } string[] array = raw.Split(',', '\n', '\r'); for (int i = 0; i < array.Length; i++) { string text = Normalize(array[i]); if (!string.IsNullOrEmpty(text)) { target[text] = AmbienceSoundConfig.ClampConfiguredVolume(sharedVolume); } } } private static void ParseConfigVolumeSection(string sectionName, Dictionary<string, float> target) { string pluginConfigFilePath = AmbienceSoundConfig.PluginConfigFilePath; if (string.IsNullOrEmpty(pluginConfigFilePath) || !File.Exists(pluginConfigFilePath)) { return; } bool flag = false; string[] array = File.ReadAllLines(pluginConfigFilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrEmpty(text) && !text.StartsWith("#")) { if (text.StartsWith("[") && text.EndsWith("]")) { flag = text.Substring(1, text.Length - 2).Trim().Equals(sectionName, StringComparison.OrdinalIgnoreCase); } else if (flag) { ParseVolumeLine(text, target); } } } } private static void ParseVolumeLine(string line, Dictionary<string, float> target) { string text = line.Trim(); if (string.IsNullOrEmpty(text) || text.StartsWith("#")) { return; } string name = text; string text2 = null; int num = text.IndexOf('='); if (num < 0) { num = text.IndexOf(':'); } if (num >= 0) { name = text.Substring(0, num); text2 = text.Substring(num + 1); } name = Normalize(name); if (!string.IsNullOrEmpty(name)) { float value = 1f; if (!string.IsNullOrEmpty(text2) && float.TryParse(text2.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { value = result; } target[name] = AmbienceSoundConfig.ClampConfiguredVolume(value); } } private static string Normalize(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num >= 0) { name = name.Substring(0, num); } return name.Trim(); } } [HarmonyPatch(typeof(AudioSource))] internal static class AudioSource_DoAll_Patch { [HarmonyPostfix] [HarmonyPatch("Play", new Type[] { })] private static void Postfix_Play(AudioSource __instance) { SoundPlayLogger.Log("SFX", ((Object)((Component)__instance).gameObject).name, ((Component)__instance).gameObject); AudioSourceFilter.Apply(__instance); } [HarmonyPostfix] [HarmonyPatch("Play", new Type[] { typeof(ulong) })] private static void Postfix_PlayDelayed(AudioSource __instance) { AudioSourceFilter.Apply(__instance); } [HarmonyPostfix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip) })] private static void Postfix_PlayOneShot(AudioSource __instance) { AudioSourceFilter.Apply(__instance); } [HarmonyPrefix] [HarmonyPatch("set_volume")] private static void Prefix_SetVolume(AudioSource __instance, ref float value) { AudioSourceFilter.Apply(__instance, ref value); } } [HarmonyPatch(typeof(AudioSource))] internal static class AudioSource_PlayOneShotScale_Patch { [HarmonyPrefix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] private static void Prefix(AudioSource __instance, AudioClip clip, ref float volumeScale) { SoundPlayLogger.Log("CLIP", (clip != null) ? ((Object)clip).name : null, Object.op_Implicit((Object)(object)__instance) ? ((Component)__instance).gameObject : null); if (!((Object)(object)clip == (Object)null) && AudioSourceFilter.HasClipTargets && AudioSourceFilter.TryGetClipMultiplier(clip, out var multiplier)) { volumeScale = ((multiplier <= 0f) ? 0f : (volumeScale * multiplier)); } } [HarmonyPrefix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip) })] private static void Prefix_NoScale(AudioSource __instance, AudioClip clip) { SoundPlayLogger.Log("CLIP", (clip != null) ? ((Object)clip).name : null, Object.op_Implicit((Object)(object)__instance) ? ((Component)__instance).gameObject : null); } } [HarmonyPatch] public static class SettingsInject { private static Transform _gridContainer; private static bool _built; private const string ContainerName = "AmbienceGridContainer"; internal static Slider _sMaster; internal static Slider _sWind; internal static Slider _sOcean; internal static Slider _sAmbientLoop; internal static Slider _sShieldHum; internal static Slider _sExtraSfx; internal static Slider _sExtraClip; internal static TextMeshProUGUI _vMaster; internal static TextMeshProUGUI _vWind; internal static TextMeshProUGUI _vOcean; internal static TextMeshProUGUI _vAmbientLoop; internal static TextMeshProUGUI _vShieldHum; internal static TextMeshProUGUI _vExtraSfx; internal static TextMeshProUGUI _vExtraClip; private static MethodBase TargetMethod() { Type type = typeof(AudioSettings); MethodBase methodBase = Find("Initialize") ?? Find("Awake") ?? Find("Start"); if (methodBase != null) { Debug.Log((object)("[AmbienceSoundConfig] UI inject patching → " + methodBase.Name)); return methodBase; } methodBase = Find("LoadSettings"); if (methodBase != null) { Debug.Log((object)"[AmbienceSoundConfig] UI inject patching → LoadSettings (legacy fallback for Valheim 0.221.4)"); return methodBase; } throw new Exception("[AmbienceSoundConfig] No compatible AudioSettings method found to patch."); MethodBase Find(string name) { return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } [HarmonyPostfix] private static void Postfix_LoadSettings(AudioSettings __instance) { //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_0118: 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_0136: 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_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0176: 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_019f: 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_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) try { if (_built && (Object)(object)_gridContainer != (Object)null) { SyncUI(); return; } Slider value = Traverse.Create((object)__instance).Field("m_musicVolumeSlider").GetValue<Slider>(); GameObject val = ((value != null) ? ((Component)value).gameObject : null); if ((Object)(object)val == (Object)null) { Debug.LogWarning((object)"[AmbienceSoundConfig] Could not get m_musicVolumeSlider."); return; } Toggle value2 = Traverse.Create((object)__instance).Field("m_continousMusic").GetValue<Toggle>(); Transform val2 = ((value2 != null) ? ((Component)value2).transform.parent : null) ?? val.transform.parent; Transform val3 = val2.Find("AmbienceGridContainer"); if ((Object)(object)val3 != (Object)null) { Object.DestroyImmediate((Object)(object)((Component)val3).gameObject); } GameObject val4 = new GameObject("AmbienceGridContainer", new Type[3] { typeof(RectTransform), typeof(GridLayoutGroup), typeof(ContentSizeFitter) }); val4.transform.SetParent(val2, false); int siblingIndex = (((Object)(object)value2 != (Object)null) ? ((Component)value2).transform.GetSiblingIndex() : val.transform.GetSiblingIndex()) + 1; val4.transform.SetSiblingIndex(siblingIndex); RectTransform component = val4.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(1f, 1f); component.pivot = new Vector2(0.5f, 1f); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; component.anchoredPosition = new Vector2(0f, 0f); component.sizeDelta = new Vector2(0f, 0f); GridLayoutGroup component2 = val4.GetComponent<GridLayoutGroup>(); ((LayoutGroup)component2).padding = new RectOffset(0, 0, 0, 0); component2.cellSize = new Vector2(300f, 20f); component2.spacing = new Vector2(0f, 8f); component2.constraint = (Constraint)1; component2.constraintCount = 1; ((LayoutGroup)component2).childAlignment = (TextAnchor)0; ContentSizeFitter component3 = val4.GetComponent<ContentSizeFitter>(); component3.verticalFit = (FitMode)2; component3.horizontalFit = (FitMode)0; _gridContainer = val4.transform; BindOrCreateSliders(_gridContainer, val); SyncUI(); _built = true; LayoutRebuilder.ForceRebuildLayoutImmediate(((Component)val2).GetComponent<RectTransform>()); Debug.Log((object)"[AmbienceSoundConfig] AmbienceGridContainer built and anchored to top (no drift)."); } catch (Exception ex) { Debug.LogError((object)("[AmbienceSoundConfig] LoadSettings patch failed: " + ex)); } } private static void BindOrCreateSliders(Transform container, GameObject baseSlider) { CreateOrBind(container, baseSlider, "Ambience Master Volume", ref _sMaster, ref _vMaster, AmbienceSoundConfig.MasterVolume, delegate { AmbienceSoundConfig.ApplyAllVolumes(); }); CreateOrBind(container, baseSlider, "Wind Volume", ref _sWind, ref _vWind, AmbienceSoundConfig.WindVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyWindVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Ocean Volume", ref _sOcean, ref _vOcean, AmbienceSoundConfig.OceanVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyOceanVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Ambient Loop Volume", ref _sAmbientLoop, ref _vAmbientLoop, AmbienceSoundConfig.AmbientLoopVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyAmbientLoopVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Shield Hum Volume", ref _sShieldHum, ref _vShieldHum, AmbienceSoundConfig.ShieldHumVolume, delegate { AudioMan_AmbienceVolume_Patch.ApplyShieldHumVolumeWrapper(); }); CreateOrBind(container, baseSlider, "Extra SFX Volume", ref _sExtraSfx, ref _vExtraSfx, AmbienceSoundConfig.ExtraSfxVolume, delegate { AudioSourceFilter.Refresh(); AudioSource[] array = Object.FindObjectsOfType<AudioSource>(); for (int i = 0; i < array.Length; i++) { AudioSourceFilter.Apply(array[i]); } }); CreateOrBind(container, baseSlider, "Extra Clip Volume", ref _sExtraClip, ref _vExtraClip, AmbienceSoundConfig.ExtraClipVolume, delegate { AudioSourceFilter.Refresh(); AudioSourceFilter.ApplyToRunningSources(); }); } internal static void CreateOrBind(Transform parent, GameObject baseSlider, string labelText, ref Slider sliderRef, ref TextMeshProUGUI valueTextRef, ConfigEntry<float> config, Action<float> onChanged) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) string text = "Ambience_" + labelText.Replace(" ", ""); Transform val2 = parent.Find(text); GameObject val3; TextMeshProUGUI[] componentsInChildren; if ((Object)(object)val2 == (Object)null) { val3 = Object.Instantiate<GameObject>(baseSlider, parent); ((Object)val3).name = text; val3.transform.localScale = Vector3.one; val3.SetActive(true); RectTransform component = val3.GetComponent<RectTransform>(); ((Transform)component).localPosition = Vector3.zero; ((Transform)component).localRotation = Quaternion.identity; componentsInChildren = val3.GetComponentsInChildren<TextMeshProUGUI>(true); foreach (TextMeshProUGUI val4 in componentsInChildren) { if (!((Object)(object)((TMP_Text)val4).font == (Object)null)) { continue; } TextMeshProUGUI componentInChildren = baseSlider.GetComponentInChildren<TextMeshProUGUI>(true); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)((TMP_Text)componentInChildren).font != (Object)null) { ((TMP_Text)val4).font = ((TMP_Text)componentInChildren).font; ((TMP_Text)val4).fontSharedMaterial = ((TMP_Text)componentInChildren).fontSharedMaterial; continue; } TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); if (array.Length != 0) { ((TMP_Text)val4).font = array[0]; } } } else { val3 = ((Component)val2).gameObject; } Slider componentInChildren2 = val3.GetComponentInChildren<Slider>(true); if ((Object)(object)componentInChildren2 == (Object)null) { Debug.LogError((object)("[AmbienceSoundConfig] Slider missing for " + labelText)); return; } TextMeshProUGUI val5 = null; TextMeshProUGUI valueText = null; componentsInChildren = val3.GetComponentsInChildren<TextMeshProUGUI>(true); foreach (TextMeshProUGUI val6 in componentsInChildren) { string text2 = ((Object)val6).name.ToLowerInvariant(); if (text2.Contains("value")) { valueText = val6; } else if (text2.Contains("text") || text2.Contains("label")) { val5 = val6; } } if ((Object)(object)val5 != (Object)null) { ((TMP_Text)val5).text = labelText; ((TMP_Text)val5).enableAutoSizing = true; ((TMP_Text)val5).fontSizeMin = 10f; ((TMP_Text)val5).fontSizeMax = 14f; ((Graphic)val5).raycastTarget = false; } if ((Object)(object)valueText != (Object)null) { ((Graphic)valueText).raycastTarget = false; ((TMP_Text)valueText).text = $"{Mathf.RoundToInt(config.Value * 100f)}%"; } componentInChildren2.minValue = 0f; componentInChildren2.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); ((UnityEventBase)componentInChildren2.onValueChanged).RemoveAllListeners(); ((UnityEvent<float>)(object)componentInChildren2.onValueChanged).AddListener((UnityAction<float>)delegate(float val) { float num = AmbienceSoundConfig.ClampConfiguredVolume(val); config.Value = num; AmbienceSoundConfig.SaveConfigPreservingCustomSections(((ConfigEntryBase)config).ConfigFile); if ((Object)(object)valueText != (Object)null) { ((TMP_Text)valueText).text = $"{Mathf.RoundToInt(num * 100f)}%"; } onChanged?.Invoke(num); }); sliderRef = componentInChildren2; valueTextRef = valueText; componentInChildren2.value = config.Value; } public static void SyncUI() { try { if ((Object)(object)_gridContainer == (Object)null || (Object)(object)((Component)_gridContainer).gameObject == (Object)null) { return; } if ((Object)(object)_sMaster != (Object)null) { _sMaster.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sMaster.SetValueWithoutNotify(AmbienceSoundConfig.MasterVolume.Value); if ((Object)(object)_vMaster != (Object)null) { ((TMP_Text)_vMaster).text = $"{Mathf.RoundToInt(_sMaster.value * 100f)}%"; } } if ((Object)(object)_sWind != (Object)null) { _sWind.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sWind.SetValueWithoutNotify(AmbienceSoundConfig.WindVolume.Value); if ((Object)(object)_vWind != (Object)null) { ((TMP_Text)_vWind).text = $"{Mathf.RoundToInt(_sWind.value * 100f)}%"; } } if ((Object)(object)_sOcean != (Object)null) { _sOcean.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sOcean.SetValueWithoutNotify(AmbienceSoundConfig.OceanVolume.Value); if ((Object)(object)_vOcean != (Object)null) { ((TMP_Text)_vOcean).text = $"{Mathf.RoundToInt(_sOcean.value * 100f)}%"; } } if ((Object)(object)_sAmbientLoop != (Object)null) { _sAmbientLoop.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sAmbientLoop.SetValueWithoutNotify(AmbienceSoundConfig.AmbientLoopVolume.Value); if ((Object)(object)_vAmbientLoop != (Object)null) { ((TMP_Text)_vAmbientLoop).text = $"{Mathf.RoundToInt(_sAmbientLoop.value * 100f)}%"; } } if ((Object)(object)_sShieldHum != (Object)null) { _sShieldHum.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sShieldHum.SetValueWithoutNotify(AmbienceSoundConfig.ShieldHumVolume.Value); if ((Object)(object)_vShieldHum != (Object)null) { ((TMP_Text)_vShieldHum).text = $"{Mathf.RoundToInt(_sShieldHum.value * 100f)}%"; } } if ((Object)(object)_sExtraSfx != (Object)null) { _sExtraSfx.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sExtraSfx.SetValueWithoutNotify(AmbienceSoundConfig.ExtraSfxVolume.Value); if ((Object)(object)_vExtraSfx != (Object)null) { ((TMP_Text)_vExtraSfx).text = $"{Mathf.RoundToInt(_sExtraSfx.value * 100f)}%"; } } if ((Object)(object)_sExtraClip != (Object)null) { _sExtraClip.maxValue = AmbienceSoundConfig.GetVolumeSliderMax(); _sExtraClip.SetValueWithoutNotify(AmbienceSoundConfig.ExtraClipVolume.Value); if ((Object)(object)_vExtraClip != (Object)null) { ((TMP_Text)_vExtraClip).text = $"{Mathf.RoundToInt(_sExtraClip.value * 100f)}%"; } } } catch (Exception ex) { Debug.LogError((object)("[AmbienceSoundConfig] SyncUI failed: " + ex)); } } private static GameObject FindDeepChild(Transform parent, string name) { Transform val = FindDeepChildTransform(parent, name); if (!Object.op_Implicit((Object)(object)val)) { return null; } return ((Component)val).gameObject; } private static Transform FindDeepChildTransform(Transform parent, string name) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown foreach (Transform item in parent) { Transform val = item; if (((Object)val).name == name) { return val; } Transform val2 = FindDeepChildTransform(val, name); if ((Object)(object)val2 != (Object)null) { return val2; } } return null; } } internal static class SoundPlayLogger { private static readonly HashSet<string> _seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static string _logPath; internal static void Init(ConfigFile config) { try { _logPath = Path.Combine(Path.GetDirectoryName(config.ConfigFilePath), "AmbienceSoundConfig_SoundLog.txt"); } catch { _logPath = Path.Combine(Paths.ConfigPath, "AmbienceSoundConfig_SoundLog.txt"); } } internal static void Log(string type, string name, GameObject go = null) { if (!AmbienceSoundConfig.EnableSoundLogging.Value || string.IsNullOrEmpty(name) || (AmbienceSoundConfig.LogOnlyUnique.Value && !_seen.Add(type + "|" + name))) { return; } string text = DateTime.Now.ToString("HH:mm:ss.fff"); string text2 = (((Object)(object)go != (Object)null) ? ("[" + text + "] " + type + ": " + name + " @ " + ((Object)go).name) : ("[" + text + "] " + type + ": " + name)); Debug.Log((object)("[ASC-LOG] " + text2)); try { File.AppendAllText(_logPath, text2 + Environment.NewLine); } catch { } } } [HarmonyPatch(typeof(ZSFX))] internal static class ZSFX_VolumeScale_Patch { [HarmonyPostfix] [HarmonyPatch("CustomUpdate")] private static void Postfix(ZSFX __instance) { if (!AudioSourceFilter.HasClipTargets) { return; } AudioSource component = ((Component)__instance).GetComponent<AudioSource>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.clip == (Object)null)) { if (component.isPlaying) { SoundPlayLogger.Log("ZSFX", ((Object)component.clip).name, ((Component)__instance).gameObject); } float num = 1f; if (AudioSourceFilter.TryGetClipMultiplier(component.clip, out var multiplier)) { num = multiplier; } __instance.SetVolumeModifier((num <= 0f) ? 0f : num); } } } }