Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ZoneAudio v1.1.0
ZoneAudio.dll
Decompiled 14 hours agousing 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("\"", "\\\"") + "\""; } }