using 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 _clipCache = new Dictionary(); private readonly Dictionary onReady)>> _loadWaiters = new Dictionary)>>(); 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 _biomeMap = new Dictionary(); private bool _locEnabled = true; private float _locVolume = 0.65f; private float _locRadius = 90f; private float _locDungeonYMin = 4500f; private float _locDungeonYMax = 5500f; private List _locList = new List(); private bool _terrEnabled = true; private List _terrList = new List(); private bool _bossEnabled = true; private float _bossVolume = 0.7f; private float _bossRadius = 120f; private Dictionary _bossMap = new Dictionary(); 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 _locPosCache = new Dictionary(); private readonly HashSet _locNoMatchSet = new HashSet(); private float _locCacheTimer = 0f; private const float LocCacheLifetime = 60f; private readonly List _charBuffer = new List(64); private readonly Dictionary _prefabNameCache = new Dictionary(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(); _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(); 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 files = new HashSet(); 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 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 files = new HashSet(); 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 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 onReady) { if (_loadWaiters.TryGetValue(fileName, out var existingWaiters)) { existingWaiters.Add((cfgVol, trackId, onReady)); yield break; } List<(float cfgVol, string trackId, Action onReady)> waiters = new List<(float, string, Action)> { (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 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> 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)new DeserializerBuilder()).WithNamingConvention(UnderscoredNamingConvention.Instance).IgnoreUnmatchedProperties().Build(); YamlConfig yamlConfig = val.Deserialize(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 dictionary = new Dictionary(yamlConfig.biomes.tracks.Count); foreach (KeyValuePair track in yamlConfig.biomes.tracks) { if (Enum.TryParse(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 list = new List(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 list2 = new List(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 dictionary2 = new Dictionary(yamlConfig.bosses.tracks.Count); foreach (KeyValuePair 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/\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/\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 _musicSourceRef = AccessTools.FieldRefAccess("m_musicSource"); private static float _lastKnownVolume = 1f; public static bool VanillaMuted = false; private static readonly List _locationSources = new List(); 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() ?? __instance.GetComponentInChildren(); 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 tracks { get; set; } = new Dictionary(); } 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 tracks { get; set; } = new List(); } public class YamlTerritories { public bool enabled { get; set; } = true; public List tracks { get; set; } = new List(); } public class YamlBosses { public bool enabled { get; set; } = true; public float volume { get; set; } = 0.7f; public float radius { get; set; } = 120f; public Dictionary tracks { get; set; } = new Dictionary(); } 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; } } }