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 _zones = new List(); 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 _remainingShuffleIndexes = new List(); 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 _volumeSmoothingSeconds; private ConfigEntry _masterVolume; private ConfigEntry _checkInterval; private ConfigEntry _defaultEdgeFadeFraction; private ConfigEntry _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("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("General", "MasterVolume", 1f, "Master volume multiplier (0-1), applied on top of each zone's own volume."); _checkInterval = ((BaseUnityPlugin)this).Config.Bind("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("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("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.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(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 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 list2 = new List(); 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 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 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 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 NormalizeAudioList(string zoneName, List audioFiles) { List list = new List(); 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 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 weather = new List(); public List audioFiles = new List(); public List dayAudioFiles = new List(); public List nightAudioFiles = new List(); [NonSerialized] public string audioPath; [NonSerialized] public bool hasPosition; } [Serializable] public class ZoneList { public List 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 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 OriginalVolumes = new Dictionary(); 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(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 Parse(string yaml) { List list = new List(); 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 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 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("\"", "\\\"") + "\""; } }