Decompiled source of ChatSounds v1.0.0

ChatSounds.dll

Decompiled 9 hours ago
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.