Decompiled source of ChatSounds v1.0.0
ChatSounds.dll
Decompiled 9 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using UnityEngine.Networking; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ChatSounds")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ChatSounds")] [assembly: AssemblyTitle("ChatSounds")] [assembly: AssemblyVersion("1.0.0.0")] namespace ChatSounds; internal static class ChatCommands { public const string CommandApiGuid = "com.on-together-mods.commandapi"; private static readonly string[] Names = new string[3] { "chatsound", "chatsounds", "csnd" }; public static bool TryExecute(string text) { if (string.IsNullOrEmpty(text) || text[0] != '/') { return false; } string[] array = text.Substring(1).Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0 || Array.IndexOf(Names, array[0].ToLowerInvariant()) < 0) { return false; } Execute(array.Skip(1).ToArray()); return true; } public static void Execute(string[] args) { //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_03f3: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null) { return; } Lang current = Lang.Current; if (args.Length == 0) { instance.ToggleWindow(); return; } string text = args[0].ToLowerInvariant(); switch (text) { case "on": instance.SetEnabled(enabled: true, notify: true); return; case "off": instance.SetEnabled(enabled: false, notify: true); return; case "toggle": instance.SetEnabled(!instance.Enabled.Value, notify: true); return; case "ui": case "window": case "settings": instance.ToggleWindow(); return; case "vol": case "volume": Volume(instance, current, args); return; case "sound": SetSound(instance, current, args); return; case "sounds": case "list": instance.Sounds.Rescan(); GameAccess.Notify(string.Format(current.SoundsList, string.Join(", ", instance.Sounds.Choices()))); return; case "test": case "play": Test(instance, current, args); return; case "when": When(instance, current, args); return; case "kw": case "keywords": case "keyword": Keyword(instance, current, args); return; case "reload": case "rescan": GameAccess.Notify(string.Format(current.Rescanned, instance.Sounds.Rescan())); return; case "status": GameAccess.Notify(instance.DescribeStatus(current)); return; } SoundCategory soundCategory = ParseCategory(instance, text); if (soundCategory != null) { Switch(current, soundCategory, args); } else { GameAccess.Notify(string.Format(current.Help, instance.WindowKey.Value, instance.ToggleKey.Value)); } } private static void Switch(Lang lang, SoundCategory category, string[] args) { bool flag; switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "toggle") { case "toggle": flag = !category.Enabled.Value; break; case "on": flag = true; break; case "off": flag = false; break; default: GameAccess.Notify(lang.UsageSwitch); return; } category.Enabled.Value = flag; GameAccess.Notify(string.Format(flag ? lang.CategoryOn : lang.CategoryOff, category.Title(lang))); } private static void Volume(Plugin plugin, Lang lang, string[] args) { SoundCategory soundCategory = ((args.Length > 2) ? ParseCategory(plugin, args[1]) : null); string text = ((args.Length > 2) ? args[2] : ((args.Length > 1) ? args[1] : null)); if ((args.Length > 2 && soundCategory == null) || !TryParsePercent(text, out var percent)) { GameAccess.Notify(lang.UsageVolume); } else if (soundCategory == null) { plugin.MasterVolume.Value = percent; GameAccess.Notify(string.Format(lang.MasterVolumeSet, percent)); } else { soundCategory.Volume.Value = percent; GameAccess.Notify(string.Format(lang.VolumeSet, soundCategory.Title(lang), percent)); plugin.Preview(soundCategory); } } private static void SetSound(Plugin plugin, Lang lang, string[] args) { SoundCategory soundCategory = ((args.Length > 2) ? ParseCategory(plugin, args[1]) : null); if (soundCategory == null) { GameAccess.Notify(lang.UsageSound); return; } plugin.Sounds.Rescan(); string text = string.Join(" ", args, 2, args.Length - 2); string text2 = plugin.Sounds.Canonicalize(text); if (text2 == null) { GameAccess.Notify(string.Format(lang.UnknownSound, text)); return; } soundCategory.Sound.Value = text2; GameAccess.Notify(string.Format(lang.SoundSet, soundCategory.Title(lang), plugin.Sounds.DisplayName(text2, lang))); plugin.Preview(soundCategory); } private static void Test(Plugin plugin, Lang lang, string[] args) { if (args.Length < 2) { plugin.PreviewAll(); return; } SoundCategory soundCategory = ParseCategory(plugin, args[1]); if (soundCategory != null) { plugin.Preview(soundCategory); return; } plugin.Sounds.Rescan(); string text = string.Join(" ", args, 1, args.Length - 1); string text2 = plugin.Sounds.Canonicalize(text); if (text2 == null) { GameAccess.Notify(string.Format(lang.UnknownSound, text)); } else { plugin.Sounds.Play(text2, plugin.EffectiveVolume(plugin.Local)); } } private static void When(Plugin plugin, Lang lang, string[] args) { SoundCategory soundCategory = ((args.Length > 2) ? ParseCategory(plugin, args[1]) : null); if (soundCategory == null || !TryParseWhen(args[2], out var when)) { GameAccess.Notify(lang.UsageWhen); return; } soundCategory.When.Value = when; GameAccess.Notify(string.Format(lang.WhenSet, soundCategory.Title(lang), lang.Describe(when))); } private static void Keyword(Plugin plugin, Lang lang, string[] args) { string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "list"); List<string> list = MentionMatcher.ParseKeywords((args.Length > 2) ? string.Join(" ", args, 2, args.Length - 2) : null); List<string> list2 = MentionMatcher.ParseKeywords(plugin.Keywords.Value); switch (text) { case "add": if (list.Count == 0) { break; } foreach (string word2 in list) { if (!list2.Exists((string w) => string.Equals(w, word2, StringComparison.OrdinalIgnoreCase))) { list2.Add(word2); } } plugin.Keywords.Value = MentionMatcher.JoinKeywords(list2); GameAccess.Notify(string.Format(lang.KeywordAdded, MentionMatcher.JoinKeywords(list))); return; case "remove": case "delete": { if (list.Count == 0) { break; } int num = 0; foreach (string word in list) { num += list2.RemoveAll((string w) => string.Equals(w, word, StringComparison.OrdinalIgnoreCase)); } if (num == 0) { GameAccess.Notify(string.Format(lang.KeywordMissing, MentionMatcher.JoinKeywords(list))); return; } plugin.Keywords.Value = MentionMatcher.JoinKeywords(list2); GameAccess.Notify(string.Format(lang.KeywordRemoved, MentionMatcher.JoinKeywords(list))); return; } case "clear": plugin.Keywords.Value = string.Empty; GameAccess.Notify(lang.KeywordsCleared); return; case "list": GameAccess.Notify(string.Format(lang.KeywordsList, (list2.Count > 0) ? MentionMatcher.JoinKeywords(list2) : lang.None)); return; } GameAccess.Notify(lang.UsageKeyword); } private static SoundCategory ParseCategory(Plugin plugin, string text) { switch (text.ToLowerInvariant()) { case "g": case "global": return plugin.Global; case "l": case "local": return plugin.Local; case "m": case "mentions": case "mention": return plugin.Mentions; default: return null; } } private static bool TryParseWhen(string text, out PlayWhen when) { switch (text.ToLowerInvariant()) { case "always": when = PlayWhen.Always; return true; case "notviewing": case "hidden": when = PlayWhen.NotViewing; return true; case "unfocused": case "background": when = PlayWhen.Unfocused; return true; default: when = PlayWhen.Always; return false; } } private static bool TryParsePercent(string text, out int percent) { percent = 0; if (string.IsNullOrEmpty(text) || !int.TryParse(text.TrimEnd('%'), out percent)) { return false; } percent = Math.Max(0, Math.Min(100, percent)); return true; } public static void RegisterWithCommandApi() { if (!Chainloader.PluginInfos.TryGetValue("com.on-together-mods.commandapi", out var value) || (Object)(object)value.Instance == (Object)null) { return; } try { Assembly assembly = ((object)value.Instance).GetType().Assembly; Type type = assembly.GetType("CommandAPI.CommandRegistry"); Type type2 = assembly.GetType("CommandAPI.Parameter"); Type type3 = assembly.GetType("CommandAPI.ParameterType"); if (type == null || type2 == null || type3 == null) { return; } MethodInfo method = type.GetMethod("Register", new Type[5] { typeof(string), typeof(string), typeof(Action<string[]>), typeof(string), type2.MakeArrayType() }); if (!(method == null)) { object obj = Enum.Parse(type3, "String"); Array array = Array.CreateInstance(type2, 2); array.SetValue(Activator.CreateInstance(type2, "action", obj, true, null, null), 0); array.SetValue(Activator.CreateInstance(type2, "value", obj, true, null, null), 1); Action<string[]> action = Execute; string[] names = Names; foreach (string text in names) { method.Invoke(null, new object[5] { text, "ChatSounds", action, "Chat message sounds: settings window, on/off, volume, sound, test, keywords, status", array }); } Plugin.Log.LogInfo((object)"Registered /chatsound with CommandAPI."); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not register with CommandAPI (the command still works): " + ex.Message)); } } } [HarmonyPatch(typeof(TextChannelManager), "OnEnterPressed")] internal static class ChatCommandPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix() { try { UIManager i = MonoSingleton<UIManager>.I; TMP_InputField val = (((Object)(object)i != (Object)null) ? i.MessageInput : null); if ((Object)(object)val != (Object)null && ChatCommands.TryExecute(val.text)) { val.text = string.Empty; } } catch (Exception ex) { Plugin.Log.LogError((object)("Chat command failed: " + ex)); } } } internal static class GameAccess { private const float LookupInterval = 1f; private static TextChannelManager _chat; private static UIManager _ui; private static DataManager _data; private static SFXManager _sfx; private static SoundManager _music; private static float _nextChatLookup; private static float _nextUiLookup; private static float _nextDataLookup; private static float _nextSfxLookup; private static float _nextMusicLookup; private static readonly FieldRef<TextChannelManager, TMP_Text> TextPrefabRef = TryFieldRef<TMP_Text>("_textPrefab"); private static readonly FieldRef<TextChannelManager, List<GameObject>> LocalMessagesRef = TryFieldRef<List<GameObject>>("_messageObjectsLocal"); public static TextChannelManager Chat => Lookup<TextChannelManager>(ref _chat, ref _nextChatLookup, (Func<TextChannelManager>)(() => NetworkSingleton<TextChannelManager>.I)); public static UIManager UI => Lookup<UIManager>(ref _ui, ref _nextUiLookup, (Func<UIManager>)(() => MonoSingleton<UIManager>.I)); public static DataManager Data => Lookup<DataManager>(ref _data, ref _nextDataLookup, (Func<DataManager>)(() => MonoSingleton<DataManager>.I)); public static SFXManager Sfx => Lookup<SFXManager>(ref _sfx, ref _nextSfxLookup, (Func<SFXManager>)(() => MonoSingleton<SFXManager>.I)); public static SoundManager Music => Lookup<SoundManager>(ref _music, ref _nextMusicLookup, (Func<SoundManager>)(() => MonoSingleton<SoundManager>.I)); public static float GameMasterVolume { get { DataManager data = Data; SettingsData val = (((Object)(object)data != (Object)null) ? data.SettingsData : null); if (val == null) { return 1f; } return Mathf.Clamp01(val.MasterVolume); } } public static string LocalPlayerName { get { TextChannelManager chat = Chat; if (!((Object)(object)chat != (Object)null)) { return null; } return chat.UserName; } } private static T Lookup<T>(ref T cached, ref float nextLookup, Func<T> find) where T : Object { if ((Object)(object)cached == (Object)null && Time.unscaledTime >= nextLookup) { nextLookup = Time.unscaledTime + 1f; cached = find(); } return cached; } public static bool IsLocalPlayerFocusing() { TextChannelManager chat = Chat; PlayerFocusController val = (((Object)(object)chat != (Object)null) ? chat.MainFocusController : null); if ((Object)(object)val != (Object)null) { return val.IsFocus; } return false; } public static bool IsChannelVisible(bool isLocal) { if (!Application.isFocused) { return false; } UIManager uI = UI; TextChannelManager chat = Chat; if ((Object)(object)uI != (Object)null && !uI.IsMessagePanelHidden && (Object)(object)chat != (Object)null) { return chat.Islocal == isLocal; } return false; } public static bool IsAnyTextFieldFocused() { EventSystem current = EventSystem.current; if ((Object)(object)current == (Object)null) { return false; } GameObject currentSelectedGameObject = current.currentSelectedGameObject; if ((Object)(object)currentSelectedGameObject == (Object)null) { return false; } TMP_InputField component = currentSelectedGameObject.GetComponent<TMP_InputField>(); if ((Object)(object)component != (Object)null && component.isFocused) { return true; } InputField component2 = currentSelectedGameObject.GetComponent<InputField>(); if ((Object)(object)component2 != (Object)null) { return component2.isFocused; } return false; } public static void Notify(string message) { TextChannelManager chat = Chat; if ((Object)(object)chat == (Object)null) { return; } string text = "<color=#9FD1FF>[ChatSounds]</color> <noparse>" + message + "</noparse>"; try { if (!chat.Islocal || !TryAddLocalLine(chat, text)) { chat.AddNotification(text); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not show chat notification: " + ex.Message)); } } private static bool TryAddLocalLine(TextChannelManager chat, string text) { if (TextPrefabRef == null) { return false; } UIManager uI = UI; Transform val = (((Object)(object)uI != (Object)null) ? uI.TextContentLocalTransform : null); TMP_Text val2 = TextPrefabRef.Invoke(chat); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { return false; } TMP_Text val3 = Object.Instantiate<TMP_Text>(val2, val); Button component = ((Component)val3).GetComponent<Button>(); if ((Object)(object)component != (Object)null) { ((Selectable)component).interactable = false; } val3.text = text; List<GameObject> list = ((LocalMessagesRef != null) ? LocalMessagesRef.Invoke(chat) : null); if (list != null) { list.Add(((Component)val3).gameObject); int localMessageLimitCount = ScriptableSingleton<GameSettings>.I.LocalMessageLimitCount; while (list.Count > localMessageLimitCount && list.Count > 0) { GameObject val4 = list[0]; list.RemoveAt(0); if ((Object)(object)val4 != (Object)null) { Object.Destroy((Object)(object)val4); } } } return true; } private static FieldRef<TextChannelManager, T> TryFieldRef<T>(string fieldName) { try { return AccessTools.FieldRefAccess<TextChannelManager, T>(fieldName); } catch (Exception) { return null; } } } internal sealed class Lang { public string Title; public string SoundsEnabled; public string MasterVolume; public string FollowGameVolume; public string QuietDuringFocus; public string GlobalChat; public string LocalChat; public string Mentions; public string Sound; public string Volume; public string When; public string Cooldown; public string NoCooldown; public string SecondsFormat; public bool DecimalComma; public string WhenAlways; public string WhenNotViewing; public string WhenUnfocused; public string MatchMyName; public string MatchMyNameFormat; public string Keywords; public string None; public string KeywordsHint; public string OpenFolder; public string Rescan; public string Close; public string WindowHint; public string FilesHint; public string GamePrefix; public string FilePrefix; public string[] SynthNames; public string On; public string Off; public string SoundsOn; public string SoundsOff; public string CategoryOn; public string CategoryOff; public string MasterVolumeSet; public string VolumeSet; public string SoundSet; public string WhenSet; public string UnknownSound; public string SoundsList; public string KeywordAdded; public string KeywordRemoved; public string KeywordMissing; public string KeywordsList; public string KeywordsCleared; public string Rescanned; public string StatusFormat; public string CategoryStatus; public string MentionStatus; public string UsageSwitch; public string UsageVolume; public string UsageSound; public string UsageWhen; public string UsageKeyword; public string Help; public static readonly Lang English = new Lang { Title = "Chat Sounds", SoundsEnabled = "Sounds for new chat messages", MasterVolume = "Master volume", FollowGameVolume = "Follow the game's master volume", QuietDuringFocus = "During focus sessions, only mentions", GlobalChat = "Global chat", LocalChat = "Local chat", Mentions = "Mentions", Sound = "Sound", Volume = "Volume", When = "Play", Cooldown = "Cooldown", NoCooldown = "off", SecondsFormat = "{0} s", WhenAlways = "Always", WhenNotViewing = "When the tab isn't visible", WhenUnfocused = "When the game is in background", MatchMyName = "Your name", MatchMyNameFormat = "Your name ({0})", Keywords = "Keywords", None = "none", KeywordsHint = "Change with /chatsound keyword add | remove <word>", OpenFolder = "Sounds folder", Rescan = "Rescan", Close = "Close", WindowHint = "{0}: this window · {1}: all sounds on/off · /chatsound help: commands", FilesHint = "Your sound files: {0} (.wav, .ogg, .mp3 in BepInEx/config/ChatSounds)", GamePrefix = "Game: ", FilePrefix = "File: ", SynthNames = new string[8] { "Ping", "Chime", "Bubble", "Drop", "Marimba", "Bell", "Soft", "Alert" }, On = "on", Off = "off", SoundsOn = "Chat sounds: on", SoundsOff = "Chat sounds: off", CategoryOn = "{0}: sound on", CategoryOff = "{0}: sound off", MasterVolumeSet = "Master volume: {0}%", VolumeSet = "{0}: volume {1}%", SoundSet = "{0}: sound {1}", WhenSet = "{0}: {1}", UnknownSound = "Unknown sound \"{0}\". /chatsound sounds lists them all", SoundsList = "Sounds: {0}", KeywordAdded = "Keyword added: {0}", KeywordRemoved = "Keyword removed: {0}", KeywordMissing = "No such keyword: {0}", KeywordsList = "Keywords: {0}", KeywordsCleared = "Keywords cleared", Rescanned = "Sound files found: {0}", StatusFormat = "Chat sounds: {0}, master volume {1}%", CategoryStatus = "{0}: {1}, {2}, {3}%, {4}", MentionStatus = "Mentions match: name {0}; keywords {1}", UsageSwitch = "Usage: /chatsound global | local | mentions [on | off]", UsageVolume = "Usage: /chatsound volume [global | local | mentions] <0-100>", UsageSound = "Usage: /chatsound sound <global | local | mentions> <sound>", UsageWhen = "Usage: /chatsound when <global | local | mentions> always | notviewing | unfocused", UsageKeyword = "Usage: /chatsound keyword add | remove <word>, /chatsound keyword list | clear", Help = "Chat sounds, commands:\n/chatsound - settings window ({0})\n/chatsound on | off - all sounds ({1})\n/chatsound global | local | mentions [on | off]\n/chatsound volume [global | local | mentions] <0-100>\n/chatsound sound <global | local | mentions> <sound>\n/chatsound sounds - list of sounds\n/chatsound test [global | local | mentions | <sound>]\n/chatsound when <global | local | mentions> always | notviewing | unfocused\n/chatsound keyword add | remove <word>, keyword list | clear\n/chatsound reload - rescan your sound files\n/chatsound status" }; public static readonly Lang Russian = new Lang { Title = "Звуки чата", SoundsEnabled = "Звуки новых сообщений", MasterVolume = "Общая громкость", FollowGameVolume = "Учитывать общую громкость игры", QuietDuringFocus = "Во время фокуса только упоминания", GlobalChat = "Глобальный чат", LocalChat = "Локальный чат", Mentions = "Упоминания", Sound = "Звук", Volume = "Громкость", When = "Когда", Cooldown = "Пауза", NoCooldown = "нет", SecondsFormat = "{0} с", DecimalComma = true, WhenAlways = "Всегда", WhenNotViewing = "Если вкладка не видна", WhenUnfocused = "Если игра в фоне", MatchMyName = "Ваше имя", MatchMyNameFormat = "Ваше имя ({0})", Keywords = "Слова", None = "нет", KeywordsHint = "Изменить: /chatsound keyword add | remove <слово>", OpenFolder = "Папка звуков", Rescan = "Обновить", Close = "Закрыть", WindowHint = "{0}: это окно · {1}: вкл/выкл все звуки · /chatsound help: команды", FilesHint = "Ваших звуковых файлов: {0} (.wav, .ogg, .mp3 в BepInEx/config/ChatSounds)", GamePrefix = "Игра: ", FilePrefix = "Файл: ", SynthNames = new string[8] { "Пинг", "Перезвон", "Пузырьки", "Капля", "Маримба", "Колокол", "Мягкий", "Сигнал" }, On = "вкл", Off = "выкл", SoundsOn = "Звуки чата включены", SoundsOff = "Звуки чата выключены", CategoryOn = "{0}: звук включён", CategoryOff = "{0}: звук выключен", MasterVolumeSet = "Общая громкость: {0}%", VolumeSet = "{0}: громкость {1}%", SoundSet = "{0}: звук {1}", WhenSet = "{0}: {1}", UnknownSound = "Неизвестный звук «{0}». Список: /chatsound sounds", SoundsList = "Звуки: {0}", KeywordAdded = "Добавлено: {0}", KeywordRemoved = "Удалено: {0}", KeywordMissing = "Такого слова нет: {0}", KeywordsList = "Слова-упоминания: {0}", KeywordsCleared = "Слова-упоминания удалены", Rescanned = "Найдено звуковых файлов: {0}", StatusFormat = "Звуки чата: {0}, общая громкость {1}%", CategoryStatus = "{0}: {1}, {2}, {3}%, {4}", MentionStatus = "Упоминания: имя {0}; слова {1}", UsageSwitch = "Использование: /chatsound global | local | mentions [on | off]", UsageVolume = "Использование: /chatsound volume [global | local | mentions] <0-100>", UsageSound = "Использование: /chatsound sound <global | local | mentions> <звук>", UsageWhen = "Использование: /chatsound when <global | local | mentions> always | notviewing | unfocused", UsageKeyword = "Использование: /chatsound keyword add | remove <слово>, /chatsound keyword list | clear", Help = "Звуки чата, команды:\n/chatsound - окно настроек ({0})\n/chatsound on | off - все звуки ({1})\n/chatsound global | local | mentions [on | off] - глобальный чат, локальный чат, упоминания\n/chatsound volume [global | local | mentions] <0-100> - громкость\n/chatsound sound <global | local | mentions> <звук> - выбрать звук\n/chatsound sounds - список звуков\n/chatsound test [global | local | mentions | <звук>] - прослушать\n/chatsound when <global | local | mentions> always | notviewing | unfocused - когда играть\n/chatsound keyword add | remove <слово>, keyword list | clear - слова-упоминания\n/chatsound reload - перечитать папку со звуками\n/chatsound status - текущие настройки" }; private static Lang _auto = English; private static float _nextAutoCheck; public static Lang Current { get { Plugin instance = Plugin.Instance; switch (((Object)(object)instance != (Object)null) ? instance.Language.Value : UiLanguage.Auto) { case UiLanguage.English: return English; case UiLanguage.Russian: return Russian; default: if (Time.unscaledTime >= _nextAutoCheck) { _nextAutoCheck = Time.unscaledTime + 10f; _auto = (DetectRussian() ? Russian : English); } return _auto; } } } public static void Invalidate() { _nextAutoCheck = 0f; } public string Describe(PlayWhen when) { return when switch { PlayWhen.NotViewing => WhenNotViewing, PlayWhen.Unfocused => WhenUnfocused, _ => WhenAlways, }; } public string Seconds(float value) { string text = value.ToString("0.#", CultureInfo.InvariantCulture); return string.Format(SecondsFormat, DecimalComma ? text.Replace('.', ',') : text); } public string SoundName(string builtin) { int num = Array.IndexOf(Synth.Names, builtin); if (num < 0 || num >= SynthNames.Length) { return builtin; } return SynthNames[num]; } private static bool DetectRussian() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 try { string text = GameLocaleCode(); if (!string.IsNullOrEmpty(text)) { return text.StartsWith("ru", StringComparison.OrdinalIgnoreCase); } } catch (Exception) { } return (int)Application.systemLanguage == 30; } [MethodImpl(MethodImplOptions.NoInlining)] private static string GameLocaleCode() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) Locale selectedLocale = LocalizationSettings.SelectedLocale; if (!((Object)(object)selectedLocale != (Object)null)) { return null; } LocaleIdentifier identifier = selectedLocale.Identifier; return ((LocaleIdentifier)(ref identifier)).Code; } } internal static class MentionMatcher { private static readonly Regex RichTextTag = new Regex("<[^<>]*>"); private static readonly char[] KeywordSeparators = new char[4] { ',', ';', '\n', '\r' }; public static string StripTags(string text) { if (!string.IsNullOrEmpty(text)) { return RichTextTag.Replace(text, string.Empty); } return string.Empty; } public static List<string> ParseKeywords(string value) { List<string> list = new List<string>(); if (string.IsNullOrEmpty(value)) { return list; } string[] array = value.Split(KeywordSeparators, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string word = text.Trim(); if (word.Length > 0 && !list.Exists((string w) => string.Equals(w, word, StringComparison.OrdinalIgnoreCase))) { list.Add(word); } } return list; } public static string JoinKeywords(IEnumerable<string> words) { return string.Join(", ", words); } public static bool IsMention(string message, string playerName, IList<string> keywords) { if (string.IsNullOrEmpty(message)) { return false; } string text = StripTags(message).ToLowerInvariant(); string text2 = StripTags(playerName).Trim(); if (text2.Length >= 2 && ContainsWord(text, text2.ToLowerInvariant())) { return true; } if (keywords == null) { return false; } foreach (string keyword in keywords) { if (ContainsWord(text, keyword.Trim().ToLowerInvariant())) { return true; } } return false; } public static bool ContainsWord(string text, string word) { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(word)) { return false; } for (int num = text.IndexOf(word, StringComparison.Ordinal); num >= 0; num = text.IndexOf(word, num + 1, StringComparison.Ordinal)) { int num2 = num + word.Length; bool num3 = num == 0 || !IsWordChar(text[num - 1]) || !IsWordChar(word[0]); bool flag = num2 >= text.Length || !IsWordChar(text[num2]) || !IsWordChar(word[word.Length - 1]); if (num3 && flag) { return true; } } return false; } private static bool IsWordChar(char c) { if (c < '⺀') { if (!char.IsLetterOrDigit(c)) { return c == '_'; } return true; } return false; } } [HarmonyPatch(typeof(TextChannelManager), "AddMessageUI")] internal static class MessagePatch { [HarmonyPostfix] private static void Postfix(string text, bool isLocal, bool sendByPlayer) { if (!sendByPlayer) { Plugin instance = Plugin.Instance; if ((Object)(object)instance != (Object)null) { instance.OnMessageShown(text, isLocal); } } } } public enum PlayWhen { Always, NotViewing, Unfocused } public enum UiLanguage { Auto, English, Russian } [BepInPlugin("ontogether.chatsounds", "ChatSounds", "1.0.0")] [BepInProcess("OnTogether.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "ontogether.chatsounds"; public const string PluginName = "ChatSounds"; public const string PluginVersion = "1.0.0"; private const float ConfigPollInterval = 1f; private const float ConfigSettleTime = 0.5f; private const float SaveDelay = 0.75f; private const float RetryDelay = 2f; internal ConfigEntry<bool> Enabled; internal ConfigEntry<int> MasterVolume; internal ConfigEntry<bool> FollowGameVolume; internal ConfigEntry<bool> QuietDuringFocus; internal ConfigEntry<UiLanguage> Language; internal ConfigEntry<KeyboardShortcut> WindowKey; internal ConfigEntry<KeyboardShortcut> ToggleKey; internal ConfigEntry<bool> MatchMyName; internal ConfigEntry<string> Keywords; internal SoundCategory Global; internal SoundCategory Local; internal SoundCategory Mentions; internal SoundCategory[] Categories; private GameObject _host; private SettingsWindow _window; private Harmony _harmony; private List<string> _keywords = new List<string>(); private readonly Dictionary<ConfigEntryBase, object> _unsaved = new Dictionary<ConfigEntryBase, object>(); private bool _reloading; private float _saveAt = -1f; private float _nextConfigPoll; private float _reloadAt = -1f; private DateTime _configStamp; private DateTime _pendingStamp; private bool _fileErrorLogged; private string _lastError; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal SoundLibrary Sounds { get; private set; } internal IList<string> KeywordList => _keywords; private void Awake() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_014f: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; BindConfig(); ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; _keywords = MentionMatcher.ParseKeywords(Keywords.Value); _configStamp = ConfigStamp(); _host = new GameObject("ChatSounds") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)_host); Sounds = _host.AddComponent<SoundLibrary>(); Sounds.Init(Path.Combine(Paths.ConfigPath, "ChatSounds")); _window = _host.AddComponent<SettingsWindow>(); ((Behaviour)_window).enabled = false; _harmony = new Harmony("ontogether.chatsounds"); Patch(typeof(MessagePatch), "no sounds will play"); Patch(typeof(ChatCommandPatch), "the /chatsound command will not work"); ChatCommands.RegisterWithCommandApi(); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; SoundCategory[] categories = Categories; foreach (SoundCategory soundCategory in categories) { Sounds.Preload(soundCategory.Sound.Value); } Log.LogInfo((object)string.Format("{0} {1} loaded. Settings window: {2} or /chatsound", "ChatSounds", "1.0.0", WindowKey.Value)); } private void BindConfig() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Play a sound when another player's chat message arrives."); MasterVolume = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MasterVolume", 80, new ConfigDescription("Volume of all chat sounds, in percent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); FollowGameVolume = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "FollowGameVolume", true, "Also scale chat sounds by the game's Master volume setting."); QuietDuringFocus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "QuietDuringFocus", false, "While you are in a focus session, only mentions make a sound."); Language = ((BaseUnityPlugin)this).Config.Bind<UiLanguage>("General", "Language", UiLanguage.Auto, "Language of the settings window and of the mod's chat messages. Auto follows the game's language."); WindowKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "SettingsWindow", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Opens or closes the settings window (same as typing /chatsound). Ignored while typing."); ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Hotkeys", "ToggleSounds", new KeyboardShortcut((KeyCode)290, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Turns all chat sounds on or off. Ignored while typing."); Global = new SoundCategory(((BaseUnityPlugin)this).Config, "Global", "Play a sound for new messages in the global chat.", "Ping", 60, 3f); Local = new SoundCategory(((BaseUnityPlugin)this).Config, "Local", "Play a sound for new messages in the local chat (players near you).", "Bubble", 80, 1f); Mentions = new SoundCategory(((BaseUnityPlugin)this).Config, "Mentions", "Play a separate sound when a message mentions you (in either chat). For that message it replaces the global/local sound.", "Alert", 100, 1f); MatchMyName = ((BaseUnityPlugin)this).Config.Bind<bool>("Mentions", "MatchMyName", true, "A message that contains your in-game name counts as a mention."); Keywords = ((BaseUnityPlugin)this).Config.Bind<string>("Mentions", "Keywords", "", "More words that count as a mention, separated by commas, e.g. nicknames: mark, markus. Case does not matter; only whole words match."); Categories = new SoundCategory[3] { Global, Local, Mentions }; } private void Patch(Type patchClass, string consequence) { try { _harmony.CreateClassProcessor(patchClass).Patch(); } catch (Exception arg) { Log.LogError((object)$"Could not patch the game ({consequence}): {arg}"); } } private void Update() { try { HandleHotkeys(); PollConfigFile(); if (_saveAt >= 0f && Time.unscaledTime >= _saveAt) { SaveNow(); } } catch (Exception e) { LogOnce("Update failed: ", e); } } private void HandleHotkeys() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = ToggleKey.Value; KeyboardShortcut value2 = WindowKey.Value; bool flag = Pressed(value); bool flag2 = Pressed(value2); if (!flag && !flag2) { return; } if (flag && flag2) { if (((KeyboardShortcut)(ref value)).Modifiers.Count() >= ((KeyboardShortcut)(ref value2)).Modifiers.Count()) { flag2 = false; } else { flag = false; } } if (!GameAccess.IsAnyTextFieldFocused()) { if (flag) { SetEnabled(!Enabled.Value, notify: true); } else { ToggleWindow(); } } } private static bool Pressed(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) KeyCode mainKey = ((KeyboardShortcut)(ref shortcut)).MainKey; if ((int)mainKey == 0 || !Input.GetKeyDown(mainKey)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (!Input.GetKey(modifier)) { return false; } } return true; } internal void OnMessageShown(string text, bool isLocal) { try { if (!Enabled.Value) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if ((!Mentions.Enabled.Value || !IsMention(text) || !TryPlay(Mentions, isLocal, realtimeSinceStartup)) && (!QuietDuringFocus.Value || !GameAccess.IsLocalPlayerFocusing())) { SoundCategory soundCategory = (isLocal ? Local : Global); if (soundCategory.Enabled.Value) { TryPlay(soundCategory, isLocal, realtimeSinceStartup); } } } catch (Exception e) { LogOnce("Could not play a chat sound: ", e); } } private bool IsMention(string text) { string playerName = (MatchMyName.Value ? GameAccess.LocalPlayerName : null); return MentionMatcher.IsMention(text, playerName, _keywords); } private bool TryPlay(SoundCategory category, bool isLocal, float now) { if (!IsAllowed(category.When.Value, isLocal)) { return false; } float num = EffectiveVolume(category); if (num <= 0f || !category.TryStartCooldown(now)) { return false; } Sounds.Play(category.Sound.Value, num); return true; } private static bool IsAllowed(PlayWhen when, bool isLocal) { return when switch { PlayWhen.NotViewing => !GameAccess.IsChannelVisible(isLocal), PlayWhen.Unfocused => !Application.isFocused, _ => true, }; } internal float EffectiveVolume(SoundCategory category) { float num = (float)MasterVolume.Value / 100f * ((float)category.Volume.Value / 100f); if (FollowGameVolume.Value) { num *= GameAccess.GameMasterVolume; } return Mathf.Clamp01(num); } internal void Preview(SoundCategory category) { Sounds.Play(category.Sound.Value, EffectiveVolume(category)); } internal void PreviewAll() { ((MonoBehaviour)this).StartCoroutine(PreviewSequence()); } private IEnumerator PreviewSequence() { SoundCategory[] categories = Categories; foreach (SoundCategory category in categories) { Preview(category); yield return (object)new WaitForSecondsRealtime(1f); } } internal void SetEnabled(bool enabled, bool notify) { Enabled.Value = enabled; if (notify) { GameAccess.Notify(enabled ? Lang.Current.SoundsOn : Lang.Current.SoundsOff); } } internal void ToggleWindow() { _window.Toggle(); } internal string DescribeStatus(Lang lang) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat(lang.StatusFormat, Enabled.Value ? lang.On : lang.Off, MasterVolume.Value); SoundCategory[] categories = Categories; foreach (SoundCategory soundCategory in categories) { stringBuilder.Append('\n').AppendFormat(lang.CategoryStatus, soundCategory.Title(lang), soundCategory.Enabled.Value ? lang.On : lang.Off, Sounds.DisplayName(soundCategory.Sound.Value, lang), soundCategory.Volume.Value, lang.Describe(soundCategory.When.Value)); } string text = MentionMatcher.StripTags(GameAccess.LocalPlayerName); stringBuilder.Append('\n').AppendFormat(lang.MentionStatus, (MatchMyName.Value && text.Length > 0) ? text : lang.None, (_keywords.Count > 0) ? MentionMatcher.JoinKeywords(_keywords) : lang.None); return stringBuilder.ToString(); } private void OnSettingChanged(object sender, SettingChangedEventArgs e) { ConfigEntryBase changedSetting = e.ChangedSetting; if (!_reloading) { _unsaved[changedSetting] = changedSetting.BoxedValue; _saveAt = Time.unscaledTime + 0.75f; } if ((object)changedSetting == Keywords) { _keywords = MentionMatcher.ParseKeywords(Keywords.Value); return; } if ((object)changedSetting == Language) { Lang.Invalidate(); return; } SoundCategory[] categories = Categories; foreach (SoundCategory soundCategory in categories) { if ((object)changedSetting == soundCategory.Sound) { Sounds.Preload(soundCategory.Sound.Value); } } } private void PollConfigFile() { float unscaledTime = Time.unscaledTime; if (_reloadAt >= 0f) { if (!(unscaledTime < _reloadAt)) { DateTime dateTime = ConfigStamp(); if (dateTime == _configStamp) { _reloadAt = -1f; } else if (dateTime != _pendingStamp) { _pendingStamp = dateTime; _reloadAt = unscaledTime + 0.5f; } else { _reloadAt = (TryReloadConfig() ? (-1f) : (unscaledTime + 2f)); } } } else if (!(unscaledTime < _nextConfigPoll)) { _nextConfigPoll = unscaledTime + 1f; DateTime dateTime2 = ConfigStamp(); if (!(dateTime2 == _configStamp)) { _pendingStamp = dateTime2; _reloadAt = unscaledTime + 0.5f; } } } private bool TryReloadConfig() { if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath)) { SaveNow(); return true; } DateTime configStamp = ConfigStamp(); _reloading = true; try { ((BaseUnityPlugin)this).Config.Reload(); foreach (KeyValuePair<ConfigEntryBase, object> item in _unsaved) { item.Key.BoxedValue = item.Value; } } catch (Exception ex) { LogFileErrorOnce("Could not read the config file, will try again: " + ex.Message); return false; } finally { _reloading = false; } _configStamp = configStamp; _fileErrorLogged = false; Log.LogInfo((object)"Settings reloaded from the config file."); return true; } private void SaveNow() { _saveAt = -1f; if (File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) && ConfigStamp() != _configStamp && !TryReloadConfig()) { _saveAt = Time.unscaledTime + 2f; return; } try { ((BaseUnityPlugin)this).Config.Save(); _unsaved.Clear(); _configStamp = ConfigStamp(); _fileErrorLogged = false; } catch (Exception ex) { LogFileErrorOnce("Could not save the settings, will try again: " + ex.Message); _saveAt = Time.unscaledTime + 2f; } } private void LogFileErrorOnce(string message) { if (!_fileErrorLogged) { _fileErrorLogged = true; Log.LogWarning((object)message); } } private DateTime ConfigStamp() { try { return File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath); } catch (Exception) { return default(DateTime); } } private void LogOnce(string what, Exception e) { string text = what + e.GetType().Name + ": " + e.Message; if (!(text == _lastError)) { _lastError = text; Log.LogError((object)(what + e)); } } private void OnApplicationQuit() { if (_saveAt >= 0f) { SaveNow(); } } private void OnDestroy() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; if (_saveAt >= 0f) { SaveNow(); } if (_harmony != null) { _harmony.UnpatchSelf(); } if ((Object)(object)_host != (Object)null) { Object.Destroy((Object)(object)_host); } } } internal sealed class SettingsWindow : MonoBehaviour { private const int WindowId = 1129532996; private const float Width = 470f; private const float LabelWidth = 118f; private const float ArrowWidth = 26f; private const float PlayWidth = 32f; private const float ValueWidth = 54f; private const float MaxCooldown = 60f; private const float PreviewDelay = 0.3f; private static readonly GUILayoutOption[] Shrinkable = (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.MinWidth(40f), GUILayout.ExpandWidth(true) }; private UiSkin _skin; private WindowFunction _drawWindow; private Rect _rect; private bool _placed; private float _screenHeight; private Vector2 _scroll; private float _contentHeight = 600f; private SoundCategory _pendingPreview; private float _previewAt; private int _hotControl; private GameObject _blockerCanvas; private RectTransform _blocker; public void Toggle() { SetOpen(!((Behaviour)this).enabled); } public void SetOpen(bool open) { if (open == ((Behaviour)this).enabled) { return; } ((Behaviour)this).enabled = open; if (open) { Lang.Invalidate(); Plugin.Instance.Sounds.Rescan(); return; } _pendingPreview = null; if (_hotControl != 0 && GUIUtility.hotControl == _hotControl) { GUIUtility.hotControl = 0; } _hotControl = 0; } private void OnEnable() { if ((Object)(object)_blockerCanvas != (Object)null) { _blockerCanvas.SetActive(true); } } private void OnDisable() { if ((Object)(object)_blockerCanvas != (Object)null) { _blockerCanvas.SetActive(false); } } private void Update() { if (_pendingPreview != null && Time.unscaledTime >= _previewAt) { Plugin.Instance.Preview(_pendingPreview); _pendingPreview = null; } } private void OnGUI() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Invalid comparison between Unknown and I4 //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Plugin.Instance == (Object)null)) { if (_skin == null) { _skin = new UiSkin(); } if (_drawWindow == null) { _drawWindow = new WindowFunction(DrawWindow); } float num = Mathf.Clamp((float)Screen.height / 1080f, 1f, 3f); Matrix4x4 matrix = GUI.matrix; GUI.matrix = Matrix4x4.Scale(new Vector3(num, num, 1f)); float num2 = (float)Screen.width / num; _screenHeight = (float)Screen.height / num; if (!_placed) { _rect = new Rect(Mathf.Round((num2 - 470f) * 0.5f), Mathf.Round(_screenHeight * 0.08f), 470f, 0f); _placed = true; } _rect = GUILayout.Window(1129532996, _rect, _drawWindow, GUIContent.none, _skin.Window, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(470f) }); ((Rect)(ref _rect)).x = Mathf.Clamp(((Rect)(ref _rect)).x, 0f, Mathf.Max(0f, num2 - ((Rect)(ref _rect)).width)); ((Rect)(ref _rect)).y = Mathf.Clamp(((Rect)(ref _rect)).y, 0f, Mathf.Max(0f, _screenHeight - ((Rect)(ref _rect)).height)); GUI.matrix = matrix; if ((int)Event.current.type == 7) { UpdateBlocker(num); } } } private void DrawWindow(int id) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Invalid comparison between Unknown and I4 //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Plugin.Instance; Lang current = Lang.Current; int hotControl = GUIUtility.hotControl; GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(current.Title, _skin.Title, Array.Empty<GUILayoutOption>()); GUILayout.FlexibleSpace(); if (GUILayout.Button("×", _skin.CloseButton, Array.Empty<GUILayoutOption>())) { SetOpen(open: false); } GUILayout.EndHorizontal(); float num = Mathf.Min(_contentHeight + 4f, Mathf.Max(200f, _screenHeight - 140f)); _scroll = GUILayout.BeginScrollView(_scroll, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUIStyle.none, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num) }); GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); DrawSettings(instance, current); GUILayout.EndVertical(); if ((int)Event.current.type == 7) { Rect lastRect = GUILayoutUtility.GetLastRect(); _contentHeight = ((Rect)(ref lastRect)).height; } GUILayout.EndScrollView(); GUILayout.Space(10f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button(current.OpenFolder, _skin.Button, Array.Empty<GUILayoutOption>())) { instance.Sounds.OpenFolder(); } if (GUILayout.Button(current.Rescan, _skin.Button, Array.Empty<GUILayoutOption>())) { instance.Sounds.Rescan(); } GUILayout.FlexibleSpace(); if (GUILayout.Button(current.Close, _skin.Button, Array.Empty<GUILayoutOption>())) { SetOpen(open: false); } GUILayout.EndHorizontal(); GUILayout.Space(6f); GUILayout.Label(string.Format(current.FilesHint, instance.Sounds.FileCount), _skin.Hint, Array.Empty<GUILayoutOption>()); GUILayout.Label(string.Format(current.WindowHint, instance.WindowKey.Value, instance.ToggleKey.Value), _skin.Hint, Array.Empty<GUILayoutOption>()); GUI.DragWindow(new Rect(0f, 0f, 10000f, 40f)); if (GUIUtility.hotControl != hotControl) { _hotControl = GUIUtility.hotControl; } } private void DrawSettings(Plugin plugin, Lang lang) { ToggleRow(plugin.Enabled, lang.SoundsEnabled); PercentRow(plugin.MasterVolume, lang.MasterVolume, plugin.Global); ToggleRow(plugin.FollowGameVolume, lang.FollowGameVolume); ToggleRow(plugin.QuietDuringFocus, lang.QuietDuringFocus); SoundCategory[] categories = plugin.Categories; foreach (SoundCategory category in categories) { CategoryPanel(plugin, lang, category); } } private void CategoryPanel(Plugin plugin, Lang lang, SoundCategory category) { GUILayout.BeginVertical(_skin.Panel, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(_skin.Row, Array.Empty<GUILayoutOption>()); bool flag = GUILayout.Toggle(category.Enabled.Value, GUIContent.none, _skin.Check, Array.Empty<GUILayoutOption>()); if (GUILayout.Button(category.Title(lang), _skin.SectionTitle, Array.Empty<GUILayoutOption>())) { flag = !category.Enabled.Value; } GUILayout.EndHorizontal(); if (flag != category.Enabled.Value) { category.Enabled.Value = flag; } bool enabled = GUI.enabled; GUI.enabled = enabled && category.Enabled.Value; if (category == plugin.Mentions) { MentionRows(plugin, lang); } SoundRow(plugin, lang, category); PercentRow(category.Volume, lang.Volume, category); WhenRow(lang, category); CooldownRow(lang, category); GUI.enabled = enabled; GUILayout.EndVertical(); } private void MentionRows(Plugin plugin, Lang lang) { string text = MentionMatcher.StripTags(GameAccess.LocalPlayerName).Trim(); ToggleRow(plugin.MatchMyName, (text.Length > 0) ? string.Format(lang.MatchMyNameFormat, text) : lang.MatchMyName); GUILayout.BeginHorizontal(_skin.Row, Array.Empty<GUILayoutOption>()); GUILayout.Label(lang.Keywords, _skin.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(118f) }); IList<string> keywordList = plugin.KeywordList; GUILayout.Label((keywordList.Count > 0) ? MentionMatcher.JoinKeywords(keywordList) : lang.None, _skin.ToggleLabel, Shrinkable); GUILayout.EndHorizontal(); GUILayout.Label(lang.KeywordsHint, _skin.Hint, Array.Empty<GUILayoutOption>()); } private void ToggleRow(ConfigEntry<bool> entry, string label) { GUILayout.BeginHorizontal(_skin.Row, Array.Empty<GUILayoutOption>()); bool flag = GUILayout.Toggle(entry.Value, GUIContent.none, _skin.Check, Array.Empty<GUILayoutOption>()); if (GUILayout.Button(label, _skin.ToggleLabel, Shrinkable)) { flag = !entry.Value; } GUILayout.EndHorizontal(); if (flag != entry.Value) { entry.Value = flag; } } private void PercentRow(ConfigEntry<int> entry, string label, SoundCategory preview) { GUILayout.BeginHorizontal(_skin.Row, Array.Empty<GUILayoutOption>()); GUILayout.Label(label, _skin.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(118f) }); int num = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)entry.Value, 0f, 100f, _skin.Slider, _skin.Thumb, Array.Empty<GUILayoutOption>())); GUILayout.Label(num + "%", _skin.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) }); GUILayout.EndHorizontal(); if (num != entry.Value) { entry.Value = num; _pendingPreview = preview; _previewAt = Time.unscaledTime + 0.3f; } } private void SoundRow(Plugin plugin, Lang lang, SoundCategory category) { GUILayout.BeginHorizontal(_skin.Row, Array.Empty<GUILayoutOption>()); GUILayout.Label(lang.Sound, _skin.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(118f) }); int num = 0; if (GUILayout.Button("◄", _skin.SmallButton, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(26f) })) { num = -1; } GUILayout.Label(plugin.Sounds.DisplayName(category.Sound.Value, lang), _skin.Field, Shrinkable); if (GUILayout.Button("►", _skin.SmallButton, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(26f) })) { num = 1; } if (GUILayout.Button("♪", _skin.SmallButton, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(32f) })) { plugin.Preview(category); } GUILayout.EndHorizontal(); if (num != 0) { List<string> list = plugin.Sounds.Choices(); int num2 = list.FindIndex((string c) => string.Equals(c, category.Sound.Value, StringComparison.OrdinalIgnoreCase)); num2 = ((num2 >= 0) ? ((num2 + num + list.Count) % list.Count) : ((num <= 0) ? (list.Count - 1) : 0)); category.Sound.Value = list[num2]; plugin.Preview(category); } } private void WhenRow(Lang lang, SoundCategory category) { GUILayout.BeginHorizontal(_skin.Row, Array.Empty<GUILayoutOption>()); GUILayout.Label(lang.When, _skin.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(118f) }); int num = 0; if (GUILayout.Button("◄", _skin.SmallButton, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(26f) })) { num = -1; } GUILayout.Label(lang.Describe(category.When.Value), _skin.Field, Shrinkable); if (GUILayout.Button("►", _skin.SmallButton, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(26f) })) { num = 1; } GUILayout.Label(GUIContent.none, _skin.Spacer, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(32f) }); GUILayout.EndHorizontal(); if (num != 0) { int length = Enum.GetValues(typeof(PlayWhen)).Length; category.When.Value = (PlayWhen)((int)(category.When.Value + num + length) % length); } } private void CooldownRow(Lang lang, SoundCategory category) { float num = Mathf.Sqrt(Mathf.Clamp(category.Cooldown.Value, 0f, 60f) / 60f); GUILayout.BeginHorizontal(_skin.Row, Array.Empty<GUILayoutOption>()); GUILayout.Label(lang.Cooldown, _skin.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(118f) }); float num2 = GUILayout.HorizontalSlider(num, 0f, 1f, _skin.Slider, _skin.Thumb, Array.Empty<GUILayoutOption>()); float value = category.Cooldown.Value; GUILayout.Label((value <= 0f) ? lang.NoCooldown : lang.Seconds(value), _skin.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) }); GUILayout.EndHorizontal(); if (num2 != num) { float num3 = 60f * num2 * num2; category.Cooldown.Value = ((num3 < 10f) ? (Mathf.Round(num3 * 2f) / 2f) : Mathf.Round(num3)); } } private void UpdateBlocker(float scale) { //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_blocker == (Object)null) { if ((Object)(object)_blockerCanvas != (Object)null) { Object.Destroy((Object)(object)_blockerCanvas); } _blockerCanvas = new GameObject("ChatSounds.InputBlocker") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)_blockerCanvas); Canvas obj = _blockerCanvas.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 32767; _blockerCanvas.AddComponent<GraphicRaycaster>(); GameObject val = new GameObject("Blocker", new Type[1] { typeof(RectTransform) }) { hideFlags = (HideFlags)61 }; val.transform.SetParent(_blockerCanvas.transform, false); Image obj2 = val.AddComponent<Image>(); ((Graphic)obj2).color = new Color(0f, 0f, 0f, 0f); ((Graphic)obj2).raycastTarget = true; _blocker = (RectTransform)val.transform; _blocker.anchorMin = new Vector2(0f, 1f); _blocker.anchorMax = new Vector2(0f, 1f); _blocker.pivot = new Vector2(0f, 1f); } _blocker.anchoredPosition = new Vector2(((Rect)(ref _rect)).x * scale, (0f - ((Rect)(ref _rect)).y) * scale); _blocker.sizeDelta = new Vector2(((Rect)(ref _rect)).width * scale, ((Rect)(ref _rect)).height * scale); } private void OnDestroy() { if (_skin != null) { _skin.Destroy(); } if ((Object)(object)_blockerCanvas != (Object)null) { Object.Destroy((Object)(object)_blockerCanvas); } } } internal sealed class SoundCategory { private const string PlayWhenHelp = "When to play:\nAlways;\nNotViewing - only when you cannot see the message's chat tab right now (the other tab is open, the chat is hidden or the game window is in the background);\nUnfocused - only while the game window is in the background."; public readonly string Section; public readonly ConfigEntry<bool> Enabled; public readonly ConfigEntry<string> Sound; public readonly ConfigEntry<int> Volume; public readonly ConfigEntry<PlayWhen> When; public readonly ConfigEntry<float> Cooldown; private float _lastPlayed = -1000000f; public SoundCategory(ConfigFile config, string section, string enabledHelp, string sound, int volume, float cooldown) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown Section = section; Enabled = config.Bind<bool>(section, "Enabled", true, enabledHelp); Sound = config.Bind<string>(section, "Sound", sound, "Sound to play.\nBuilt-in: Ping, Chime, Bubble, Drop, Marimba, Bell, Soft, Alert.\nGame sounds: game:CompleteTask, game:EarnTicket, game:PetUnlock, game:FocusEnd, game:Bell1 and more (the settings window lists all of them).\nYour own file: put a .wav, .ogg or .mp3 file into BepInEx/config/ChatSounds and write file:<name>, e.g. file:ding.ogg (a full path works too)."); Volume = config.Bind<int>(section, "Volume", volume, new ConfigDescription("Volume in percent (multiplied by General.MasterVolume).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); When = config.Bind<PlayWhen>(section, "PlayWhen", PlayWhen.Always, "When to play:\nAlways;\nNotViewing - only when you cannot see the message's chat tab right now (the other tab is open, the chat is hidden or the game window is in the background);\nUnfocused - only while the game window is in the background."); Cooldown = config.Bind<float>(section, "Cooldown", cooldown, new ConfigDescription("Minimum seconds between two of these sounds; messages that arrive sooner stay silent.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>())); } public string Title(Lang lang) { string section = Section; if (!(section == "Global")) { if (section == "Local") { return lang.LocalChat; } return lang.Mentions; } return lang.GlobalChat; } public bool TryStartCooldown(float now) { if (now - _lastPlayed < Cooldown.Value) { return false; } _lastPlayed = now; return true; } } internal sealed class SoundLibrary : MonoBehaviour { private sealed class FileSound { public AudioClip Clip; public DateTime Stamp; public bool Loading; public float PendingVolume = -1f; public float PendingSince; } public const string DefaultSound = "Ping"; internal const string ConfigHelp = "Sound to play.\nBuilt-in: Ping, Chime, Bubble, Drop, Marimba, Bell, Soft, Alert.\nGame sounds: game:CompleteTask, game:EarnTicket, game:PetUnlock, game:FocusEnd, game:Bell1 and more (the settings window lists all of them).\nYour own file: put a .wav, .ogg or .mp3 file into BepInEx/config/ChatSounds and write file:<name>, e.g. file:ding.ogg (a full path works too)."; private const string ReadmeText = "Put your own notification sounds here: .wav, .ogg or .mp3 files. Short sounds (under 2 seconds) work best.\r\nThen pick them in the settings window (F9 or /chatsound in the chat; press \"Rescan\" after adding files),\r\nor write file:<name> into a Sound setting in BepInEx/config/ontogether.chatsounds.cfg, e.g. file:ding.ogg\r\n\r\nПоложите сюда свои звуки уведомлений: файлы .wav, .ogg или .mp3. Лучше всего подходят короткие звуки (до 2 секунд).\r\nЗатем выберите их в окне настроек (F9 или /chatsound в чате; после добавления файлов нажмите «Обновить»)\r\nили впишите file:<имя> в параметр Sound в BepInEx/config/ontogether.chatsounds.cfg, например file:ding.ogg\r\n"; private static readonly string[] GameClips = new string[17] { "CompleteTask", "EarnTicket", "BuyItem", "PetUnlock", "FocusStart", "FocusEnd", "FishAlertSound", "FishSuccessSound", "FishCatchSound", "BalloonPop", "LemonadeSound", "PetAppearSound", "StoreOpen", "InventoryOpen", "UIClick", "UIChange", "UIError" }; private const string BellPrefix = "Bell"; private const long MaxFileBytes = 20971520L; private const long MaxDirectWavBytes = 4194304L; private const float MaxPlayDelay = 3f; private const float SfxLookupInterval = 2f; private static readonly FieldRef<SFXManager, SFXSettings> SfxSettingsRef = TrySfxSettingsRef(); private readonly Dictionary<string, AudioClip> _builtins = new Dictionary<string, AudioClip>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, FileSound> _files = new Dictionary<string, FileSound>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, FieldInfo> _gameFields = new Dictionary<string, FieldInfo>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _warned = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private List<string> _fileNames = new List<string>(); private AudioSource _source; private SFXSettings _sfxSettings; private float _nextSfxLookup; public string Folder { get; private set; } public int FileCount => _fileNames.Count; public void Init(string folder) { Folder = folder; _source = ((Component)this).gameObject.AddComponent<AudioSource>(); _source.playOnAwake = false; _source.loop = false; _source.spatialBlend = 0f; _source.priority = 0; _source.volume = 1f; _source.bypassEffects = true; _source.bypassListenerEffects = true; _source.bypassReverbZones = true; _source.ignoreListenerPause = true; EnsureFolder(); Rescan(); } public void Play(string value, float volume) { if (volume <= 0f) { return; } try { SoundRef soundRef = SoundRef.Parse(value); switch (soundRef.Kind) { case SoundKind.Game: { AudioClip val2 = GameClip(soundRef.Name); PlayClip(((Object)(object)val2 != (Object)null) ? val2 : Fallback(value), volume); break; } case SoundKind.File: PlayFile(ResolvePath(soundRef.Name), value, volume); break; default: { AudioClip val = Builtin(soundRef.Name); PlayClip(((Object)(object)val != (Object)null) ? val : Fallback(value), volume); break; } } } catch (Exception arg) { WarnOnce("play:" + value, $"Could not play sound \"{value}\": {arg}"); } } public void Preload(string value) { SoundRef soundRef = SoundRef.Parse(value); if (soundRef.Kind == SoundKind.File) { FileEntry(ResolvePath(soundRef.Name)); } } public List<string> Choices() { List<string> list = new List<string>(Synth.Names); string[] gameClips = GameClips; foreach (string text in gameClips) { if ((Object)(object)GameClip(text) != (Object)null) { list.Add("game:" + text); } } List<AudioClip> list2 = Bells(); if (list2 != null) { for (int j = 0; j < list2.Count; j++) { if ((Object)(object)list2[j] != (Object)null) { list.Add("game:Bell" + (j + 1)); } } } foreach (string fileName in _fileNames) { list.Add("file:" + fileName); } return list; } public string DisplayName(string value, Lang lang) { SoundRef soundRef = SoundRef.Parse(value); switch (soundRef.Kind) { case SoundKind.Game: return lang.GamePrefix + SoundRef.Friendly(soundRef.Name); case SoundKind.File: return lang.FilePrefix + SafeFileName(soundRef.Name); default: { string text = Synth.Canonical(soundRef.Name); if (text == null) { return soundRef.Name; } return lang.SoundName(text); } } } public string Canonicalize(string input) { SoundRef soundRef = SoundRef.Parse(input); switch (soundRef.Kind) { case SoundKind.Game: return CanonicalGameSound(soundRef.Name); case SoundKind.File: { string text2 = ResolvePath(soundRef.Name); if (text2 == null || !File.Exists(text2)) { return null; } return "file:" + soundRef.Name; } default: { string text = Synth.Canonical(soundRef.Name); if (text != null) { return text; } foreach (string fileName in _fileNames) { if (string.Equals(Path.GetFileNameWithoutExtension(fileName), soundRef.Name, StringComparison.OrdinalIgnoreCase)) { return "file:" + fileName; } } return CanonicalGameSound(soundRef.Name); } } } private string CanonicalGameSound(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (TryParseBell(name, out var number)) { return "game:Bell" + number; } FieldInfo fieldInfo = GameField(name); if (!(fieldInfo != null)) { return null; } return "game:" + fieldInfo.Name; } public int Rescan() { List<string> list = new List<string>(); try { if (Directory.Exists(Folder)) { string[] files = Directory.GetFiles(Folder); foreach (string path in files) { if (SoundRef.IsAudioFileName(path)) { list.Add(Path.GetFileName(path)); } } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not list the sounds folder: " + ex.Message)); } list.Sort(StringComparer.OrdinalIgnoreCase); _fileNames = list; return list.Count; } public void OpenFolder() { EnsureFolder(); try { Process.Start("explorer.exe", "\"" + Folder + "\"")?.Dispose(); } catch (Exception) { try { Application.OpenURL(new Uri(Folder).AbsoluteUri); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Could not open the sounds folder: " + ex2.Message)); } } } private void EnsureFolder() { try { Directory.CreateDirectory(Folder); string path = Path.Combine(Folder, "README.txt"); if (!File.Exists(path)) { File.WriteAllText(path, "Put your own notification sounds here: .wav, .ogg or .mp3 files. Short sounds (under 2 seconds) work best.\r\nThen pick them in the settings window (F9 or /chatsound in the chat; press \"Rescan\" after adding files),\r\nor write file:<name> into a Sound setting in BepInEx/config/ontogether.chatsounds.cfg, e.g. file:ding.ogg\r\n\r\nПоложите сюда свои звуки уведомлений: файлы .wav, .ogg или .mp3. Лучше всего подходят короткие звуки (до 2 секунд).\r\nЗатем выберите их в окне настроек (F9 или /chatsound в чате; после добавления файлов нажмите «Обновить»)\r\nили впишите file:<имя> в параметр Sound в BepInEx/config/ontogether.chatsounds.cfg, например file:ding.ogg\r\n"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not create the sounds folder " + Folder + ": " + ex.Message)); } } private void PlayClip(AudioClip clip, float volume) { if (!((Object)(object)clip == (Object)null) && !((Object)(object)_source == (Object)null)) { _source.PlayOneShot(clip, Mathf.Clamp01(volume)); } } private AudioClip Fallback(string value) { WarnOnce(value, "Sound \"" + value + "\" is not available; playing Ping instead."); return Builtin("Ping"); } private AudioClip Builtin(string name) { string text = Synth.Canonical(name); if (text == null) { return null; } if (_builtins.TryGetValue(text, out var value) && (Object)(object)value != (Object)null) { return value; } int num = ((AudioSettings.outputSampleRate > 0) ? AudioSettings.outputSampleRate : 48000); float[] array = Synth.Render(text, num); value = AudioClip.Create("ChatSounds." + text, array.Length, 1, num, false); value.SetData(array, 0); ((Object)value).hideFlags = (HideFlags)32; _builtins[text] = value; return value; } private AudioClip GameClip(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (TryParseBell(name, out var number)) { List<AudioClip> list = Bells(); if (list == null || number > list.Count) { return null; } return list[number - 1]; } SFXSettings val = SfxSettings(); FieldInfo fieldInfo = (((Object)(object)val != (Object)null) ? GameField(name) : null); if (!(fieldInfo != null)) { return null; } object? value = fieldInfo.GetValue(val); return (AudioClip)((value is AudioClip) ? value : null); } private static bool TryParseBell(string name, out int number) { number = 0; if (name.Length > "Bell".Length && name.StartsWith("Bell", StringComparison.OrdinalIgnoreCase) && int.TryParse(name.Substring("Bell".Length), out number)) { return number >= 1; } return false; } private FieldInfo GameField(string name) { if (_gameFields.TryGetValue(name, out var value)) { return value; } FieldInfo[] fields = typeof(SFXSettings).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(AudioClip) && string.Equals(fieldInfo.Name, name, StringComparison.OrdinalIgnoreCase)) { value = fieldInfo; break; } } _gameFields[name] = value; return value; } private static List<AudioClip> Bells() { SoundManager music = GameAccess.Music; if (!((Object)(object)music != (Object)null)) { return null; } return music.BellSounds; } private SFXSettings SfxSettings() { if ((Object)(object)_sfxSettings != (Object)null) { return _sfxSettings; } if (Time.unscaledTime < _nextSfxLookup) { return null; } _nextSfxLookup = Time.unscaledTime + 2f; SFXManager sfx = GameAccess.Sfx; if ((Object)(object)sfx != (Object)null && SfxSettingsRef != null) { _sfxSettings = SfxSettingsRef.Invoke(sfx); } if ((Object)(object)_sfxSettings == (Object)null) { SFXSettings[] array = Resources.FindObjectsOfTypeAll<SFXSettings>(); if (array.Length != 0) { _sfxSettings = array[0]; } } return _sfxSettings; } private string ResolvePath(string name) { if (string.IsNullOrEmpty(name)) { return null; } try { return Path.IsPathRooted(name) ? name : Path.Combine(Folder, name); } catch (ArgumentException) { return null; } } private void PlayFile(string path, string value, float volume) { FileSound fileSound = FileEntry(path); if (fileSound == null) { PlayClip(Fallback(value), volume); } else if (fileSound.Loading) { fileSound.PendingVolume = volume; fileSound.PendingSince = Time.realtimeSinceStartup; } else { PlayClip(((Object)(object)fileSound.Clip != (Object)null) ? fileSound.Clip : Fallback(value), volume); } } private FileSound FileEntry(string path) { DateTime lastWriteTimeUtc; try { if (string.IsNullOrEmpty(path) || !File.Exists(path)) { return null; } lastWriteTimeUtc = File.GetLastWriteTimeUtc(path); } catch (Exception) { return null; } if (!_files.TryGetValue(path, out var value)) { value = new FileSound(); _files[path] = value; } if (!value.Loading && value.Stamp != lastWriteTimeUtc) { value.Stamp = lastWriteTimeUtc; ((MonoBehaviour)this).StartCoroutine(Load(path, value)); } return value; } private IEnumerator Load(string path, FileSound sound) { sound.Loading = true; AudioClip clip = null; string error = null; bool flag = false; try { long length = new FileInfo(path).Length; flag = length > 20971520; if (flag) { error = "the file is larger than 20 MB"; } else if (length <= 4194304 && path.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)) { clip = LoadWav(path, out error); } } catch (Exception ex) { error = ex.Message; } UnityWebRequest request = null; UnityWebRequestAsyncOperation val = null; string earlierError = error; if ((Object)(object)clip == (Object)null && !flag) { try { request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, AudioTypeOf(path)); DownloadHandler downloadHandler = request.downloadHandler; DownloadHandlerAudioClip val2 = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (val2 != null) { val2.streamAudio = false; } val = request.SendWebRequest(); } catch (Exception ex2) { error = ex2.Message; } } if (val != null) { yield return val; } if (request != null) { try { if ((int)request.result == 1) { clip = DownloadHandlerAudioClip.GetContent(request); if ((Object)(object)clip != (Object)null && clip.samples <= 0) { clip = null; } error = (((Object)(object)clip != (Object)null) ? null : "no audio data"); } else { error = request.error; } } catch (Exception ex3) { error = ex3.Message; } finally { request.Dispose(); } if ((Object)(object)clip == (Object)null && earlierError != null) { error = earlierError + "; " + error; } } try { Finish(path, sound, clip, error); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"Could not use sound file {path}: {arg}"); } finally { sound.Loading = false; } } private void Finish(string path, FileSound sound, AudioClip clip, string error) { string text = SafeFileName(path); if ((Object)(object)clip != (Object)null) { ((Object)clip).name = "ChatSounds." + text; ((Object)clip).hideFlags = (HideFlags)32; if ((Object)(object)sound.Clip != (Object)null && (Object)(object)sound.Clip != (Object)(object)clip) { Object.Destroy((Object)(object)sound.Clip); } sound.Clip = clip; Plugin.Log.LogInfo((object)$"Loaded sound file {text} ({clip.length:0.##} s)"); } else { Plugin.Log.LogWarning((object)("Could not load sound file " + path + ": " + error)); } sound.Loading = false; if (sound.PendingVolume >= 0f) { float pendingVolume = sound.PendingVolume; sound.PendingVolume = -1f; if (Time.realtimeSinceStartup - sound.PendingSince <= 3f) { PlayClip(((Object)(object)sound.Clip != (Object)null) ? sound.Clip : Builtin("Ping"), pendingVolume); } } } private static AudioClip LoadWav(string path, out string error) { if (!WavReader.TryRead(File.ReadAllBytes(path), out var samples, out var channels, out var sampleRate, out error)) { return null; } AudioClip obj = AudioClip.Create("ChatSounds.wav", samples.Length / channels, channels, sampleRate, false); obj.SetData(samples, 0); return obj; } private static AudioType AudioTypeOf(string path) { return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch { ".ogg" => 14, ".mp3" => 13, ".wav" => 20, _ => 0, }); } private static string SafeFileName(string path) { try { return Path.GetFileName(path); } catch (ArgumentException) { return path; } } private void WarnOnce(string key, string message) { if (_warned.Add(key ?? string.Empty)) { Plugin.Log.LogWarning((object)message); } } private static FieldRef<SFXManager, SFXSettings> TrySfxSettingsRef() { try { return AccessTools.FieldRefAccess<SFXManager, SFXSettings>("_sfxSettings"); } catch (Exception) { return null; } } private void OnDestroy() { foreach (AudioClip value in _builtins.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } foreach (FileSound value2 in _files.Values) { if ((Object)(object)value2.Clip != (Object)null) { Object.Destroy((Object)(object)value2.Clip); } } } } internal enum SoundKind { Builtin, Game, File } internal readonly struct SoundRef { public const string GamePrefix = "game:"; public const string FilePrefix = "file:"; private static readonly string[] AudioExtensions = new string[3] { ".wav", ".ogg", ".mp3" }; public readonly SoundKind Kind; public readonly string Name; private SoundRef(SoundKind kind, string name) { Kind = kind; Name = name; } public static SoundRef Parse(string value) { string text = (value ?? string.Empty).Trim(); if (text.StartsWith("game:", StringComparison.OrdinalIgnoreCase)) { return new SoundRef(SoundKind.Game, text.Substring("game:".Length).Trim()); } if (text.StartsWith("file:", StringComparison.OrdinalIgnoreCase)) { return new SoundRef(SoundKind.File, Unquote(text.Substring("file:".Length).Trim())); } if (IsAudioFileName(text)) { return new SoundRef(SoundKind.File, Unquote(text)); } return new SoundRef(SoundKind.Builtin, text); } public static bool IsAudioFileName(string path) { if (string.IsNullOrEmpty(path)) { return false; } string text = path.Trim().Trim('"'); string[] audioExtensions = AudioExtensions; foreach (string value in audioExtensions) { if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public static string Friendly(string identifier) { if (string.IsNullOrEmpty(identifier)) { return string.Empty; } string text = ((identifier.Length > 5 && identifier.EndsWith("Sound", StringComparison.Ordinal)) ? identifier.Substring(0, identifier.Length - 5) : identifier); StringBuilder stringBuilder = new StringBuilder(text.Length + 4); for (int i = 0; i < text.Length; i++) { char c = text[i]; if (i > 0) { char c2 = text[i - 1]; bool flag = i + 1 < text.Length && char.IsLower(text[i + 1]); bool num = char.IsUpper(c) && (char.IsLower(c2) || char.IsDigit(c2) || (char.IsUpper(c2) && flag)); bool flag2 = char.IsDigit(c) && !char.IsDigit(c2); if (num || flag2) { stringBuilder.Append(' '); } } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static string Unquote(string text) { if (text.Length < 2 || text[0] != '"' || text[text.Length - 1] != '"') { return text; } return text.Substring(1, text.Length - 2); } } internal static class Synth { private readonly struct Partial { public readonly float Ratio; public readonly float Amplitude; public readonly float Decay; public Partial(float ratio, float amplitude, float decay) { Ratio = ratio; Amplitude = amplitude; Decay = decay; } } private sealed class Note { public float Start; public float Frequency; public float EndFrequency; public float Glide; public float Attack = 0.003f; public float Decay; public float Length; public float Gain = 1f; public Partial[] Partials; } public static readonly string[] Names = new string[8] { "Ping", "Chime", "Bubble", "Drop", "Marimba", "Bell", "Soft", "Alert" }; private const float PeakLevel = 0.85f; private const double ReleaseTime = 0.015; private static readonly Partial[] Sine = new Partial[1] { new Partial(1f, 1f, 1f) }; private static readonly Partial[] Clear = new Partial[3] { new Partial(1f, 1f, 1f), new Partial(2f, 0.22f, 0.55f), new Partial(3f, 0.07f, 0.35f) }; private static readonly Partial[] Round = new Partial[2] { new Partial(1f, 1f, 1f), new Partial(2f, 0.12f, 0.5f) }; private static readonly Partial[] Warm = new Partial[2] { new Partial(1f, 1f, 1f), new Partial(2f, 0.25f, 0.6f) }; private static readonly Partial[] Glass = new Partial[4] { new Partial(1f, 1f, 1f), new Partial(2f, 0.35f, 0.6f), new Partial(3f, 0.12f, 0.4f), new Partial(4.2f, 0.05f, 0.25f) }; private static readonly Partial[] Wood = new Partial[3] { new Partial(1f, 1f, 1f), new Partial(3.93f, 0.3f, 0.25f), new Partial(9.2f, 0.08f, 0.12f) }; private static readonly Partial[] Church = new Partial[5] { new Partial(1f, 1f, 1f), new Partial(2f, 0.55f, 0.7f), new Partial(2.76f, 0.38f, 0.5f), new Partial(5.4f, 0.22f, 0.3f), new Partial(8.93f, 0.1f, 0.18f) }; public static string Canonical(string name) { if (string.IsNullOrEmpty(name)) { return null; } string b = name.Trim(); string[] names = Names; foreach (string text in names) { if (string.Equals(text, b, StringComparison.OrdinalIgnoreCase)) { return text; } } return null; } public static float[] Render(string name, int sampleRate) { string name2 = Canonical(name); Note[] array = Preset(name2); if (array == null || sampleRate <= 0) { return null; } float num = 0f; Note[] array2 = array; foreach (Note note in array2) { num = Math.Max(num, note.Start + note.Length); } float[] array3 = new float[(int)Math.Ceiling(num * (float)sampleRate) + 1]; array2 = array; for (int i = 0; i < array2.Length; i++) { RenderNote(array2[i], array3, sampleRate); } Normalize(array3, 0.85f * Loudness(name2)); return array3; } private static void RenderNote(Note note, float[] buffer, int sampleRate) { int num = (int)Math.Round(note.Start * (float)sampleRate); int num2 = Math.Min((int)Math.Round(note.Length * (float)sampleRate), buffer.Length - num); double num3 = (double)sampleRate * 0.45; double[] array = new double[note.Partials.Length]; for (int i = 0; i < num2; i++) { double num4 = (double)i / (double)sampleRate; double num5 = note.Frequency; if (note.Glide > 0f && note.EndFrequency > 0f) { num5 *= Math.Pow(note.EndFrequency / note.Frequency, Math.Min(num4 / (double)note.Glide, 1.0)); } double num6 = ((num4 < (double)note.Attack) ? Math.Sin(Math.PI / 2.0 * num4 / (double)note.Attack) : 1.0); double num7 = (double)(num2 - 1 - i) / (double)sampleRate; if (num7 < 0.015) { num6 *= Math.Sin(Math.PI / 2.0 * num7 / 0.015); } double num8 = 0.0; for (int j = 0; j < array.Length; j++) { Partial partial = note.Partials[j]; double num9 = num5 * (double)partial.Ratio; array[j] += Math.PI * 2.0 * num9 / (double)sampleRate; if (num9 < num3) { num8 += (double)partial.Amplitude * Math.Exp((0.0 - num4) / (double)(note.Decay * partial.Decay)) * Math.Sin(array[j]); } } buffer[num + i] += (float)((double)note.Gain * num6 * num8); } } private static void Normalize(float[] buffer, float peak) { float num = 0f; foreach (float value in buffer) { num = Math.Max(num, Math.Abs(value)); } if (!(num <= 0f)) { float num2 = peak / num; for (int j = 0; j < buffer.Length; j++) { buffer[j] *= num2; } } } private static float Loudness(string name) { switch (name) { case "Ping": return 0.8f; case "Chime": return 0.85f; case "Drop": return 0.9f; case "Bell": return 0.75f; case "Alert": return 0.85f; case "Bubble": case "Marimba": return 0.95f; default: return 1f; } } private static Note[] Preset(string name) { return name switch { "Ping" => new Note[1] { new Note { Frequency = 1318.51f, Attack = 0.004f, Decay = 0.16f, Length = 0.55f, Partials = Clear } }, "Chime" => new Note[2] { new Note { Frequency = 783.99f, Decay = 0.3f, Length = 0.7f, Partials = Glass }, new Note { Start = 0.13f, Frequency = 1046.5f, Decay = 0.38f, Length = 0.85f, Partials = Glass } }, "Bubble" => new Note[2] { new Note { Frequency = 320f, EndFrequency = 1100f, Glide = 0.07f, Attack = 0.002f, Decay = 0.055f, Length = 0.16f, Partials = Round }, new Note { Start = 0.085f, Frequency = 450f, EndFrequency = 1500f, Glide = 0.06f, Attack = 0.002f, Decay = 0.05f, Length = 0.15f, Gain = 0.75f, Partials = Round } }, "Drop" => new Note[1] { new Note { Frequency = 1900f, EndFrequency = 650f, Glide = 0.11f, Attack = 0.002f, Decay = 0.07f, Length = 0.24f, Partials = Round } }, "Marimba" => new Note[2] { new Note { Frequency = 659.25f, Attack = 0.0015f, Decay = 0.11f, Length = 0.42f, Partials = Wood }, new Note { Start = 0.11f, Frequency = 880f, Attack = 0.0015f, Decay = 0.13f, Length = 0.5f, Partials = Wood } }, "Bell" => new Note[1] { new Note { Frequency = 880f, Attack = 0.002f, Decay = 0.55f, Length = 1.6f, Partials = Church } }, "Soft" => new Note[2] { new Note { Frequency = 440f, Attack = 0.02f, Decay = 0.32f, Length = 0.9f, Partials = Warm }, new Note { Frequency = 659.25f, Attack = 0.02f, Decay = 0.3f, Length = 0.9f, Gain = 0.45f, Partials = Sine } }, "Alert" => new Note[3] { new Note { Frequency = 1046.5f, Decay = 0.2f, Length = 0.45f, Partials = Clear }, new Note { Start = 0.09f, Frequency = 1318.51f, Decay = 0.2f, Length = 0.45f, Partials = Clear }, new Note { Start = 0.18f, Frequency = 1567.98f, Decay = 0.3f, Length = 0.7f, Partials = Clear } }, _ => null, }; } } internal sealed class UiSkin { private static readonly Color TextColor = Rgb(15133943); private static readonly Color LabelColor = Rgb(12832224); private static readonly Color MutedColor = Rgb(9345715); private static readonly Color AccentColor = Rgb(6990079); private static readonly Color AccentHover = Rgb(9157887); private static readonly Color WindowColor = Rgb(1514278, 0.97f); private static readonly Color WindowBorder = Rgb(3226197); private static readonly Color PanelColor = Rgb(1975346); private static readonly Color PanelBorder = Rgb(2765384); private static readonly Color FieldColor = Rgb(1251106); private static readonly Color ButtonColor = Rgb(2765382); private static readonly Color ButtonHover = Rgb(3489882); private static readonly Color ButtonActive = Rgb(4214899); private static readonly Color ButtonBorder = Rgb(3818848); private static readonly Color CheckColor = Rgb(1251106); private static readonly Color CheckBorder = Rgb(5924224); private static readonly Color TrackColor = Rgb(3818329); private static readonly Color ThumbColor = Rgb(14083327); public readonly GUIStyle Window; public readonly GUIStyle Panel; public readonly GUIStyle Row; public readonly GUIStyle Title; public readonly GUIStyle Label; public readonly GUIStyle ToggleLabel; public readonly GUIStyle SectionTitle; public readonly GUIStyle Value; public readonly GUIStyle Field; public readonly GUIStyle Hint; public readonly GUIStyle Button; public readonly GUIStyle SmallButton; public readonly GUIStyle CloseButton; public readonly GUIStyle Spacer; public readonly GUIStyle Check; public readonly GUIStyle Slider; public readonly GUIStyle Thumb; private readonly List<Texture2D> _textures = new List<Texture2D>(); public UiSkin() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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) //IL_004b: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_0076: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_00d8: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_0121: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Expected O, but got Unknown //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Expected O, but got Unknown //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Expected O, but got Unknown //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Expected O, but got Unknown //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Expected O, but got Unknown //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Expected O, but got Unknown //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Unknown result type (might be due to invalid IL or missing references) //IL_0396: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03e0: Expected O, but got Unknown //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_03f6: Expected O, but got Unknown //IL_0400: Unknown result type (might be due to invalid IL or missing references) //IL_040a: Expected O, but got Unknown //IL_0428: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Unknown result type (might be due to invalid IL or missing references) //IL_045d: Expected O, but got Unknown //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_0471: Expected O, but got Unknown //IL_048f: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Unknown result type (might be due to invalid IL or missing references) //IL_04d4: Expected O, but got Unknown //IL_04de: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Expected O, but got Unknown //IL_04f3: Unknown result type (might be due to invalid IL or missing references) //IL_0521: Unknown result type (might be due to invalid IL or missing references) //IL_0526: Unknown result type (might be due to invalid IL or missing references) //IL_0531: Unknown result type (might be due to invalid IL or missing references) //IL_0536: Unknown result type (might be due to invalid IL or missing references) //IL_0540: Expected O, but got Unknown //IL_0545: Expected O, but got Unknown //IL_0546: Unknown result type (might be due to invalid IL or missing references) //IL_054b: Unknown result type (might be due to invalid IL or missing references) //IL_0556: Unknown result type (might be due to invalid IL or missing references) //IL_0561: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_0570: Expected O, but got Unknown //IL_0575: Expected O, but got Unknown //IL_060a: Unknown result type (might be due to invalid IL or missing references) //IL_060f: Unknown result type (might be due to invalid IL or missing references) //IL_061a: Unknown result type (might be due to invalid IL or missing references) //IL_061f: Unknown result type (might be due to invalid IL or missing references) //IL_0629: Expected O, but got Unknown //IL_0629: Unknown result type (might be due to invalid IL or missing references) //IL_062e: Unknown result type (might be due to invalid IL or missing references) //IL_0638: Expected O, but got Unknown //IL_063d: Expected O, but got Unknown //IL_0654: Unknown result type (might be due to invalid IL or missing references) //IL_0659: Unknown result type (might be due to invalid IL or missing references) //IL_0664: Unknown result type (might be due to invalid IL or missing references) //IL_0674: Expected O, but got Unknown //IL_0675: Unknown result type (might be due to invalid IL or missing references) //IL_0682: Unknown result type (might be due to invalid IL or missing references) Texture2D background = Box(56, 56, 12f, WindowColor, WindowBorder, 1f, 10); Window = new GUIStyle { border = new RectOffset(24, 24, 24, 24), overflow = new RectOffset(10, 10, 10, 10), padding = new RectOffset(18, 18, 12, 14) }; Window.normal.background = background; Window.onNormal.background = background; Panel = new GUIStyle { border = new RectOffset(9, 9, 9, 9), padding = new RectOffset(12, 12, 8, 10), margin = new RectOffset(0, 0, 10, 0) }; Panel.normal.background = Box(24, 24, 8f, PanelColor, PanelBorder); Row = new GUIStyle { margin = new RectOffset(0, 0, 1, 1) }; Title = Text(16, TextColor, (FontStyle)1, (TextAnchor)3); Title.fixedHeight = 26f; Label = Text(13, LabelColor, (FontStyle)0, (TextAnchor)3); Label.fixedHeight = 24f; Texture2D background2 = Make(1, 1, (Color[])(object)new Color[1] { new Color(0f, 0f, 0f, 0f) }); ToggleLabel = Text(13, TextColor, (FontStyle)0, (TextAnchor)3); ToggleLabel.fixedHeight = 24f; ToggleLabel.padding = new RectOffset(8, 0, 0, 0); ToggleLabel.hover.textColor = Color.white; ToggleLabel.hover.background = background2; SectionTitle = Text(14, AccentColor, (FontStyle)1, (TextAnchor)3); SectionTitle.fixedHeight = 24f; SectionTitle.padding = new RectOffset(8, 0, 0, 0); SectionTitle.hover.textColor = AccentHover; SectionTitle.hover.background = background2; Value = Text(13, TextColor, (FontStyle)0, (TextAnchor)5); Value.fixedHeight = 24f; Field = Text(13, TextColor, (FontStyle)0, (TextAnchor)4); Field.fixedHeight = 24f; Field.border = new RectOffset(6, 6, 6, 6); Field.padding = new RectOffset(8, 8, 0, 0); Field.margin = new RectOffset(2, 2, 0, 0); Field.normal.background = Box(16, 16, 5f, FieldColor, PanelBorder); Hint = Text(11, MutedColor, (FontStyle)0, (TextAnchor)3); Hint.wordWrap = true; Hint.clipping = (TextClipping)0; Hint.margin = new RectOffset(0, 0, 2, 0); Texture2D normal = Box(16, 16, 6f, ButtonColor, ButtonBorder); Texture2D val = Box(16, 16, 6f, ButtonHover, ButtonBorder); Texture2D val2 = Box(16, 16, 6f, ButtonActive, AccentColor); Button = Text(13, TextColor, (FontStyle)0, (TextAnchor)4); Button.