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 GlitnirSounds v0.1.9
GlitnirMusicZones.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Networking; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("GlitnirMusicZones")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("GlitnirMusicZones")] [assembly: AssemblyTitle("GlitnirMusicZones")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GlitnirMusicZones { [BepInPlugin("glitnir.musiczones", "Glitnir Music Zones", "2.0.2")] public class Plugin : BaseUnityPlugin { private enum MusicState { None, Biome, Location, Territory, Boss } private GameObject _menuObject; private AudioSource _menuSource; private AudioClip _menuClip; private bool _menuClipReady = false; private GameObject _zoneObject; private AudioSource _zoneSourceA; private AudioSource _zoneSourceB; private bool _zoneActiveSrcIsA = true; private Coroutine _fadeCoroutine = null; private bool _isFading = false; private const float FadeDuration = 1f; private FileSystemWatcher _configWatcher; private volatile bool _reloadPending = false; private volatile int _watcherEventTick = 0; private const int WatcherSettleMs = 400; private float _reloadCooldown = 0f; private Harmony _harmony; private readonly Dictionary<string, (AudioClip clip, float lastUsed)> _clipCache = new Dictionary<string, (AudioClip, float)>(); private readonly Dictionary<string, List<(float cfgVol, string trackId, Action<AudioClip, float, string> onReady)>> _loadWaiters = new Dictionary<string, List<(float, string, Action<AudioClip, float, string>)>>(); private bool _preloadAllClips = false; private int _maxCachedClips = 5; private float _unloadUnusedAfterSecs = 600f; private bool _debugLogging = false; private MusicEntry _menuEntry; private float _menuVolume = 0.3f; private bool _menuEnabled = true; private bool _biomesEnabled = true; private float _biomeVolume = 0.6f; private Dictionary<Biome, MusicEntry> _biomeMap = new Dictionary<Biome, MusicEntry>(); private bool _locEnabled = true; private float _locVolume = 0.65f; private float _locRadius = 90f; private float _locDungeonYMin = 4500f; private float _locDungeonYMax = 5500f; private List<LocationEntry> _locList = new List<LocationEntry>(); private bool _terrEnabled = true; private List<TerritoryEntry> _terrList = new List<TerritoryEntry>(); private bool _bossEnabled = true; private float _bossVolume = 0.7f; private float _bossRadius = 120f; private Dictionary<string, MusicEntry> _bossMap = new Dictionary<string, MusicEntry>(); private MusicState _currentState = MusicState.None; private string _currentTrackId = ""; private bool _worldLoaded = false; private float _checkInterval = 2f; private float _checkTimer = 0f; private Vector3 _lastCheckPos = Vector3.zero; private readonly Dictionary<string, Vector3> _locPosCache = new Dictionary<string, Vector3>(); private readonly HashSet<string> _locNoMatchSet = new HashSet<string>(); private float _locCacheTimer = 0f; private const float LocCacheLifetime = 60f; private readonly List<Character> _charBuffer = new List<Character>(64); private readonly Dictionary<string, string> _prefabNameCache = new Dictionary<string, string>(64); private float _currentZoneCfgVol = 1f; private float _evictTimer = 0f; private AudioSource ZoneActiveSource => _zoneActiveSrcIsA ? _zoneSourceA : _zoneSourceB; private AudioSource ZoneInactiveSource => _zoneActiveSrcIsA ? _zoneSourceB : _zoneSourceA; private string ConfigPath => Path.Combine(Paths.ConfigPath, "glitnir.musiczones", "music_config.yaml"); private string MusicDir => Path.Combine(Paths.ConfigPath, "glitnir.musiczones", "music"); private void CreateMenuPlayer() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if (!((Object)(object)_menuObject != (Object)null)) { _menuObject = new GameObject("Glitnir Menu Music Player"); Object.DontDestroyOnLoad((Object)(object)_menuObject); _menuSource = _menuObject.AddComponent<AudioSource>(); _menuSource.loop = true; _menuSource.spatialBlend = 0f; _menuSource.volume = 0f; ConnectToMixerWithRetry(_menuSource, "menu"); } } private void CreateZoneMusicPlayer() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if (!((Object)(object)_zoneObject != (Object)null)) { _zoneObject = new GameObject("Glitnir Zone Music Player"); Object.DontDestroyOnLoad((Object)(object)_zoneObject); _zoneSourceA = AddConfiguredZoneSource(); _zoneSourceB = AddConfiguredZoneSource(); } } private AudioSource AddConfiguredZoneSource() { AudioSource val = _zoneObject.AddComponent<AudioSource>(); val.loop = true; val.spatialBlend = 0f; val.volume = 0f; ConnectToMixerWithRetry(val, "zona"); return val; } private void ConnectToMixerWithRetry(AudioSource src, string label) { AudioMixerGroup musicGroup = MusicManPatches.GetMusicGroup(); if ((Object)(object)musicGroup != (Object)null) { src.outputAudioMixerGroup = musicGroup; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Glitnir] AudioSource (" + label + ") conectado ao mixer.")); } else { ((MonoBehaviour)this).StartCoroutine(RetryConnectMixer(src, label)); } } private IEnumerator RetryConnectMixer(AudioSource src, string label) { do { yield return (object)new WaitForSeconds(0.5f); AudioMixerGroup group = MusicManPatches.GetMusicGroup(); if ((Object)(object)group != (Object)null) { src.outputAudioMixerGroup = group; ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Glitnir] AudioSource (" + label + ") conectado ao mixer (retry).")); yield break; } } while (!((Object)(object)ZNet.instance != (Object)null) || !((Object)(object)Player.m_localPlayer != (Object)null)); ((BaseUnityPlugin)this).Logger.LogError((object)("[Glitnir] AudioSource (" + label + ") não conectou ao mixer — slider de música pode não afetar.")); } private void PlayZoneTrack(AudioClip clip, float cfgVol, string trackId) { MusicManPatches.VanillaMuted = true; _currentZoneCfgVol = cfgVol; if (_fadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_fadeCoroutine); } _isFading = true; _fadeCoroutine = ((MonoBehaviour)this).StartCoroutine(CrossfadeTo(clip, cfgVol)); LogDebug("[Glitnir] → " + trackId + " (" + (((Object)(object)clip != (Object)null) ? ((Object)clip).name : "?") + ")"); } private IEnumerator CrossfadeTo(AudioClip nextClip, float cfgVol) { AudioSource fadeOut = ZoneActiveSource; AudioSource fadeIn = ZoneInactiveSource; fadeIn.clip = nextClip; fadeIn.volume = 0f; fadeIn.time = 0f; fadeIn.loop = true; fadeIn.Play(); _zoneActiveSrcIsA = !_zoneActiveSrcIsA; float startVol = (fadeOut.isPlaying ? fadeOut.volume : 0f); float targetVol = cfgVol * MusicManPatches.RealMusicVolume; float t = 0f; while (t < 1f) { t += Time.deltaTime; float ratio = t / 1f; fadeOut.volume = Mathf.Lerp(startVol, 0f, ratio); fadeIn.volume = Mathf.Lerp(0f, targetVol, ratio); targetVol = cfgVol * MusicManPatches.RealMusicVolume; yield return null; } fadeOut.Stop(); fadeOut.clip = null; fadeIn.volume = targetVol; _isFading = false; } private void StopZoneMusic(bool andUnmuteVanilla) { if (_fadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_fadeCoroutine); _fadeCoroutine = null; } _isFading = false; AudioSource zoneSourceA = _zoneSourceA; if (zoneSourceA != null) { zoneSourceA.Stop(); } AudioSource zoneSourceB = _zoneSourceB; if (zoneSourceB != null) { zoneSourceB.Stop(); } if ((Object)(object)_zoneSourceA != (Object)null) { _zoneSourceA.clip = null; } if ((Object)(object)_zoneSourceB != (Object)null) { _zoneSourceB.clip = null; } if (andUnmuteVanilla) { MusicManPatches.VanillaMuted = false; } } private static AudioType GuessAudioType(string fileName) { //IL_002d: 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_0032: 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) string text = Path.GetExtension(fileName).ToLowerInvariant(); string text2 = text; if (!(text2 == ".ogg")) { if (text2 == ".wav") { return (AudioType)20; } return (AudioType)13; } return (AudioType)14; } private IEnumerator LoadLocalClipInto(string fileName) { if (string.IsNullOrWhiteSpace(fileName) || _clipCache.ContainsKey(fileName)) { yield break; } if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || fileName.Contains("..") || fileName.Contains("/") || fileName.Contains("\\")) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Glitnir] Nome de arquivo inválido ignorado: '" + fileName + "'")); _clipCache[fileName] = (null, 0f); yield break; } string path = Path.Combine(MusicDir, fileName); if (!File.Exists(path)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Glitnir] Arquivo não encontrado: " + path)); _clipCache[fileName] = (null, 0f); yield break; } string uri = new Uri(path).AbsoluteUri; UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(uri, GuessAudioType(fileName)); try { ((DownloadHandlerAudioClip)req.downloadHandler).compressed = true; yield return req.SendWebRequest(); if ((int)req.result != 1) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Glitnir] Falha ao decodificar " + fileName + ": " + req.error)); _clipCache[fileName] = (null, 0f); yield break; } AudioClip clip = DownloadHandlerAudioClip.GetContent(req); if ((Object)(object)clip != (Object)null) { ((Object)clip).name = Path.GetFileNameWithoutExtension(fileName); } _clipCache[fileName] = (clip, Time.realtimeSinceStartup); } finally { ((IDisposable)req)?.Dispose(); } } private IEnumerator LoadMenuClip() { _menuClipReady = false; if (_menuEntry != null && _menuEnabled && _menuEntry.IsActive) { if (_clipCache.TryGetValue(_menuEntry.clip, out var cached) && (Object)(object)cached.clip == (Object)null) { _clipCache.Remove(_menuEntry.clip); } yield return LoadLocalClipInto(_menuEntry.clip); if (TryGetClip(_menuEntry.clip, out var clip)) { _menuClip = clip; _menuClipReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir] Menu pronto."); } } } private IEnumerator LoadAllConfiguredClips() { HashSet<string> files = new HashSet<string>(); foreach (MusicEntry e in _biomeMap.Values) { if (e.IsActive && e.mode == TerritoryMode.Custom) { files.Add(e.clip); } } foreach (TerritoryEntry t in _terrList) { if (t.IsActive && t.mode == TerritoryMode.Custom) { files.Add(t.clip); } } foreach (LocationEntry l in _locList) { if (l.IsActive) { files.Add(l.clip); } } foreach (MusicEntry e2 in _bossMap.Values) { if (e2.IsActive) { files.Add(e2.clip); } } if (_menuEntry != null && _menuEntry.IsActive) { files.Add(_menuEntry.clip); } List<string> removed = _clipCache.Keys.Where((string k) => !files.Contains(k)).ToList(); foreach (string key in removed) { if ((Object)(object)_clipCache[key].clip != (Object)null) { Object.Destroy((Object)(object)_clipCache[key].clip); } _clipCache.Remove(key); } if (removed.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[Glitnir] {removed.Count} clipe(s) removidos da memória."); } foreach (string file in files) { if (_clipCache.TryGetValue(file, out var c) && (Object)(object)c.clip == (Object)null) { _clipCache.Remove(file); } c = default((AudioClip, float)); } foreach (string file2 in files) { yield return LoadLocalClipInto(file2); } (AudioClip, float) value; int ok = files.Count((string f) => _clipCache.TryGetValue(f, out value) && (Object)(object)value.Item1 != (Object)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[Glitnir] {ok}/{files.Count} clipes pré-carregados."); } private void PurgeRemovedClips() { HashSet<string> files = new HashSet<string>(); foreach (MusicEntry value in _biomeMap.Values) { if (value.IsActive && value.mode == TerritoryMode.Custom) { files.Add(value.clip); } } foreach (TerritoryEntry terr in _terrList) { if (terr.IsActive && terr.mode == TerritoryMode.Custom) { files.Add(terr.clip); } } foreach (LocationEntry loc in _locList) { if (loc.IsActive) { files.Add(loc.clip); } } foreach (MusicEntry value2 in _bossMap.Values) { if (value2.IsActive) { files.Add(value2.clip); } } if (_menuEntry != null && _menuEntry.IsActive) { files.Add(_menuEntry.clip); } List<string> list = _clipCache.Keys.Where((string k) => !files.Contains(k)).ToList(); foreach (string item in list) { if ((Object)(object)_clipCache[item].clip != (Object)null) { Object.Destroy((Object)(object)_clipCache[item].clip); } _clipCache.Remove(item); } if (list.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[Glitnir] {list.Count} clipe(s) removidos da memória."); } } private IEnumerator LoadAndThenPlay(string fileName, float cfgVol, string trackId, Action<AudioClip, float, string> onReady) { if (_loadWaiters.TryGetValue(fileName, out var existingWaiters)) { existingWaiters.Add((cfgVol, trackId, onReady)); yield break; } List<(float cfgVol, string trackId, Action<AudioClip, float, string> onReady)> waiters = new List<(float, string, Action<AudioClip, float, string>)> { (cfgVol, trackId, onReady) }; _loadWaiters[fileName] = waiters; if (_clipCache.TryGetValue(fileName, out var cached) && (Object)(object)cached.clip == (Object)null) { _clipCache.Remove(fileName); } yield return LoadLocalClipInto(fileName); _loadWaiters.Remove(fileName); AudioClip clip; bool loaded = TryGetClip(fileName, out clip); foreach (var waiter in waiters) { if (_currentTrackId != waiter.trackId) { LogDebug("[Glitnir] Carregamento de '" + fileName + "' concluído mas zona '" + waiter.trackId + "' já não é mais a atual — descartado."); } else if (loaded) { waiter.onReady(clip, waiter.cfgVol, waiter.trackId); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Glitnir] Não foi possível carregar '" + fileName + "'.")); _currentTrackId = ""; } } } private void EvictLruClips() { _evictTimer -= Time.deltaTime; if (_evictTimer > 0f) { return; } _evictTimer = 30f; float now = Time.realtimeSinceStartup; string menuFileName = _menuEntry?.clip ?? ""; List<string> list = (from kv in _clipCache where (Object)(object)kv.Value.clip != (Object)null && kv.Key != menuFileName && now - kv.Value.lastUsed > _unloadUnusedAfterSecs select kv.Key).ToList(); foreach (string item in list) { Object.Destroy((Object)(object)_clipCache[item].clip); _clipCache.Remove(item); } List<KeyValuePair<string, (AudioClip, float)>> list2 = (from kv in _clipCache where (Object)(object)kv.Value.clip != (Object)null && kv.Key != menuFileName orderby kv.Value.lastUsed select kv).ToList(); int num = list2.Count - _maxCachedClips; for (int num2 = 0; num2 < num; num2++) { Object.Destroy((Object)(object)list2[num2].Value.Item1); _clipCache.Remove(list2[num2].Key); } int num3 = list.Count + Math.Max(0, num); if (num3 > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[Glitnir] LRU: {num3} clipe(s) descarregados da RAM."); } } private bool TryGetClip(string fileName, out AudioClip clip) { clip = null; if (string.IsNullOrWhiteSpace(fileName)) { return false; } if (!_clipCache.TryGetValue(fileName, out (AudioClip, float) value)) { return false; } if ((Object)(object)value.Item1 == (Object)null) { return false; } _clipCache[fileName] = (value.Item1, Time.realtimeSinceStartup); (clip, _) = value; return true; } private void StartConfigWatcher() { try { string directoryName = Path.GetDirectoryName(ConfigPath); string fileName = Path.GetFileName(ConfigPath); if (Directory.Exists(directoryName)) { _configWatcher = new FileSystemWatcher(directoryName, fileName) { NotifyFilter = NotifyFilters.LastWrite, EnableRaisingEvents = true }; _configWatcher.Changed += delegate { _watcherEventTick = Environment.TickCount; _reloadPending = true; }; ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir] Watcher de config ativo."); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Glitnir] Watcher não iniciado: " + ex.Message)); } } private static TerritoryMode ParseMode(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return TerritoryMode.Custom; } string text = raw.Trim().ToLowerInvariant(); if (1 == 0) { } TerritoryMode result = ((text == "vanilla") ? TerritoryMode.Vanilla : ((text == "mute") ? TerritoryMode.Mute : TerritoryMode.Custom)); if (1 == 0) { } return result; } private void ParseConfigFile() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) if (!File.Exists(ConfigPath)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Glitnir] Config não encontrada: " + ConfigPath)); return; } try { string text = File.ReadAllText(ConfigPath); IDeserializer val = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(UnderscoredNamingConvention.Instance).IgnoreUnmatchedProperties().Build(); YamlConfig yamlConfig = val.Deserialize<YamlConfig>(text) ?? throw new Exception("YAML resultou em objeto nulo."); MusicEntry menuEntry = new MusicEntry(yamlConfig.menu.enabled, yamlConfig.menu.clip); float volume = yamlConfig.menu.volume; bool enabled = yamlConfig.menu.enabled; bool enabled2 = yamlConfig.biomes.enabled; float volume2 = yamlConfig.biomes.volume; Dictionary<Biome, MusicEntry> dictionary = new Dictionary<Biome, MusicEntry>(yamlConfig.biomes.tracks.Count); foreach (KeyValuePair<string, YamlTrack> track in yamlConfig.biomes.tracks) { if (Enum.TryParse<Biome>(track.Key, out Biome result)) { dictionary[result] = new MusicEntry(track.Value.enabled, track.Value.clip, ParseMode(track.Value.mode)); } } bool enabled3 = yamlConfig.locations.enabled; float volume3 = yamlConfig.locations.volume; float radius = yamlConfig.locations.radius; float dungeon_y_min = yamlConfig.locations.dungeon_y_min; float dungeon_y_max = yamlConfig.locations.dungeon_y_max; List<LocationEntry> list = new List<LocationEntry>(yamlConfig.locations.tracks.Count); foreach (YamlLocationTrack track2 in yamlConfig.locations.tracks) { list.Add(new LocationEntry { location = track2.location, radius = track2.radius, dungeonYMin = track2.dungeon_y_min, dungeonYMax = track2.dungeon_y_max, dungeonOnly = track2.dungeon_only, enabled = track2.enabled, clip = track2.clip }); } bool enabled4 = yamlConfig.territories.enabled; List<TerritoryEntry> list2 = new List<TerritoryEntry>(yamlConfig.territories.tracks.Count); foreach (YamlTerritoryTrack track3 in yamlConfig.territories.tracks) { list2.Add(new TerritoryEntry { name = track3.name, x = track3.x, z = track3.z, radius = track3.radius, volume = track3.volume, enabled = track3.enabled, mode = ParseMode(track3.mode), clip = track3.clip }); } bool enabled5 = yamlConfig.bosses.enabled; float volume4 = yamlConfig.bosses.volume; float radius2 = yamlConfig.bosses.radius; Dictionary<string, MusicEntry> dictionary2 = new Dictionary<string, MusicEntry>(yamlConfig.bosses.tracks.Count); foreach (KeyValuePair<string, YamlTrack> track4 in yamlConfig.bosses.tracks) { if (track4.Value.enabled && !string.IsNullOrWhiteSpace(track4.Value.clip)) { dictionary2[track4.Key] = new MusicEntry(track4.Value.enabled, track4.Value.clip); } } bool preload_all_clips = yamlConfig.performance.preload_all_clips; int maxCachedClips = Math.Max(1, yamlConfig.performance.max_cached_clips); float unloadUnusedAfterSecs = Math.Max(60f, yamlConfig.performance.unload_unused_after_seconds); bool debug_logging = yamlConfig.performance.debug_logging; _locPosCache.Clear(); _locNoMatchSet.Clear(); _prefabNameCache.Clear(); _menuEnabled = enabled; _menuVolume = volume; _menuEntry = menuEntry; _biomesEnabled = enabled2; _biomeVolume = volume2; _biomeMap = dictionary; _locEnabled = enabled3; _locVolume = volume3; _locRadius = radius; _locDungeonYMin = dungeon_y_min; _locDungeonYMax = dungeon_y_max; _locList = list; _terrEnabled = enabled4; _terrList = list2; _bossEnabled = enabled5; _bossVolume = volume4; _bossRadius = radius2; _bossMap = dictionary2; _preloadAllClips = preload_all_clips; _maxCachedClips = maxCachedClips; _unloadUnusedAfterSecs = unloadUnusedAfterSecs; _debugLogging = debug_logging; ((BaseUnityPlugin)this).Logger.LogInfo((object)($"[Glitnir] Config carregada: {dictionary.Count} biomas, " + $"{list.Count} locations, {list2.Count} territories, " + $"{dictionary2.Count} bosses.")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[Glitnir] Erro ao ler config: " + ex.Message)); ((BaseUnityPlugin)this).Logger.LogError((object)("[Glitnir] Verifique: " + ConfigPath)); } } private void EnsureDefaultConfig() { if (!File.Exists(ConfigPath)) { File.WriteAllText(ConfigPath, "# ╔══════════════════════════════════════════════════════════════════╗\r\n# ║ GLITNIR MUSIC ZONES — music_config.yaml ║\r\n# ╚══════════════════════════════════════════════════════════════════╝\r\n# Os arquivos de música ficam em:\r\n# BepInEx/config/glitnir.musiczones/music/<nome_do_arquivo>\r\n# Formatos aceitos: .mp3 .ogg .wav\r\n#\r\n# Campo \"clip\" = nome do ARQUIVO. Ex: clip: \"minha_musica.mp3\"\r\n#\r\n# Após salvar este arquivo, a config recarrega automaticamente no jogo.\r\n\r\n# ─── PERFORMANCE ───────────────────────────────────────────────────\r\n# preload_all_clips: false = carrega só quando entrar na zona (padrão)\r\n# true = pré-carrega tudo na inicialização\r\n# max_cached_clips: máximo de músicas em RAM ao mesmo tempo (modo sob demanda)\r\n# unload_unused_after_seconds: tempo sem uso antes de descarregar da RAM\r\n# debug_logging: false = só loga erros (padrão, recomendado em produção)\r\n# true = loga todas as trocas de zona (pra debugar)\r\nperformance:\r\n preload_all_clips: false\r\n max_cached_clips: 5\r\n unload_unused_after_seconds: 600\r\n debug_logging: false\r\n\r\n# ─── MENU PRINCIPAL ────────────────────────────────────────────────\r\nmenu:\r\n enabled: false\r\n volume: 0.6000\r\n clip: \"\"\r\n\r\n# ─── BIOMAS ────────────────────────────────────────────────────────\r\n# Biomas disponíveis: Meadows, BlackForest, Swamp, Mountain, Plains,\r\n# Mistlands, AshLands, DeepNorth, Ocean\r\n# mode: custom = toca o clip configurado (silencia vanilla)\r\n# vanilla = deixa a música do jogo tocar normalmente\r\n# mute = silêncio total (nem mod nem vanilla)\r\nbiomes:\r\n enabled: true\r\n volume: 0.6000\r\n tracks:\r\n Meadows:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n BlackForest:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Swamp:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Mountain:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Plains:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Mistlands:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n AshLands:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n DeepNorth:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Ocean:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n\r\n# ─── LOCATIONS (Pontos de interesse do ZoneSystem) ─────────────────\r\nlocations:\r\n enabled: false\r\n volume: 0.6000\r\n radius: 90.0\r\n dungeon_y_min: 4500.0\r\n dungeon_y_max: 5500.0\r\n tracks:\r\n - enabled: false\r\n location: StartTemple\r\n radius: 60.0\r\n dungeon_only: false\r\n dungeon_y_min: -1\r\n dungeon_y_max: -1\r\n clip: \"\"\r\n\r\n# ─── TERRITORIES (Zonas por coordenada XZ) ─────────────────────────\r\nterritories:\r\n enabled: true\r\n tracks:\r\n - enabled: true\r\n name: BosquePrmadico\r\n x: 25186.0\r\n z: 4411.0\r\n radius: 700.0\r\n volume: 0.6000\r\n mode: custom\r\n clip: \"\"\r\n\r\n# ─── BOSSES ────────────────────────────────────────────────────────\r\n# Prefab name do boss sem '(Clone)'. Toca enquanto o boss estiver vivo no raio.\r\nbosses:\r\n enabled: true\r\n volume: 0.6000\r\n radius: 120.0\r\n tracks:\r\n Eikthyr:\r\n enabled: true\r\n clip: \"\"\r\n gd_king:\r\n enabled: true\r\n clip: \"\"\r\n Bonemass:\r\n enabled: true\r\n clip: \"\"\r\n Dragon:\r\n enabled: true\r\n clip: \"\"\r\n GoblinKing:\r\n enabled: true\r\n clip: \"\"\r\n SeekerQueen:\r\n enabled: true\r\n clip: \"\"\r\n Fader:\r\n enabled: true\r\n clip: \"\"\r\n"); ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Glitnir] Config padrão criada em: " + ConfigPath)); } } private void Awake() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir Music Zones] v2.0.2 iniciando (modo local + AudioSource próprio)..."); _harmony = new Harmony("glitnir.musiczones"); try { MusicManPatches.ApplyAll(_harmony); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir] Patches Harmony aplicados."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[Glitnir] Falha ao aplicar patches: " + ex.Message)); } Directory.CreateDirectory(MusicDir); EnsureDefaultConfig(); ParseConfigFile(); CreateMenuPlayer(); CreateZoneMusicPlayer(); StartConfigWatcher(); ((MonoBehaviour)this).StartCoroutine(LoadMenuClip()); if (_preloadAllClips) { ((MonoBehaviour)this).StartCoroutine(LoadAllConfiguredClips()); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir] Modo sob demanda ativo — clipes carregam ao entrar na zona."); } } private void LogDebug(string msg) { if (_debugLogging) { ((BaseUnityPlugin)this).Logger.LogInfo((object)msg); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _configWatcher?.Dispose(); MusicManPatches.VanillaMuted = false; foreach (var value in _clipCache.Values) { if ((Object)(object)value.clip != (Object)null) { Object.Destroy((Object)(object)value.clip); } } _clipCache.Clear(); } private void Update() { if (_reloadCooldown > 0f) { _reloadCooldown -= Time.deltaTime; } if (_reloadPending && _reloadCooldown <= 0f) { int num = Environment.TickCount - _watcherEventTick; if (num >= 400) { _reloadPending = false; _reloadCooldown = 1f; ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir] Config alterada — recarregando..."); ParseConfigFile(); ((MonoBehaviour)this).StartCoroutine(LoadMenuClip()); if (_preloadAllClips) { ((MonoBehaviour)this).StartCoroutine(LoadAllConfiguredClips()); } else { PurgeRemovedClips(); } _currentState = MusicState.None; _currentTrackId = ""; } } if (!_isFading) { if ((Object)(object)_zoneSourceA != (Object)null && _zoneSourceA.isPlaying) { _zoneSourceA.volume = MusicManPatches.RealMusicVolume * _currentZoneCfgVol; } if ((Object)(object)_zoneSourceB != (Object)null && _zoneSourceB.isPlaying) { _zoneSourceB.volume = MusicManPatches.RealMusicVolume * _currentZoneCfgVol; } } if (!_preloadAllClips) { EvictLruClips(); } bool flag = (Object)(object)ZNet.instance != (Object)null; if (flag && !_worldLoaded && (Object)(object)Player.m_localPlayer != (Object)null) { _worldLoaded = true; if ((Object)(object)_menuSource != (Object)null) { _menuSource.Stop(); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir] Mundo carregado."); } if (!flag && _worldLoaded) { _worldLoaded = false; StopZoneMusic(andUnmuteVanilla: true); _currentState = MusicState.None; _currentTrackId = ""; _locPosCache.Clear(); _locNoMatchSet.Clear(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Glitnir] Voltou ao menu."); } if (!flag) { if (!_menuEnabled || _menuEntry == null || !_menuEntry.IsActive || (Object)(object)_menuSource == (Object)null) { if ((Object)(object)_menuSource != (Object)null && _menuSource.isPlaying) { _menuSource.Stop(); } MusicManPatches.VanillaMuted = false; return; } MusicManPatches.VanillaMuted = true; if (_menuClipReady && !_menuSource.isPlaying) { _menuSource.clip = _menuClip; _menuSource.Play(); } if (_menuSource.isPlaying) { _menuSource.volume = _menuVolume * MusicManPatches.RealMusicVolume; } } else if (_worldLoaded) { _checkTimer -= Time.deltaTime; if (!(_checkTimer > 0f)) { _checkTimer = _checkInterval; UpdateInGameMusic(); } } } private unsafe void UpdateInGameMusic() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_004e: 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_0075: 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_00c6: 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_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Unknown result type (might be due to invalid IL or missing references) //IL_03a6: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } Vector3 position = ((Component)localPlayer).transform.position; Vector3 val = position - _lastCheckPos; _checkInterval = ((val.x * val.x + val.z * val.z + val.y * val.y < 9f) ? 4f : 2f); _lastCheckPos = position; _locCacheTimer += _checkInterval; if (_locCacheTimer > 60f) { _locPosCache.Clear(); _locNoMatchSet.Clear(); _locCacheTimer = 0f; } bool flag = position.y >= _locDungeonYMin && position.y <= _locDungeonYMax; if (_bossEnabled && _bossMap.Count > 0) { BuildCharBuffer(position); foreach (Character item in _charBuffer) { if (BossHelper.IsBoss(item) == false) { continue; } string prefabName = GetPrefabName(item); if (!_bossMap.TryGetValue(prefabName, out var value) || !value.IsActive) { continue; } string text = "boss:" + prefabName; if (!(_currentTrackId != text)) { return; } if (TryGetClip(value.clip, out var clip)) { PlayZoneTrack(clip, _bossVolume, text); _currentState = MusicState.Boss; _currentTrackId = text; return; } _currentTrackId = text; ((MonoBehaviour)this).StartCoroutine(LoadAndThenPlay(value.clip, _bossVolume, text, delegate(AudioClip ac, float v, string t) { PlayZoneTrack(ac, v, t); _currentState = MusicState.Boss; _currentTrackId = t; })); return; } } if (_locEnabled) { LocationEntry locationMatch = GetLocationMatch(position); if (locationMatch != null) { string text2 = "loc:" + locationMatch.location; if (!(_currentTrackId != text2)) { return; } if (TryGetClip(locationMatch.clip, out var clip2)) { PlayZoneTrack(clip2, _locVolume, text2); _currentState = MusicState.Location; _currentTrackId = text2; return; } _currentTrackId = text2; ((MonoBehaviour)this).StartCoroutine(LoadAndThenPlay(locationMatch.clip, _locVolume, text2, delegate(AudioClip ac, float v, string t) { PlayZoneTrack(ac, v, t); _currentState = MusicState.Location; _currentTrackId = t; })); return; } } if (_terrEnabled) { TerritoryEntry territoryMatch = GetTerritoryMatch(position); if (territoryMatch != null) { ApplyZoneMode(territoryMatch.mode, territoryMatch.clip, territoryMatch.volume, "terr:" + territoryMatch.name, MusicState.Territory); return; } } if (flag && _currentState != MusicState.None) { LogDebug("[Glitnir] Y de dungeon detectado sem location específica — mantendo faixa atual."); return; } if (_biomesEnabled) { Biome key = (Biome)((WorldGenerator.instance != null) ? ((int)WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false)) : 0); if (_biomeMap.TryGetValue(key, out var value2) && value2.IsActive) { ApplyZoneMode(value2.mode, value2.clip, _biomeVolume, "biome:" + ((object)(*(Biome*)(&key))/*cast due to .constrained prefix*/).ToString(), MusicState.Biome); return; } } if (_currentState != MusicState.None) { StopZoneMusic(andUnmuteVanilla: true); _currentState = MusicState.None; LogDebug("[Glitnir] Sem zona ativa — vanilla tocando."); } _currentTrackId = ""; } private void ApplyZoneMode(TerritoryMode mode, string clipFile, float vol, string trackId, MusicState state) { if (_currentTrackId == trackId) { return; } switch (mode) { case TerritoryMode.Custom: { if (TryGetClip(clipFile, out var clip)) { PlayZoneTrack(clip, vol, trackId); _currentState = state; _currentTrackId = trackId; break; } _currentTrackId = trackId; ((MonoBehaviour)this).StartCoroutine(LoadAndThenPlay(clipFile, vol, trackId, delegate(AudioClip ac, float v, string t) { PlayZoneTrack(ac, v, t); _currentState = state; _currentTrackId = t; })); break; } case TerritoryMode.Mute: LogDebug($"[Glitnir] [{state}/Mute] Silenciando."); StopZoneMusic(andUnmuteVanilla: false); MusicManPatches.VanillaMuted = true; _currentState = state; _currentTrackId = trackId; break; case TerritoryMode.Vanilla: LogDebug($"[Glitnir] [{state}/Vanilla] Restaurando vanilla."); StopZoneMusic(andUnmuteVanilla: true); _currentState = state; _currentTrackId = trackId; break; } } private void BuildCharBuffer(Vector3 pos) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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) _charBuffer.Clear(); if (_bossMap.Count == 0) { return; } float num = _bossRadius * _bossRadius; foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead()) { continue; } Vector3 val = pos - ((Component)allCharacter).transform.position; float num2 = val.x * val.x + val.z * val.z + val.y * val.y; if (!(num2 > num)) { _charBuffer.Add(allCharacter); if (_charBuffer.Count >= 64) { break; } } } } private string GetPrefabName(Character c) { string name = ((Object)c).name; if (!_prefabNameCache.TryGetValue(name, out var value)) { value = name.Replace("(Clone)", "").Trim(); _prefabNameCache[name] = value; if (_prefabNameCache.Count > 256) { _prefabNameCache.Clear(); } } return value; } private LocationEntry GetLocationMatch(Vector3 origin) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: 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_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0178: 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_0129: 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_013c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance == (Object)null) { return null; } LocationInstance val = default(LocationInstance); foreach (LocationEntry loc in _locList) { if (!loc.IsActive) { continue; } float num = ((loc.dungeonYMin >= 0f) ? loc.dungeonYMin : _locDungeonYMin); float num2 = ((loc.dungeonYMax >= 0f) ? loc.dungeonYMax : _locDungeonYMax); bool flag = origin.y >= num && origin.y <= num2; if (loc.dungeonOnly) { if (!flag) { continue; } return loc; } if (!_locPosCache.TryGetValue(loc.location, out var value)) { if (_locNoMatchSet.Contains(loc.location)) { continue; } if (!ZoneSystem.instance.FindClosestLocation(loc.location, origin, ref val)) { _locNoMatchSet.Add(loc.location); continue; } value = val.m_position; _locPosCache[loc.location] = value; } float num3 = ((loc.radius > 0f) ? loc.radius : _locRadius); float num4 = origin.x - value.x; float num5 = origin.z - value.z; if (!(num4 * num4 + num5 * num5 <= num3 * num3)) { continue; } return loc; } return null; } private TerritoryEntry GetTerritoryMatch(Vector3 origin) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) float x = origin.x; float z = origin.z; foreach (TerritoryEntry terr in _terrList) { if (terr.IsActive) { float num = x - terr.x; float num2 = z - terr.z; if (num * num + num2 * num2 <= terr.radius * terr.radius) { return terr; } } } return null; } } internal static class DefaultConfig { public const string YAML = "# ╔══════════════════════════════════════════════════════════════════╗\r\n# ║ GLITNIR MUSIC ZONES — music_config.yaml ║\r\n# ╚══════════════════════════════════════════════════════════════════╝\r\n# Os arquivos de música ficam em:\r\n# BepInEx/config/glitnir.musiczones/music/<nome_do_arquivo>\r\n# Formatos aceitos: .mp3 .ogg .wav\r\n#\r\n# Campo \"clip\" = nome do ARQUIVO. Ex: clip: \"minha_musica.mp3\"\r\n#\r\n# Após salvar este arquivo, a config recarrega automaticamente no jogo.\r\n\r\n# ─── PERFORMANCE ───────────────────────────────────────────────────\r\n# preload_all_clips: false = carrega só quando entrar na zona (padrão)\r\n# true = pré-carrega tudo na inicialização\r\n# max_cached_clips: máximo de músicas em RAM ao mesmo tempo (modo sob demanda)\r\n# unload_unused_after_seconds: tempo sem uso antes de descarregar da RAM\r\n# debug_logging: false = só loga erros (padrão, recomendado em produção)\r\n# true = loga todas as trocas de zona (pra debugar)\r\nperformance:\r\n preload_all_clips: false\r\n max_cached_clips: 5\r\n unload_unused_after_seconds: 600\r\n debug_logging: false\r\n\r\n# ─── MENU PRINCIPAL ────────────────────────────────────────────────\r\nmenu:\r\n enabled: false\r\n volume: 0.6000\r\n clip: \"\"\r\n\r\n# ─── BIOMAS ────────────────────────────────────────────────────────\r\n# Biomas disponíveis: Meadows, BlackForest, Swamp, Mountain, Plains,\r\n# Mistlands, AshLands, DeepNorth, Ocean\r\n# mode: custom = toca o clip configurado (silencia vanilla)\r\n# vanilla = deixa a música do jogo tocar normalmente\r\n# mute = silêncio total (nem mod nem vanilla)\r\nbiomes:\r\n enabled: true\r\n volume: 0.6000\r\n tracks:\r\n Meadows:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n BlackForest:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Swamp:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Mountain:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Plains:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Mistlands:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n AshLands:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n DeepNorth:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n Ocean:\r\n enabled: true\r\n mode: custom\r\n clip: \"\"\r\n\r\n# ─── LOCATIONS (Pontos de interesse do ZoneSystem) ─────────────────\r\nlocations:\r\n enabled: false\r\n volume: 0.6000\r\n radius: 90.0\r\n dungeon_y_min: 4500.0\r\n dungeon_y_max: 5500.0\r\n tracks:\r\n - enabled: false\r\n location: StartTemple\r\n radius: 60.0\r\n dungeon_only: false\r\n dungeon_y_min: -1\r\n dungeon_y_max: -1\r\n clip: \"\"\r\n\r\n# ─── TERRITORIES (Zonas por coordenada XZ) ─────────────────────────\r\nterritories:\r\n enabled: true\r\n tracks:\r\n - enabled: true\r\n name: BosquePrmadico\r\n x: 25186.0\r\n z: 4411.0\r\n radius: 700.0\r\n volume: 0.6000\r\n mode: custom\r\n clip: \"\"\r\n\r\n# ─── BOSSES ────────────────────────────────────────────────────────\r\n# Prefab name do boss sem '(Clone)'. Toca enquanto o boss estiver vivo no raio.\r\nbosses:\r\n enabled: true\r\n volume: 0.6000\r\n radius: 120.0\r\n tracks:\r\n Eikthyr:\r\n enabled: true\r\n clip: \"\"\r\n gd_king:\r\n enabled: true\r\n clip: \"\"\r\n Bonemass:\r\n enabled: true\r\n clip: \"\"\r\n Dragon:\r\n enabled: true\r\n clip: \"\"\r\n GoblinKing:\r\n enabled: true\r\n clip: \"\"\r\n SeekerQueen:\r\n enabled: true\r\n clip: \"\"\r\n Fader:\r\n enabled: true\r\n clip: \"\"\r\n"; } internal static class MusicManPatches { private static readonly FieldRef<MusicMan, AudioSource> _musicSourceRef = AccessTools.FieldRefAccess<MusicMan, AudioSource>("m_musicSource"); private static float _lastKnownVolume = 1f; public static bool VanillaMuted = false; private static readonly List<AudioSource> _locationSources = new List<AudioSource>(); private static AudioSource MusicManSource { get { if ((Object)(object)MusicMan.instance == (Object)null) { return null; } try { return _musicSourceRef.Invoke(MusicMan.instance); } catch { return null; } } } public static float RealMusicVolume => _lastKnownVolume; public static AudioMixerGroup GetMusicGroup() { AudioSource musicManSource = MusicManSource; return (musicManSource != null) ? musicManSource.outputAudioMixerGroup : null; } private static void PostfixMusicLocationAwake(Component __instance) { AudioSource val = __instance.GetComponent<AudioSource>() ?? __instance.GetComponentInChildren<AudioSource>(); if ((Object)(object)val != (Object)null && !_locationSources.Contains(val)) { _locationSources.Add(val); } } private static void PrefixUpdate() { _lastKnownVolume = MusicMan.m_masterMusicVolume; } private static void PostfixUpdate() { if (!VanillaMuted) { return; } AudioSource musicManSource = MusicManSource; if ((Object)(object)musicManSource != (Object)null) { if (musicManSource.isPlaying) { musicManSource.Stop(); } musicManSource.volume = 0f; } for (int num = _locationSources.Count - 1; num >= 0; num--) { AudioSource val = _locationSources[num]; if ((Object)(object)val == (Object)null) { _locationSources.RemoveAt(num); } else { if (val.isPlaying) { val.Stop(); } val.volume = 0f; } } } private static bool PrefixStartMusic() { return !VanillaMuted; } private static bool PrefixQueue() { return !VanillaMuted; } public static void ApplyAll(Harmony harmony) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown Type typeFromHandle = typeof(MusicManPatches); BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.NonPublic; HarmonyMethod val = new HarmonyMethod(typeFromHandle.GetMethod("PrefixStartMusic", bindingAttr)); HarmonyMethod val2 = new HarmonyMethod(typeFromHandle.GetMethod("PrefixQueue", bindingAttr)); HarmonyMethod val3 = new HarmonyMethod(typeFromHandle.GetMethod("PrefixUpdate", bindingAttr)); HarmonyMethod val4 = new HarmonyMethod(typeFromHandle.GetMethod("PostfixUpdate", bindingAttr)); HarmonyMethod val5 = new HarmonyMethod(typeFromHandle.GetMethod("PostfixMusicLocationAwake", bindingAttr)); MethodInfo methodInfo = null; MethodInfo[] methods = typeof(MusicMan).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (methodInfo2.Name == "StartMusic") { harmony.Patch((MethodBase)methodInfo2, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else if (methodInfo2.Name == "Queue") { harmony.Patch((MethodBase)methodInfo2, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else if (methodInfo2.Name == "Update") { methodInfo = methodInfo2; } } if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, val3, val4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo3 = AccessTools.TypeByName("MusicLocation")?.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, val5, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } internal static class BossHelper { private static readonly FieldInfo F = AccessTools.Field(typeof(Character), "m_boss"); public static bool? IsBoss(Character c) { if (F == null || (Object)(object)c == (Object)null) { return null; } try { return (bool)F.GetValue(c); } catch { return null; } } } public class YamlConfig { public YamlMenu menu { get; set; } = new YamlMenu(); public YamlBiomes biomes { get; set; } = new YamlBiomes(); public YamlLocations locations { get; set; } = new YamlLocations(); public YamlTerritories territories { get; set; } = new YamlTerritories(); public YamlBosses bosses { get; set; } = new YamlBosses(); public YamlPerformance performance { get; set; } = new YamlPerformance(); } public class YamlPerformance { public bool preload_all_clips { get; set; } = false; public int max_cached_clips { get; set; } = 5; public int unload_unused_after_seconds { get; set; } = 600; public bool debug_logging { get; set; } = false; } public class YamlMenu { public bool enabled { get; set; } = true; public float volume { get; set; } = 0.3f; public string clip { get; set; } = ""; } public class YamlBiomes { public bool enabled { get; set; } = true; public float volume { get; set; } = 0.6f; public Dictionary<string, YamlTrack> tracks { get; set; } = new Dictionary<string, YamlTrack>(); } public class YamlLocations { public bool enabled { get; set; } = true; public float volume { get; set; } = 0.65f; public float radius { get; set; } = 90f; public float dungeon_y_min { get; set; } = 4500f; public float dungeon_y_max { get; set; } = 5500f; public List<YamlLocationTrack> tracks { get; set; } = new List<YamlLocationTrack>(); } public class YamlTerritories { public bool enabled { get; set; } = true; public List<YamlTerritoryTrack> tracks { get; set; } = new List<YamlTerritoryTrack>(); } public class YamlBosses { public bool enabled { get; set; } = true; public float volume { get; set; } = 0.7f; public float radius { get; set; } = 120f; public Dictionary<string, YamlTrack> tracks { get; set; } = new Dictionary<string, YamlTrack>(); } public class YamlTrack { public bool enabled { get; set; } = true; public string mode { get; set; } = "custom"; public string clip { get; set; } = ""; } public class YamlLocationTrack { public bool enabled { get; set; } = true; public string location { get; set; } = ""; public float radius { get; set; } = 0f; public bool dungeon_only { get; set; } = false; public float dungeon_y_min { get; set; } = -1f; public float dungeon_y_max { get; set; } = -1f; public string clip { get; set; } = ""; } public class YamlTerritoryTrack { public bool enabled { get; set; } = true; public string name { get; set; } = ""; public float x { get; set; } = 0f; public float z { get; set; } = 0f; public float radius { get; set; } = 60f; public float volume { get; set; } = 0.65f; public string mode { get; set; } = "custom"; public string clip { get; set; } = ""; } public enum TerritoryMode { Custom, Vanilla, Mute } internal class LocationEntry { public string location; public float radius; public float dungeonYMin; public float dungeonYMax; public bool dungeonOnly; public bool enabled; public string clip; public bool IsActive => enabled && !string.IsNullOrWhiteSpace(clip) && !string.IsNullOrWhiteSpace(location); } internal class TerritoryEntry { public string name; public string clip; public float x; public float z; public float radius; public float volume; public bool enabled; public TerritoryMode mode; public bool IsActive => enabled && (mode != TerritoryMode.Custom || !string.IsNullOrWhiteSpace(clip)); } internal class MusicEntry { public bool enabled; public string clip; public TerritoryMode mode; public bool IsActive => enabled && (mode != TerritoryMode.Custom || !string.IsNullOrWhiteSpace(clip)); public MusicEntry(bool enabled, string clip, TerritoryMode mode = TerritoryMode.Custom) { this.enabled = enabled; this.clip = clip ?? ""; this.mode = mode; } } }