Decompiled source of PickaxeSoundChanger v1.0.4

plugins/PickaxeSoundChanger.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("LuminairPrime")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Changes the high-pitched pickaxe sound to use the default axe sound.")]
[assembly: AssemblyFileVersion("1.0.4.0")]
[assembly: AssemblyInformationalVersion("1.0.4+d772a278c412db69091713670daa432af72fe4bf")]
[assembly: AssemblyProduct("PickaxeSoundChanger")]
[assembly: AssemblyTitle("PickaxeSoundChanger")]
[assembly: AssemblyVersion("1.0.4.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PickaxeSoundChanger
{
	internal static class DonorPool
	{
		internal enum Lookup
		{
			Found,
			Missing,
			WorldNotReady
		}

		private static Dictionary<string, AudioClip[]> Cache = new Dictionary<string, AudioClip[]>(StringComparer.Ordinal);

		private static HashSet<string> Swept = new HashSet<string>(StringComparer.Ordinal);

		internal static void Clear()
		{
			Cache = new Dictionary<string, AudioClip[]>(StringComparer.Ordinal);
			Swept = new HashSet<string>(StringComparer.Ordinal);
		}

		internal static void Learn(string name, AudioClip[]? clips)
		{
			if (clips != null && clips.Length != 0 && !Cache.ContainsKey(name))
			{
				Cache[name] = clips;
			}
		}

		internal static Lookup Resolve(string donorName, out AudioClip[]? clips)
		{
			clips = null;
			if (Cache.TryGetValue(donorName, out AudioClip[] value) && value.Length != 0)
			{
				clips = value;
				return Lookup.Found;
			}
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return Lookup.WorldNotReady;
			}
			GameObject prefab = instance.GetPrefab(donorName);
			AudioClip[] array = ((!((Object)(object)prefab != (Object)null)) ? null : prefab.GetComponent<ZSFX>()?.m_audioClips);
			if (array != null && array.Length != 0)
			{
				Cache[donorName] = array;
				clips = array;
				return Lookup.Found;
			}
			if (!Swept.Add(donorName))
			{
				return Lookup.Missing;
			}
			ZSFX[] array2 = Resources.FindObjectsOfTypeAll<ZSFX>();
			foreach (ZSFX val in array2)
			{
				if (!((Object)(object)val == (Object)null) && val.m_audioClips != null && val.m_audioClips.Length != 0 && Utils.GetPrefabName(((Component)val).gameObject) == donorName)
				{
					Cache[donorName] = val.m_audioClips;
					clips = val.m_audioClips;
					return Lookup.Found;
				}
			}
			return Lookup.Missing;
		}
	}
	[BepInPlugin("LuminairPrime.valheim.pickaxesoundchanger", "Pickaxe Sound Changer", "1.0.4")]
	public sealed class PickaxeSoundChangerPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "LuminairPrime.valheim.pickaxesoundchanger";

		public const string PluginName = "Pickaxe Sound Changer";

		public const string PluginVersion = "1.0.4";

		private const string GeneralSection = "00 - General";

		private static readonly string[] MappingSlots = new string[8] { "Mapping 1", "Mapping 2", "Mapping 3", "Mapping 4", "Mapping 5", "Mapping 6", "Mapping 7", "Mapping 8" };

		private static readonly (string Sound, string Pitch, string Volume)[] SlotDefaults = new(string, string, string)[3]
		{
			("sfx_pickaxe_hit = sfx_axe_hit", "0.95-1.0", "0.4-0.45"),
			("sfx_rock_hit = sfx_rock_destroyed", "1", "0.2"),
			("sfx_gui_repairitem_forge = sfx_gui_repairitem_workbench", "0.9-1.0", "0.6-0.7")
		};

		private const string MappingDescription = "Replace a sound using the format 'original = replacement'. Mute a sound with 'original = mute'. Enter one 'original' sound to tweak its volume/pitch.";

		private static readonly ConfigEntry<string>[] Mappings = new ConfigEntry<string>[MappingSlots.Length];

		private static readonly ConfigEntry<string>[] Pitches = new ConfigEntry<string>[MappingSlots.Length];

		private static readonly ConfigEntry<string>[] Volumes = new ConfigEntry<string>[MappingSlots.Length];

		private const string PitchDescription = "A value between 0.0 and 1.0. A two-value range chooses a random intermediate value. Empty field acts as default.";

		private const string VolumeDescription = "A value between 0.0 and 1.0. A two-value range chooses a random intermediate value. Empty field acts as default.";

		internal static ConfigEntry<string> ToggleKey = null;

		internal static ConfigEntry<bool> LogSoundNames = null;

		private static KeyboardShortcut _toggleShortcut = KeyboardShortcut.Empty;

		private Harmony? _harmony;

		internal static ManualLogSource Log { get; private set; } = null;

		internal static bool SoundChangesEnabled { get; private set; } = true;

		private static string SectionFor(int index)
		{
			return $"{index + 1:D2} - {MappingSlots[index]}";
		}

		private void Awake()
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			SoundChangesEnabled = true;
			ToggleKey = ((BaseUnityPlugin)this).Config.Bind<string>("00 - General", "Toggle Key", "k", "This key in Valheim switches the mod off and on so you can compare how things sound. Empty field disables hotkey.");
			ToggleKey.SettingChanged += OnToggleKeyChanged;
			ParseToggleKey();
			LogSoundNames = ((BaseUnityPlugin)this).Config.Bind<bool>("00 - General", "Log Sound Names", false, "Enable this to monitor Valheim's sound effects in the BepInEx console. You can copy a sound name into the empty mappings below to change it!");
			for (int i = 0; i < Mappings.Length; i++)
			{
				BindSlot(i);
			}
			ApplyMappings();
			_harmony = new Harmony("LuminairPrime.valheim.pickaxesoundchanger");
			_harmony.PatchAll(typeof(PickaxeSoundChangerPlugin).Assembly);
			Log.LogInfo((object)string.Format("{0} v{1} loaded ({2} mapping(s)). Sound changes start on for this game session.", "Pickaxe Sound Changer", "1.0.4", SoundMap.Count));
		}

		private void BindSlot(int index)
		{
			(string, string, string) tuple = ((index < SlotDefaults.Length) ? SlotDefaults[index] : (string.Empty, string.Empty, string.Empty));
			string text = SectionFor(index);
			Mappings[index] = ((BaseUnityPlugin)this).Config.Bind<string>(text, "Sound", tuple.Item1, "Replace a sound using the format 'original = replacement'. Mute a sound with 'original = mute'. Enter one 'original' sound to tweak its volume/pitch.");
			Volumes[index] = ((BaseUnityPlugin)this).Config.Bind<string>(text, "Volume", tuple.Item3, "A value between 0.0 and 1.0. A two-value range chooses a random intermediate value. Empty field acts as default.");
			Pitches[index] = ((BaseUnityPlugin)this).Config.Bind<string>(text, "Pitch", tuple.Item2, "A value between 0.0 and 1.0. A two-value range chooses a random intermediate value. Empty field acts as default.");
			Mappings[index].SettingChanged += OnMappingsChanged;
			Pitches[index].SettingChanged += OnMappingsChanged;
			Volumes[index].SettingChanged += OnMappingsChanged;
		}

		private void Update()
		{
			if (((KeyboardShortcut)(ref _toggleShortcut)).IsDown() && !IsTyping())
			{
				SoundChangesEnabled = !SoundChangesEnabled;
				Log.LogInfo((object)("Sound changes are now " + (SoundChangesEnabled ? "on" : "off") + " for this game session."));
			}
		}

		private static void ParseToggleKey()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			List<KeyCode> list = new List<KeyCode>();
			foreach (string item in ToggleKeyParser.Normalize(ToggleKey.Value))
			{
				if (!string.Equals(item, "None", StringComparison.OrdinalIgnoreCase))
				{
					if (!Enum.TryParse<KeyCode>(item, ignoreCase: true, out KeyCode result) || !Enum.IsDefined(typeof(KeyCode), result))
					{
						Log.LogWarning((object)("'Toggle Key' ignores '" + item + "': not the name of a Unity key. See the README for accepted spellings."));
					}
					else
					{
						list.Add(result);
					}
				}
			}
			_toggleShortcut = (KeyboardShortcut)((list.Count == 0) ? KeyboardShortcut.Empty : new KeyboardShortcut(list[0], list.Skip(1).ToArray()));
			if ((int)((KeyboardShortcut)(ref _toggleShortcut)).MainKey != 0)
			{
				Log.LogInfo((object)("Toggle key bound to " + string.Join(" + ", list) + "."));
			}
			else if (list.Count == 0 && ToggleKey.Value.Trim().Length > 0)
			{
				Log.LogWarning((object)("'Toggle Key' ('" + ToggleKey.Value.Trim() + "') holds no usable key, so nothing is bound."));
			}
		}

		private static void OnToggleKeyChanged(object? sender, EventArgs e)
		{
			ParseToggleKey();
		}

		private static bool IsTyping()
		{
			if ((!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Console.IsVisible())
			{
				return TextInput.IsVisible();
			}
			return true;
		}

		private void OnDestroy()
		{
			ToggleKey.SettingChanged -= OnToggleKeyChanged;
			for (int i = 0; i < Mappings.Length; i++)
			{
				Mappings[i].SettingChanged -= OnMappingsChanged;
				Pitches[i].SettingChanged -= OnMappingsChanged;
				Volumes[i].SettingChanged -= OnMappingsChanged;
			}
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
		}

		private static void OnMappingsChanged(object? sender, EventArgs e)
		{
			ApplyMappings();
			Log.LogInfo((object)$"Sound mappings reloaded ({SoundMap.Count} mapping(s)).");
		}

		private static void ApplyMappings()
		{
			List<(string, string, string, string)> list = new List<(string, string, string, string)>(Mappings.Length);
			for (int i = 0; i < Mappings.Length; i++)
			{
				list.Add((MappingSlots[i], Mappings[i].Value, Pitches[i].Value, Volumes[i].Value));
			}
			IReadOnlyList<string> readOnlyList = SoundMap.Rebuild(list);
			DonorPool.Clear();
			SfxAwakePatch.Reset();
			foreach (string item in readOnlyList)
			{
				Log.LogWarning((object)item);
			}
		}
	}
	[HarmonyPatch(typeof(ZSFX), "Awake")]
	internal static class SfxAwakePatch
	{
		private const int MaxReportedFailures = 64;

		private static readonly AudioClip[] Silent = Array.Empty<AudioClip>();

		private static HashSet<string> Warned = new HashSet<string>(StringComparer.Ordinal);

		private static HashSet<string> Swapped = new HashSet<string>(StringComparer.Ordinal);

		private static HashSet<string> Errored = new HashSet<string>(StringComparer.Ordinal);

		internal static void Reset()
		{
			Warned = new HashSet<string>(StringComparer.Ordinal);
			Swapped = new HashSet<string>(StringComparer.Ordinal);
			Errored = new HashSet<string>(StringComparer.Ordinal);
		}

		private static void Postfix(ZSFX __instance)
		{
			try
			{
				bool value = PickaxeSoundChangerPlugin.LogSoundNames.Value;
				if ((!value && (SoundMap.Count == 0 || !PickaxeSoundChangerPlugin.SoundChangesEnabled)) || (Object)(object)__instance == (Object)null)
				{
					return;
				}
				string prefabName = Utils.GetPrefabName(((Component)__instance).gameObject);
				if (string.IsNullOrEmpty(prefabName))
				{
					return;
				}
				if (value)
				{
					LogSoundName(__instance, prefabName);
				}
				if (!PickaxeSoundChangerPlugin.SoundChangesEnabled)
				{
					return;
				}
				if (SoundMap.IsDonor(prefabName))
				{
					DonorPool.Learn(prefabName, __instance.m_audioClips);
				}
				if (!SoundMap.TryGetDonor(prefabName, out string donor))
				{
					return;
				}
				if (SoundMap.IsNoDonor(donor))
				{
					ApplyTuningOnly(__instance, prefabName);
					return;
				}
				AudioClip[] array;
				if (SoundMap.IsMuteDonor(donor))
				{
					array = Silent;
				}
				else
				{
					AudioClip[] clips;
					DonorPool.Lookup lookup = DonorPool.Resolve(donor, out clips);
					if (lookup == DonorPool.Lookup.WorldNotReady)
					{
						return;
					}
					if (lookup != DonorPool.Lookup.Found || clips == null)
					{
						if (Warned.Add(donor))
						{
							PickaxeSoundChangerPlugin.Log.LogWarning((object)("Could not find the donor sound '" + donor + "' for '" + prefabName + "'; the original sound is unchanged."));
						}
						return;
					}
					array = clips;
				}
				if (SoundMap.TryGetTuning(prefabName, out var tuning) && !ApplyTuning(__instance, in tuning))
				{
					tuning = default(Tuning);
				}
				if (__instance.m_audioClips == array)
				{
					return;
				}
				__instance.m_audioClips = array;
				if (Swapped.Add(prefabName))
				{
					if (array == Silent)
					{
						PickaxeSoundChangerPlugin.Log.LogInfo((object)("'" + prefabName + "' is now silent."));
						return;
					}
					ReportIfMuted(__instance, prefabName);
					string text = Describe(in tuning);
					string text2 = ((text.Length == 0) ? string.Empty : ("; " + text));
					PickaxeSoundChangerPlugin.Log.LogInfo((object)$"'{prefabName}' now plays the clips from '{donor}' ({array.Length} clip(s){text2}).");
				}
			}
			catch (Exception e)
			{
				ReportOnce(e, "Failed to process a sound effect; it was left unchanged.");
			}
		}

		private static bool ApplyTuning(ZSFX zsfx, in Tuning tuning)
		{
			try
			{
				if (tuning.Pitch.Has)
				{
					zsfx.m_minPitch = tuning.Pitch.Min;
					zsfx.m_maxPitch = tuning.Pitch.Max;
				}
				if (tuning.Volume.Has)
				{
					zsfx.m_minVol = tuning.Volume.Min;
					zsfx.m_maxVol = tuning.Volume.Max;
				}
				return true;
			}
			catch (Exception e)
			{
				ReportOnce(e, "Could not fully apply pitch/volume tuning; the clip swap will still be attempted.");
				return false;
			}
		}

		private static void ApplyTuningOnly(ZSFX zsfx, string name)
		{
			if (SoundMap.TryGetTuning(name, out var tuning) && tuning.HasAny && ApplyTuning(zsfx, in tuning) && Swapped.Add(name))
			{
				ReportIfMuted(zsfx, name);
				PickaxeSoundChangerPlugin.Log.LogInfo((object)("'" + name + "' keeps its own clips and now plays at " + Describe(in tuning) + "."));
			}
		}

		private static string Describe(in Tuning tuning)
		{
			List<string> list = new List<string>();
			if (tuning.Pitch.Has)
			{
				list.Add("pitch " + tuning.Pitch.Text);
			}
			if (tuning.Volume.Has)
			{
				list.Add("volume " + tuning.Volume.Text);
			}
			return string.Join(", ", list);
		}

		private static void ReportIfMuted(ZSFX zsfx, string name)
		{
			try
			{
				AudioSource component = ((Component)zsfx).GetComponent<AudioSource>();
				if ((Object)(object)component != (Object)null && component.mute)
				{
					PickaxeSoundChangerPlugin.Log.LogWarning((object)("'" + name + "' is muted, so editing its clips will not be audible. Another mod or the game has muted this sound effect; unmute it there for this mapping to be heard."));
				}
			}
			catch (Exception e)
			{
				ReportOnce(e, "Could not check whether a sound effect is muted.");
			}
		}

		private static void LogSoundName(ZSFX zsfx, string name)
		{
			try
			{
				AudioSource component = ((Component)zsfx).GetComponent<AudioSource>();
				AudioClip[] audioClips = zsfx.m_audioClips;
				int num = ((audioClips != null) ? audioClips.Length : 0);
				PickaxeSoundChangerPlugin.Log.LogInfo((object)($"[sfx] {name} ({num} clip(s), mute={(Object)(object)component != (Object)null && component.mute}, " + $"vibration={zsfx.m_useVibration})"));
			}
			catch (Exception e)
			{
				ReportOnce(e, "Could not read a sound effect's diagnostic information.");
			}
		}

		private static void ReportOnce(Exception e, string what)
		{
			if (Errored.Count < 64 && Errored.Add(e.GetType().Name + ": " + e.Message))
			{
				PickaxeSoundChangerPlugin.Log.LogWarning((object)$"{what} {e}");
			}
		}
	}
	internal readonly struct TuningRange
	{
		internal readonly bool Has;

		internal readonly float Min;

		internal readonly float Max;

		internal readonly string Text;

		internal TuningRange(float min, float max, string text)
		{
			Has = true;
			Min = min;
			Max = max;
			Text = text;
		}
	}
	internal readonly struct Tuning
	{
		internal readonly TuningRange Pitch;

		internal readonly TuningRange Volume;

		internal bool HasAny
		{
			get
			{
				if (!Pitch.Has)
				{
					return Volume.Has;
				}
				return true;
			}
		}

		internal Tuning(TuningRange pitch, TuningRange volume)
		{
			Pitch = pitch;
			Volume = volume;
		}
	}
	internal static class SoundMap
	{
		private static readonly char[] ExtraSeparators = new char[3] { ';', '\r', '\n' };

		public const string PickaxeHitMapping = "sfx_pickaxe_hit = sfx_axe_hit";

		public const string PickaxeHitPitch = "0.95-1.0";

		public const string PickaxeHitVolume = "0.4-0.45";

		public const string RockHitMapping = "sfx_rock_hit = sfx_rock_destroyed";

		public const string RockHitPitch = "1";

		public const string RockHitVolume = "0.2";

		public const string ForgeRepairMapping = "sfx_gui_repairitem_forge = sfx_gui_repairitem_workbench";

		public const string ForgeRepairPitch = "0.9-1.0";

		public const string ForgeRepairVolume = "0.6-0.7";

		internal const string MuteDonor = "mute";

		internal const string NoDonor = "";

		private static Dictionary<string, string> _targets = new Dictionary<string, string>(StringComparer.Ordinal);

		private static HashSet<string> _donors = new HashSet<string>(StringComparer.Ordinal);

		private static Dictionary<string, Tuning> _tunings = new Dictionary<string, Tuning>(StringComparer.Ordinal);

		internal static int Count => _targets.Count;

		internal static bool TryGetTuning(string name, out Tuning tuning)
		{
			return _tunings.TryGetValue(name, out tuning);
		}

		internal static bool IsMuteDonor(string value)
		{
			return string.Equals(value, "mute", StringComparison.OrdinalIgnoreCase);
		}

		internal static bool IsNoDonor(string value)
		{
			return value.Length == 0;
		}

		internal static bool IsDonor(string name)
		{
			return _donors.Contains(name);
		}

		internal static bool TryGetDonor(string name, out string donor)
		{
			if (_targets.TryGetValue(name, out string value))
			{
				donor = value;
				return true;
			}
			donor = string.Empty;
			return false;
		}

		internal static IReadOnlyList<string> Rebuild(IReadOnlyList<(string Slot, string Value, string Pitch, string Volume)> slots)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			Dictionary<string, (string, string)> dictionary2 = new Dictionary<string, (string, string)>(StringComparer.Ordinal);
			List<string> list = new List<string>();
			Dictionary<string, Tuning> dictionary3 = new Dictionary<string, Tuning>(StringComparer.Ordinal);
			List<(string, string, string, string)> list2 = new List<(string, string, string, string)>();
			foreach (var slot in slots)
			{
				string item = slot.Slot;
				string item2 = slot.Value;
				string item3 = slot.Pitch;
				string item4 = slot.Volume;
				string text = item2.Trim();
				if (text.Length == 0)
				{
					continue;
				}
				if (text.IndexOfAny(ExtraSeparators) >= 0)
				{
					list.Add(item + " ('" + text + "') was dropped: each slot holds one 'target = donor' pair, so use another slot for a second mapping.");
					continue;
				}
				int num = text.IndexOf('=');
				string text2;
				string text3;
				if (num < 0)
				{
					if (!AcceptTuningOnly(item, text, item3, item4, list))
					{
						continue;
					}
					text2 = text;
					text3 = "";
				}
				else
				{
					if (text.IndexOf('=', num + 1) >= 0)
					{
						list.Add(item + " ('" + text + "') was dropped: only one '=' belongs in a slot.");
						continue;
					}
					text2 = text.Substring(0, num).Trim();
					text3 = text.Substring(num + 1).Trim();
					if (text2.Length == 0 || text3.Length == 0)
					{
						list.Add(item + " ('" + text + "') was dropped: both sides of '=' need a name.");
						continue;
					}
				}
				if (IsMuteDonor(text2))
				{
					list.Add(item + " ('" + text + "') was dropped: 'mute' is a donor, so it belongs on the right of the '='.");
					continue;
				}
				if (string.Equals(text2, text3, StringComparison.Ordinal))
				{
					list.Add(item + " ('" + text + "') was dropped: a sound cannot map to itself.");
					continue;
				}
				if (dictionary2.TryGetValue(text2, out var value))
				{
					list.Add(item + " ('" + text + "') overrides '" + value.Item2 + "' from " + value.Item1 + "; both target '" + text2 + "'.");
				}
				dictionary[text2] = text3;
				dictionary2[text2] = (item, text);
				for (int num2 = list2.Count - 1; num2 >= 0; num2--)
				{
					if (string.Equals(list2[num2].Item1, text2, StringComparison.Ordinal))
					{
						list2.RemoveAt(num2);
					}
				}
				if (!IsMuteDonor(text3))
				{
					list2.Add((text2, item, item3, item4));
				}
			}
			foreach (var item9 in list2)
			{
				string item5 = item9.Item1;
				string item6 = item9.Item2;
				string item7 = item9.Item3;
				string item8 = item9.Item4;
				Tuning value2 = new Tuning(ParseTuning(item6, "pitch", item7, list), ParseTuning(item6, "volume", item8, list));
				if (value2.HasAny)
				{
					dictionary3[item5] = value2;
				}
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (string value3 in dictionary.Values)
			{
				if (!IsMuteDonor(value3) && !IsNoDonor(value3))
				{
					hashSet.Add(value3);
				}
			}
			_targets = dictionary;
			_donors = hashSet;
			_tunings = dictionary3;
			return list;
		}

		private static bool AcceptTuningOnly(string slot, string text, string pitch, string volume, List<string> problems)
		{
			if (pitch.Trim().Length == 0 && volume.Trim().Length == 0)
			{
				problems.Add(slot + " ('" + text + "') was dropped: no '=' was found, and with no pitch or volume set there is nothing for it to change.");
				return false;
			}
			if (text.IndexOf(' ') >= 0 || text.IndexOf('\t') >= 0)
			{
				problems.Add(slot + " ('" + text + "') was dropped: no '=' was found.");
				return false;
			}
			return true;
		}

		private static TuningRange ParseTuning(string slot, string field, string value, List<string> problems)
		{
			string text = value.Trim();
			if (text.Length == 0)
			{
				return default(TuningRange);
			}
			string text2 = null;
			float result;
			bool flag = float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
			float result2 = result;
			if (!flag)
			{
				string[] array = text.Split('-');
				flag = array.Length == 2 && float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out result) && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result2);
			}
			if (!flag)
			{
				text2 = "expected a number like '0.95' or a range like '0.95-1.0'. Use a dot as the decimal separator.";
			}
			else if (!float.IsFinite(result) || !float.IsFinite(result2))
			{
				text2 = "expected a finite number like '0.95' or '0.95-1.0'.";
			}
			else if (field == "pitch" && (result <= 0f || result2 <= 0f))
			{
				text2 = "pitch must be greater than 0. Use the 'mute' donor to silence a sound.";
			}
			else if (field == "volume" && (result < 0f || result2 < 0f))
			{
				text2 = "volume cannot be negative.";
			}
			else if (result > result2)
			{
				text2 = "the first number must not be larger than the second.";
			}
			if (text2 != null)
			{
				problems.Add(slot + " " + field + " ('" + text + "') was ignored: " + text2);
				return default(TuningRange);
			}
			return new TuningRange(result, result2, text);
		}
	}
	internal static class ToggleKeyParser
	{
		private static readonly char[] Separators = new char[5] { ' ', '+', ',', ';', '|' };

		private static readonly Dictionary<string, string> Aliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["0"] = "Alpha0",
			["1"] = "Alpha1",
			["2"] = "Alpha2",
			["3"] = "Alpha3",
			["4"] = "Alpha4",
			["5"] = "Alpha5",
			["6"] = "Alpha6",
			["7"] = "Alpha7",
			["8"] = "Alpha8",
			["9"] = "Alpha9",
			["enter"] = "Return",
			["esc"] = "Escape",
			["ctrl"] = "LeftControl",
			["control"] = "LeftControl",
			["shift"] = "LeftShift",
			["alt"] = "LeftAlt",
			["spacebar"] = "Space"
		};

		internal static IReadOnlyList<string> Normalize(string? raw)
		{
			List<string> list = new List<string>();
			if (raw == null)
			{
				return list;
			}
			string[] array = raw.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in array)
			{
				if (Aliases.TryGetValue(text, out string value))
				{
					list.Add(value);
					continue;
				}
				char c = ((text.Length == 1) ? text[0] : '\0');
				bool flag = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
				list.Add(flag ? char.ToUpperInvariant(c).ToString() : text);
			}
			return list;
		}
	}
}