Decompiled source of GlitnirSounds v0.1.9

GlitnirMusicZones.dll

Decompiled a month ago
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<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;
		}
	}
}