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 Valheim Music Plus v1.14.3
plugins/BiomeMusic/BiomeMusic.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("BiomeMusic")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BiomeMusic")] [assembly: AssemblyTitle("BiomeMusic")] [assembly: AssemblyVersion("1.0.0.0")] namespace BiomeMusic; [BepInPlugin("kadabra.biomemusic", "BiomeMusic", "1.14.3")] public class BiomeMusicPlugin : BaseUnityPlugin { private static ManualLogSource Log; private ConfigEntry<float> cfgVolume; private ConfigEntry<float> cfgMinGap; private ConfigEntry<float> cfgMaxGap; private ConfigEntry<float> cfgFadeTime; private ConfigEntry<float> cfgBossRadius; private ConfigEntry<float> cfgBuildingRadius; private ConfigEntry<float> cfgHomeGrace; private ConfigEntry<float> cfgFireplaceRadius; private ConfigEntry<bool> cfgMiningMusic; private ConfigEntry<float> cfgMiningLinger; private AudioSource source; private readonly Dictionary<string, List<AudioClip>> clips = new Dictionary<string, List<AudioClip>>(); private readonly List<AudioClip> globalClips = new List<AudioClip>(); private readonly Dictionary<string, (AudioClip clip, float pos)> _saved = new Dictionary<string, (AudioClip, float)>(); private readonly Dictionary<string, Queue<AudioClip>> _queues = new Dictionary<string, Queue<AudioClip>>(); private string _currentZone = ""; private bool _isFading; private bool _skipNextGap; private float _lastShelterTime = -999f; private float _lastMineTime = -999f; private float _fade = 1f; private static readonly Dictionary<string, string> BossFolders = new Dictionary<string, string> { { "eikthyr", "eikthyr" }, { "elder", "gd_king" }, { "bonemass", "bonemass" }, { "moder", "dragon" }, { "yagluth", "goblinking" }, { "queen", "seekerqueen" }, { "fader", "fader" } }; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; cfgVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "Volume", 0.8f, "Volume of your tracks (0.0-1.0)"); cfgMinGap = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "MinGap", 1f, "Min seconds gap between tracks (natural endings)"); cfgMaxGap = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "MaxGap", 3f, "Max seconds gap between tracks (natural endings)"); cfgFadeTime = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "FadeTime", 2f, "Seconds to fade in/out on zone change"); cfgBossRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "BossRadius", 150f, "Meters to detect a nearby boss"); cfgBuildingRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "BuildingRadius", 1f, "Meters to detect nearby structural pieces"); cfgHomeGrace = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "HomeGrace", 3f, "Seconds home music lingers after leaving shelter"); cfgFireplaceRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "FireplaceRadius", 12f, "Meters to detect a burning fireplace outdoors"); cfgMiningMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("Mining", "Enabled", true, "EXPERIMENTAL: play mining/ tracks while swinging a pickaxe"); cfgMiningLinger = ((BaseUnityPlugin)this).Config.Bind<float>("Mining", "Linger", 20f, "Seconds mining music keeps playing after the last pickaxe swing"); string text = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "music"); if (!Directory.Exists(text)) { text = Path.Combine(Paths.PluginPath, "BiomeMusic", "music"); } Log.LogInfo((object)("[BiomeMusic] Music folder: " + text)); string text2 = Path.Combine(Paths.ConfigPath, "BiomeMusic", "music-personal"); string text3 = Path.Combine(Path.GetDirectoryName(text), "music-personal"); Log.LogInfo((object)("[BiomeMusic] Personal folder: " + text2)); ((MonoBehaviour)this).StartCoroutine(LoadAllClips(text, text2, text3)); } private void SilenceMusicMan() { MusicMan instance = MusicMan.instance; if ((Object)(object)instance == (Object)null) { return; } AudioSource[] componentsInChildren = ((Component)instance).GetComponentsInChildren<AudioSource>(); foreach (AudioSource val in componentsInChildren) { if ((Object)(object)val != (Object)null && val.isPlaying) { val.Stop(); } } } private void AttachToMusicMixer() { if ((Object)(object)source == (Object)null || (Object)(object)source.outputAudioMixerGroup != (Object)null) { return; } AudioMan instance = AudioMan.instance; if ((Object)(object)instance == (Object)null) { return; } AudioMixerGroup val = null; try { object? obj = typeof(AudioMan).GetField("m_musicMixer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance); val = (AudioMixerGroup)((obj is AudioMixerGroup) ? obj : null); } catch { } if ((Object)(object)val == (Object)null) { List<AudioSource> list = new List<AudioSource>(((Component)instance).GetComponentsInChildren<AudioSource>(true)); if ((Object)(object)MusicMan.instance != (Object)null) { list.AddRange(((Component)MusicMan.instance).GetComponentsInChildren<AudioSource>(true)); } foreach (AudioSource item in list) { AudioMixer val2 = (((Object)(object)item != (Object)null && (Object)(object)item.outputAudioMixerGroup != (Object)null) ? item.outputAudioMixerGroup.audioMixer : null); if ((Object)(object)val2 == (Object)null) { continue; } AudioMixerGroup[] array = val2.FindMatchingGroups("Music"); foreach (AudioMixerGroup val3 in array) { if (((Object)val3).name == "Music") { val = val3; break; } } if ((Object)(object)val != (Object)null) { break; } } } if (!((Object)(object)val == (Object)null)) { source.outputAudioMixerGroup = val; Log.LogInfo((object)("[BiomeMusic] Using the game's music channel (" + ((Object)val).name + ") (same as vanilla music)")); } } private IEnumerator VanillaSilencer() { while (true) { AttachToMusicMixer(); if ((Object)(object)Player.m_localPlayer != (Object)null) { SilenceMusicMan(); } yield return (object)new WaitForSeconds(0.5f); } } private void Update() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Invalid comparison between Unknown and I4 if ((Object)(object)source != (Object)null) { source.volume = _fade * cfgVolume.Value * MusicMan.m_masterMusicVolume; } if (cfgMiningMusic == null || !cfgMiningMusic.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } try { ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon(); if (currentWeapon != null && (int)currentWeapon.m_shared.m_skillType == 12 && ((Character)localPlayer).InAttack()) { _lastMineTime = Time.time; } } catch { } } private bool IsMining() { if (cfgMiningMusic.Value) { return Time.time - _lastMineTime < cfgMiningLinger.Value; } return false; } private bool HasClips(string zone) { if (clips.TryGetValue(zone, out var value)) { return value.Count > 0; } return false; } private AudioClip NextClip(string zone) { if (!clips.ContainsKey(zone) || clips[zone].Count == 0) { return null; } if (!_queues.ContainsKey(zone) || _queues[zone].Count == 0) { List<AudioClip> list = new List<AudioClip>(clips[zone]); for (int num = list.Count - 1; num > 0; num--) { int index = Random.Range(0, num + 1); AudioClip value = list[num]; list[num] = list[index]; list[index] = value; } _queues[zone] = new Queue<AudioClip>(list); Log.LogInfo((object)$"[BiomeMusic] [{zone}] reshuffled ({list.Count} tracks)"); } return _queues[zone].Dequeue(); } private IEnumerator FadeOutAndStop() { _isFading = true; float startVol = _fade; float elapsed = 0f; float duration = cfgFadeTime.Value; while (elapsed < duration && source.isPlaying) { _fade = Mathf.Lerp(startVol, 0f, elapsed / duration); elapsed += Time.deltaTime; yield return null; } source.Stop(); _fade = 1f; _isFading = false; } private IEnumerator FadeIn() { float elapsed = 0f; float duration = cfgFadeTime.Value; while (elapsed < duration) { _fade = Mathf.Lerp(0f, 1f, elapsed / duration); elapsed += Time.deltaTime; yield return null; } _fade = 1f; } private Character GetNearbyBoss(Player player) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { Collider[] array = Physics.OverlapSphere(((Component)player).transform.position, cfgBossRadius.Value); for (int i = 0; i < array.Length; i++) { Character componentInParent = ((Component)array[i]).GetComponentInParent<Character>(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent != (Object)(object)player && componentInParent.IsBoss() && !componentInParent.IsDead()) { return componentInParent; } } } catch { } return null; } private static string BossZone(string prefabOrFolder) { string text = prefabOrFolder.ToLowerInvariant(); if (BossFolders.TryGetValue(text, out var value)) { text = value; } return "boss_" + text; } private string GetBossKey(Player player) { Character nearbyBoss = GetNearbyBoss(player); if ((Object)(object)nearbyBoss == (Object)null) { return null; } string text = BossZone(((Object)((Component)nearbyBoss).gameObject).name.Replace("(Clone)", "").Trim()); if (HasClips(text)) { return text; } if (HasClips("boss")) { return "boss"; } return null; } private bool IsNearFireplace(Player player) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { Collider[] array = Physics.OverlapSphere(((Component)player).transform.position, cfgFireplaceRadius.Value); for (int i = 0; i < array.Length; i++) { Fireplace componentInParent = ((Component)array[i]).GetComponentInParent<Fireplace>(); if ((Object)(object)componentInParent != (Object)null && componentInParent.IsBurning()) { return true; } } } catch { } return false; } private bool IsNearBuilding(Player player) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) int num = 0; Collider[] array = Physics.OverlapSphere(((Component)player).transform.position, cfgBuildingRadius.Value); foreach (Collider val in array) { if ((Object)(object)((Component)val).GetComponentInParent<WearNTear>() != (Object)null) { float[] array2 = new float[3]; Bounds bounds = val.bounds; array2[0] = ((Bounds)(ref bounds)).size.x; bounds = val.bounds; array2[1] = ((Bounds)(ref bounds)).size.y; bounds = val.bounds; array2[2] = ((Bounds)(ref bounds)).size.z; if (Mathf.Max(array2) > 0.8f && ++num >= 5) { return true; } } } return false; } private string GetBiomeKey(Player player) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Invalid comparison between Unknown and I4 //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Invalid comparison between Unknown and I4 //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected I4, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Invalid comparison between Unknown and I4 //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Invalid comparison between Unknown and I4 //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Invalid comparison between Unknown and I4 //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Invalid comparison between Unknown and I4 //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Invalid comparison between Unknown and I4 //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Invalid comparison between Unknown and I4 string bossKey = GetBossKey(player); if (bossKey != null) { return bossKey; } if (HasClips("mining") && IsMining()) { return "mining"; } bool num = player.InShelter(); bool flag = IsNearFireplace(player); if (num && flag) { _lastShelterTime = Time.time; return "home"; } if (Time.time - _lastShelterTime < cfgHomeGrace.Value) { return "home"; } if (HasClips("approaching") && IsNearBuilding(player)) { return "approaching"; } Biome currentBiome = player.GetCurrentBiome(); if ((int)currentBiome <= 16) { switch (currentBiome - 1) { default: if ((int)currentBiome != 8) { if ((int)currentBiome != 16) { break; } return "plains"; } return "blackforest"; case 0: return "meadows"; case 1: return "swamp"; case 3: return "mountain"; case 2: break; } } else if ((int)currentBiome <= 64) { if ((int)currentBiome == 32) { return "ashlands"; } if ((int)currentBiome == 64) { return "deepnorth"; } } else { if ((int)currentBiome == 256) { return "ocean"; } if ((int)currentBiome == 512) { return "mistlands"; } } return "meadows"; } private IEnumerator LoadAllClips(params string[] roots) { string[] biomes = new string[13] { "meadows", "blackforest", "swamp", "mountain", "plains", "ocean", "mistlands", "ashlands", "deepnorth", "home", "boss", "approaching", "mining" }; foreach (string root in roots) { if (!Directory.Exists(root)) { continue; } string[] array = biomes; foreach (string biome in array) { string dir = Path.Combine(root, biome); if (Directory.Exists(dir)) { if (!clips.ContainsKey(biome)) { clips[biome] = new List<AudioClip>(); } string[] files = Directory.GetFiles(dir, "*.ogg"); foreach (string path in files) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path, clips[biome], (AudioType)14)); } files = Directory.GetFiles(dir, "*.wav"); foreach (string path2 in files) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path2, clips[biome], (AudioType)20)); } } } string path3 = Path.Combine(root, "boss"); if (Directory.Exists(path3)) { array = Directory.GetDirectories(path3); foreach (string biome in array) { string dir = BossZone(Path.GetFileName(biome)); if (!clips.ContainsKey(dir)) { clips[dir] = new List<AudioClip>(); } string[] files = Directory.GetFiles(biome, "*.ogg"); foreach (string path4 in files) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path4, clips[dir], (AudioType)14)); } files = Directory.GetFiles(biome, "*.wav"); foreach (string path5 in files) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path5, clips[dir], (AudioType)20)); } } } string globalDir = Path.Combine(root, "global"); if (Directory.Exists(globalDir)) { array = Directory.GetFiles(globalDir, "*.ogg"); foreach (string path6 in array) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path6, globalClips, (AudioType)14)); } array = Directory.GetFiles(globalDir, "*.wav"); foreach (string path7 in array) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path7, globalClips, (AudioType)20)); } } } foreach (KeyValuePair<string, List<AudioClip>> clip in clips) { Log.LogInfo((object)$"[BiomeMusic] {clip.Key}: {clip.Value.Count} track(s)"); } if (globalClips.Count > 0) { Log.LogInfo((object)$"[BiomeMusic] global: {globalClips.Count} track(s)"); string[] array2 = new string[11] { "meadows", "blackforest", "swamp", "mountain", "plains", "ocean", "mistlands", "ashlands", "deepnorth", "home", "approaching" }; foreach (string key in array2) { if (!clips.ContainsKey(key)) { clips[key] = new List<AudioClip>(); } clips[key].AddRange(globalClips); } } GameObject val = new GameObject("BiomeMusicPlayer"); Object.DontDestroyOnLoad((Object)(object)val); source = val.AddComponent<AudioSource>(); source.spatialBlend = 0f; source.loop = false; _fade = 1f; int num = 0; foreach (List<AudioClip> value in clips.Values) { num += value.Count; } Log.LogInfo((object)$"[BiomeMusic] Ready — {num} total slots."); if (num == 0) { Log.LogWarning((object)("[BiomeMusic] No tracks found in " + string.Join(", ", roots) + " — leaving vanilla music on.")); yield break; } ((MonoBehaviour)this).StartCoroutine(VanillaSilencer()); ((MonoBehaviour)this).StartCoroutine(MusicLoop()); } private IEnumerator LoadClip(string path, List<AudioClip> target, AudioType type) { //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) UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip("file://" + path, type); try { ((DownloadHandlerAudioClip)req.downloadHandler).streamAudio = false; yield return req.SendWebRequest(); if ((int)req.result == 1) { AudioClip content = DownloadHandlerAudioClip.GetContent(req); ((Object)content).name = Path.GetFileNameWithoutExtension(path); target.Add(content); } else { Log.LogWarning((object)("[BiomeMusic] Load failed: " + path + " — " + req.error)); } } finally { ((IDisposable)req)?.Dispose(); } } private IEnumerator MusicLoop() { yield return (object)new WaitForSeconds(20f); while (true) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { yield return (object)new WaitForSeconds(2f); continue; } string biomeKey = GetBiomeKey(localPlayer); if (_isFading) { yield return (object)new WaitForSeconds(0.1f); continue; } if (source.isPlaying) { if (biomeKey != _currentZone) { _saved[_currentZone] = (source.clip, source.time); _skipNextGap = true; Log.LogInfo((object)$"[BiomeMusic] [{_currentZone}→{biomeKey}] saved {source.time:F0}s — fading"); ((MonoBehaviour)this).StartCoroutine(FadeOutAndStop()); } yield return (object)new WaitForSeconds(1f); continue; } if (!_skipNextGap) { float num = Random.Range(cfgMinGap.Value, cfgMaxGap.Value); yield return (object)new WaitForSeconds(num); } _skipNextGap = false; localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { continue; } biomeKey = GetBiomeKey(localPlayer); if (!clips.ContainsKey(biomeKey) || clips[biomeKey].Count == 0) { yield return (object)new WaitForSeconds(2f); continue; } float num2 = 0f; AudioClip val; if (_saved.TryGetValue(biomeKey, out (AudioClip, float) value) && (Object)(object)value.Item1 != (Object)null && value.Item1.length - value.Item2 > 10f) { (val, num2) = value; _saved.Remove(biomeKey); Log.LogInfo((object)$"[BiomeMusic] ↩ Resume [{biomeKey}] {((Object)val).name} from {num2:F0}s"); } else { val = NextClip(biomeKey); if (_saved.ContainsKey(biomeKey)) { _saved.Remove(biomeKey); } Log.LogInfo((object)$"[BiomeMusic] ▶ [{biomeKey}] {((Object)val).name} ({val.length:F0}s)"); } _currentZone = biomeKey; source.clip = val; source.time = num2; _fade = 0f; source.Play(); yield return ((MonoBehaviour)this).StartCoroutine(FadeIn()); } } }