Decompiled source of ZoneAudio v1.1.0

ZoneAudio.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ZoneAudio")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ZoneAudio")]
[assembly: AssemblyTitle("ZoneAudio")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ZoneAudio;

[BepInPlugin("yourname.valheim.zoneaudio", "ZoneAudio", "1.1.0")]
public class ZoneAudioPlugin : BaseUnityPlugin
{
	public const string PluginGUID = "yourname.valheim.zoneaudio";

	public const string PluginName = "ZoneAudio";

	public const string PluginVersion = "1.1.0";

	internal static ManualLogSource Log;

	private readonly List<AudioZone> _zones = new List<AudioZone>();

	private AudioZone _currentZone;

	private AudioZone _loadedZone;

	private bool _loading;

	private string _loadedAudioPath;

	private string _loadedTrackSetKey;

	private int _playlistIndex = -1;

	private int _playedTrackCount;

	private readonly List<int> _remainingShuffleIndexes = new List<int>();

	private readonly Random _random = new Random();

	private AudioSource _audioSource;

	private float _checkTimer;

	private float _vanillaMusicTimer;

	private bool _reloadRequested;

	private bool _vanillaMusicSuppressed;

	private FileSystemWatcher _zoneFileWatcher;

	private ConfigEntry<float> _volumeSmoothingSeconds;

	private ConfigEntry<float> _masterVolume;

	private ConfigEntry<float> _checkInterval;

	private ConfigEntry<float> _defaultEdgeFadeFraction;

	private ConfigEntry<bool> _overrideVanillaMusic;

	private string ConfigDir => Path.Combine(Paths.ConfigPath, "ZoneAudio");

	private string AudioDir => Path.Combine(ConfigDir, "audio");

	private string ZonesFile => Path.Combine(ConfigDir, "zones.yaml");

	private string LegacyZonesFile => Path.Combine(ConfigDir, "zones.json");

	private void Awake()
	{
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ed: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		_volumeSmoothingSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "VolumeSmoothingSeconds", 0.4f, "How quickly volume glides toward its target as you move (roughly the time to close half the gap). Higher = smoother/laggier, lower = snappier.");
		_masterVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MasterVolume", 1f, "Master volume multiplier (0-1), applied on top of each zone's own volume.");
		_checkInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CheckIntervalSeconds", 0.5f, "How often (in seconds) to re-evaluate which zone the player is in. Volume itself updates every frame regardless.");
		_defaultEdgeFadeFraction = ((BaseUnityPlugin)this).Config.Bind<float>("General", "DefaultEdgeFadeFraction", 0.3f, "For zones that don't set their own 'edgeFadeMeters' in zones.yaml, the fade band is this fraction of the zone's radius (0.3 = outer 30% of the radius fades in/out).");
		_overrideVanillaMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "OverrideVanillaMusic", true, "When true, Valheim's normal music is stopped/muted while ZoneAudio is active so tracks do not overlap.");
		Directory.CreateDirectory(AudioDir);
		EnsureExampleConfig();
		LoadZones();
		StartZoneFileWatcher();
		GameObject val = new GameObject("ZoneAudioSource");
		Object.DontDestroyOnLoad((Object)(object)val);
		_audioSource = val.AddComponent<AudioSource>();
		_audioSource.loop = true;
		_audioSource.playOnAwake = false;
		_audioSource.spatialBlend = 0f;
		_audioSource.volume = 0f;
		Log.LogInfo((object)$"ZoneAudio loaded with {_zones.Count} zone(s) from {ZonesFile}");
	}

	private void OnDestroy()
	{
		if (_zoneFileWatcher != null)
		{
			_zoneFileWatcher.EnableRaisingEvents = false;
			_zoneFileWatcher.Dispose();
			_zoneFileWatcher = null;
		}
		VanillaMusicBridge.SetSuppressed(suppressed: false);
	}

	private void EnsureExampleConfig()
	{
		if (!File.Exists(ZonesFile))
		{
			if (TryMigrateLegacyJson())
			{
				Log.LogInfo((object)("Migrated zones.json to " + ZonesFile + ". Future edits should use zones.yaml."));
				return;
			}
			File.WriteAllText(ZonesFile, "zones:\n# Required fields:\n#   position: [x, y, z]\n#   radius: meters from the position\n#   audio: one file name, or a list of file names under audio:\n#\n# Optional fields:\n#   name: label used in logs\n#   volume: 0.0 to 1.0, default 1.0\n#   priority: higher wins when zones overlap, default 0\n#   biome: only play in this biome, example Meadows\n#   weather: only play during these weather names\n#   onlyDay: true/false, default false\n#   onlyNight: true/false, default false\n#   loop: true/false, default true\n#   shuffle: true/false, default false\n#   edgeFade: fade distance in meters near the zone edge\n#   dayAudio: optional daytime playlist\n#   nightAudio: optional nighttime playlist\n#\n# Audio files belong in BepInEx/config/ZoneAudio/audio.\n\n  - name: Example file\n    position: [1250, 34, -820]\n    radius: 20\n    audio: Example file.ogg\n    volume: 0.8\n    priority: 100\n    biome: Meadows\n    weather:\n      - Clear\n      - LightRain\n    onlyDay: false\n    onlyNight: false\n    loop: true\n    shuffle: false\n    edgeFade: 10\n");
			Log.LogInfo((object)("Wrote example zones.yaml to " + ZonesFile + ". Edit it and place matching audio files in " + AudioDir));
		}
	}

	private bool TryMigrateLegacyJson()
	{
		if (!File.Exists(LegacyZonesFile))
		{
			return false;
		}
		try
		{
			string text = File.ReadAllText(LegacyZonesFile);
			ZoneList zoneList = JsonUtility.FromJson<ZoneList>(text);
			if (zoneList?.zones == null || zoneList.zones.Count == 0)
			{
				return false;
			}
			File.WriteAllText(ZonesFile, ZoneYaml.ToYaml(zoneList.zones));
			return true;
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Could not migrate zones.json to zones.yaml: " + ex.Message));
			return false;
		}
	}

	private void StartZoneFileWatcher()
	{
		_zoneFileWatcher = new FileSystemWatcher(ConfigDir, Path.GetFileName(ZonesFile));
		_zoneFileWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime;
		_zoneFileWatcher.Changed += OnZonesFileChanged;
		_zoneFileWatcher.Created += OnZonesFileChanged;
		_zoneFileWatcher.Renamed += OnZonesFileChanged;
		_zoneFileWatcher.EnableRaisingEvents = true;
	}

	private void OnZonesFileChanged(object sender, FileSystemEventArgs e)
	{
		_reloadRequested = true;
	}

	private void LoadZones()
	{
		if (!File.Exists(ZonesFile))
		{
			Log.LogWarning((object)"zones.yaml not found, keeping the current zone list.");
			return;
		}
		try
		{
			string yaml = File.ReadAllText(ZonesFile);
			List<AudioZone> list = ZoneYaml.Parse(yaml);
			if (list.Count == 0)
			{
				Log.LogWarning((object)"zones.yaml parsed but contained no zones. Keeping the current zone list.");
				return;
			}
			List<AudioZone> list2 = new List<AudioZone>();
			foreach (AudioZone item in list)
			{
				EnsureZoneName(item);
				if (!item.hasPosition)
				{
					Log.LogWarning((object)("Zone '" + item.name + "': position is required, skipping this zone."));
					continue;
				}
				if (item.radius <= 0f)
				{
					Log.LogWarning((object)("Zone '" + item.name + "': radius must be > 0, skipping this zone."));
					continue;
				}
				NormalizeZoneAudioFiles(item);
				if (!HasAnyAudioFiles(item))
				{
					Log.LogWarning((object)("Zone '" + item.name + "': no usable audio files found, skipping this zone."));
					continue;
				}
				if (item.edgeFadeMeters <= 0f)
				{
					item.edgeFadeMeters = item.radius * _defaultEdgeFadeFraction.Value;
				}
				item.edgeFadeMeters = Mathf.Min(item.edgeFadeMeters, item.radius);
				list2.Add(item);
			}
			ReplaceZones(list2);
		}
		catch (Exception arg)
		{
			Log.LogError((object)$"Failed to load zones.yaml. Keeping the previous zone list. Error: {arg}");
		}
	}

	private void ReplaceZones(List<AudioZone> loadedZones)
	{
		AudioZone currentZone = _currentZone;
		AudioZone loadedZone = _loadedZone;
		_zones.Clear();
		_zones.AddRange(loadedZones);
		_currentZone = FindMatchingZone(currentZone);
		_loadedZone = FindMatchingZone(loadedZone);
		if (currentZone != null && _currentZone == null)
		{
			_checkTimer = _checkInterval.Value;
		}
		if (_currentZone != null && _loadedZone != _currentZone && !_loading)
		{
			StartNextTrack(_currentZone, resetPlaylist: true);
		}
	}

	private AudioZone FindMatchingZone(AudioZone zone)
	{
		if (zone == null)
		{
			return null;
		}
		foreach (AudioZone zone2 in _zones)
		{
			if (string.Equals(zone2.name, zone.name, StringComparison.OrdinalIgnoreCase) && string.Equals(zone2.audioFile, zone.audioFile, StringComparison.OrdinalIgnoreCase))
			{
				return zone2;
			}
		}
		return null;
	}

	private void Update()
	{
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		if (_reloadRequested)
		{
			_reloadRequested = false;
			LoadZones();
			Log.LogInfo((object)$"Reloaded ZoneAudio zones from {ZonesFile}; {_zones.Count} zone(s) active.");
		}
		Player localPlayer = Player.m_localPlayer;
		if (!((Object)(object)localPlayer == (Object)null))
		{
			Vector3 position = ((Component)localPlayer).transform.position;
			_checkTimer += Time.unscaledDeltaTime;
			if (_checkTimer >= _checkInterval.Value)
			{
				_checkTimer = 0f;
				UpdateZoneSelection(position);
			}
			UpdatePlaylistPlayback();
			UpdateVolume(position);
			UpdateVanillaMusicOverride();
		}
	}

	private void UpdateZoneSelection(Vector3 pos)
	{
		//IL_000f: 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_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		if (_currentZone != null)
		{
			float num = Vector3.Distance(pos, Center(_currentZone));
			if (num <= _currentZone.radius && ZoneCanPlayNow(_currentZone))
			{
				return;
			}
		}
		AudioZone audioZone = null;
		int num2 = int.MinValue;
		float num3 = float.MaxValue;
		foreach (AudioZone zone in _zones)
		{
			if (ZoneCanPlayNow(zone))
			{
				float num4 = Vector3.Distance(pos, Center(zone));
				if (num4 <= zone.radius && (zone.priority > num2 || (zone.priority == num2 && num4 < num3)))
				{
					audioZone = zone;
					num2 = zone.priority;
					num3 = num4;
				}
			}
		}
		if (audioZone != _currentZone)
		{
			_currentZone = audioZone;
			if (audioZone != null && audioZone != _loadedZone && !_loading)
			{
				StartNextTrack(audioZone, resetPlaylist: true);
			}
		}
	}

	private void UpdateVolume(Vector3 pos)
	{
		//IL_0025: 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)
		float num = 0f;
		if (_currentZone != null && _loadedZone == _currentZone)
		{
			float num2 = Vector3.Distance(pos, Center(_currentZone));
			float num3 = _currentZone.radius - num2;
			float num4 = Mathf.Clamp01(num3 / Mathf.Max(0.01f, _currentZone.edgeFadeMeters));
			num = num4 * Mathf.Clamp01(_currentZone.volume * _masterVolume.Value);
		}
		float num5 = Mathf.Max(0.01f, _volumeSmoothingSeconds.Value);
		float num6 = 1f - Mathf.Pow(0.5f, Time.unscaledDeltaTime / num5);
		_audioSource.volume = Mathf.Lerp(_audioSource.volume, num, num6);
		if (_currentZone == null && _audioSource.isPlaying && _audioSource.volume < 0.002f)
		{
			_audioSource.Stop();
			_loadedZone = null;
			_loadedAudioPath = null;
			_loadedTrackSetKey = null;
		}
	}

	private static Vector3 Center(AudioZone z)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		return new Vector3(z.x, z.y, z.z);
	}

	private void UpdatePlaylistPlayback()
	{
		if (_currentZone != null && !_loading)
		{
			string trackSetKey = GetTrackSetKey(_currentZone);
			if (_loadedZone != _currentZone || _loadedTrackSetKey != trackSetKey)
			{
				StartNextTrack(_currentZone, resetPlaylist: true);
			}
			else if ((Object)(object)_audioSource.clip != (Object)null && !_audioSource.loop && !_audioSource.isPlaying)
			{
				StartNextTrack(_currentZone, resetPlaylist: false);
			}
		}
	}

	private void StartNextTrack(AudioZone zone, bool resetPlaylist)
	{
		string text = SelectNextTrack(zone, resetPlaylist);
		if (string.IsNullOrEmpty(text))
		{
			Log.LogWarning((object)("Zone '" + zone?.name + "': no track available for the current time of day."));
			if (!zone.loop)
			{
				_currentZone = null;
			}
		}
		else
		{
			((MonoBehaviour)this).StartCoroutine(LoadZoneAudio(zone, text, GetTrackSetKey(zone)));
		}
	}

	private string SelectNextTrack(AudioZone zone, bool resetPlaylist)
	{
		List<string> activeAudioFiles = GetActiveAudioFiles(zone);
		if (activeAudioFiles.Count == 0)
		{
			return null;
		}
		if (resetPlaylist)
		{
			_playlistIndex = -1;
			_playedTrackCount = 0;
			_remainingShuffleIndexes.Clear();
		}
		if (zone.shuffle)
		{
			if (_remainingShuffleIndexes.Count == 0)
			{
				if (!zone.loop && _playedTrackCount >= activeAudioFiles.Count)
				{
					return null;
				}
				for (int i = 0; i < activeAudioFiles.Count; i++)
				{
					_remainingShuffleIndexes.Add(i);
				}
			}
			int index = _random.Next(_remainingShuffleIndexes.Count);
			_playlistIndex = _remainingShuffleIndexes[index];
			_remainingShuffleIndexes.RemoveAt(index);
		}
		else
		{
			if (!zone.loop && _playedTrackCount >= activeAudioFiles.Count)
			{
				return null;
			}
			_playlistIndex = (_playlistIndex + 1) % activeAudioFiles.Count;
		}
		_playedTrackCount++;
		return activeAudioFiles[_playlistIndex];
	}

	private IEnumerator LoadZoneAudio(AudioZone zone, string audioPath, string trackSetKey)
	{
		_loading = true;
		if (zone == null)
		{
			_loading = false;
			yield break;
		}
		AudioClip clip = null;
		yield return LoadClip(audioPath, delegate(AudioClip c)
		{
			clip = c;
		});
		if ((Object)(object)clip == (Object)null)
		{
			Log.LogWarning((object)("Could not load audio clip for zone '" + zone.name + "' at '" + audioPath + "'."));
			_loading = false;
		}
		else if (_currentZone != zone)
		{
			_loading = false;
		}
		else
		{
			_audioSource.clip = clip;
			_audioSource.loop = zone.loop && GetActiveAudioFiles(zone).Count <= 1;
			_audioSource.volume = 0f;
			_audioSource.Play();
			_loadedZone = zone;
			_loadedAudioPath = audioPath;
			_loadedTrackSetKey = trackSetKey;
			_loading = false;
		}
	}

	private bool ZoneCanPlayNow(AudioZone zone)
	{
		string text = (zone.time ?? "always").Trim().ToLowerInvariant();
		if (zone.onlyDay && !DayNightBridge.IsDay())
		{
			return false;
		}
		if (zone.onlyNight && DayNightBridge.IsDay())
		{
			return false;
		}
		if (text == "day" && !DayNightBridge.IsDay())
		{
			return false;
		}
		if (text == "night" && DayNightBridge.IsDay())
		{
			return false;
		}
		if (!string.IsNullOrEmpty(zone.biome) && !WorldConditionBridge.IsBiome(zone.biome))
		{
			return false;
		}
		if (zone.weather.Count > 0 && !WorldConditionBridge.IsWeather(zone.weather))
		{
			return false;
		}
		return GetActiveAudioFiles(zone).Count > 0;
	}

	private List<string> GetActiveAudioFiles(AudioZone zone)
	{
		bool flag = DayNightBridge.IsDay();
		if (flag && zone.dayAudioFiles.Count > 0)
		{
			return zone.dayAudioFiles;
		}
		if (!flag && zone.nightAudioFiles.Count > 0)
		{
			return zone.nightAudioFiles;
		}
		return zone.audioFiles;
	}

	private string GetTrackSetKey(AudioZone zone)
	{
		return DayNightBridge.IsDay() ? "day" : "night";
	}

	private void NormalizeZoneAudioFiles(AudioZone zone)
	{
		zone.audioFiles = NormalizeAudioList(zone.name, zone.audioFiles);
		zone.dayAudioFiles = NormalizeAudioList(zone.name, zone.dayAudioFiles);
		zone.nightAudioFiles = NormalizeAudioList(zone.name, zone.nightAudioFiles);
		if (!string.IsNullOrEmpty(zone.audioFile))
		{
			string text = Path.Combine(AudioDir, zone.audioFile);
			if (File.Exists(text) && !zone.audioFiles.Contains(text))
			{
				zone.audioFiles.Insert(0, text);
			}
			else if (!File.Exists(text))
			{
				Log.LogWarning((object)("Zone '" + zone.name + "': audio file not found at '" + text + "', skipping that track."));
			}
		}
	}

	private List<string> NormalizeAudioList(string zoneName, List<string> audioFiles)
	{
		List<string> list = new List<string>();
		if (audioFiles == null)
		{
			return list;
		}
		foreach (string audioFile in audioFiles)
		{
			if (!string.IsNullOrEmpty(audioFile))
			{
				string text = Path.Combine(AudioDir, audioFile);
				if (!File.Exists(text))
				{
					Log.LogWarning((object)("Zone '" + zoneName + "': audio file not found at '" + text + "', skipping that track."));
				}
				else
				{
					list.Add(text);
				}
			}
		}
		return list;
	}

	private static bool HasAnyAudioFiles(AudioZone zone)
	{
		return zone.audioFiles.Count > 0 || zone.dayAudioFiles.Count > 0 || zone.nightAudioFiles.Count > 0;
	}

	private static void EnsureZoneName(AudioZone zone)
	{
		if (string.IsNullOrEmpty(zone.name))
		{
			zone.name = "Zone at " + zone.x.ToString(CultureInfo.InvariantCulture) + ", " + zone.y.ToString(CultureInfo.InvariantCulture) + ", " + zone.z.ToString(CultureInfo.InvariantCulture);
		}
	}

	private void UpdateVanillaMusicOverride()
	{
		if (!_overrideVanillaMusic.Value)
		{
			if (_vanillaMusicSuppressed)
			{
				VanillaMusicBridge.SetSuppressed(suppressed: false);
				_vanillaMusicSuppressed = false;
			}
			return;
		}
		bool flag = _currentZone != null || _audioSource.volume > 0.01f;
		if (flag != _vanillaMusicSuppressed)
		{
			VanillaMusicBridge.SetSuppressed(flag);
			_vanillaMusicSuppressed = flag;
			_vanillaMusicTimer = 0f;
		}
		if (flag)
		{
			_vanillaMusicTimer += Time.unscaledDeltaTime;
			if (_vanillaMusicTimer >= 1f)
			{
				_vanillaMusicTimer = 0f;
				VanillaMusicBridge.SetSuppressed(suppressed: true);
			}
		}
	}

	private IEnumerator LoadClip(string path, Action<AudioClip> onLoaded)
	{
		AudioType type = GuessAudioType(path);
		string uri = "file://" + path.Replace("\\", "/");
		UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(uri, type);
		try
		{
			yield return req.SendWebRequest();
			if (req.isNetworkError || req.isHttpError)
			{
				Log.LogError((object)("Failed loading audio '" + path + "': " + req.error));
				onLoaded(null);
				yield break;
			}
			onLoaded(DownloadHandlerAudioClip.GetContent(req));
		}
		finally
		{
			((IDisposable)req)?.Dispose();
		}
	}

	private static AudioType GuessAudioType(string path)
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: 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)
		return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch
		{
			".wav" => 20, 
			".ogg" => 14, 
			".mp3" => 13, 
			_ => 20, 
		});
	}
}
[Serializable]
public class AudioZone
{
	public string name;

	public float x;

	public float y;

	public float z;

	public float radius;

	public float edgeFadeMeters;

	public string audioFile;

	public float volume = 1f;

	public int priority;

	public string biome;

	public string time = "always";

	public bool onlyDay;

	public bool onlyNight;

	public bool loop = true;

	public bool shuffle;

	public List<string> weather = new List<string>();

	public List<string> audioFiles = new List<string>();

	public List<string> dayAudioFiles = new List<string>();

	public List<string> nightAudioFiles = new List<string>();

	[NonSerialized]
	public string audioPath;

	[NonSerialized]
	public bool hasPosition;
}
[Serializable]
public class ZoneList
{
	public List<AudioZone> zones;
}
public static class DayNightBridge
{
	public static bool IsDay()
	{
		object singleton = ReflectionBridge.GetSingleton("EnvMan");
		if (singleton == null)
		{
			return true;
		}
		if (ReflectionBridge.CallNoArg(singleton, "IsDay") is bool result)
		{
			return result;
		}
		if (ReflectionBridge.CallNoArg(singleton, "IsNight") is bool flag)
		{
			return !flag;
		}
		object obj = ReflectionBridge.CallNoArg(singleton, "GetDayFraction");
		if (obj is float num)
		{
			return num >= 0.25f && num < 0.75f;
		}
		if (obj is double num2)
		{
			return num2 >= 0.25 && num2 < 0.75;
		}
		return true;
	}
}
public static class WorldConditionBridge
{
	public static bool IsBiome(string expectedBiome)
	{
		if ((Object)(object)Player.m_localPlayer == (Object)null || string.IsNullOrEmpty(expectedBiome))
		{
			return true;
		}
		object obj = ReflectionBridge.CallNoArg(Player.m_localPlayer, "GetCurrentBiome");
		if (obj == null)
		{
			return true;
		}
		return string.Equals(obj.ToString(), expectedBiome, StringComparison.OrdinalIgnoreCase);
	}

	public static bool IsWeather(List<string> expectedWeather)
	{
		if (expectedWeather == null || expectedWeather.Count == 0)
		{
			return true;
		}
		string currentWeather = GetCurrentWeather();
		if (string.IsNullOrEmpty(currentWeather))
		{
			return true;
		}
		foreach (string item in expectedWeather)
		{
			if (string.Equals(item, currentWeather, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}

	private static string GetCurrentWeather()
	{
		object singleton = ReflectionBridge.GetSingleton("EnvMan");
		if (singleton == null)
		{
			return null;
		}
		return (ReflectionBridge.CallNoArg(singleton, "GetCurrentEnvironment") ?? ReflectionBridge.CallNoArg(singleton, "GetCurrentEnv") ?? ReflectionBridge.GetFieldOrProperty(singleton, "m_currentEnv") ?? ReflectionBridge.GetFieldOrProperty(singleton, "m_currentEnvironment"))?.ToString();
	}
}
public static class VanillaMusicBridge
{
	private static readonly Dictionary<AudioSource, float> OriginalVolumes = new Dictionary<AudioSource, float>();

	public static void SetSuppressed(bool suppressed)
	{
		object singleton = ReflectionBridge.GetSingleton("MusicMan");
		if (singleton != null)
		{
			if (suppressed)
			{
				ReflectionBridge.CallNoArg(singleton, "StopMusic");
				ReflectionBridge.CallNoArg(singleton, "Stop");
				SetAudioSourcesMuted(singleton, muted: true);
			}
			else
			{
				SetAudioSourcesMuted(singleton, muted: false);
				ReflectionBridge.CallNoArg(singleton, "ResetMusic");
				ReflectionBridge.CallNoArg(singleton, "Reset");
			}
		}
	}

	private static void SetAudioSourcesMuted(object owner, bool muted)
	{
		Component val = (Component)((owner is Component) ? owner : null);
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		AudioSource[] componentsInChildren = val.GetComponentsInChildren<AudioSource>(true);
		AudioSource[] array = componentsInChildren;
		foreach (AudioSource val2 in array)
		{
			float value;
			if (muted)
			{
				if (!OriginalVolumes.ContainsKey(val2))
				{
					OriginalVolumes[val2] = val2.volume;
				}
				val2.volume = 0f;
				val2.Stop();
			}
			else if (OriginalVolumes.TryGetValue(val2, out value))
			{
				val2.volume = value;
			}
		}
		if (!muted)
		{
			OriginalVolumes.Clear();
		}
	}
}
public static class ReflectionBridge
{
	private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

	private const BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	public static object GetSingleton(string typeName)
	{
		Type type = Type.GetType(typeName + ", assembly_valheim");
		if (type == null)
		{
			return null;
		}
		object obj = GetStaticFieldOrProperty(type, "instance") ?? GetStaticFieldOrProperty(type, "m_instance") ?? GetStaticFieldOrProperty(type, "s_instance");
		if (obj != null)
		{
			return obj;
		}
		return Object.FindObjectOfType(type);
	}

	public static object CallNoArg(object target, string methodName)
	{
		if (target == null)
		{
			return null;
		}
		MethodInfo method = target.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
		if (method == null)
		{
			return null;
		}
		try
		{
			return method.Invoke(target, null);
		}
		catch (Exception ex)
		{
			ManualLogSource log = ZoneAudioPlugin.Log;
			if (log != null)
			{
				log.LogDebug((object)("Could not call " + target.GetType().Name + "." + methodName + ": " + ex.Message));
			}
			return null;
		}
	}

	public static object GetFieldOrProperty(object target, string memberName)
	{
		if (target == null)
		{
			return null;
		}
		FieldInfo field = target.GetType().GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (field != null)
		{
			return field.GetValue(target);
		}
		return target.GetType().GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target, null);
	}

	private static object GetStaticFieldOrProperty(Type type, string memberName)
	{
		FieldInfo field = type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		if (field != null)
		{
			return field.GetValue(null);
		}
		return type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null, null);
	}
}
public static class ZoneYaml
{
	public static List<AudioZone> Parse(string yaml)
	{
		List<AudioZone> list = new List<AudioZone>();
		AudioZone audioZone = null;
		bool flag = false;
		string text = null;
		string[] array = yaml.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' });
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = StripComment(array[i]).Trim();
			if (text2.Length == 0)
			{
				continue;
			}
			if (!flag)
			{
				if (!(text2 == "zones:"))
				{
					throw new FormatException($"Line {i + 1}: expected 'zones:' at the top of the file.");
				}
				flag = true;
			}
			else if (text2.StartsWith("-"))
			{
				if (audioZone != null && text != null)
				{
					AddListValue(audioZone, text, text2.Substring(1).Trim(), i + 1);
					continue;
				}
				if (audioZone != null)
				{
					list.Add(audioZone);
				}
				audioZone = new AudioZone();
				text = null;
				string text3 = text2.Substring(1).Trim();
				if (text3.Length > 0)
				{
					text = ApplyProperty(audioZone, text3, i + 1);
				}
			}
			else
			{
				if (audioZone == null)
				{
					throw new FormatException($"Line {i + 1}: expected a zone item beginning with '-'.");
				}
				text = ApplyProperty(audioZone, text2, i + 1);
			}
		}
		if (audioZone != null)
		{
			list.Add(audioZone);
		}
		return list;
	}

	public static string ToYaml(List<AudioZone> zones)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.AppendLine("zones:");
		foreach (AudioZone zone in zones)
		{
			stringBuilder.AppendLine("  - name: " + Escape(zone.name));
			stringBuilder.AppendLine("    position: [" + zone.x.ToString(CultureInfo.InvariantCulture) + ", " + zone.y.ToString(CultureInfo.InvariantCulture) + ", " + zone.z.ToString(CultureInfo.InvariantCulture) + "]");
			stringBuilder.AppendLine("    radius: " + zone.radius.ToString(CultureInfo.InvariantCulture));
			if (zone.edgeFadeMeters > 0f)
			{
				stringBuilder.AppendLine("    edgeFade: " + zone.edgeFadeMeters.ToString(CultureInfo.InvariantCulture));
			}
			stringBuilder.AppendLine("    audio: " + Escape(zone.audioFile));
			stringBuilder.AppendLine("    volume: " + zone.volume.ToString(CultureInfo.InvariantCulture));
			stringBuilder.AppendLine();
		}
		return stringBuilder.ToString();
	}

	private static string ApplyProperty(AudioZone zone, string line, int lineNumber)
	{
		int num = line.IndexOf(':');
		if (num <= 0)
		{
			throw new FormatException($"Line {lineNumber}: expected 'key: value'.");
		}
		string text = line.Substring(0, num).Trim();
		string text2 = text.ToLowerInvariant();
		string text3 = Unquote(line.Substring(num + 1).Trim());
		switch (text2)
		{
		case "name":
			zone.name = text3;
			break;
		case "position":
		case "location":
			ApplyPosition(zone, text3, lineNumber);
			break;
		case "x":
			zone.x = ParseFloat(text3, text, lineNumber);
			zone.hasPosition = true;
			break;
		case "y":
			zone.y = ParseFloat(text3, text, lineNumber);
			zone.hasPosition = true;
			break;
		case "z":
			zone.z = ParseFloat(text3, text, lineNumber);
			zone.hasPosition = true;
			break;
		case "radius":
			zone.radius = ParseFloat(text3, text, lineNumber);
			break;
		case "edgefademeters":
		case "edgefade":
			zone.edgeFadeMeters = ParseFloat(text3, text, lineNumber);
			break;
		case "audio":
			if (string.IsNullOrEmpty(text3))
			{
				return "audio";
			}
			if (LooksLikeInlineList(text3))
			{
				AddInlineList(zone.audioFiles, text3);
			}
			else
			{
				zone.audioFile = text3;
			}
			break;
		case "audiofile":
			zone.audioFile = text3;
			break;
		case "audiofiles":
			if (string.IsNullOrEmpty(text3))
			{
				return "audio";
			}
			AddInlineList(zone.audioFiles, text3);
			break;
		case "dayaudio":
		case "dayaudiofiles":
			if (string.IsNullOrEmpty(text3))
			{
				return "dayaudio";
			}
			AddInlineList(zone.dayAudioFiles, text3);
			break;
		case "nightaudio":
		case "nightaudiofiles":
			if (string.IsNullOrEmpty(text3))
			{
				return "nightaudio";
			}
			AddInlineList(zone.nightAudioFiles, text3);
			break;
		case "volume":
			zone.volume = ParseFloat(text3, text, lineNumber);
			break;
		case "priority":
			zone.priority = ParseInt(text3, text, lineNumber);
			break;
		case "biome":
			zone.biome = text3;
			break;
		case "weather":
			if (string.IsNullOrEmpty(text3))
			{
				return "weather";
			}
			AddInlineList(zone.weather, text3);
			break;
		case "onlyday":
			zone.onlyDay = ParseBool(text3, text, lineNumber);
			break;
		case "onlynight":
			zone.onlyNight = ParseBool(text3, text, lineNumber);
			break;
		case "loop":
			zone.loop = ParseBool(text3, text, lineNumber);
			break;
		case "shuffle":
			zone.shuffle = ParseBool(text3, text, lineNumber);
			break;
		case "time":
			zone.time = text3;
			break;
		default:
			throw new FormatException($"Line {lineNumber}: unknown zone setting '{text}'.");
		}
		return null;
	}

	private static void AddListValue(AudioZone zone, string listKey, string value, int lineNumber)
	{
		value = Unquote(value);
		switch (listKey)
		{
		case "audio":
			zone.audioFiles.Add(value);
			break;
		case "dayaudio":
			zone.dayAudioFiles.Add(value);
			break;
		case "nightaudio":
			zone.nightAudioFiles.Add(value);
			break;
		case "weather":
			zone.weather.Add(value);
			break;
		default:
			throw new FormatException($"Line {lineNumber}: unknown list '{listKey}'.");
		}
	}

	private static void ApplyPosition(AudioZone zone, string value, int lineNumber)
	{
		string text = value.Trim();
		if (!text.StartsWith("[") || !text.EndsWith("]"))
		{
			throw new FormatException($"Line {lineNumber}: position must look like [x, y, z].");
		}
		string[] array = text.Substring(1, text.Length - 2).Split(new char[1] { ',' });
		if (array.Length != 3)
		{
			throw new FormatException($"Line {lineNumber}: position must contain exactly three numbers.");
		}
		zone.x = ParseFloat(array[0].Trim(), "position x", lineNumber);
		zone.y = ParseFloat(array[1].Trim(), "position y", lineNumber);
		zone.z = ParseFloat(array[2].Trim(), "position z", lineNumber);
		zone.hasPosition = true;
	}

	private static void AddInlineList(List<string> list, string value)
	{
		string text = value.Trim();
		if (LooksLikeInlineList(text))
		{
			string text2 = text.Substring(1, text.Length - 2);
			string[] array = text2.Split(new char[1] { ',' });
			foreach (string text3 in array)
			{
				string text4 = Unquote(text3.Trim());
				if (!string.IsNullOrEmpty(text4))
				{
					list.Add(text4);
				}
			}
		}
		else
		{
			list.Add(Unquote(text));
		}
	}

	private static bool LooksLikeInlineList(string value)
	{
		string text = value.Trim();
		return text.StartsWith("[") && text.EndsWith("]");
	}

	private static int ParseInt(string value, string key, int lineNumber)
	{
		if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
		{
			return result;
		}
		throw new FormatException($"Line {lineNumber}: '{key}' must be a whole number.");
	}

	private static bool ParseBool(string value, string key, int lineNumber)
	{
		if (bool.TryParse(value, out var result))
		{
			return result;
		}
		throw new FormatException($"Line {lineNumber}: '{key}' must be true or false.");
	}

	private static float ParseFloat(string value, string key, int lineNumber)
	{
		if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
		{
			return result;
		}
		throw new FormatException($"Line {lineNumber}: '{key}' must be a number.");
	}

	private static string StripComment(string line)
	{
		bool flag = false;
		bool flag2 = false;
		for (int i = 0; i < line.Length; i++)
		{
			char c = line[i];
			if (c == '\'' && !flag2)
			{
				flag = !flag;
			}
			if (c == '"' && !flag)
			{
				flag2 = !flag2;
			}
			if (c == '#' && !flag && !flag2)
			{
				return line.Substring(0, i);
			}
		}
		return line;
	}

	private static string Unquote(string value)
	{
		if (value.Length >= 2 && ((value[0] == '"' && value[value.Length - 1] == '"') || (value[0] == '\'' && value[value.Length - 1] == '\'')))
		{
			return value.Substring(1, value.Length - 2);
		}
		return value;
	}

	private static string Escape(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return "\"\"";
		}
		if (!value.StartsWith(" ") && !value.EndsWith(" ") && !value.Contains(":") && !value.Contains("#") && !value.Contains("\"") && !value.Contains("'"))
		{
			return value;
		}
		return "\"" + value.Replace("\"", "\\\"") + "\"";
	}
}