Decompiled source of Music Overhaul v1.1.3

plugins/TNSMusicOverhaul.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 BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("TNSMusicOverhaul")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("TNSMusicOverhaul")]
[assembly: AssemblyTitle("TNSMusicOverhaul")]
[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 BiomeMusic
{
	[BepInPlugin("TNS-Music-Overhaul", "TNS Music Overhaul", "1.1.3")]
	public class BiomeMusicPlugin : BaseUnityPlugin
	{
		private sealed class PlaceSong
		{
			public readonly MusicLocation Location;

			public readonly AudioSource Source;

			public readonly string Place;

			public readonly string VanillaClip;

			public readonly string Folder;

			public readonly bool Loops;

			public readonly float GameVolume;

			public bool LoopTakenOver;

			public float LastTime;

			public float SilentFor;

			public bool IsLoaded
			{
				get
				{
					if ((Object)(object)Location != (Object)null && ((Behaviour)Location).isActiveAndEnabled)
					{
						return (Object)(object)Source != (Object)null;
					}
					return false;
				}
			}

			public PlaceSong(MusicLocation location, AudioSource source, string place, string vanillaClip, string folder, bool loops, float gameVolume)
			{
				Location = location;
				Source = source;
				Place = place;
				VanillaClip = vanillaClip;
				Folder = folder;
				Loops = loops;
				GameVolume = gameVolume;
			}
		}

		public const string Guid = "TNS-Music-Overhaul";

		public const string Name = "TNS Music Overhaul";

		public const string Version = "1.1.3";

		private const string VanillaHomeMusic = "home";

		private const string VanillaSailingMusic = "sailing";

		private const string VanillaAshlandsSailingMusic = "sailing_ashlands";

		private const string OceanFolder = "Ocean";

		private Harmony _harmony;

		private ConfigEntry<bool> _fallbackToClear;

		private ConfigEntry<string> _clearKeywords;

		private ConfigEntry<string> _rainKeywords;

		private ConfigEntry<string> _stormKeywords;

		private const string DefaultStormKeywords = "thunder,storm,blizzard,meteor";

		private ConfigEntry<float> _volume;

		private ConfigEntry<float> _fadeInTime;

		private ConfigEntry<string> _disabledBiomes;

		private ConfigEntry<bool> _sailingUsesOcean;

		private ConfigEntry<bool> _skipVanillaTransitions;

		private ConfigEntry<bool> _raidMusic;

		private readonly Dictionary<string, ConfigEntry<bool>> _placeMusic = new Dictionary<string, ConfigEntry<bool>>();

		private readonly Dictionary<string, ConfigEntry<float>> _volumes = new Dictionary<string, ConfigEntry<float>>(StringComparer.OrdinalIgnoreCase);

		private const float MaxPlaceVolume = 5f;

		private ConfigEntry<bool> _logChanges;

		private ConfigEntry<bool> _logNearbySounds;

		private float _nextSoundScan;

		private readonly HashSet<string> _loggedSounds = new HashSet<string>();

		private ConfigEntry<float> _maxSilence;

		private ConfigEntry<string> _musicFolder;

		private string _lastLoggedSlot;

		private string _lastLoggedRaid;

		private string _lastLoggedLocation;

		private bool _loggedSilence;

		private bool _wroteWeatherList;

		private static readonly FieldRef<MusicMan, NamedMusic> CurrentMusic = AccessTools.FieldRefAccess<MusicMan, NamedMusic>("m_currentMusic");

		private static readonly FieldRef<MusicMan, float> PlayingVolume = AccessTools.FieldRefAccess<MusicMan, float>("m_musicVolume");

		private EnvMan _outdoorPresetsFor;

		private HashSet<string> _outdoorPresets = new HashSet<string>();

		private readonly List<AudioSource> _locationSources = new List<AudioSource>();

		private readonly HashSet<string> _loggedPlaces = new HashSet<string>();

		private static readonly FieldRef<MusicLocation, bool> PlaceOneTime = AccessTools.FieldRefAccess<MusicLocation, bool>("m_oneTime");

		private static readonly FieldRef<MusicLocation, bool> PlaceNotIfEnemies = AccessTools.FieldRefAccess<MusicLocation, bool>("m_notIfEnemies");

		private static readonly FieldRef<MusicLocation, float> PlaceRadius = AccessTools.FieldRefAccess<MusicLocation, float>("m_radius");

		private static readonly FieldRef<MusicLocation, float> PlaceBaseVolume = AccessTools.FieldRefAccess<MusicLocation, float>("m_baseVolume");

		private static readonly Func<MusicLocation, bool> PlaceHasPlayed = AccessTools.MethodDelegate<Func<MusicLocation, bool>>(AccessTools.Method(typeof(MusicLocation), "HasPlayed", (Type[])null, (Type[])null), (object)null, true);

		private readonly HashSet<int> _loggedQuietPlaces = new HashSet<int>();

		private readonly List<(MusicLocation Location, AudioSource Source, string Folder)> _places = new List<(MusicLocation, AudioSource, string)>();

		private readonly Dictionary<int, PlaceSong> _placeSongs = new Dictionary<int, PlaceSong>();

		private PlaceSong _placeTurn;

		private const float HandOverMargin = 0.15f;

		private const float SongEndWindow = 2f;

		private static readonly FieldRef<MusicLocation, float> PlaceFade = AccessTools.FieldRefAccess<MusicLocation, float>("volume");

		private readonly Dictionary<string, float> _vanillaVolumes = new Dictionary<string, float>();

		private static readonly Func<MusicMan, bool> IsSailing = AccessTools.MethodDelegate<Func<MusicMan, bool>>(AccessTools.Method(typeof(MusicMan), "IsSailing", (Type[])null, (Type[])null), (object)null, true);

		internal static BiomeMusicPlugin Instance { get; private set; }

		internal MusicLibrary Library { get; private set; }

		private void BindVolume(string name, string description, float max)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			ConfigEntry<float> val = ((BaseUnityPlugin)this).Config.Bind<float>("Volume", name, 1f, new ConfigDescription(description + " Valheim's own music isn't changed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, max), Array.Empty<object>()));
			val.SettingChanged += delegate
			{
				ApplyVolumes();
			};
			_volumes[name] = val;
		}

		private static string VolumeGroupDescription(string group)
		{
			return group switch
			{
				"Ocean" => "Your songs on the open sea, sailing included", 
				"BlackForest" => "Your songs in the Black Forest, in every weather", 
				"DeepNorth" => "Your songs in the Deep North, in every weather", 
				"AshLands" => "Your songs in the Ashlands, in every weather", 
				"AshlandsOcean" => "Your songs on the boiling sea around the Ashlands, sailing included", 
				"HomeRest" => "Your songs while resting at a base, in every biome", 
				"Bosses" => "Your boss fight songs", 
				"Dungeons" => "Your dungeon songs", 
				"Raids" => "Your raid songs", 
				"Transitions" => "Your sunrise and sunset pieces", 
				_ => "Your songs in the " + group + ", in every weather", 
			};
		}

		private void Awake()
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Expected O, but got Unknown
			//IL_05e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f1: Expected O, but got Unknown
			if (Application.isBatchMode)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Running on a dedicated server: music is client-side only, so the mod stays idle.");
				return;
			}
			Instance = this;
			string text = Path.Combine(Paths.ConfigPath, "TNS-Music-Overhaul", "Music");
			string persistentDataPath = Application.persistentDataPath;
			string text2 = (string.IsNullOrEmpty(persistentDataPath) ? text : Path.GetFullPath(Path.Combine(persistentDataPath, "TNS-MusicOverhaul")));
			_musicFolder = ((BaseUnityPlugin)this).Config.Bind<string>("Music", "MusicFolder", "", "Where all the music lives. Leave it empty for the default, AppData\\LocalLow\\IronGate\\Valheim\\TNS-MusicOverhaul on each PC: next to Valheim's own save files and outside every mod manager profile, so all your profiles share one copy, profile exports stay small, and a profile shared with friends works on their PCs too. Or name a folder of your own, such as D:\\ValheimMusic; %USERPROFILE% and other environment variables work, such as %USERPROFILE%\\Music\\Valheim. Change this and the music, including songs you added yourself, moves to the new folder the next time you start the game, for every profile on this PC. If the new folder already has music of its own, both are left as they are and the new one is used. If the folder can't be used at all, say a typo, a drive that isn't there, or another Windows user's folder from a shared config, the music stays where it is and a warning goes in the log. Only changing this moves the music: a profile that names a folder with no music in it, such as one shared from someone else's PC, plays from wherever the music already is.");
			if (MusicMover.IsStandardMusicFolder(_musicFolder.Value))
			{
				_musicFolder.Value = "";
			}
			_fallbackToClear = ((BaseUnityPlugin)this).Config.Bind<bool>("Music", "FallbackToClear", true, "If a weather folder is empty, borrow another weather's songs from the same biome and time of day (clear weather first) instead of playing the vanilla music.");
			_volume = ((BaseUnityPlugin)this).Config.Bind<float>("Music", "Volume", 1f, new ConfigDescription("Volume of all your songs (0-1). Still scaled by the in-game music volume slider, and [Volume] sets each biome, place and kind of music on top of it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			_volume.SettingChanged += delegate
			{
				ApplyVolumes();
			};
			_fadeInTime = ((BaseUnityPlugin)this).Config.Bind<float>("Music", "FadeInTime", 3f, "Seconds to fade a custom track in.");
			_maxSilence = ((BaseUnityPlugin)this).Config.Bind<float>("Music", "MaxSilenceSeconds", 60f, "Longest the game waits before starting the next biome song, e.g. after you leave your base. Valheim picks a random wait each time; this caps it. 0 keeps Valheim's own waits.");
			_disabledBiomes = ((BaseUnityPlugin)this).Config.Bind<string>("Music", "DisabledBiomes", "", "Comma-separated biomes that always keep their vanilla music, even if their folders have songs. Empty by default: a biome whose folders are empty plays vanilla music anyway. Names: " + string.Join(", ", MusicLibrary.Biomes));
			if (new HashSet<string>(SplitList(_disabledBiomes.Value), StringComparer.OrdinalIgnoreCase).SetEquals(new string[2] { "AshLands", "Mistlands" }))
			{
				_disabledBiomes.Value = "";
			}
			_clearKeywords = ((BaseUnityPlugin)this).Config.Bind<string>("Weather", "ClearKeywords", "clear", "Comma-separated. A weather preset whose name contains any of these counts as Clear, whatever else its name says (Ashlands_ashrain_clear is the ash rain clearing up). Checked first. Every preset and the folder it lands in is listed in vanilla-weather.txt.");
			_stormKeywords = ((BaseUnityPlugin)this).Config.Bind<string>("Weather", "ThunderstormKeywords", "thunder,storm,blizzard,meteor", "Comma-separated. A weather preset whose name contains any of these counts as Thunderstorm. Checked before Rain.");
			_rainKeywords = ((BaseUnityPlugin)this).Config.Bind<string>("Weather", "RainKeywords", "rain,snow", "Comma-separated. A weather preset whose name contains any of these, or that makes the player wet, counts as Rain.");
			if (_stormKeywords.Value == "thunder,storm,blizzard")
			{
				_stormKeywords.Value = "thunder,storm,blizzard,meteor";
			}
			_sailingUsesOcean = ((BaseUnityPlugin)this).Config.Bind<bool>("Sailing", "UseOceanMusic", true, "While sailing, play the Ocean folder for the current weather and time of day instead of the vanilla sailing music. In the boiling sea around the Ashlands the AshlandsOcean folder is used instead, and while that is empty the vanilla music plays there.");
			_skipVanillaTransitions = ((BaseUnityPlugin)this).Config.Bind<bool>("Transitions", "SkipVanillaWhenEmpty", true, "The game plays a short piece at sunrise and sunset. Tracks in Music/Transition/Morning and Music/Transition/Evening replace it. If that folder is empty: true skips the vanilla piece, false keeps it.");
			_raidMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("Raids", "Enabled", true, "Use the Music/Raids folders for raid music. Valheim gives every raid one of four levels of raid music; Raids/Tier1 to Tier4 replace those levels. A folder named after a single raid (e.g. Raids/army_moder, the names are in the log when a raid starts) replaces the music for just that raid.");
			foreach (string folder in MusicLibrary.PlaceFolderPaths)
			{
				ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Places", PlaceSettingName(folder), true, "Music at this place: your songs from " + folder.Replace('/', '\\') + ", or Valheim's own while that folder is empty. false silences it completely, Valheim's own music included.");
				val.SettingChanged += delegate
				{
					ApplyPlaceSetting(folder);
				};
				_placeMusic[folder] = val;
			}
			foreach (string volumeGroup in MusicLibrary.VolumeGroups)
			{
				BindVolume(volumeGroup, VolumeGroupDescription(volumeGroup) + ", 0 to 1, on top of Volume under [Music].", 1f);
			}
			foreach (string placeFolderPath in MusicLibrary.PlaceFolderPaths)
			{
				BindVolume(PlaceSettingName(placeFolderPath), "Your songs at this place, from " + placeFolderPath.Replace('/', '\\') + ". 1 plays them as loud as the game plays " + $"its own music here, which is quiet; up to {5f:0} times that. On top of Volume under [Music].", 5f);
			}
			_logChanges = ((BaseUnityPlugin)this).Config.Bind<bool>("Troubleshooting", "LogChanges", true, "Log the biome, time, weather preset and chosen folder whenever they change, each raid's name, and each place with music of its own. Useful for tuning the weather keywords and for bug reports.");
			_logNearbySounds = ((BaseUnityPlugin)this).Config.Bind<bool>("Troubleshooting", "LogNearbySounds", false, "Log long sounds playing near you that aren't part of the music system, such as the music at Haldor's camp. Each one is logged once, with its name and the object playing it. For finding out what a sound is; leave it off otherwise.");
			DropStaleSettings();
			((BaseUnityPlugin)this).Config.Save();
			bool flag = !MusicMover.SamePath(text2, text) && MusicMover.SamePath(_musicFolder.Value, text);
			if (flag)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Moving the music to its new home, " + text2));
				_musicFolder.Value = "";
			}
			HashSet<string> disabledBiomes = new HashSet<string>(SplitList(_disabledBiomes.Value), StringComparer.OrdinalIgnoreCase);
			string shippedRoot = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "Music");
			string path = Path.Combine(Paths.ConfigPath, "TNS-Music-Overhaul");
			string text3 = Path.Combine(path, "music-folder.txt");
			string text4 = MusicMover.ReadRecordedFolder(text3, ((BaseUnityPlugin)this).Logger);
			MusicMover musicMover = new MusicMover(text2, Path.Combine(text2, "music-folder.txt"), text3, Paths.BepInExRootPath, ((BaseUnityPlugin)this).Logger);
			string text5 = musicMover.ChooseFolder(_musicFolder.Value, flag ? text : null);
			MusicInstaller installer = new MusicInstaller(oldRecordIsOurs: text4 == null || MusicMover.SamePath(text4, text5) || MusicMover.SamePath(text4, text), shippedRoot: shippedRoot, userRoot: text5, recordPath: Path.Combine(text5, "installed-songs.txt"), oldRecordPath: Path.Combine(path, "installed-songs.txt"), log: ((BaseUnityPlugin)this).Logger);
			Library = new MusicLibrary(text5, ((BaseUnityPlugin)this).Logger);
			((MonoBehaviour)this).StartCoroutine(SetUpThenLoad(musicMover, installer, disabledBiomes));
			_harmony = new Harmony("TNS-Music-Overhaul");
			_harmony.PatchAll(typeof(BiomeMusicPlugin).Assembly);
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			if (!((Object)(object)Instance == (Object)null))
			{
				UpdatePlaceTurn();
				if (_logNearbySounds != null && _logNearbySounds.Value && !(Time.time < _nextSoundScan))
				{
					_nextSoundScan = Time.time + 2f;
					LogNearbySounds();
				}
			}
		}

		private void LogNearbySounds()
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			AudioSource val = (((Object)(object)MusicMan.instance != (Object)null) ? AccessTools.FieldRefAccess<MusicMan, AudioSource>("m_musicSource").Invoke(MusicMan.instance) : null);
			AudioSource[] array = Object.FindObjectsByType<AudioSource>((FindObjectsSortMode)0);
			foreach (AudioSource val2 in array)
			{
				AudioClip clip = val2.clip;
				if ((Object)(object)val2 == (Object)(object)val || !val2.isPlaying || (Object)(object)clip == (Object)null || clip.length < 15f)
				{
					continue;
				}
				float num = Vector3.Distance(((Component)localPlayer).transform.position, ((Component)val2).transform.position);
				if (!(num > 100f))
				{
					string text = ObjectPath(((Component)val2).transform);
					if (_loggedSounds.Add(((Object)clip).name + "|" + text))
					{
						ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
						string text2 = string.Format("Nearby sound '{0}' ({1:0} s, loops {2}, ", ((Object)clip).name, clip.length, val2.loop ? "yes" : "no");
						AudioMixerGroup outputAudioMixerGroup = val2.outputAudioMixerGroup;
						logger.LogInfo((object)(text2 + string.Format("mixer '{0}', volume {1:0.00}) ", ((outputAudioMixerGroup != null) ? ((Object)outputAudioMixerGroup).name : null) ?? "none", val2.volume) + $"from '{text}', {num:0} m away"));
					}
				}
			}
		}

		private static string ObjectPath(Transform transform)
		{
			List<string> list = new List<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null && list.Count < 6)
			{
				list.Add(((Object)val).name.Replace("(Clone)", ""));
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list);
		}

		private void DropStaleSettings()
		{
			try
			{
				if (AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary { Count: not 0 } dictionary)
				{
					dictionary.Clear();
					((BaseUnityPlugin)this).Config.Save();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not tidy up old settings: " + ex.Message));
			}
		}

		private IEnumerator SetUpThenLoad(MusicMover mover, MusicInstaller installer, ICollection<string> disabledBiomes)
		{
			yield return ((MonoBehaviour)this).StartCoroutine(mover.MoveFromPreviousFolder());
			Library.EnsureFolders();
			yield return ((MonoBehaviour)this).StartCoroutine(installer.Install());
			Library.UpdateNotes();
			yield return ((MonoBehaviour)this).StartCoroutine(Library.Load(_volume.Value, _fadeInTime.Value, disabledBiomes, _raidMusic.Value));
			ApplyNamedReplacements(MusicMan.instance);
			ApplyVolumes();
		}

		private void ApplyVolumes()
		{
			if (Library == null || !Library.IsLoaded)
			{
				return;
			}
			foreach (NamedMusic ownEntry in Library.OwnEntries)
			{
				ownEntry.m_volume = VolumeFor(ownEntry.m_name);
			}
			MusicMan instance = MusicMan.instance;
			if ((Object)(object)instance != (Object)null)
			{
				foreach (NamedMusic item in instance.m_music.Where((NamedMusic m) => m.m_name != null && !m.m_name.StartsWith("BiomeMusic_") && Library.HasPool(m.m_name)))
				{
					item.m_volume = VolumeFor(item.m_name);
				}
				NamedMusic val = CurrentMusic.Invoke(instance);
				if (val != null && Library.HasPool(val.m_name))
				{
					PlayingVolume.Invoke(instance) = val.m_volume;
				}
			}
			foreach (PlaceSong item2 in _placeSongs.Values.Where((PlaceSong s) => (Object)(object)s.Location != (Object)null))
			{
				PlaceBaseVolume.Invoke(item2.Location) = PlaceVolumeFor(item2.Place, item2.VanillaClip, item2.GameVolume);
			}
		}

		private float VolumeFor(string entryName)
		{
			return _volume.Value * GroupVolume(MusicLibrary.VolumeGroupFor(entryName));
		}

		private float PlaceVolumeFor(string place, string vanillaClip, float gameVolume)
		{
			string text = MusicLibrary.LocationFolderFor(place, vanillaClip);
			float num = ((text == null) ? 1f : GroupVolume(PlaceSettingName(text)));
			return Mathf.Clamp01(gameVolume * num * _volume.Value);
		}

		private float GroupVolume(string group)
		{
			if (group == null || !_volumes.TryGetValue(group, out var value))
			{
				return 1f;
			}
			return value.Value;
		}

		internal NamedMusic PickBiomeMusic(NamedMusic vanilla)
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if (!Library.IsLoaded)
			{
				return null;
			}
			EnvMan instance = EnvMan.instance;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)instance == (Object)null || (Object)(object)localPlayer == (Object)null)
			{
				return null;
			}
			if (!_wroteWeatherList)
			{
				_wroteWeatherList = true;
				WriteWeatherList(instance);
			}
			TimeOfDay timeOfDay = ((!EnvMan.IsDay()) ? TimeOfDay.Night : TimeOfDay.Day);
			EnvSetup currentEnvironment = instance.GetCurrentEnvironment();
			NamedMusic val = Library.FindDungeon(currentEnvironment?.m_name, ((object)instance.GetCurrentBiome()/*cast due to .constrained prefix*/).ToString());
			if (val != null)
			{
				val.m_loop = false;
				LogSlot(MusicLibrary.DungeonFolderFor(currentEnvironment.m_name) + " (weather preset '" + currentEnvironment.m_name + "', vanilla '" + (vanilla?.m_name ?? "none") + "')", val);
				return val;
			}
			string text = ((!EnvMan.IsDay()) ? currentEnvironment?.m_musicNight : currentEnvironment?.m_musicDay);
			if (vanilla != null && !string.IsNullOrEmpty(text) && vanilla.m_name == text && !OutdoorPresets(instance).Contains(currentEnvironment.m_name))
			{
				if (_logChanges.Value)
				{
					string text2 = "Weather preset '" + currentEnvironment.m_name + "' has music of its own, '" + vanilla.m_name + "'";
					if (text2 != _lastLoggedSlot)
					{
						_lastLoggedSlot = text2;
						((BaseUnityPlugin)this).Logger.LogInfo((object)(text2 + " -> " + (Library.HasPool(vanilla.m_name) ? "your songs" : ("vanilla music (add songs in " + (MusicLibrary.FriendlyFolderFor(vanilla.m_name)?.Replace('/', '\\') ?? ("Special\\" + vanilla.m_name)) + " to replace it)"))));
					}
				}
				return null;
			}
			string text3 = CurrentArea(instance, localPlayer);
			bool flag;
			if (vanilla == null)
			{
				if (((Character)localPlayer).InInterior())
				{
					return null;
				}
				flag = false;
			}
			else if (vanilla.m_name == "home")
			{
				flag = true;
			}
			else
			{
				if (!(vanilla.m_name == instance.GetAmbientMusic()))
				{
					return null;
				}
				flag = false;
			}
			Weather weather = Classify(currentEnvironment);
			string area;
			NamedMusic val2;
			if (flag)
			{
				area = "HomeRest/" + text3;
				val2 = FindFor(area, currentEnvironment, timeOfDay, weather);
				if (val2 == null)
				{
					area = "HomeRest/Fallback";
					val2 = FindFor(area, currentEnvironment, timeOfDay, weather);
				}
			}
			else if (text3 == "Ocean")
			{
				val2 = FindForSea(currentEnvironment, timeOfDay, weather, out area);
			}
			else if (currentEnvironment?.m_name == "Ashlands_SeaStorm" && text3 != "AshlandsOcean")
			{
				area = "AshlandsOcean";
				val2 = FindFor(area, currentEnvironment, timeOfDay, weather);
				if (val2 == null)
				{
					area = text3;
					val2 = FindFor(area, currentEnvironment, timeOfDay, weather);
				}
			}
			else
			{
				area = text3;
				val2 = FindFor(area, currentEnvironment, timeOfDay, weather);
			}
			if (val2 != null)
			{
				ApplyPlayStyle(val2, vanilla);
			}
			LogSlot($"{area}/{timeOfDay}/{weather} (weather preset '{currentEnvironment?.m_name}', " + "vanilla '" + (vanilla?.m_name ?? "none") + "')", val2);
			return val2;
		}

		private HashSet<string> OutdoorPresets(EnvMan env)
		{
			if ((Object)(object)env == (Object)(object)_outdoorPresetsFor)
			{
				return _outdoorPresets;
			}
			_outdoorPresetsFor = env;
			try
			{
				List<BiomeEnvSetup> source = AccessTools.FieldRefAccess<EnvMan, List<BiomeEnvSetup>>("m_biomes").Invoke(env);
				_outdoorPresets = new HashSet<string>(from e in source.Where((BiomeEnvSetup b) => b?.m_environments != null).SelectMany((BiomeEnvSetup b) => b.m_environments)
					select e.m_environment into n
					where n != null
					select n);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not read the weather table: " + ex.Message));
				_outdoorPresets = new HashSet<string>();
			}
			return _outdoorPresets;
		}

		private void LogSlot(string slot, NamedMusic replacement)
		{
			if (_logChanges.Value && !(slot == _lastLoggedSlot))
			{
				_lastLoggedSlot = slot;
				((BaseUnityPlugin)this).Logger.LogInfo((object)(slot + " -> " + ((replacement != null) ? replacement.m_name : "vanilla music")));
			}
		}

		private unsafe static string CurrentArea(EnvMan env, Player player)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			Biome currentBiome = env.GetCurrentBiome();
			Vector3 position = ((Component)player).transform.position;
			if ((int)currentBiome == 256 && WorldGenerator.IsAshlands(position.x, position.z))
			{
				return "AshlandsOcean";
			}
			return ((object)(*(Biome*)(&currentBiome))/*cast due to .constrained prefix*/).ToString();
		}

		internal void OnMusicLocationAwake(MusicLocation location, AudioSource source)
		{
			if ((Object)(object)source == (Object)null || (Object)(object)source.clip == (Object)null)
			{
				return;
			}
			string name = ((Object)source.clip).name;
			string text = PlaceName(location);
			string text2 = MusicLibrary.LocationFolderFor(text, name);
			bool flag = !PlaceMusicOn(text2);
			if (text2 != null)
			{
				_places.RemoveAll(((MusicLocation Location, AudioSource Source, string Folder) p) => (Object)(object)p.Location == (Object)null);
				_places.Add((location, source, text2));
			}
			if (!flag && (Library == null || !Library.IsLoaded))
			{
				return;
			}
			AudioClip val = null;
			string folder = null;
			float num = PlaceBaseVolume.Invoke(location);
			if (Library != null && Library.IsLoaded)
			{
				_locationSources.RemoveAll((AudioSource s) => (Object)(object)s == (Object)null);
				List<AudioClip> inUse = _locationSources.Select((AudioSource s) => s.clip).ToList();
				val = Library.PickLocationSong(text, name, inUse, out folder);
				if ((Object)(object)val != (Object)null)
				{
					source.clip = val;
					PlaceBaseVolume.Invoke(location) = PlaceVolumeFor(text, name, num);
					_locationSources.Add(source);
					foreach (int item in (from p in _placeSongs
						where (Object)(object)p.Value.Location == (Object)null
						select p.Key).ToList())
					{
						_placeSongs.Remove(item);
					}
					_placeSongs[((Object)location).GetInstanceID()] = new PlaceSong(location, source, text, name, folder, source.loop, num);
				}
			}
			if (flag)
			{
				SetPlaceSilenced(location, source, silenced: true);
			}
			if (!_logChanges.Value)
			{
				return;
			}
			if (flag)
			{
				if (_loggedPlaces.Add("silenced|" + text2))
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Place '" + text + "' is silent: " + PlaceSettingName(text2) + " is switched off under [Places] in the config"));
				}
			}
			else if ((Object)(object)val != (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Place '" + text + "' plays '" + ((Object)val).name + "' from " + folder + " instead of '" + name + "' " + $"(volume {PlaceBaseVolume.Invoke(location):0.##}; the game's own plays at {num:0.##})"));
			}
			else if (_loggedPlaces.Add(text + "|" + name))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Place '" + text + "' has its own music '" + name + "': put songs in " + (MusicLibrary.LocationFolderFor(text, name) ?? ("Locations/" + name)) + " (or Locations/" + text + " for this kind of place only) to replace it"));
			}
		}

		private static string PlaceName(MusicLocation location)
		{
			return (((Object)(object)((Component)location).transform.parent != (Object)null) ? ((Object)((Component)location).transform.parent).name : ((Object)location).name).Replace("(Clone)", "").Trim();
		}

		internal void OnPlaceHeldBack(MusicLocation location)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!_logChanges.Value || (Object)(object)localPlayer == (Object)null || _loggedQuietPlaces.Contains(((Object)location).GetInstanceID()))
			{
				return;
			}
			string text;
			try
			{
				if (PlaceOneTime.Invoke(location) && PlaceHasPlayed(location))
				{
					text = "it plays its music only once per world, and it already has";
				}
				else
				{
					if (!PlaceNotIfEnemies.Invoke(location) || !BaseAI.HaveEnemyInRange((Character)(object)localPlayer, ((Component)location).transform.position, PlaceRadius.Invoke(location)))
					{
						return;
					}
					text = "it waits until there are no enemies nearby";
				}
			}
			catch (Exception ex)
			{
				text = "couldn't tell why (" + ex.GetType().Name + ")";
			}
			_loggedQuietPlaces.Add(((Object)location).GetInstanceID());
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Place '" + PlaceName(location) + "' is quiet although you're close: " + text + ". That's the game's own rule."));
		}

		private static string PlaceSettingName(string folder)
		{
			return folder.Substring(folder.IndexOf('/') + 1);
		}

		private bool PlaceMusicOn(string folder)
		{
			if (folder != null && _placeMusic.TryGetValue(folder, out var value))
			{
				return value.Value;
			}
			return true;
		}

		private void ApplyPlaceSetting(string folder)
		{
			_places.RemoveAll(((MusicLocation Location, AudioSource Source, string Folder) p) => (Object)(object)p.Location == (Object)null);
			foreach (var item in _places.Where(((MusicLocation Location, AudioSource Source, string Folder) p) => p.Folder == folder))
			{
				SetPlaceSilenced(item.Location, item.Source, !PlaceMusicOn(folder));
			}
		}

		private void SetPlaceSilenced(MusicLocation location, AudioSource source, bool silenced)
		{
			if ((Object)(object)source != (Object)null)
			{
				if (silenced)
				{
					source.Stop();
				}
				((Behaviour)source).enabled = !silenced;
			}
			((Behaviour)location).enabled = !silenced;
			if (_placeSongs.TryGetValue(((Object)location).GetInstanceID(), out var value))
			{
				value.LastTime = 0f;
			}
		}

		private void UpdatePlaceTurn()
		{
			//IL_0071: 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_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			PlaceSong placeTurn = _placeTurn;
			if (placeTurn == null)
			{
				return;
			}
			if (!placeTurn.IsLoaded)
			{
				_placeTurn = null;
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			bool isPlaying = placeTurn.Source.isPlaying;
			placeTurn.SilentFor = (isPlaying ? 0f : (placeTurn.SilentFor + Time.deltaTime));
			float num = PlaceFade.Invoke(placeTurn.Location);
			if (isPlaying)
			{
				Vector3 position = ((Component)localPlayer).transform.position;
				float num2 = Closeness(placeTurn, position);
				PlaceSong placeSong = null;
				float num3 = 0f;
				foreach (PlaceSong value in _placeSongs.Values)
				{
					if (value != placeTurn && !(value.Folder != placeTurn.Folder) && value.IsLoaded && (!PlaceOneTime.Invoke(value.Location) || !PlaceHasPlayed(value.Location)))
					{
						float num4 = Closeness(value, position);
						if (!(num4 <= num3))
						{
							placeSong = value;
							num3 = num4;
						}
					}
				}
				if (placeSong != null && (num3 > num2 + 0.15f || num <= 0f))
				{
					HandOver(placeTurn, placeSong);
					return;
				}
			}
			if (num <= 0f || (Settings.ContinousMusic && placeTurn.SilentFor > 1f))
			{
				placeTurn.Source.Stop();
				placeTurn.LastTime = 0f;
				_placeTurn = null;
			}
		}

		private static float Closeness(PlaceSong place, Vector3 playerPosition)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			float num = PlaceRadius.Invoke(place.Location);
			return 1f - Utils.SmoothStep(num * 0.5f, num, Vector3.Distance(((Component)place.Location).transform.position, playerPosition));
		}

		private void HandOver(PlaceSong from, PlaceSong to)
		{
			float time = from.Source.time;
			to.Source.clip = from.Source.clip;
			to.Source.volume = from.Source.volume;
			PlaceFade.Invoke(to.Location) = PlaceFade.Invoke(from.Location);
			to.Source.Play();
			to.Source.time = time;
			from.Source.Stop();
			from.LastTime = 0f;
			to.LastTime = time;
			to.SilentFor = 0f;
			_placeTurn = to;
			if (_logChanges.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("'" + ((Object)to.Source.clip).name + "' moves on from '" + from.Place + "' to '" + to.Place + "', carrying on where it was"));
			}
		}

		internal bool BeforePlaceUpdate(MusicLocation location, AudioSource source)
		{
			if ((Object)(object)source == (Object)null || (Object)(object)source.clip == (Object)null || Library == null || !_placeSongs.TryGetValue(((Object)location).GetInstanceID(), out var value))
			{
				return true;
			}
			if (_placeTurn != null && _placeTurn != value && _placeTurn.IsLoaded)
			{
				if (source.isPlaying)
				{
					source.Stop();
				}
				PlaceFade.Invoke(location) = 0f;
				value.LastTime = 0f;
				return false;
			}
			if (!Settings.ContinousMusic)
			{
				if (value.LoopTakenOver)
				{
					source.loop = value.Loops;
					value.LoopTakenOver = false;
				}
				value.LastTime = 0f;
				return true;
			}
			if (!value.LoopTakenOver)
			{
				source.loop = false;
				value.LoopTakenOver = true;
			}
			if (source.isPlaying)
			{
				value.LastTime = source.time;
				return true;
			}
			bool num = value.LastTime > 0f && value.LastTime >= source.clip.length - 2f;
			value.LastTime = 0f;
			if (!num)
			{
				return true;
			}
			_locationSources.RemoveAll((AudioSource s) => (Object)(object)s == (Object)null);
			List<AudioClip> inUse = (from s in _locationSources
				where (Object)(object)s != (Object)(object)source
				select s.clip).ToList();
			string folder;
			AudioClip val = Library.PickLocationSong(value.Place, value.VanillaClip, inUse, out folder);
			if ((Object)(object)val == (Object)null)
			{
				return true;
			}
			source.clip = val;
			if (PlaceFade.Invoke(location) <= 0f || (PlaceOneTime.Invoke(location) && PlaceHasPlayed(location)))
			{
				return true;
			}
			source.time = 0f;
			source.Play();
			if (_logChanges.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Place '" + value.Place + "' goes straight on to '" + ((Object)val).name + "' from " + folder));
			}
			return true;
		}

		internal void AfterPlaceUpdate(MusicLocation location, AudioSource source)
		{
			if ((_placeTurn == null || !_placeTurn.IsLoaded) && !((Object)(object)source == (Object)null) && source.isPlaying && !(PlaceFade.Invoke(location) <= 0f) && _placeSongs.TryGetValue(((Object)location).GetInstanceID(), out var value))
			{
				value.SilentFor = 0f;
				_placeTurn = value;
			}
		}

		internal void OnStartMusic(NamedMusic music)
		{
			if (Library != null && Library.HasPool(music.m_name))
			{
				Library.PrepareToStart(music);
			}
		}

		internal void OnMusicPlaying(NamedMusic music, AudioClip clip)
		{
			if (Library == null || music == null || !Library.HasPool(music.m_name))
			{
				return;
			}
			string text = Library.ConfirmStarted(music.m_name, clip);
			if (text != null && _logChanges.Value)
			{
				bool num = music.m_name.EndsWith("/sailing");
				string text2 = (num ? "Sailing, playing" : (music.m_name.StartsWith("BiomeMusic_") ? "Playing" : "Replacing vanilla, playing"));
				object obj;
				if (!num)
				{
					obj = "";
				}
				else
				{
					EnvMan instance = EnvMan.instance;
					obj = " (weather preset '" + ((instance == null) ? null : instance.GetCurrentEnvironment()?.m_name) + "')";
				}
				string text3 = (string)obj;
				((BaseUnityPlugin)this).Logger.LogInfo((object)(text2 + " '" + text + "' from " + music.m_name + text3));
			}
		}

		internal void ApplyNamedReplacements(MusicMan musicMan)
		{
			if ((Object)(object)musicMan == (Object)null || Library == null)
			{
				return;
			}
			WriteVanillaMusicList(musicMan);
			if (!Library.IsLoaded)
			{
				return;
			}
			foreach (KeyValuePair<string, AudioClip[]> replacement in Library.NamedReplacements)
			{
				NamedMusic val = musicMan.m_music.Find((NamedMusic m) => string.Equals(m.m_name, replacement.Key, StringComparison.OrdinalIgnoreCase));
				if (val == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("There is no vanilla music named '" + replacement.Key + "' to replace. Check the folder name against vanilla-music.txt."));
					continue;
				}
				Library.AttachTo(val, replacement.Value);
				val.m_volume = VolumeFor(val.m_name);
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Vanilla '{val.m_name}' now plays {replacement.Value.Length} song(s) of yours");
			}
		}

		private void WriteVanillaMusicList(MusicMan musicMan)
		{
			try
			{
				List<string> list = new List<string> { "# Every music track Valheim has, by name. To replace one, make a folder named exactly", "# like it inside Music\\Special and put your songs in it. Written each time the game starts.", "#", "# name | loops | songs in vanilla | vanilla volume" };
				foreach (NamedMusic item in musicMan.m_music)
				{
					if (item.m_name != null && !_vanillaVolumes.ContainsKey(item.m_name))
					{
						_vanillaVolumes[item.m_name] = item.m_volume;
					}
				}
				list.AddRange(musicMan.m_music.Where((NamedMusic m) => !m.m_name.StartsWith("BiomeMusic_")).OrderBy<NamedMusic, string>((NamedMusic m) => m.m_name, StringComparer.OrdinalIgnoreCase).Select(delegate(NamedMusic m)
				{
					object[] obj = new object[4]
					{
						m.m_name,
						m.m_loop ? "yes" : "no",
						null,
						null
					};
					AudioClip[] clips = m.m_clips;
					obj[2] = ((clips != null) ? clips.Length : 0);
					obj[3] = _vanillaVolumes[m.m_name];
					return string.Format("{0} | {1} | {2} | {3:0.##}", obj);
				}));
				string text = Path.Combine(Paths.ConfigPath, "TNS-Music-Overhaul");
				Directory.CreateDirectory(text);
				File.WriteAllLines(Path.Combine(text, "vanilla-music.txt"), list);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not write vanilla-music.txt: " + ex.Message));
			}
		}

		internal bool AllowLocationMusic(string name)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			EnvMan instance = EnvMan.instance;
			string preset = ((instance == null) ? null : instance.GetCurrentEnvironment()?.m_name);
			if (Library != null && Library.IsLoaded && (Object)(object)instance != (Object)null && name != null && string.Equals(MusicLibrary.DungeonTrackFor(preset), name, StringComparison.OrdinalIgnoreCase) && Library.FindDungeon(preset, ((object)instance.GetCurrentBiome()/*cast due to .constrained prefix*/).ToString()) != null)
			{
				if (_logChanges.Value && name != _lastLoggedLocation)
				{
					_lastLoggedLocation = name;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Room music '" + name + "' ignored: " + MusicLibrary.DungeonFolderFor(preset) + " is already playing for the whole dungeon"));
				}
				return false;
			}
			OnLocationMusic(name);
			return true;
		}

		internal void OnLocationMusic(string name)
		{
			if (_logChanges.Value && !string.IsNullOrEmpty(name) && !(name == _lastLoggedLocation))
			{
				_lastLoggedLocation = name;
				bool flag = Library != null && Library.HasPool(name);
				string text = MusicLibrary.FriendlyFolderFor(name)?.Replace('/', '\\') ?? ("Special\\" + name);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Location music '" + name + "' -> " + (flag ? ("your songs from " + text) : ("vanilla music (add songs in " + text + " to replace it)"))));
			}
		}

		private void ApplyPlayStyle(NamedMusic ours, NamedMusic vanilla)
		{
			bool flag = vanilla?.m_loop ?? false;
			ours.m_loop = flag && Library.PoolSize(ours.m_name) < 2;
			if (vanilla != null)
			{
				ours.m_resume = vanilla.m_resume;
			}
		}

		internal void CapSilence(MusicMan musicMan, ref float interval)
		{
			if (Settings.ContinousMusic)
			{
				interval = 0f;
				return;
			}
			if (!_loggedSilence)
			{
				_loggedSilence = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)($"Vanilla waits {musicMan.m_randomMusicIntervalMin:0}-{musicMan.m_randomMusicIntervalMax:0} s " + $"between biome songs; MaxSilenceSeconds = {_maxSilence.Value:0} (0 = vanilla)"));
			}
			float value = _maxSilence.Value;
			if (value > 0f && interval > value)
			{
				interval = value;
			}
		}

		internal string PickSailingMusic(string vanilla)
		{
			if (!_sailingUsesOcean.Value || !Library.IsLoaded)
			{
				return null;
			}
			string text;
			if (vanilla == "sailing")
			{
				text = "Ocean";
			}
			else
			{
				if (!(vanilla == "sailing_ashlands"))
				{
					return null;
				}
				text = "AshlandsOcean";
			}
			EnvMan instance = EnvMan.instance;
			MusicMan instance2 = MusicMan.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null)
			{
				return null;
			}
			TimeOfDay time = ((!EnvMan.IsDay()) ? TimeOfDay.Night : TimeOfDay.Day);
			EnvSetup currentEnvironment = instance.GetCurrentEnvironment();
			Weather weather = Classify(currentEnvironment);
			string area;
			NamedMusic val = ((text == "Ocean") ? FindForSea(currentEnvironment, time, weather, out area) : FindFor(text, currentEnvironment, time, weather));
			if (val == null)
			{
				return null;
			}
			if (val.m_name.StartsWith("BiomeMusic_" + "AshlandsOcean".ToLowerInvariant() + "/"))
			{
				if (!IsSailing(instance2))
				{
					return null;
				}
				ApplyPlayStyle(val, instance2.m_music.Find((NamedMusic m) => m.m_name == vanilla));
				return val.m_name;
			}
			NamedMusic variant = Library.GetVariant(val, "sailing");
			ApplyPlayStyle(variant, instance2.m_music.Find((NamedMusic m) => m.m_name == vanilla));
			return variant.m_name;
		}

		internal bool OnTriggerMusic(ref string name)
		{
			if (!MusicLibrary.IsTransition(name) || !Library.IsLoaded)
			{
				return true;
			}
			NamedMusic val = Library.FindTransition(name);
			bool flag = val != null || !_skipVanillaTransitions.Value;
			if (_logChanges.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Transition '" + name + "' -> " + ((val != null) ? val.m_name : (flag ? "vanilla music" : "skipped"))));
			}
			if (val != null)
			{
				name = val.m_name;
			}
			return flag;
		}

		internal string PickRaidMusic(RandomEvent raid)
		{
			if (raid == null || !_raidMusic.Value || !Library.IsLoaded)
			{
				return null;
			}
			NamedMusic val = Library.FindRaid(raid.m_name);
			if (_logChanges.Value && raid.m_name != _lastLoggedRaid)
			{
				_lastLoggedRaid = raid.m_name;
				string text = MusicLibrary.FriendlyFolderFor(raid.m_forceMusic);
				string text2 = ((val != null) ? ("its own folder, Raids\\" + raid.m_name) : (Library.HasPool(raid.m_forceMusic) ? ("your songs in " + text?.Replace('/', '\\')) : ("vanilla music (add songs in " + (text?.Replace('/', '\\') ?? ("Raids\\" + raid.m_name)) + " to replace it)")));
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Raid '" + raid.m_name + "' uses level '" + raid.m_forceMusic + "' -> " + text2));
			}
			return val?.m_name;
		}

		private Weather Classify(EnvSetup env)
		{
			if (env == null)
			{
				return Weather.Clear;
			}
			string name = env.m_name ?? "";
			if (Matches(name, _clearKeywords.Value))
			{
				return Weather.Clear;
			}
			if (Matches(name, _stormKeywords.Value))
			{
				return Weather.Thunderstorm;
			}
			if (Matches(name, _rainKeywords.Value) || env.m_isWet)
			{
				return Weather.Rain;
			}
			return Weather.Clear;
		}

		private NamedMusic FindFor(string area, EnvSetup env, TimeOfDay time, Weather weather)
		{
			string text = MusicLibrary.DefaultPreset(area);
			return ((text != null && env != null && env.m_name != null) ? Library.FindPreset(area, env.m_name, time) : null) ?? ((text != null) ? Library.FindPreset(area, text, time) : null) ?? Library.FindBiome(area, time, weather, _fallbackToClear.Value);
		}

		private NamedMusic FindForSea(EnvSetup env, TimeOfDay time, Weather weather, out string area)
		{
			if (env?.m_name == "Ashlands_SeaStorm")
			{
				area = "AshlandsOcean";
				NamedMusic val = FindFor(area, env, time, weather);
				if (val != null)
				{
					return val;
				}
			}
			area = "Ocean";
			return FindFor(area, env, time, weather);
		}

		internal void WriteWeatherList(EnvMan env)
		{
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				List<EnvSetup> source = AccessTools.FieldRefAccess<EnvMan, List<EnvSetup>>("m_environments").Invoke(env);
				List<BiomeEnvSetup> source2 = AccessTools.FieldRefAccess<EnvMan, List<BiomeEnvSetup>>("m_biomes").Invoke(env);
				Dictionary<string, EnvSetup> byName = (from e in source
					where e?.m_name != null
					group e by e.m_name).ToDictionary((IGrouping<string, EnvSetup> g) => g.Key, (IGrouping<string, EnvSetup> g) => g.First());
				List<string> list = new List<string> { "# Every weather preset in the game, by biome, how often it comes up, and the folder in that", "# biome whose songs play during it (with Day or Night on the end: RainyNight, CinderRainDay, ...).", "# 'Own music' is a track the game plays for that preset instead of the biome's. The mod keeps it", "# for presets below the biome list (dungeons, arenas, events: replace it through Dungeons, Bosses", "# or Special); for normal weather its folders play as usual. Written once per game session.", "#", "# biome | preset | chance | folder" };
				foreach (BiomeEnvSetup item in source2.Where((BiomeEnvSetup b) => b?.m_environments != null))
				{
					float num = item.m_environments.Sum((EnvEntry e) => e.m_weight);
					foreach (EnvEntry item2 in item.m_environments.OrderByDescending((EnvEntry e) => e.m_weight))
					{
						list.Add($"{item.m_biome} | {item2.m_environment} | " + $"{((num > 0f) ? (item2.m_weight / num * 100f) : 0f):0}% | " + FolderFor(((object)Unsafe.As<Biome, Biome>(ref item.m_biome)/*cast due to .constrained prefix*/).ToString(), item2.m_environment) + OwnMusic(item2.m_environment));
					}
				}
				list.Add("");
				list.Add("# Presets that aren't normal weather: dungeon interiors, boss arenas, events, ...");
				HashSet<string> rolled = new HashSet<string>(from e in source2.Where((BiomeEnvSetup b) => b?.m_environments != null).SelectMany((BiomeEnvSetup b) => b.m_environments)
					select e.m_environment);
				list.Add("# The ones with a folder here get their own music, inside that dungeon.");
				list.AddRange((from n in byName.Keys
					where !rolled.Contains(n)
					orderby n
					select n).Select(delegate(string n)
				{
					string text2 = MusicLibrary.DungeonFolderFor(n);
					return ((text2 != null) ? (n + " | " + text2) : n) + OwnMusic(n);
				}));
				string text = Path.Combine(Paths.ConfigPath, "TNS-Music-Overhaul");
				Directory.CreateDirectory(text);
				File.WriteAllLines(Path.Combine(text, "vanilla-weather.txt"), list);
				string FolderFor(string biome, string preset)
				{
					if (!byName.TryGetValue(preset, out var value))
					{
						return "?";
					}
					return MusicLibrary.FolderLabel(biome, preset, Classify(value));
				}
				string OwnMusic(string preset)
				{
					if (!byName.TryGetValue(preset, out var value))
					{
						return "";
					}
					string text2 = (string.IsNullOrEmpty(value.m_musicDay) ? "-" : value.m_musicDay);
					string text3 = (string.IsNullOrEmpty(value.m_musicNight) ? "-" : value.m_musicNight);
					if (!(text2 == "-") || !(text3 == "-"))
					{
						return " | own music: " + text2 + " by day, " + text3 + " by night";
					}
					return "";
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not write vanilla-weather.txt: " + ex.Message));
			}
		}

		private static IEnumerable<string> SplitList(string value)
		{
			return from s in value.Split(',')
				select s.Trim() into s
				where s.Length > 0
				select s;
		}

		private static bool Matches(string name, string keywords)
		{
			return SplitList(keywords).Any((string k) => name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0);
		}
	}
	[HarmonyPatch(typeof(MusicMan), "GetEnvironmentMusic")]
	internal static class GetEnvironmentMusicPatch
	{
		private static void Postfix(ref NamedMusic __result)
		{
			NamedMusic val = BiomeMusicPlugin.Instance?.PickBiomeMusic(__result);
			if (val != null)
			{
				__result = val;
			}
		}
	}
	[HarmonyPatch(typeof(RandEventSystem), "GetMusicOverride")]
	internal static class GetMusicOverridePatch
	{
		private static void Postfix(RandEventSystem __instance, ref string __result)
		{
			if (!string.IsNullOrEmpty(__result))
			{
				string text = BiomeMusicPlugin.Instance?.PickRaidMusic(__instance.GetActiveEvent());
				if (text != null)
				{
					__result = text;
				}
			}
		}
	}
	[HarmonyPatch(typeof(MusicMan), "StartMusic", new Type[] { typeof(NamedMusic) })]
	internal static class StartMusicPatch
	{
		private static readonly FieldRef<MusicMan, NamedMusic> CurrentMusic = AccessTools.FieldRefAccess<MusicMan, NamedMusic>("m_currentMusic");

		private static readonly FieldRef<MusicMan, NamedMusic> QueuedMusic = AccessTools.FieldRefAccess<MusicMan, NamedMusic>("m_queuedMusic");

		private static readonly FieldRef<MusicMan, float> ResetMusicTimer = AccessTools.FieldRefAccess<MusicMan, float>("m_resetMusicTimer");

		private static void Prefix(MusicMan __instance, NamedMusic music)
		{
			if (music != null && music != CurrentMusic.Invoke(__instance) && music != QueuedMusic.Invoke(__instance))
			{
				if (music.m_name != null && music.m_name.StartsWith("BiomeMusic_"))
				{
					ResetMusicTimer.Invoke(__instance) = 0f;
				}
				BiomeMusicPlugin.Instance?.OnStartMusic(music);
			}
		}
	}
	[HarmonyPatch(typeof(MusicMan), "UpdateMusic")]
	internal static class UpdateMusicPatch
	{
		private static readonly FieldRef<MusicMan, NamedMusic> CurrentMusic = AccessTools.FieldRefAccess<MusicMan, NamedMusic>("m_currentMusic");

		private static readonly FieldRef<MusicMan, AudioSource> Source = AccessTools.FieldRefAccess<MusicMan, AudioSource>("m_musicSource");

		private static void Postfix(MusicMan __instance)
		{
			NamedMusic val = CurrentMusic.Invoke(__instance);
			AudioSource val2 = Source.Invoke(__instance);
			if (val != null && !((Object)(object)val2 == (Object)null) && val2.isPlaying)
			{
				BiomeMusicPlugin.Instance?.OnMusicPlaying(val, val2.clip);
			}
		}
	}
	[HarmonyPatch(typeof(MusicMan), "HandleEnvironmentMusic")]
	internal static class HandleEnvironmentMusicPatch
	{
		private static readonly FieldRef<MusicMan, float> RandomAmbientInterval = AccessTools.FieldRefAccess<MusicMan, float>("m_randomAmbientInterval");

		private static void Prefix(MusicMan __instance)
		{
			BiomeMusicPlugin.Instance?.CapSilence(__instance, ref RandomAmbientInterval.Invoke(__instance));
		}
	}
	[HarmonyPatch(typeof(MusicMan), "GetSailingMusic")]
	internal static class GetSailingMusicPatch
	{
		private static void Postfix(ref string __result)
		{
			string text = BiomeMusicPlugin.Instance?.PickSailingMusic(__result);
			if (text != null)
			{
				__result = text;
			}
		}
	}
	[HarmonyPatch(typeof(MusicMan), "Awake")]
	internal static class MusicManAwakePatch
	{
		private static void Postfix(MusicMan __instance)
		{
			BiomeMusicPlugin.Instance?.ApplyNamedReplacements(__instance);
		}
	}
	[HarmonyPatch(typeof(MusicMan), "LocationMusic")]
	internal static class LocationMusicPatch
	{
		private static bool Prefix(string name)
		{
			return BiomeMusicPlugin.Instance?.AllowLocationMusic(name) ?? true;
		}
	}
	[HarmonyPatch(typeof(MusicLocation), "Awake")]
	internal static class MusicLocationAwakePatch
	{
		private static readonly FieldRef<MusicLocation, AudioSource> Source = AccessTools.FieldRefAccess<MusicLocation, AudioSource>("m_audioSource");

		private static void Postfix(MusicLocation __instance)
		{
			BiomeMusicPlugin.Instance?.OnMusicLocationAwake(__instance, Source.Invoke(__instance));
		}
	}
	[HarmonyPatch(typeof(MusicLocation), "Update")]
	internal static class MusicLocationUpdatePatch
	{
		private static readonly FieldRef<MusicLocation, AudioSource> Source = AccessTools.FieldRefAccess<MusicLocation, AudioSource>("m_audioSource");

		private static readonly FieldRef<MusicLocation, float> Volume = AccessTools.FieldRefAccess<MusicLocation, float>("volume");

		private static readonly FieldRef<MusicLocation, bool> PlayedThisVisit = AccessTools.FieldRefAccess<MusicLocation, bool>("m_blockLoopAndFade");

		private static bool Prefix(MusicLocation __instance)
		{
			return BiomeMusicPlugin.Instance?.BeforePlaceUpdate(__instance, Source.Invoke(__instance)) ?? true;
		}

		private static void Postfix(MusicLocation __instance)
		{
			BiomeMusicPlugin.Instance?.AfterPlaceUpdate(__instance, Source.Invoke(__instance));
			if (!(Volume.Invoke(__instance) <= 0f) && !PlayedThisVisit.Invoke(__instance))
			{
				AudioSource val = Source.Invoke(__instance);
				if (!((Object)(object)val == (Object)null) && !val.isPlaying)
				{
					BiomeMusicPlugin.Instance?.OnPlaceHeldBack(__instance);
				}
			}
		}
	}
	[HarmonyPatch(typeof(MusicMan), "TriggerMusic")]
	internal static class TriggerMusicPatch
	{
		private static bool Prefix(ref string name)
		{
			return BiomeMusicPlugin.Instance?.OnTriggerMusic(ref name) ?? true;
		}
	}
	[HarmonyPatch(typeof(MusicMan), "FindMusic")]
	internal static class FindMusicPatch
	{
		private static bool Prefix(string musicName, ref NamedMusic __result)
		{
			if (musicName == null || !musicName.StartsWith("BiomeMusic_"))
			{
				return true;
			}
			__result = BiomeMusicPlugin.Instance?.Library?.FindByName(musicName);
			return __result == null;
		}
	}
	internal class MusicInstaller
	{
		public const string RecordFileName = "installed-songs.txt";

		private static readonly string[] Extensions = new string[2] { ".ogg", ".wav" };

		private readonly string _shippedRoot;

		private readonly string _userRoot;

		private readonly string _recordPath;

		private readonly string _oldRecordPath;

		private readonly bool _oldRecordIsOurs;

		private readonly ManualLogSource _log;

		public MusicInstaller(string shippedRoot, string userRoot, string recordPath, string oldRecordPath, bool oldRecordIsOurs, ManualLogSource log)
		{
			_shippedRoot = shippedRoot;
			_userRoot = userRoot;
			_recordPath = recordPath;
			_oldRecordPath = oldRecordPath;
			_oldRecordIsOurs = oldRecordIsOurs;
			_log = log;
		}

		public IEnumerator Install()
		{
			if (!Directory.Exists(_shippedRoot))
			{
				yield break;
			}
			List<string> list = (from f in Directory.GetFiles(_shippedRoot, "*", SearchOption.AllDirectories)
				where Extensions.Contains(Path.GetExtension(f).ToLowerInvariant())
				select f).OrderBy<string, string>((string f) => f, StringComparer.OrdinalIgnoreCase).ToList();
			if (list.Count == 0)
			{
				yield break;
			}
			HashSet<string> installed = ReadRecord();
			int copied = 0;
			int failed = 0;
			Stopwatch watch = Stopwatch.StartNew();
			foreach (string item in list)
			{
				string text = item.Substring(_shippedRoot.Length).TrimStart(Path.DirectorySeparatorChar);
				string text2 = Path.Combine(_userRoot, text);
				if (!installed.Contains(text) && !File.Exists(text2))
				{
					try
					{
						Directory.CreateDirectory(Path.GetDirectoryName(text2));
						File.Copy(item, text2, overwrite: false);
						copied++;
					}
					catch (Exception ex)
					{
						_log.LogWarning((object)("Could not install " + text + ": " + ex.Message));
						failed++;
						continue;
					}
				}
				installed.Add(text);
				try
				{
					File.Delete(item);
				}
				catch (Exception ex2)
				{
					_log.LogWarning((object)("Could not remove the mod's copy of " + text + ": " + ex2.Message));
				}
				yield return null;
			}
			WriteRecord(installed);
			if (failed == 0)
			{
				try
				{
					Directory.Delete(_shippedRoot, recursive: true);
				}
				catch (Exception ex3)
				{
					_log.LogWarning((object)("Could not remove " + _shippedRoot + ": " + ex3.Message));
				}
			}
			if (copied > 0 || failed > 0)
			{
				_log.LogInfo((object)($"Installed {copied} song(s) into {_userRoot} in {watch.Elapsed.TotalSeconds:0.0} s" + ((failed > 0) ? $" ({failed} failed)" : "")));
			}
		}

		private HashSet<string> ReadRecord()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string text = (File.Exists(_recordPath) ? _recordPath : ((_oldRecordIsOurs && File.Exists(_oldRecordPath)) ? _oldRecordPath : null));
			if (text == null)
			{
				if (File.Exists(_oldRecordPath))
				{
					_log.LogInfo((object)("Not using " + _oldRecordPath + ": it came with a profile from another PC or folder, so it says nothing about the songs here."));
				}
				return hashSet;
			}
			if (!Directory.Exists(_userRoot) || !Directory.GetFiles(_userRoot, "*", SearchOption.AllDirectories).Any((string f) => Extensions.Contains(Path.GetExtension(f).ToLowerInvariant())))
			{
				_log.LogInfo((object)"No songs found in the music folder; installing the mod's songs again.");
				return hashSet;
			}
			try
			{
				string[] array = File.ReadAllLines(text);
				foreach (string text2 in array)
				{
					if (text2.Length > 0 && !text2.StartsWith("#"))
					{
						hashSet.Add(text2.Trim());
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Could not read " + text + ": " + ex.Message));
			}
			return hashSet;
		}

		private void WriteRecord(IEnumerable<string> installed)
		{
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(_recordPath));
				File.WriteAllLines(_recordPath, new string[6] { "# Songs TNS Music Overhaul has already copied into its music folder.", "# Listed songs are not copied again, so ones you delete stay deleted.", "#", "# To get deleted songs back, reinstall the mod in your mod manager and start", "# the game: that is the only time the mod has its own songs to hand out again.", "# Removing lines here (or this whole file) decides what it hands back then." }.Concat(installed.OrderBy<string, string>((string p) => p, StringComparer.OrdinalIgnoreCase)));
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Could not write " + _recordPath + ": " + ex.Message));
				return;
			}
			try
			{
				if (!string.IsNullOrEmpty(_oldRecordPath) && File.Exists(_oldRecordPath))
				{
					File.Delete(_oldRecordPath);
				}
			}
			catch (Exception ex2)
			{
				_log.LogWarning((object)("Could not remove " + _oldRecordPath + ": " + ex2.Message));
			}
		}
	}
	internal enum TimeOfDay
	{
		Day,
		Night
	}
	internal enum Weather
	{
		Clear,
		Rain,
		Thunderstorm
	}
	internal class MusicLibrary
	{
		public static readonly string[] Biomes = new string[9] { "Meadows", "BlackForest", "Swamp", "Mountain", "Plains", "Mistlands", "AshLands", "DeepNorth", "Ocean" };

		private static readonly HashSet<string> SnowyBiomes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Mountain", "DeepNorth" };

		private static readonly Dictionary<string, Weather[]> AreaWeathers = new Dictionary<string, Weather[]>(StringComparer.OrdinalIgnoreCase)
		{
			["Swamp"] = new Weather[1] { Weather.Rain },
			["Mountain"] = new Weather[2]
			{
				Weather.Rain,
				Weather.Thunderstorm
			},
			["Plains"] = new Weather[2]
			{
				Weather.Clear,
				Weather.Rain
			}
		};

		public const string AshlandsFolder = "AshLands";

		public const string AshlandsOceanFolder = "AshlandsOcean";

		public const string SeaStormPreset = "Ashlands_SeaStorm";

		private static readonly (string Folder, string Preset)[] AshlandsPresets = new(string, string)[4]
		{
			("AshRain", "Ashlands_ashrain"),
			("CinderRain", "Ashlands_CinderRain"),
			("Misty", "Ashlands_misty"),
			("Storm", "Ashlands_storm")
		};

		private static readonly Dictionary<string, (string Folder, string Preset)[]> PresetAreas = new Dictionary<string, (string, string)[]>(StringComparer.OrdinalIgnoreCase)
		{
			["AshLands"] = AshlandsPresets,
			["HomeRest/AshLands"] = AshlandsPresets,
			["AshlandsOcean"] = new(string, string)[1] { ("SeaStorm", "Ashlands_SeaStorm") }
		};

		public const string HomeFolder = "HomeRest";

		public const string HomeFallbackFolder = "HomeRest/Fallback";

		public const string SpecialFolder = "Special";

		public const string RaidsFolder = "Raids";

		private static readonly (string Folder, string Track)[] FriendlyFolders = new(string, string)[17]
		{
			("Raids/Tier1", "CombatEventL1"),
			("Raids/Tier2", "CombatEventL2"),
			("Raids/Tier3", "CombatEventL3"),
			("Raids/Tier4", "CombatEventL4"),
			("Bosses/Eikthyr", "boss_eikthyr"),
			("Bosses/TheElder", "boss_gdking"),
			("Bosses/Bonemass", "boss_bonemass"),
			("Bosses/Moder", "boss_moder"),
			("Bosses/Yagluth", "boss_goblinking"),
			("Bosses/TheQueen", "boss_queen"),
			("Bosses/Fader", "boss_fader"),
			("Bosses/DeepNorthBoss", "boss_frozenking"),
			("Dungeons/BurialChambers", "forestcrypt"),
			("Dungeons/FrostCaves", "frostcaves"),
			("Dungeons/SmoulderingTomb", "forestcrypthildir"),
			("Dungeons/HowlingCavern", "frostcaveshildir"),
			("Dungeons/SealedTower", "plainstower")
		};

		public const string LocationsFolder = "Locations";

		public const string ExtraFolder = "Extra";

		private static readonly string[] PlaceFolders = new string[2] { "Locations", "Extra" };

		private static readonly (string Folder, string[] Names)[] LocationFolders = new(string, string[])[14]
		{
			("Locations/Haldor", new string[1] { "Black Forest - Haldor" }),
			("Locations/GreydwarfCamp", new string[1] { "Black Forest - Greydwarf Camp" }),
			("Locations/Hildir", new string[1] { "Meadows - Hildir" }),
			("Locations/BogWitch", new string[1] { "Swamps - Bogwitch v2" }),
			("Locations/FulingCamp", new string[1] { "Plains - Fuling Camp" }),
			("Locations/DvergrTower", new string[1] { "Mistlands - Dvergr Tower" }),
			("Locations/MistlandsExcavation", new string[1] { "Mistlands - Dvergr Excavation Site" }),
			("Locations/AshlandsExcavation", new string[1] { "CharredTowerRuins1_dvergr" }),
			("Locations/CharredFortress", new string[1] { "Ashland - Fortress" }),
			("Locations/AshlandsRuins", new string[3] { "Ashlands - Ruins 1", "Ashlands - Ruins 2", "Ashlands - Ruins 3" }),
			("Extra/MeadowsVillage", new string[1] { "Meadows - Village & Farm" }),
			("Extra/MountainCottage", new string[1] { "Mountains - Abandoned cottages" }),
			("Extra/StoneHenge", new string[1] { "Plains - Stone Henges" }),
			("Extra/Discovery", new string[1] { "LocationReveal" })
		};

		private static readonly (string Folder, string Preset, string Biome, string Track)[] DungeonWeathers = new(string, string, string, string)[3]
		{
			("Dungeons/SunkenCrypt", "SunkenCrypt", null, null),
			("Dungeons/InfestedMines", "InfectedMine", null, null),
			("Dungeons/FrostCaves", "Caves", "Mountain", "frostcaves")
		};

		public const string TransitionFolder = "Transition";

		public static readonly string[] Transitions = new string[2] { "Morning", "Evening" };

		public const string NamePrefix = "BiomeMusic_";

		private static readonly string[] Extensions = new string[2] { ".ogg", ".wav" };

		private static readonly string[] UnplayableExtensions = new string[6] { ".mp3", ".flac", ".m4a", ".aac", ".wma", ".opus" };

		private readonly Dictionary<string, NamedMusic> _tracks = new Dictionary<string, NamedMusic>();

		private readonly Dictionary<string, NamedMusic> _byName = new Dictionary<string, NamedMusic>();

		private readonly Dictionary<string, AudioClip[]> _pools = new Dictionary<string, AudioClip[]>();

		private readonly Dictionary<string, Queue<int>> _shuffles = new Dictionary<string, Queue<int>>();

		private readonly Dictionary<string, int> _lastPlayed = new Dictionary<string, int>();

		private readonly Dictionary<string, AudioClip[]> _named = new Dictionary<string, AudioClip[]>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, string> _locationPools = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private readonly Random _random = new Random();

		private readonly string _root;

		private readonly ManualLogSource _log;

		private int _clipCount;

		private double _mainThreadMs;

		private double _slowestClipMs;

		public const string NoteName = "PUT_MUSIC_HERE.txt";

		private static readonly string NoteText = string.Join(Environment.NewLine, "Drop .ogg or .wav files in this folder and restart the game. File names don't matter;", "one song is picked at random each time. Until then, this moment plays the music", "described in the READ ME at the top of the music folder.", "", "This note is only here while the folder has no songs; the mod removes it once it does.");

		private readonly List<string> _songFolders = new List<string>();

		private static readonly Weather[] AllWeathers = new Weather[3]
		{
			Weather.Clear,
			Weather.Rain,
			Weather.Thunderstorm
		};

		private readonly Dictionary<string, int> _reserved = new Dictionary<string, int>();

		public const string BossesGroup = "Bosses";

		public const string DungeonsGroup = "Dungeons";

		public const string TransitionsGroup = "Transitions";

		private static IEnumerable<string> SlotAreas => Biomes.Append("HomeRest/Fallback");

		private static IEnumerable<string> HomeBiomes => Biomes.Where((string b) => b != "Ocean");

		public bool IsLoaded { get; private set; }

		public static IEnumerable<string> PlaceFolderPaths => LocationFolders.Select(((string Folder, string[] Names) l) => l.Folder);

		public static IEnumerable<string> VolumeGroups => Biomes.Append("AshlandsOcean").Concat(new string[5] { "HomeRest", "Bosses", "Dungeons", "Raids", "Transitions" });

		public IEnumerable<NamedMusic> OwnEntries => _byName.Values;

		public IEnumerable<KeyValuePair<string, AudioClip[]>> NamedReplacements => _named;

		public MusicLibrary(string root, ManualLogSource log)
		{
			_root = root;
			_log = log;
		}

		public void EnsureFolders()
		{
			foreach (string item in Biomes.Where((string b) => !PresetAreas.ContainsKey(b)).Append("HomeRest/Fallback").Concat(from b in HomeBiomes
				where !PresetAreas.ContainsKey("HomeRest/" + b)
				select Path.Combine("HomeRest", b)))
			{
				foreach (TimeOfDay value2 in Enum.GetValues(typeof(TimeOfDay)))
				{
					foreach (Weather item2 in WeathersOf(Path.GetFileName(item)))
					{
						MakeSongFolder(Path.Combine(_root, item, FolderName(item, item2, value2)));
					}
				}
			}
			foreach (KeyValuePair<string, (string, string)[]> presetArea in PresetAreas)
			{
				(string, string)[] value = presetArea.Value;
				for (int num = 0; num < value.Length; num++)
				{
					(string, string) tuple = value[num];
					foreach (TimeOfDay value3 in Enum.GetValues(typeof(TimeOfDay)))
					{
						MakeSongFolder(Path.Combine(_root, presetArea.Key, tuple.Item1 + value3));
					}
				}
			}
			(string, string)[] friendlyFolders = FriendlyFolders;
			for (int num = 0; num < friendlyFolders.Length; num++)
			{
				(string, string) tuple2 = friendlyFolders[num];
				MakeSongFolder(Path.Combine(_root, tuple2.Item1));
			}
			(string, string, string, string)[] dungeonWeathers = DungeonWeathers;
			for (int num = 0; num < dungeonWeathers.Length; num++)
			{
				(string, string, string, string) tuple3 = dungeonWeathers[num];
				MakeSongFolder(Path.Combine(_root, tuple3.Item1));
			}
			(string, string[])[] locationFolders = LocationFolders;
			for (int num = 0; num < locationFolders.Length; num++)
			{
				(string, string[]) tuple4 = locationFolders[num];
				MakeSongFolder(Path.Combine(_root, tuple4.Item1));
			}
			WriteModFile(Path.Combine(_root, "Extra", "READ ME.txt"), new string[9] { "Smaller places with music of their own. The mod doesn't come with songs for these,", "but you can add your own, and they work just like the Locations folders:", "", "  MeadowsVillage   - abandoned villages and farms in the Meadows", "  MountainCottage  - abandoned cottages in the Mountains", "  StoneHenge       - the standing stone circles in the Plains", "  Discovery        - the short cue the first time you walk into a point of interest", "", "An empty folder keeps the game's own music there." });
			string text = Path.Combine(_root, "Special");
			Directory.CreateDirectory(text);
			WriteModFile(Path.Combine(text, "READ ME.txt"), new string[7] { "Replace any of Valheim's own music tracks: dungeons, boss fights, the main menu,", "anything the game plays by name.", "", "Make a folder here named exactly like the track, and put your songs in it.", "Every track name is listed in vanilla-music.txt, in the mod's settings folder", "next to TNS-Music-Overhaul.cfg. With LogChanges on, the log also names each", "dungeon's music as you walk in." });
			string[] transitions = Transitions;
			foreach (string path in transitions)
			{
				MakeSongFolder(Path.Combine(_root, "Transition", path));
			}
			WriteModFile(Path.Combine(_root, "READ ME.txt"), new string[28]
			{
				"All music for TNS Music Overhaul lives here, and SURVIVES MOD UPDATES.", "", "The songs that come with the mod are copied here the first time you run it, and", "updates add any new ones. Everything else is yours to change:", "", "  - Add songs: drop .ogg or .wav files into the folder for the moment you want", "    them to play, for example Meadows\\ClearNight, then restart the game.", "    One song from that folder is picked at random each time.", "  - Remove songs: delete the files. They stay gone; updates won't bring them back.", "  - Each biome folder has a folder per weather that really happens there, by day",
				"    and by night. An empty one borrows another weather's songs from the same", "    biome and time of day, or plays Valheim's own music if there are none.", "  - HomeRest has a folder per biome, for resting at a base built there.", "    HomeRest\\Fallback plays wherever a biome's home folder is empty.", "  - Bosses, Dungeons, Raids, Locations and Extra: an empty folder keeps", "    Valheim's own music for that fight or place.", "  - Want the mod's songs back? Reinstall the mod in your mod manager, then", "    start the game. This folder is the only copy once the mod has run.", "", "To keep a biome's original music, add it to DisabledBiomes in",
				"TNS-Music-Overhaul.cfg (in your mod manager profile's BepInEx\\config folder).", "To silence a place completely, Valheim's own music included, switch it off", "under [Places] in the same file.", "", "installed-songs.txt and music-folder.txt are the mod's notes on what it has", "installed here and where the music is; best left alone.", "", "This file is the mod's and is rewritten when an update changes it."
			});
		}

		private void MakeSongFolder(string dir)
		{
			Directory.CreateDirectory(dir);
			_songFolders.Add(dir);
		}

		private void WriteModFile(string path, string[] lines)
		{
			string text = string.Join(Environment.NewLine, lines);
			try
			{
				if (!File.Exists(path) || File.ReadAllText(path) != text)
				{
					File.WriteAllText(path, text);
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Could not write " + path + ": " + ex.Message));
			}
		}

		public void UpdateNotes()
		{
			foreach (string item in _songFolders.Distinct<string>(StringComparer.OrdinalIgnoreCase))
			{
				string text = Path.Combine(item, "PUT_MUSIC_HERE.txt");
				try
				{
					if (Directory.EnumerateFiles(item).Any((string f) => Extensions.Contains(Path.GetExtension(f).ToLowerInvariant())))
					{
						if (File.Exists(text))
						{
							File.Delete(text);
						}
					}
					else if (!File.Exists(text))
					{
						File.WriteAllText(text, NoteText);
					}
				}
				catch (Exception ex)
				{
					_log.LogWarning((object)("Could not update " + text + ": " + ex.Message));
				}
			}
		}

		public IEnumerator Load(float volume, float fadeInTime, ICollection<string> disabledBiomes, bool raidMusic)
		{
			Stopwatch total = Stopwatch.StartNew();
			List<string> list = SlotAreas.Where((string a) => !disabledBiomes.Contains(a)).Concat(HomeBiomes.Select((string b) => Path.Combine("HomeRest", b))).ToList();
			ReportUnusedFolders(list);
			foreach (string area in list)
			{
				foreach (TimeOfDay time in Enum.GetValues(typeof(TimeOfDay)))
				{
					foreach (Weather item in LoadedWeathers(area))
					{
						IEnumerable<string> dirs = from name in SlotFolders(time, item)
							select Path.Combine(_root, area, name);
						yield return LoadSlot(dirs, BiomeKey(area.Replace('\\', '/'), time, item), volume, fadeInTime, loop: false);
					}
				}
			}
			(string Folder, string Preset)[] value;
			foreach (KeyValuePair<string, (string, string)[]> presetArea in PresetAreas.Where((KeyValuePair<string, (string Folder, string Preset)[]> p) => !disabledBiomes.Contains(p.Key)))
			{
				value = presetArea.Value;
				for (int num = 0; num < value.Length; num++)
				{
					(string Folder, string Preset) weather = value[num];
					foreach (TimeOfDay value3 in Enum.GetValues(typeof(TimeOfDay)))
					{
						yield return LoadSlot(Path.Combine(_root, presetArea.Key, weather.Folder + value3), PresetKey(presetArea.Key, weather.Preset, value3), volume, fadeInTime, loop: false);
					}
				}
			}
			value = FriendlyFolders;
			for (int num = 0; num < value.Length; num++)
			{
				(string, string) tuple = value[num];
				if (raidMusic || !IsRaidTierFolder(tuple.Item1))
				{
					yield return LoadNamed(new string[1] { Path.Combine(_root, tuple.Item1) }, tuple.Item2);
				}
			}
			(string Folder, string Preset, string Biome, string Track)[] dungeonWeathers = DungeonWeathers;
			for (int num = 0; num < dungeonWeathers.Length; num++)
			{
				(string, string, string, string) tuple2 = dungeonWeathers[num];
				if (tuple2.Item4 != null && _named.TryGetValue(tuple2.Item4, out var value2))
				{
					AddSlot(DungeonKey(tuple2.Item2), value2, volume, fadeInTime, loop: false, countClips: false);
				}
				else
				{
					yield return LoadSlot(Path.Combine(_root, tuple2.Item1), DungeonKey(tuple2.Item2), volume, fadeInTime, loop: false);
				}
			}
			string[] placeFolders = PlaceFolders;
			foreach (string parent in placeFolders)
			{
				string path = Path.Combine(_root, parent);
				if (Directory.Exists(path))
				{
					string[] directories = Directory.GetDirectories(path);
					foreach (string dir in directories)
					{
						yield return LoadLocation(parent, dir);
					}
				}
			}
			string path2 = Path.Combine(_root, "Special");
			if (Directory.Exists(path2))
			{
				placeFolders = Directory.GetDirectories(path2);
				foreach (string text in placeFolders)
				{
					yield return LoadNamed(new string[1] { text }, Path.GetFileName(text));
				}
			}
			string path3 = Path.Combine(_root, "Raids");
			if (raidMusic && Directory.Exists(path3))
			{
				placeFolders = Directory.GetDirectories(path3);
				foreach (string text2 in placeFolders)
				{
					if (!IsRaidTierFolder("Raids/" + Path.GetFileName(text2)))
					{
						yield return LoadSlot(text2, RaidKey(Path.GetFileName(text2)), volume, fadeInTime, loop: true);
					}
				}
			}
			placeFolders = Transitions;
			foreach (string text3 in placeFolders)
			{
				yield return LoadSlot(Path.Combine(_root, "Transition", text3), TransitionKey(text3), volume, fadeInTime, loop: false);
			}
			IsLoaded = true;
			_log.LogInfo((object)($"Loaded {_clipCount} track(s) into {_tracks.Count} slot(s) from {_root} " + $"in {total.Elapsed.TotalSeconds:0.0} s (main thread {_mainThreadMs:0} ms, slowest track {_slowestClipMs:0} ms)"));
		}

		public NamedMusic FindBiome(string biome, TimeOfDay time, Weather weather, bool fallback)
		{
			if (_tracks.TryGetValue(BiomeKey(biome, time, weather), out var value))
			{
				return value;
			}
			if (!fallback)
			{
				return null;
			}
			Weather[] array = new Weather[3]
			{
				Weather.Clear,
				Weather.Rain,
				Weather.Thunderstorm
			};
			foreach (Weather weather2 in array)
			{
				if (weather2 != weather && _tracks.TryGetValue(BiomeKey(biome, time, weather2), out value))
				{
					return value;
				}
			}
			return null;
		}

		public NamedMusic FindPreset(string area, string preset, TimeOfDay time)
		{
			if (!_tracks.TryGetValue(PresetKey(area, preset, time), out var value))
			{
				return null;
			}
			return value;
		}

		public NamedMusic FindDungeon(string preset, string biome)
		{
			if (preset == null)
			{
				return null;
			}
			(string, string, string, string) tuple = DungeonWeathers.FirstOrDefault(((string Folder, string Preset, string Biome, string Track) d) => d.Preset == preset);
			if (tuple.Item1 == null || (tuple.Item3 != null && !string.Equals(tuple.Item3, biome, StringComparison.OrdinalIgnoreCase)))
			{
				return null;
			}
			if (!_tracks.TryGetValue(DungeonKey(preset), out var value))
			{
				return null;
			}
			return value;
		}

		public static string DungeonTrackFor(string preset)
		{
			return DungeonWeathers.FirstOrDefault(((string Folder, string Preset, string Biome, string Track) d) => d.Preset == preset).Track;
		}

		public static string DungeonFolderFor(string preset)
		{
			(string, string, string, string) tuple = DungeonWeathers.FirstOrDefault(((string Folder, string Preset, string Biome, string Track) d) => d.Preset == preset);
			if (tuple.Item1 != null)
			{
				if (tuple.Item3 != null)
				{
					return tuple.Item1 + " (in the " + tuple.Item3 + ")";
				}
				return tuple.Item1;
			}
			return null;
		}

		public static string DefaultPreset(string area)
		{
			if (!PresetAreas.TryGetValue(area, out (string, string)[] value))
			{
				return null;
			}
			return value[0].Item2;
		}

		public static string FolderLabel(string biome, string preset, Weather weather)
		{
			if (preset == "Ashlands_SeaStorm")
			{
				return "AshlandsOcean SeaStorm";
			}
			if (PresetAreas.TryGetValue(biome, out (string, string)[] value))
			{
				return value.FirstOrDefault(((string Folder, string Preset) p) => p.Preset == preset).Folder ?? (value[0].Item1 + " (everyday weather)");
			}
			bool flag = SnowyBiomes.Contains(biome);
			switch (weather)
			{
			case Weather.Rain:
				if (!flag)
				{
					return "Rainy";
				}
				return "Snow";
			case Weather.Thunderstorm:
				if (!flag)
				{
					return "Thunderstorm";
				}
				return "Blizzard";
			default:
				return "Clear";
			}
		}

		private static IEnumerable<Weather> LoadedWeathers(string area)
		{
			if (!PresetAreas.ContainsKey(area.Replace('\\', '/')))
			{
				return WeathersOf(Path.GetFileName(area));
			}
			return AllWeathers;
		}

		private void ReportUnusedFolders(IEnumerable<string> areas)
		{
			foreach (string area in areas)
			{
				foreach (Weather item in AllWeathers.Except(LoadedWeathers(area)))
				{
					foreach (TimeOfDay value in Enum.GetValues(typeof(TimeOfDay)))
					{
						string[] array = SlotFolders(value, item);
						foreach (string text in array)
						{
							int num = SongsIn(Path.Combine(_root, area, text));
							if (num > 0)
							{
								_log.LogInfo((object)($"{num} song(s) in {Path.Combine(area, text)} never play: {Path.GetFileName(area)} " + "never gets that weather. Move them to one of its other folders, or delete them. If they're the mod's own songs from before 1.1.0, they're already in the right folders."));
							}
						}
					}
				}
			}
			foreach (TimeOfDay value2 in Enum.GetValues(typeof(TimeOfDay)))
			{
				Weather[] allWeathers = AllWeathers;
				foreach (Weather weather in allWeathers)
				{
					string[] array = SlotFolders(value2, weather);
					foreach (string text2 in array)
					{
						int num2 = SongsIn(Path.Combine(_root, "HomeRest", text2));
						if (num2 > 0)
						{
							_log.LogInfo((object)(string.Format("{0} song(s) in {1} aren't used anymore: home music ", num2, Path.Combine("HomeRest", text2)) + "now has a folder per biome, plus " + "HomeRest/Fallback".Replace('/', '\\') + " for the rest. The mod's own songs from here are already in HomeRest\\Meadows and HomeRest\\Fallback, so move any of your own and delete the folder."));
						}
					}
				}
			}
			static int SongsIn(string dir)
			{
				if (!Directory.Exists(dir))
				{
					return 0;
				}
				return Directory.GetFiles(dir).Count((string f) => Extensions.Contains(Path.GetExtension(f).ToLowerInvariant()));
			}
		}

		private static IEnumerable<Weather> WeathersOf(string area)
		{
			if (!AreaWeathers.TryGetValue(area, out var value))
			{
				return new Weather[3]
				{
					Weather.Clear,
					Weather.Rain,
					Weather.Thunderstorm
				};
			}
			return value;
		}

		private static string FolderName(string area, Weather weather, TimeOfDay time)
		{
			string[] source = SlotFolders(time, weather);
			if (!SnowyBiomes.Contains(Path.GetFileName(area)))
			{
				return source.First();
			}
			return source.Last();
		}

		public NamedMusic FindRaid(string eventName)
		{
			if (!_tracks.TryGetValue(RaidKey(eventName), out var value))
			{
				return null;
			}
			return value;
		}

		public static string FriendlyFolderFor(string track)
		{
			return FriendlyFolders.FirstOrDefault(((string Folder, string Track) f) => string.Equals(f.Track, track, StringComparison.OrdinalIgnoreCase)).Folder;
		}

		private static bool IsRaidTierFolder(string folder)
		{
			if (folder.StartsWith("Raids/Tier", StringComparison.OrdinalIgnoreCase))
			{
				return FriendlyFolders.Any(((string Folder, string Track) f) => string.Equals(f.Folder, folder, StringComparison.OrdinalIgnoreCase));
			}
			return false;
		}

		public NamedMusic FindTransition(string vanillaName)
		{
			if (!_tracks.TryGetValue(TransitionKey(vanillaName), out var value))
			{
				return null;
			}
			return value;
		}

		public static bool IsTransition(string vanillaName)
		{
			return Transitions.Any((string t) => t.Equals(vanillaName, StringComparison.OrdinalIgnoreCase));
		}

		public NamedMusic GetVariant(NamedMusic source, string suffix)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			string text = source.m_name + "/" + suffix;
			if (_byName.TryGetValue(text, out var value))
			{
				return value;
			}
			AudioClip[] array = _pools[source.m_name];
			value = new NamedMusic
			{
				m_name = text,
				m_clips = array,
				m_volume = source.m_volume,
				m_fadeInTime = source.m_fadeInTime,
				m_alwaysFadeout = source.m_alwaysFadeout,
				m_loop = source.m_loop,
				m_resume = source.m_resume,
				m_enabled = true,
				m_ambientMusic = source.m_ambientMusic
			};
			_byName[text] = value;
			_pools[text] = array;
			return value;
		}

		public void PrepareToStart(NamedMusic music)
		{
			if (!_pools.TryGetValue(music.m_name, out var value) || value.Length < 2)
			{
				return;
			}
			AudioClip val = ((music.m_clips != null && music.m_clips.Length == 1) ? music.m_clips[0] : null);
			if (music.m_loop && music.m_resume && (Object)(object)val != (Object)null && music.m_lastPlayedTime > 0f && Time.time - music.m_lastPlayedTime < val.length * 2f)
			{
				return;
			}
			if (!_reserved.TryGetValue(music.m_name, out var value2))
			{
				if (!_shuffles.TryGetValue(music.m_name, out var value3) || value3.Count == 0)
				{
					value3 = Shuffle(value.Length, _lastPlayed.TryGetValue(music.m_name, out var value4) ? value4 : (-1));
					_shuffles[music.m_name] = value3;
				}
				value2 = value3.Dequeue();
				_reserved[music.m_name] = value2;
			}
			music.m_clips = (AudioClip[])(object)new AudioClip[1] { value[value2] };
		}

		public string ConfirmStarted(string name, AudioClip playing)
		{
			if (name == null || (Object)(object)playing == (Object)null || !_reserved.TryGetValue(name, out var value))
			{
				return null;
			}
			if (!_pools.TryGetValue(name, out var value2) || value >= value2.Length || (Object)(object)value2[value] != (Object)(object)playing)
			{
				return null;
			}
			_reserved.Remove(name);
			_lastPlayed[name] = value;
			return ((Object)playing).name;
		}

		private Queue<int> Shuffle(int count, int justPlayed)
		{
			int[] array = Enumerable.Range(0, count).ToArray();
			for (int num = count - 1; num > 0; num--)
			{
				int num2 = _random.Next(num + 1);
				ref int reference = ref array[num];
				ref int reference2 = ref array[num2];
				int num3 = array[num2];
				int num4 = array[num];
				reference = num3;
				reference2 = num4;
			}
			if (array[0] == justPlayed)
			{
				ref int reference2 = ref array[0];
				ref int reference3 = ref array[count - 1];
				int num4 = array[count - 1];
				int num3 = array[0];
				reference2 = num4;
				reference3 = num3;
			}
			return new Queue<int>(array);
		}

		public NamedMusic FindByName(string name)
		{
			if (name == null || !_byName.TryGetValue(name, out var value))
			{
				return null;
			}
			return value;
		}

		private IEnumerator LoadSlot(string dir, string key, float volume, float fadeInTime, bool loop)
		{
			return LoadSlot(new string[1] { dir }, key, volume, fadeInTime, loop);
		}

		private IEnumerator LoadSlot(IEnumerable<string> dirs, string key, float volume, float fadeInTime, bool loop)
		{
			List<AudioClip> clips = new List<AudioClip>();
			yield return LoadClips(dirs, clips);
			if (clips.Count != 0)
			{
				AddSlot(key, clips.ToArray(), volume, fadeInTime, loop, countClips: true);
			}
		}

		private void AddSlot(string key, AudioClip[] clips, float volume, float fadeInTime, bool loop, bool countClips)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: 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_002c: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			NamedMusic val = new NamedMusic
			{
				m_name = "BiomeMusic_" + key,
				m_clips = clips,
				m_volume = volume,
				m_fadeInTime = fadeInTime,
				m_alwaysFadeout = true,
				m_loop = loop,
				m_resume = false,
				m_enabled = true,
				m_ambientMusic = !loop
			};
			_tracks[key] = val;
			_byName[val.m_name] = val;
			_pools[val.m_name] = val.m_clips;
			if (countClips)
			{
				_clipCount += clips.Length;
			}
			_log.LogInfo((object)($"{key}: {clips.Length} track(s)" + (countClips ? "" : " (shared)")));
		}

		private IEnumerator LoadNamed(IEnumerable<string> dirs, string vanillaName)
		{
			List<AudioClip> clips = new List<AudioClip>();
			yield return LoadClips(dirs, clips);
			if (clips.Count != 0)
			{
				_named[vanillaName] = clips.ToArray();
				_pools[vanillaName] = _named[vanillaName];
				_clipCount += clips.Count;
				_log.LogInfo((object)$"replaces vanilla '{vanillaName}': {clips.Count} track(s)");
			}
		}

		private IEnumerator LoadLocation(string parent, string dir)
		{
			string folder = Path.GetFileName(dir);
			List<AudioClip> clips = new List<AudioClip>();
			yield return LoadClips(new string[1] { dir }, clips, stream: false);
			if (clips.Count != 0)
			{
				string path = parent + "/" + folder;
				(string Folder, string[] Names) tuple = LocationFolders.FirstOrDefault(((string Folder, string[] Names) l) => string.Equals(l.Folder, path, StringComparison.OrdinalIgnoreCase));
				string text = tuple.Folder ?? path;
				_pools[text] = clips.ToArray();
				string[] array = tuple.Names ?? new string[1] { folder };
				foreach (string key in array)
				{
					_locationPools[key] = text;
				}
				_clipCount += clips.Count;
				_log.LogInfo((object)$"{text}: {clips.Count} track(s)");
			}
		}

		public AudioClip PickLocationSong(string place, string vanillaClip, IEnumerable<AudioClip> inUse, out string folder)
		{
			folder = null;
			if ((place == null || !_locationPools.TryGetValue(place, out var value)) && (vanillaClip == null || !_locationPools.TryGetValue(vanillaClip, out value)))
			{
				return null;
			}
			folder = value;
			AudioClip[] array = _pools[value];
			List<AudioClip> playing = inUse.ToList();
			int[] uses = array.Select((AudioClip song) => playing.Count((AudioClip p) => (Object)(object)p == (Object)(object)song)).ToArray();
			int fewest = uses.Min();
			Queue<int> value2;
			List<int> list = (_shuffles.TryGetValue(value, out value2) ? value2.ToList() : new List<int>());
			int num = list.FindIndex((int i) => uses[i] == fewest);
			if (num < 0)
			{
				list = Shuffle(array.Length, _lastPlayed.TryGetValue(value, out var value3) ? value3 : (-1)).ToList();
				num = list.FindIndex((int i) => uses[i] == fewest);
			}
			int num2 = list[num];
			list.RemoveAt(num);
			_shuffles[value] = new Queue<int>(list);
			_lastPlayed[value] = num2;
			return array[num2];
		}

		public static string VolumeGroupFor(string entryName)
		{
			if (entryName == null)
			{
				return null;
			}
			if (entryName.StartsWith("BiomeMusic_"))
			{
				string area = entryName.Substring("BiomeMusic_".Length).Split('/')[0];
				if (area.Equals("dungeons", StringComparison.OrdinalIgnoreCase))
				{
					return "Dungeons";
				}
				if (area.Equals("Transition", StringComparison.OrdinalIgnoreCase))
				{
					return "Transitions";
				}
				return VolumeGroups.FirstOrDefault((string g) => g.Equals(area, StringComparison.OrdinalIgnoreCase));
			}
			string text = FriendlyFolderFor(entryName);
			return text?.Substring(0, text.IndexOf('/'));
		}

		public static string LocationFolderFor(string place, string vanillaClip)
		{
			return new string[2] { place, vanillaClip }.Select((string name) => LocationFolders.FirstOrDefault(((string Folder, string[] Names) l) => l.Names.Contains<string>(name, StringComparer.OrdinalIgnoreCase)).Folder).FirstOrDefault((string folder) => folder != null);
		}

		private IEnumerator LoadClips(IEnumerable<string> dirs, List<AudioClip> into, bool stream = true)
		{
			List<string> source = dirs.Where(Directory.Exists).SelectMany(Directory.GetFiles).ToList();
			foreach (string item in source.Where((string f) => UnplayableExtensions.Contains(Path.GetExtension(f).ToLowerInvariant())))
			{
				_log.LogWarning((object)("Skipped " + item + ": only .ogg and .wav songs play. Convert it to .ogg first."));
			}
			List<string> list = source.Where((string f) => Extensions.Contains(Path.GetExtension(f).ToLowerInvariant())).OrderBy<string, string>(Path.GetFileName, StringComparer.OrdinalIgnoreCase).ThenBy<string, string>((string f) => f, StringComparer.OrdinalIgnoreCase)
				.ToList();
			foreach (string item2 in list)
			{
				yield return LoadClip(item2, into, stream);
			}
		}

		public void AttachTo(NamedMusic entry, AudioClip[] clips)
		{
			_pools[entry.m_name] = clips;
			entry.m_clips = clips;
		}

		public bool HasPool(string name)
		{
			if (name != null)
			{
				return _pools.ContainsKey(name);
			}
			return false;
		}

		public int PoolSize(string name)
		{
			if (name == null || !_pools.TryGetValue(name, out var value))
			{
				return 0;
			}
			return value.Length;
		}

		private IEnumerator LoadClip(string path, List<AudioClip> into, bool stream)
		{
			AudioType val = (AudioType)((Path.GetExtension(path).ToLowerInvariant() == ".wav") ? 20 : 14);
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, val);
			try
			{
				DownloadHandlerAudioClip val2 = (DownloadHandlerAudioClip)request.downloadHandler;
				if (stream)
				{
					val2.streamAudio = true;
				}
				else
				{
					val2.compressed = (int)val == 14;
				}
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					_log.LogWarning((object)("Could not load " + path + ": " + request.error));
					yield break;
				}
				Stopwatch stopwatch = Stopwatch.StartNew();
				AudioClip content = DownloadHandlerAudioClip.GetContent(request);
				stopwatch.Stop();
				_mainThreadMs += stopwatch.Elapsed.TotalMilliseconds;
				_slowestClipMs = Math.Max(_slowestClipMs, stopwatch.Elapsed.TotalMilliseconds);
				((Object)content).name = Path.GetFileNameWithoutExtension(path);
				into.Add(content);
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		private static string[] SlotFolders(TimeOfDay time, Weather weather)
		{
			return weather switch
			{
				Weather.Rain => new string[2]
				{
					"Rainy" + time,
					"Snow" + time
				}, 
				Weather.Thunderstorm => new string[2]
				{
					"Thunderstorm" + time,
					"Blizzard" + time
				}, 
				_ => new string[1] { "Clear" + time }, 
			};
		}

		private static string BiomeKey(string biome, TimeOfDay time, Weather weather)
		{
			return $"{biome}/{time}/{weather}".ToLowerInvariant();
		}

		private static string PresetKey(string area, string preset, TimeOfDay time)
		{
			return $"{area}/preset/{preset}/{time}".ToLowerInvariant();
		}

		private static string RaidKey(string folder)
		{
			return ("Raids/" + folder).ToLowerInvariant();
		}

		private static string TransitionKey(string name)
		{
			return ("Transition/" + name).ToLowerInvariant();
		}

		private static string DungeonKey(string preset)
		{
			return ("dungeons/preset/" + preset).ToLowerInvariant();
		}
	}
	internal class MusicMover
	{
		private class ProfileEntry
		{
			public string Setting;

			public string Folder;
		}

		public const string RecordFileName = "music-folder.txt";

		private static readonly string[] Extensions = new string[2] { ".ogg", ".wav" };

		private readonly string _defaultFolder;

		private readonly string _recordPath;

		private readonly string _oldRecordPath;

		private readonly string _profile;

		private readonly ManualLogSource _log;

		private string _lastFolder;

		private readonly Dictionary<string, ProfileEntry> _profiles = new Dictionary<string, ProfileEntry>(StringComparer.OrdinalIgnoreCase);

		private string _currentRoot;

		private string _moveFrom;

		private string _settingToRemember;

		private bool _merge;

		public MusicMover(string defaultFolder, string recordPath, string oldRecordPath, string profile, ManualLogSource log)
		{
			_defaultFolder = defaultFolder;
			_recordPath = recordPath;
			_oldRecordPath = oldRecordPath;
			_profile = Path.GetFullPath(profile).TrimEnd(Path.DirectorySeparatorChar);
			_log = log;
		}

		public string ChooseFolder(string setting, string leftFolder = null)
		{
			ReadRecord();
			_profiles.TryGetValue(_profile, out var value);
			if (value == null)
			{
				string text = ReadRecordedFolder(_oldRecordPath, _log);
				if (text != null)
				{
					value = new ProfileEntry
					{
						Setting = text,
						Folder = text
					};
				}
			}
			if (leftFolder != null)
			{
				value = new ProfileEntry
				{
					Setting = leftFolder,
					Folder = leftFolder
				};
				_merge = true;
			}
			string problem;
			string text2 = Resolve(setting, out problem);
			_settingToRemember = setting?.Trim() ?? "";
			if (value != null && !SameSetting(setting, value.Setting))
			{
				if (text2 != null && TryCreate(text2, out problem))
				{
					_moveFrom = leftFolder ?? new string[2] { value.Folder, _lastFolder }.FirstOrDefault((string f) => f != null && !IsOtherUsersFolder(f) && HasMusic(f));
					return _currentRoot = text2;
				}
				_settingToRemember = value.Setting;
			}
			string[] array = new string[3]
			{
				text2,
				value?.Folder,
				_lastFolder
			};
			foreach (string text3 in array)
			{
				if (text3 != null && !IsOtherUsersFolder(text3) && HasMusic(text3))
				{
					if (!SamePath(text3, text2))
					{
						Explain(setting, text2, problem, text3);
					}
					return _currentRoot = text3;
				}
			}
			if (text2 != null && TryCreate(text2, out problem))
			{
				return _currentRoot = text2;
			}
			Explain(setting, text2, problem, _defaultFolder);
			return _currentRoot = _defaultFolder;
		}

		private void Explain(string setting, string wanted, string problem, string used)
		{
			if (problem != null)
			{
				_log.LogWarning((object)("Can't use the music folder '" + setting + "' (" + problem + "); using " + used));
			}
			else
			{
				string text = (string.IsNullOrWhiteSpace(setting) ? ("the default folder, " + wanted + ",") : wanted);
				_log.LogInfo((object)("MusicFolder in this profile names " + text + " but the music on this PC is in " + used + ", so it plays from there. The music only moves when MusicFolder changes in a profile that has run here before."));
			}
		}

		public IEnumerator MoveFromPreviousFolder()
		{
			if (_moveFrom != null && !SamePath(_moveFrom, _currentRoot) && !IsOtherUsersFolder(_moveFrom) && HasMusic(_moveFrom) && (_merge || !HasMusic(_currentRoot)))
			{
				yield return Move(_moveFrom);
			}
			RememberCurrentFolder();
		}

		private IEnumerator Move(string previous)
		{
			Stopwatch watch = Stopwatch.StartNew();
			int moved = 0;
			int duplicates = 0;
			int kept = 0;
			int failed = 0;
			string[] files = Directory.GetFiles(previous, "*", SearchOption.AllDirectories);
			foreach (string text in files)
			{
				if (SamePath(text, _recordPath))
				{
					continue;
				}
				string text2 = text.Substring(previous.Length).TrimStart(Path.DirectorySeparatorChar);
				string text3 = Path.Combine(_currentRoot, text2);
				try
				{
					if (!File.Exists(text3))
					{
						Directory.CreateDirectory(Path.GetDirectoryName(text3));
						File.Copy(text, text3, overwrite: false);
						moved++;
						File.Delete(text);
					}
					else if (!_merge || new FileInfo(text3).Length == new FileInfo(text).Length)
					{
						duplicates++;
						File.Delete(text);
					}
					else
					{
						_log.LogWarning((object)("Not moving " + text2 + ": a different file of that name is already in " + _currentRoot));
						kept++;
					}
				}
				catch (Exception ex)
				{
					_log.LogWarning((object)("Could not move " + text2 + ": " + ex.Message));
					failed++;
				}
				yield return null;
			}
			if (failed == 0 && kept == 0)
			{
				try
				{
					Directory.Delete(previous, recursive: true);
				}
				catch (Exception ex2)
				{
					_log.LogWarning((object)("Could not remove the old music folder " + previous + ": " + ex2.Message));
				}
			}
			int num = failed + kept;
			_log.LogInfo((object)($"Moved {moved} file(s) from {previous} to {_currentRoot} in {watch.Elapsed.TotalSeconds:0.0} s" + ((duplicates > 0) ? $"; {duplicates} were already there" : "") + ((num > 0) ? $"; {num} left in the old folder" : "")));
		}

		public static string ReadRecordedFolder(string recordPath, ManualLogSource log)
		{
			try
			{
				if (!File.Exists(recordPath))
				{
					return null;
				}
				string text = File.ReadAllLines(recordPath).FirstOrDefault((string