using 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.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.13.0")] public class BiomeMusicPlugin : BaseUnityPlugin { private static ManualLogSource Log; private ConfigEntry cfgVolume; private ConfigEntry cfgMinGap; private ConfigEntry cfgMaxGap; private ConfigEntry cfgFadeTime; private ConfigEntry cfgBossRadius; private ConfigEntry cfgBuildingRadius; private ConfigEntry cfgHomeGrace; private ConfigEntry cfgFireplaceRadius; private AudioSource source; private readonly Dictionary> clips = new Dictionary>(); private readonly List globalClips = new List(); private readonly Dictionary _saved = new Dictionary(); private readonly Dictionary> _queues = new Dictionary>(); private string _currentZone = ""; private bool _isFading; private bool _skipNextGap; private float _lastShelterTime = -999f; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; cfgVolume = ((BaseUnityPlugin)this).Config.Bind("Audio", "Volume", 0.8f, "Volume of your tracks (0.0-1.0)"); cfgMinGap = ((BaseUnityPlugin)this).Config.Bind("Audio", "MinGap", 1f, "Min seconds gap between tracks (natural endings)"); cfgMaxGap = ((BaseUnityPlugin)this).Config.Bind("Audio", "MaxGap", 3f, "Max seconds gap between tracks (natural endings)"); cfgFadeTime = ((BaseUnityPlugin)this).Config.Bind("Audio", "FadeTime", 2f, "Seconds to fade in/out on zone change"); cfgBossRadius = ((BaseUnityPlugin)this).Config.Bind("Detection", "BossRadius", 150f, "Meters to detect a nearby boss"); cfgBuildingRadius = ((BaseUnityPlugin)this).Config.Bind("Detection", "BuildingRadius", 1f, "Meters to detect nearby structural pieces"); cfgHomeGrace = ((BaseUnityPlugin)this).Config.Bind("Detection", "HomeGrace", 3f, "Seconds home music lingers after leaving shelter"); cfgFireplaceRadius = ((BaseUnityPlugin)this).Config.Bind("Detection", "FireplaceRadius", 12f, "Meters to detect a burning fireplace outdoors"); ((MonoBehaviour)this).StartCoroutine(LoadAllClips(Path.Combine(Paths.PluginPath, "BiomeMusic", "music"))); } private void SilenceMusicMan() { MusicMan instance = MusicMan.instance; if ((Object)(object)instance == (Object)null) { return; } AudioSource[] componentsInChildren = ((Component)instance).GetComponentsInChildren(); foreach (AudioSource val in componentsInChildren) { if ((Object)(object)val != (Object)null && val.isPlaying) { val.Stop(); } } } private IEnumerator VanillaSilencer() { while (true) { if ((Object)(object)Player.m_localPlayer != (Object)null) { SilenceMusicMan(); } yield return (object)new WaitForSeconds(0.5f); } } private AudioClip NextClip(string zone) { if (!clips.ContainsKey(zone) || clips[zone].Count == 0) { return null; } if (!_queues.ContainsKey(zone) || _queues[zone].Count == 0) { List list = new List(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(list); Log.LogInfo((object)$"[BiomeMusic] [{zone}] reshuffled ({list.Count} tracks)"); } return _queues[zone].Dequeue(); } private IEnumerator FadeOutAndStop() { _isFading = true; float startVol = source.volume; float elapsed = 0f; float duration = cfgFadeTime.Value; while (elapsed < duration && source.isPlaying) { source.volume = Mathf.Lerp(startVol, 0f, elapsed / duration); elapsed += Time.deltaTime; yield return null; } source.Stop(); source.volume = cfgVolume.Value; _isFading = false; } private IEnumerator FadeIn() { float elapsed = 0f; float duration = cfgFadeTime.Value; while (elapsed < duration) { source.volume = Mathf.Lerp(0f, cfgVolume.Value, elapsed / duration); elapsed += Time.deltaTime; yield return null; } source.volume = cfgVolume.Value; } private bool IsBossNearby(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(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent != (Object)(object)player && componentInParent.IsBoss() && !componentInParent.IsDead()) { return true; } } } catch { } return false; } 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(); 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() != (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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Invalid comparison between Unknown and I4 //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Invalid comparison between Unknown and I4 //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected I4, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Invalid comparison between Unknown and I4 //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Invalid comparison between Unknown and I4 //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Invalid comparison between Unknown and I4 //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Invalid comparison between Unknown and I4 //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Invalid comparison between Unknown and I4 //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Invalid comparison between Unknown and I4 if (IsBossNearby(player)) { return "boss"; } 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 (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(string root) { string[] array = new string[12] { "meadows", "blackforest", "swamp", "mountain", "plains", "ocean", "mistlands", "ashlands", "deepnorth", "home", "boss", "approaching" }; string[] array2 = array; foreach (string biome in array2) { string dir = Path.Combine(root, biome); if (Directory.Exists(dir)) { clips[biome] = new List(); 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)); } Log.LogInfo((object)$"[BiomeMusic] {biome}: {clips[biome].Count} track(s)"); } } string globalDir = Path.Combine(root, "global"); if (Directory.Exists(globalDir)) { array2 = Directory.GetFiles(globalDir, "*.ogg"); foreach (string path3 in array2) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path3, globalClips, (AudioType)14)); } array2 = Directory.GetFiles(globalDir, "*.wav"); foreach (string path4 in array2) { yield return ((MonoBehaviour)this).StartCoroutine(LoadClip(path4, globalClips, (AudioType)20)); } Log.LogInfo((object)$"[BiomeMusic] global: {globalClips.Count} track(s)"); string[] array3 = new string[11] { "meadows", "blackforest", "swamp", "mountain", "plains", "ocean", "mistlands", "ashlands", "deepnorth", "home", "approaching" }; foreach (string key in array3) { if (!clips.ContainsKey(key)) { clips[key] = new List(); } clips[key].AddRange(globalClips); } } GameObject val = new GameObject("BiomeMusicPlayer"); Object.DontDestroyOnLoad((Object)(object)val); source = val.AddComponent(); source.spatialBlend = 0f; source.loop = false; source.volume = cfgVolume.Value; int num = 0; foreach (List value in clips.Values) { num += value.Count; } Log.LogInfo((object)$"[BiomeMusic] Ready — {num} total slots."); ((MonoBehaviour)this).StartCoroutine(VanillaSilencer()); ((MonoBehaviour)this).StartCoroutine(MusicLoop()); } private IEnumerator LoadClip(string path, List 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; source.volume = 0f; source.Play(); yield return ((MonoBehaviour)this).StartCoroutine(FadeIn()); } } }