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 AudioDialogues v1.0.1
AudioDialogues.dll
Decompiled 2 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 System.Text.RegularExpressions; using AudioDialogues.Audio; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AudioDialogues { public static class AudioDialoguesConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<float> Volume; public static ConfigEntry<float> Reverb; public static ConfigEntry<bool> LogTokens; public static ConfigEntry<bool> VoiceMessages; public static ConfigEntry<int> CacheSize; public static ConfigEntry<string> PreloadPrefixes; public static ConfigEntry<float> DreamFadeIn; public static ConfigEntry<string> VoicesFolder; public static ConfigEntry<string> Language; public static void Bind(ConfigFile config) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch. Turn off to silence the mod without uninstalling it."); Volume = config.Bind<float>("General", "Volume", 1f, new ConfigDescription("Volume of the spoken lines, on top of the game's sound effects slider.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); Reverb = config.Bind<float>("General", "Reverb", 1f, new ConfigDescription("How much of a spoken line is sent to the game's reverb, as a multiple of what the game sends for its own sounds at the same distance. 1 makes a voice sit in the world exactly like a Dvergr shout, 0 keeps it dry.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); VoicesFolder = config.Bind<string>("General", "VoicesFolder", "", "Absolute path to a single folder holding index.json and the ogg files. Leave empty to use the voice packs installed under BepInEx/plugins. Set, it wins over all of them, which is what makes it useful while generating voices."); Language = config.Bind<string>("General", "Language", "", "Only load voice packs whose index.json declares this language. Leave empty to load every pack found, which is what you want unless two packs speak different languages and you would rather not uninstall one."); VoiceMessages = config.Bind<bool>("General", "VoiceMessages", true, "Also speak the few lines that go through MessageHud: boss offering stones and petting a tamed companion."); DreamFadeIn = config.Bind<float>("General", "DreamFadeIn", 1.5f, new ConfigDescription("Seconds a dream line takes to fade in, matching the text's own fade.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); CacheSize = config.Bind<int>("Performance", "CacheSize", 64, new ConfigDescription("How many decoded clips stay in memory. Raise it if lines that repeat often are reloaded too much, lower it to save memory.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 512), Array.Empty<object>())); PreloadPrefixes = config.Bind<string>("Performance", "PreloadPrefixes", "tutorial_,npc_haldor_greet,npc_hildir_greet", "Comma separated key prefixes loaded at startup, for lines that fire without warning. Ignored when index.json carries its own preload list."); LogTokens = config.Bind<bool>("Debug", "LogTokens", false, "Log every localization key the mod sees go by, resolved or not. This is how you find a line that has no voice yet, and how a game update that renames keys shows up. Each distinct key is logged once per session."); } } [BepInPlugin("Tchernobill.AudioDialogues", "Audio Dialogues", "1.0.0")] public class AudioDialoguesPlugin : BaseUnityPlugin { public const string GUID = "Tchernobill.AudioDialogues"; public const string NAME = "Audio Dialogues"; public const string VERSION = "1.0.0"; public static ManualLogSource Log { get; private set; } public static AudioDialoguesHost Host { get; private set; } private void Awake() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; AudioDialoguesConfig.Bind(((BaseUnityPlugin)this).Config); if (!AudioDialoguesConfig.Enabled.Value) { Log.LogInfo((object)"Disabled by configuration, no patch applied."); return; } GameObject val = new GameObject("AudioDialoguesHost"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; Host = val.AddComponent<AudioDialoguesHost>(); VoiceBank.Load(ResolveVoiceFolders()); ClipCache.Init((MonoBehaviour)(object)Host, AudioDialoguesConfig.CacheSize.Value); VoicePlayer.Init((MonoBehaviour)(object)Host); ClipCache.PreloadAll(VoiceBank.PreloadKeys()); new Harmony("Tchernobill.AudioDialogues").PatchAll(); } private IEnumerable<string> ResolveVoiceFolders() { string value = AudioDialoguesConfig.VoicesFolder.Value; if (!string.IsNullOrEmpty(value)) { Log.LogInfo((object)("VoicesFolder is set: only '" + value + "' is loaded.")); return new string[1] { value }; } return VoicePacks.Discover(); } } public class AudioDialoguesHost : MonoBehaviour { private void Update() { VoicePlayer.Tick(); } } } namespace AudioDialogues.Patches { [HarmonyPatch(typeof(Chat), "SetNpcText")] public static class Patch_Chat_SetNpcText { [HarmonyPostfix] public static void Postfix(GameObject talker, Vector3 offset, float cullDistance, string text) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if ((!((Object)(object)Hud.instance != (Object)null) || !Hud.instance.m_userHidden) && VoiceBank.TryResolve(text, out var key, "Chat.SetNpcText")) { key = VoiceBank.ForVoice(key, SpeakerVoice.For(talker)); VoicePlayer.PlayAt(talker, key, cullDistance, offset); } } } [HarmonyPatch(typeof(Chat), "ClearNpcText", new Type[] { typeof(NpcText) })] public static class Patch_Chat_ClearNpcText { [HarmonyPrefix] public static void Prefix(NpcText npcText) { if (npcText != null) { VoicePlayer.Stop(npcText.m_go); } } } [HarmonyPatch(typeof(Chat), "ClearAllNpcTexts")] public static class Patch_Chat_ClearAllNpcTexts { [HarmonyPrefix] public static void Prefix() { VoicePlayer.StopAll(); } } [HarmonyPatch(typeof(DreamTexts), "GetRandomDreamText")] public static class Patch_DreamTexts_GetRandomDreamText { [HarmonyPostfix] public static void Postfix(DreamText __result) { if (__result != null && VoiceBank.TryResolve(__result.m_text, out var key, "DreamTexts")) { VoicePlayer.Play2D(key, fadeIn: true); } } } [HarmonyPatch(typeof(MessageHud), "ShowMessage")] public static class Patch_MessageHud_ShowMessage { [HarmonyPostfix] public static void Postfix(string text) { if (AudioDialoguesConfig.VoiceMessages.Value && VoiceBank.TryResolve(text, out var key, "MessageHud.ShowMessage")) { VoicePlayer.Play2D(key); } } } [HarmonyPatch(typeof(TextViewer), "ShowText")] public static class Patch_TextViewer_ShowText { [HarmonyPostfix] public static void Postfix(string textId) { if (VoiceBank.TryResolve(textId, out var key, "TextViewer.ShowText")) { VoicePlayer.Play2D(key); } } } [HarmonyPatch(typeof(TextViewer), "Hide")] public static class Patch_TextViewer_Hide { [HarmonyPostfix] public static void Postfix() { if (!TextViewer.IsShowingIntro()) { VoicePlayer.Stop2D(); } } } [HarmonyPatch(typeof(TextViewer), "HideIntro")] public static class Patch_TextViewer_HideIntro { [HarmonyPostfix] public static void Postfix() { VoicePlayer.Stop2D(); } } } namespace AudioDialogues.Audio { public static class ClipCache { private class Entry { public AudioClip Clip; public UnityWebRequest Request; public float LastUsed; } private static readonly Dictionary<string, Entry> Loaded = new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, List<Action<AudioClip>>> InFlight = new Dictionary<string, List<Action<AudioClip>>>(StringComparer.OrdinalIgnoreCase); private static MonoBehaviour s_host; private static int s_capacity = 64; public static Func<AudioClip, bool> IsInUse; public static void Init(MonoBehaviour host, int capacity) { s_host = host; s_capacity = Mathf.Max(4, capacity); } public static void Request(string key, Action<AudioClip> onReady) { if (string.IsNullOrEmpty(key) || onReady == null) { return; } if (Loaded.TryGetValue(key, out var value) && (Object)(object)value.Clip != (Object)null) { value.LastUsed = Time.realtimeSinceStartup; onReady(value.Clip); return; } if (InFlight.TryGetValue(key, out var value2)) { value2.Add(onReady); return; } string path = VoiceBank.GetPath(key); if (path != null) { if ((Object)(object)s_host == (Object)null) { AudioDialoguesPlugin.Log.LogWarning((object)("No coroutine host, '" + key + "' cannot be loaded.")); return; } InFlight[key] = new List<Action<AudioClip>> { onReady }; s_host.StartCoroutine(LoadRoutine(key, path)); } } public static void Prefetch(string key) { if (!string.IsNullOrEmpty(key) && !Loaded.ContainsKey(key) && !InFlight.ContainsKey(key)) { Request(key, delegate { }); } } public static void PreloadAll(IEnumerable<string> keys) { if (keys == null) { return; } int num = 0; foreach (string key in keys) { if (num >= s_capacity / 2) { break; } Prefetch(key); num++; } if (num > 0) { AudioDialoguesPlugin.Log.LogInfo((object)$"Preloading {num} clips."); } } private static IEnumerator LoadRoutine(string key, string path) { string absoluteUri = new Uri(path).AbsoluteUri; UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(absoluteUri, (AudioType)14); DownloadHandler downloadHandler = request.downloadHandler; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (val != null) { val.streamAudio = true; } yield return request.SendWebRequest(); AudioClip val2 = null; if ((int)request.result == 1) { val2 = DownloadHandlerAudioClip.GetContent(request); if ((Object)(object)val2 != (Object)null) { ((Object)val2).name = key; } } else { AudioDialoguesPlugin.Log.LogWarning((object)("Could not load '" + key + "': " + request.error)); } if ((Object)(object)val2 == (Object)null) { request.Dispose(); } else { Loaded[key] = new Entry { Clip = val2, Request = request, LastUsed = Time.realtimeSinceStartup }; Trim(); } if (!InFlight.TryGetValue(key, out var value)) { yield break; } InFlight.Remove(key); if (!((Object)(object)val2 != (Object)null)) { yield break; } foreach (Action<AudioClip> item in value) { try { item(val2); } catch (Exception arg) { AudioDialoguesPlugin.Log.LogError((object)$"Callback for '{key}' threw: {arg}"); } } } private static void Trim() { while (Loaded.Count > s_capacity) { string text = null; float num = float.MaxValue; foreach (KeyValuePair<string, Entry> item in Loaded) { if (!(item.Value.LastUsed >= num) && (IsInUse == null || !((Object)(object)item.Value.Clip != (Object)null) || !IsInUse(item.Value.Clip))) { text = item.Key; num = item.Value.LastUsed; } } if (text == null) { break; } Evict(text); } } private static void Evict(string key) { if (Loaded.TryGetValue(key, out var value)) { Loaded.Remove(key); if ((Object)(object)value.Clip != (Object)null) { value.Clip.UnloadAudioData(); Object.Destroy((Object)(object)value.Clip); } UnityWebRequest request = value.Request; if (request != null) { request.Dispose(); } } } public static void Clear() { foreach (string item in new List<string>(Loaded.Keys)) { Evict(item); } InFlight.Clear(); } } internal static class MiniJson { public static object Parse(string text) { if (text == null) { throw new FormatException("empty document"); } int at = 0; object result = ParseValue(text, ref at); SkipWhitespace(text, ref at); if (at < text.Length) { throw new FormatException($"trailing content at offset {at}"); } return result; } private static object ParseValue(string s, ref int at) { SkipWhitespace(s, ref at); if (at >= s.Length) { throw new FormatException("unexpected end of document"); } switch (s[at]) { case '{': return ParseObject(s, ref at); case '[': return ParseArray(s, ref at); case '"': return ParseString(s, ref at); case 't': Expect(s, ref at, "true"); return true; case 'f': Expect(s, ref at, "false"); return false; case 'n': Expect(s, ref at, "null"); return null; default: return ParseNumber(s, ref at); } } private static Dictionary<string, object> ParseObject(string s, ref int at) { Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal); at++; SkipWhitespace(s, ref at); if (at < s.Length && s[at] == '}') { at++; return dictionary; } while (true) { SkipWhitespace(s, ref at); if (at >= s.Length || s[at] != '"') { throw new FormatException($"expected a key at offset {at}"); } string key = ParseString(s, ref at); SkipWhitespace(s, ref at); if (at >= s.Length || s[at] != ':') { throw new FormatException($"expected ':' at offset {at}"); } at++; dictionary[key] = ParseValue(s, ref at); SkipWhitespace(s, ref at); if (at >= s.Length) { throw new FormatException("unterminated object"); } if (s[at] != ',') { break; } at++; } if (s[at] == '}') { at++; return dictionary; } throw new FormatException($"expected ',' or '}}' at offset {at}"); } private static List<object> ParseArray(string s, ref int at) { List<object> list = new List<object>(); at++; SkipWhitespace(s, ref at); if (at < s.Length && s[at] == ']') { at++; return list; } while (true) { list.Add(ParseValue(s, ref at)); SkipWhitespace(s, ref at); if (at >= s.Length) { throw new FormatException("unterminated array"); } if (s[at] != ',') { break; } at++; } if (s[at] == ']') { at++; return list; } throw new FormatException($"expected ',' or ']' at offset {at}"); } private static string ParseString(string s, ref int at) { at++; StringBuilder stringBuilder = new StringBuilder(); while (at < s.Length) { char c = s[at++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); continue; case '\\': break; } if (at >= s.Length) { break; } char c2 = s[at++]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': if (at + 4 > s.Length) { throw new FormatException($"truncated \\u escape at offset {at}"); } stringBuilder.Append((char)Convert.ToInt32(s.Substring(at, 4), 16)); at += 4; break; default: throw new FormatException($"unknown escape '\\{c2}' at offset {at}"); } } throw new FormatException("unterminated string"); } private static double ParseNumber(string s, ref int at) { int num = at; while (at < s.Length && "+-0123456789.eE".IndexOf(s[at]) >= 0) { at++; } string text = s.Substring(num, at - num); if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { throw new FormatException($"'{text}' is not a number, at offset {num}"); } return result; } private static void Expect(string s, ref int at, string literal) { if (at + literal.Length > s.Length || string.CompareOrdinal(s, at, literal, 0, literal.Length) != 0) { throw new FormatException($"expected '{literal}' at offset {at}"); } at += literal.Length; } private static void SkipWhitespace(string s, ref int at) { while (at < s.Length) { char c = s[at]; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { at++; continue; } break; } } public static Dictionary<string, object> AsObject(object value) { return value as Dictionary<string, object>; } public static string GetString(Dictionary<string, object> o, string key, string fallback = null) { if (o == null || !o.TryGetValue(key, out var value) || !(value is string)) { return fallback; } return (string)value; } public static double GetNumber(Dictionary<string, object> o, string key, double fallback = 0.0) { if (o == null || !o.TryGetValue(key, out var value) || !(value is double)) { return fallback; } return (double)value; } public static List<object> GetArray(Dictionary<string, object> o, string key) { if (o == null || !o.TryGetValue(key, out var value)) { return null; } return value as List<object>; } } public static class SpeakerVoice { public static string For(GameObject talker) { if ((Object)(object)talker == (Object)null) { return null; } Raven component = talker.GetComponent<Raven>(); if ((Object)(object)component != (Object)null) { if (!component.m_isMunin) { return "hugin"; } return "munin"; } return null; } } [Serializable] public class VoiceIndexEntry { public string key; public string file; public float duration; public string hash; public string voice; public int seed; [NonSerialized] public string root; [NonSerialized] public string pack; } [Serializable] public class VoiceIndexFile { public int version; public string language; public string[] preload; public VoiceIndexEntry[] clips; } public static class VoiceBank { private static readonly Dictionary<string, VoiceIndexEntry> Entries = new Dictionary<string, VoiceIndexEntry>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> LoggedTokens = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly Regex TokenPattern = new Regex("\\$([A-Za-z0-9_]+)"); private static readonly List<string> PreloadList = new List<string>(); private static readonly List<string> LoadedPacks = new List<string>(); private const string VoiceSuffix = "__"; public static int Count => Entries.Count; public static string Language { get; private set; } = "English"; public static void Load(IEnumerable<string> voiceFolders) { Entries.Clear(); LoggedTokens.Clear(); PreloadList.Clear(); LoadedPacks.Clear(); if (voiceFolders != null) { foreach (string voiceFolder in voiceFolders) { LoadPack(voiceFolder); } } if (LoadedPacks.Count == 0) { AudioDialoguesPlugin.Log.LogWarning((object)"No voice pack found. The mod stays silent until one is installed: a folder named 'voices' under BepInEx/plugins, holding index.json and its ogg files. Valheim Voices is one such pack."); } else { AudioDialoguesPlugin.Log.LogInfo((object)($"Voice bank ready: {Entries.Count} clips, language {Language}, " + string.Format("from {0} pack(s): {1}.", LoadedPacks.Count, string.Join(", ", LoadedPacks.ToArray())))); } } private static void LoadPack(string voicesFolder) { if (string.IsNullOrEmpty(voicesFolder) || !Directory.Exists(voicesFolder)) { AudioDialoguesPlugin.Log.LogWarning((object)("No voices folder at '" + voicesFolder + "'.")); return; } int count = Entries.Count; string text = PackName(voicesFolder); string text2 = Path.Combine(voicesFolder, "index.json"); if (File.Exists(text2)) { if (!LoadIndex(text2, voicesFolder, text)) { return; } } else { LoadByScanning(voicesFolder, text); } LoadedPacks.Add(text); AudioDialoguesPlugin.Log.LogInfo((object)$"Voice pack '{text}': {Entries.Count - count} new clips from '{voicesFolder}'."); } private static string PackName(string voicesFolder) { try { DirectoryInfo parent = Directory.GetParent(voicesFolder); return (parent != null) ? parent.Name : voicesFolder; } catch { return voicesFolder; } } private static void Add(VoiceIndexEntry entry, string folder, string pack) { string text = Normalize(entry.key); entry.root = folder; entry.pack = pack; if (Entries.TryGetValue(text, out var value) && value.pack != pack) { AudioDialoguesPlugin.Log.LogInfo((object)("'" + text + "' from '" + pack + "' replaces the one from '" + value.pack + "'.")); } Entries[text] = entry; } private static bool LoadIndex(string indexPath, string folder, string pack) { string text; try { text = File.ReadAllText(indexPath); } catch (Exception ex) { AudioDialoguesPlugin.Log.LogError((object)("index.json of '" + pack + "' could not be read: " + ex.Message)); return false; } VoiceIndexFile voiceIndexFile; try { voiceIndexFile = ParseIndex(text); } catch (Exception ex2) { AudioDialoguesPlugin.Log.LogError((object)("index.json of '" + pack + "' is malformed: " + ex2.Message)); return false; } if (voiceIndexFile == null || voiceIndexFile.clips == null) { AudioDialoguesPlugin.Log.LogError((object)$"index.json of '{pack}' has no clips array ({text.Length} characters read)."); return false; } WarnIfJsonUtilityDisagrees(text, voiceIndexFile); string text2 = (AudioDialoguesConfig.Language.Value ?? string.Empty).Trim(); if (text2.Length != 0 && !string.IsNullOrEmpty(voiceIndexFile.language) && !string.Equals(text2, voiceIndexFile.language, StringComparison.OrdinalIgnoreCase)) { AudioDialoguesPlugin.Log.LogInfo((object)("Voice pack '" + pack + "' skipped: it speaks " + voiceIndexFile.language + ", the Language setting asks for " + text2 + ".")); return false; } if (!string.IsNullOrEmpty(voiceIndexFile.language)) { Language = voiceIndexFile.language; } if (voiceIndexFile.preload != null) { PreloadList.AddRange(voiceIndexFile.preload); } VoiceIndexEntry[] clips = voiceIndexFile.clips; foreach (VoiceIndexEntry voiceIndexEntry in clips) { if (voiceIndexEntry != null && !string.IsNullOrEmpty(voiceIndexEntry.key)) { if (string.IsNullOrEmpty(voiceIndexEntry.file)) { voiceIndexEntry.file = voiceIndexEntry.key + ".ogg"; } Add(voiceIndexEntry, folder, pack); } } return true; } private static VoiceIndexFile ParseIndex(string json) { Dictionary<string, object> dictionary = MiniJson.AsObject(MiniJson.Parse(json)); if (dictionary == null) { return null; } VoiceIndexFile voiceIndexFile = new VoiceIndexFile { version = (int)MiniJson.GetNumber(dictionary, "version", 1.0), language = MiniJson.GetString(dictionary, "language") }; List<object> array = MiniJson.GetArray(dictionary, "preload"); if (array != null) { List<string> list = new List<string>(array.Count); foreach (object item in array) { string text = item as string; if (!string.IsNullOrEmpty(text)) { list.Add(text); } } voiceIndexFile.preload = list.ToArray(); } List<object> array2 = MiniJson.GetArray(dictionary, "clips"); if (array2 == null) { return voiceIndexFile; } List<VoiceIndexEntry> list2 = new List<VoiceIndexEntry>(array2.Count); foreach (object item2 in array2) { Dictionary<string, object> dictionary2 = MiniJson.AsObject(item2); if (dictionary2 != null) { list2.Add(new VoiceIndexEntry { key = MiniJson.GetString(dictionary2, "key"), file = MiniJson.GetString(dictionary2, "file"), duration = (float)MiniJson.GetNumber(dictionary2, "duration"), hash = MiniJson.GetString(dictionary2, "hash"), voice = MiniJson.GetString(dictionary2, "voice"), seed = (int)MiniJson.GetNumber(dictionary2, "seed") }); } } voiceIndexFile.clips = list2.ToArray(); return voiceIndexFile; } private static void WarnIfJsonUtilityDisagrees(string json, VoiceIndexFile mine) { VoiceIndexFile voiceIndexFile = null; string text = null; try { voiceIndexFile = JsonUtility.FromJson<VoiceIndexFile>(json); } catch (Exception ex) { text = ex.GetType().Name + ": " + ex.Message; } int num = voiceIndexFile?.clips?.Length ?? (-1); if (text != null || num != mine.clips.Length) { AudioDialoguesPlugin.Log.LogWarning((object)("JsonUtility disagrees with the built-in reader on this index: " + (text ?? ((num < 0) ? "it returned a null clips array" : $"it found {num} clips")) + $", the built-in reader found {mine.clips.Length}. The built-in reader wins.")); } } private static void LoadByScanning(string voicesFolder, string pack) { AudioDialoguesPlugin.Log.LogInfo((object)("Voice pack '" + pack + "' has no index.json, falling back on its file names.")); string[] files = Directory.GetFiles(voicesFolder, "*.ogg"); foreach (string path in files) { string text = Normalize(Path.GetFileNameWithoutExtension(path)); if (text.Length != 0) { Add(new VoiceIndexEntry { key = text, file = Path.GetFileName(path) }, voicesFolder, pack); } } } public static IEnumerable<string> PreloadKeys() { if (PreloadList.Count > 0) { HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string preload in PreloadList) { if (!string.IsNullOrEmpty(preload)) { string text = Normalize(preload); if (Entries.ContainsKey(text) && seen.Add(text)) { yield return text; } } } yield break; } string text2 = AudioDialoguesConfig.PreloadPrefixes.Value ?? string.Empty; string[] prefixes = text2.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (prefixes.Length == 0) { yield break; } for (int i = 0; i < prefixes.Length; i++) { prefixes[i] = prefixes[i].Trim(); } foreach (KeyValuePair<string, VoiceIndexEntry> entry in Entries) { string[] array = prefixes; foreach (string text3 in array) { if (text3.Length != 0 && entry.Key.StartsWith(text3, StringComparison.OrdinalIgnoreCase)) { yield return entry.Key; break; } } } } public static bool TryResolve(string raw, out string key, string source = null) { key = null; if (string.IsNullOrEmpty(raw)) { return false; } string text = raw.Trim(); if (text.Length == 0) { return false; } if (Lookup(text, out key)) { LogToken(key, source, resolved: true); return true; } if (text[0] == '$' && Lookup(text.Substring(1), out key)) { LogToken(key, source, resolved: true); return true; } MatchCollection matchCollection = TokenPattern.Matches(text); if (matchCollection.Count > 0) { string value = matchCollection[matchCollection.Count - 1].Groups[1].Value; if (Lookup(value, out key)) { LogToken(key, source, resolved: true); return true; } LogToken(value, source, resolved: false); return false; } return false; } private static bool Lookup(string candidate, out string key) { key = null; if (string.IsNullOrEmpty(candidate)) { return false; } string text = Normalize(candidate); if (!Entries.ContainsKey(text)) { return false; } key = text; return true; } public static string GetPath(string key) { if (key == null || !Entries.TryGetValue(key, out var value)) { return null; } if (string.IsNullOrEmpty(value.root)) { return null; } return Path.Combine(value.root, value.file); } public static bool Knows(string key) { if (key != null) { return Entries.ContainsKey(key); } return false; } public static string ForVoice(string key, string voice) { if (key == null || string.IsNullOrEmpty(voice)) { return key; } string text = Normalize(key + "__" + voice); if (!Entries.ContainsKey(text)) { return key; } return text; } private static string Normalize(string key) { return key.Trim().ToLowerInvariant(); } private static void LogToken(string token, string source, bool resolved) { if (AudioDialoguesConfig.LogTokens.Value && !string.IsNullOrEmpty(token)) { string text = (resolved ? "hit " : "miss"); if (LoggedTokens.Add(text + token)) { AudioDialoguesPlugin.Log.LogInfo((object)("[" + text + "] " + token + " (" + (source ?? "?") + ")")); } } } } public static class VoicePacks { public const string FolderName = "voices"; private const int MaxDepth = 4; public static List<string> Discover() { List<string> list = new List<string>(); string pluginPath = Paths.PluginPath; if (string.IsNullOrEmpty(pluginPath) || !Directory.Exists(pluginPath)) { AudioDialoguesPlugin.Log.LogWarning((object)("No plugin folder at '" + pluginPath + "', no voice pack can be found.")); return list; } Walk(pluginPath, 0, list); list.Sort(StringComparer.OrdinalIgnoreCase); return list; } private static void Walk(string folder, int depth, List<string> found) { string[] directories; try { directories = Directory.GetDirectories(folder); } catch (Exception ex) { AudioDialoguesPlugin.Log.LogDebug((object)("'" + folder + "' could not be listed: " + ex.Message)); return; } string[] array = directories; foreach (string text in array) { if (string.Equals(Path.GetFileName(text), "voices", StringComparison.OrdinalIgnoreCase)) { if (LooksLikeVoicePack(text)) { found.Add(text); } } else if (depth + 1 < 4) { Walk(text, depth + 1, found); } } } private static bool LooksLikeVoicePack(string folder) { try { if (File.Exists(Path.Combine(folder, "index.json"))) { return true; } return Directory.GetFiles(folder, "*.ogg").Length != 0; } catch (Exception ex) { AudioDialoguesPlugin.Log.LogDebug((object)("'" + folder + "' could not be read: " + ex.Message)); return false; } } } public static class VoicePlayer { private class Voice { public AudioSource Source; public int Token; } private static readonly Dictionary<GameObject, Voice> Voices = new Dictionary<GameObject, Voice>(); private static readonly List<GameObject> DeadTalkers = new List<GameObject>(); private static MonoBehaviour s_host; private static AudioSource s_source2D; private static int s_token2D; private static float s_volume2D; private const float MenuDuck = 0.178f; private const float GlobalReverbDistance = 64f; private static AudioMixerGroup s_sfxGroup; private static AudioMixerGroup s_guiGroup; public static void Init(MonoBehaviour host) { s_host = host; ClipCache.IsInUse = IsInUse; } public static void PlayAt(GameObject talker, string key, float cullDistance, Vector3 offset) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)talker == (Object)null) { Play2D(key); return; } PruneDeadTalkers(); Voice voice = GetOrCreateVoice(talker); if (voice == null) { return; } PlaceSource(((Component)voice.Source).transform, talker.transform, offset); int token = ++voice.Token; voice.Source.Stop(); ClipCache.Request(key, delegate(AudioClip clip) { if (voice.Token == token && !((Object)(object)voice.Source == (Object)null)) { ConfigureSpatial(voice.Source, cullDistance); voice.Source.clip = clip; voice.Source.volume = AudioDialoguesConfig.Volume.Value; voice.Source.Play(); } }); } public static void Play2D(string key, bool fadeIn = false) { AudioSource source = GetOrCreate2D(); if ((Object)(object)source == (Object)null) { return; } int token = ++s_token2D; source.Stop(); ClipCache.Request(key, delegate(AudioClip clip) { if (s_token2D == token && !((Object)(object)source == (Object)null)) { source.spatialBlend = 0f; source.clip = clip; source.outputAudioMixerGroup = GetGuiGroup(); float value = AudioDialoguesConfig.Volume.Value; float num = (fadeIn ? AudioDialoguesConfig.DreamFadeIn.Value : 0f); s_volume2D = ((num > 0f) ? 0f : value); Apply2DVolume(); source.Play(); if (num > 0f && (Object)(object)s_host != (Object)null) { s_host.StartCoroutine(FadeIn(source, value, num, token)); } } }); } public static void Stop(GameObject talker) { if (!((Object)(object)talker == (Object)null) && Voices.TryGetValue(talker, out var value)) { value.Token++; if ((Object)(object)value.Source != (Object)null) { value.Source.Stop(); } } } public static void StopAll() { foreach (KeyValuePair<GameObject, Voice> voice in Voices) { voice.Value.Token++; if ((Object)(object)voice.Value.Source != (Object)null) { voice.Value.Source.Stop(); } } } public static void Stop2D() { s_token2D++; if ((Object)(object)s_source2D != (Object)null) { s_source2D.Stop(); } } public static void Tick() { if ((Object)(object)s_source2D != (Object)null && s_source2D.isPlaying) { Apply2DVolume(); } } private static void Apply2DVolume() { if (!((Object)(object)s_source2D == (Object)null)) { s_source2D.volume = s_volume2D * (Menu.IsVisible() ? 0.178f : 1f); } } private static IEnumerator FadeIn(AudioSource source, float target, float duration, int token) { float elapsed = 0f; while (elapsed < duration) { if ((Object)(object)source == (Object)null || s_token2D != token) { yield break; } elapsed += Time.unscaledDeltaTime; s_volume2D = Mathf.Lerp(0f, target, elapsed / duration); Apply2DVolume(); yield return null; } if ((Object)(object)source != (Object)null && s_token2D == token) { s_volume2D = target; Apply2DVolume(); } } private static Voice GetOrCreateVoice(GameObject talker) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (Voices.TryGetValue(talker, out var value) && (Object)(object)value.Source != (Object)null) { return value; } GameObject val = new GameObject("AudioDialogues_Voice"); val.transform.SetParent(talker.transform, false); val.transform.localPosition = Vector3.zero; AudioSource val2 = val.AddComponent<AudioSource>(); val2.playOnAwake = false; val2.loop = false; val2.priority = 64; value = new Voice { Source = val2, Token = 0 }; Voices[talker] = value; return value; } private static AudioSource GetOrCreate2D() { if ((Object)(object)s_source2D != (Object)null) { return s_source2D; } if ((Object)(object)s_host == (Object)null) { return null; } s_source2D = ((Component)s_host).gameObject.AddComponent<AudioSource>(); s_source2D.playOnAwake = false; s_source2D.loop = false; s_source2D.spatialBlend = 0f; s_source2D.priority = 32; s_source2D.bypassReverbZones = true; s_source2D.reverbZoneMix = 0f; return s_source2D; } private static void PlaceSource(Transform source, Transform talker, Vector3 offset) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (offset == Vector3.zero) { source.localPosition = Vector3.zero; } else { source.localPosition = talker.InverseTransformPoint(talker.position + offset); } } private static void ConfigureSpatial(AudioSource source, float cullDistance) { source.spatialBlend = 1f; source.dopplerLevel = 0f; source.rolloffMode = (AudioRolloffMode)1; float num = Mathf.Max(5f, cullDistance); source.minDistance = Mathf.Max(1f, num * 0.5f); source.maxDistance = num; source.outputAudioMixerGroup = GetSfxGroup(); ApplyReverb(source, num); } private static void ApplyReverb(AudioSource source, float max) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) float value = AudioDialoguesConfig.Reverb.Value; if (value <= 0f) { source.bypassReverbZones = true; source.reverbZoneMix = 0f; return; } Camera mainCamera = Utils.GetMainCamera(); float num = (((Object)(object)mainCamera != (Object)null) ? Vector3.Distance(((Component)mainCamera).transform.position, ((Component)source).transform.position) : 0f); float num2 = Mathf.Clamp01(num / 64f); float num3 = Mathf.Clamp01(max / 64f) * Mathf.Clamp01(num / max); source.bypassReverbZones = false; source.reverbZoneMix = Mathf.Clamp(Mathf.Max(num2, num3) * value, 0f, 1.1f); } private static AudioMixerGroup GetSfxGroup() { if ((Object)(object)s_sfxGroup != (Object)null) { return s_sfxGroup; } s_sfxGroup = FindGroup("SFX"); if ((Object)(object)s_sfxGroup != (Object)null) { return s_sfxGroup; } AudioMixerGroup val = null; foreach (IMonoUpdater instance in ZSFX.Instances) { ZSFX val2 = (ZSFX)(object)((instance is ZSFX) ? instance : null); if ((Object)(object)val2 == (Object)null) { continue; } AudioSource component = ((Component)val2).GetComponent<AudioSource>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.outputAudioMixerGroup == (Object)null)) { if (((Object)component.outputAudioMixerGroup).name == "SFX") { s_sfxGroup = component.outputAudioMixerGroup; return s_sfxGroup; } if ((Object)(object)val == (Object)null) { val = component.outputAudioMixerGroup; } } } if ((Object)(object)val != (Object)null) { return val; } if (!((Object)(object)AudioMan.instance != (Object)null)) { return null; } return AudioMan.instance.m_ambientMixer; } private static AudioMixerGroup GetGuiGroup() { if ((Object)(object)s_guiGroup != (Object)null) { return s_guiGroup; } s_guiGroup = FindGroup("GUI"); return s_guiGroup ?? GetSfxGroup(); } private static AudioMixerGroup FindGroup(string name) { AudioMixer val = (((Object)(object)AudioMan.instance != (Object)null) ? AudioMan.instance.m_masterMixer : null); if ((Object)(object)val == (Object)null) { return null; } AudioMixerGroup[] array = val.FindMatchingGroups(name); if (array == null) { return null; } AudioMixerGroup[] array2 = array; foreach (AudioMixerGroup val2 in array2) { if ((Object)(object)val2 != (Object)null && ((Object)val2).name == name) { return val2; } } return null; } private static bool IsInUse(AudioClip clip) { if ((Object)(object)clip == (Object)null) { return false; } if ((Object)(object)s_source2D != (Object)null && s_source2D.isPlaying && (Object)(object)s_source2D.clip == (Object)(object)clip) { return true; } foreach (KeyValuePair<GameObject, Voice> voice in Voices) { AudioSource source = voice.Value.Source; if ((Object)(object)source != (Object)null && source.isPlaying && (Object)(object)source.clip == (Object)(object)clip) { return true; } } return false; } private static void PruneDeadTalkers() { DeadTalkers.Clear(); foreach (KeyValuePair<GameObject, Voice> voice in Voices) { if ((Object)(object)voice.Key == (Object)null || (Object)(object)voice.Value.Source == (Object)null) { DeadTalkers.Add(voice.Key); } } foreach (GameObject deadTalker in DeadTalkers) { Voices.Remove(deadTalker); } } } }