Decompiled source of Village Audio Overhaul Personal v1.1.0

BepInEx/plugins/VillageAudioOverhaul/DiceKingdoms.VillageAudioOverhaul.dll

Decompiled 16 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using DiceKingdoms.Game;
using DiceKingdoms.Interface;
using DiceKingdoms.Interface.Particles;
using DiceKingdoms.Meta;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Attributes;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("DiceKingdoms.VillageAudioOverhaul")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("DiceKingdoms.VillageAudioOverhaul")]
[assembly: AssemblyTitle("DiceKingdoms.VillageAudioOverhaul")]
[assembly: AssemblyVersion("1.1.0.0")]
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;
		}
	}
}
namespace DiceKingdoms.VillageAudioOverhaul
{
	public sealed class AudioFileHandler : DownloadHandler
	{
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate IntPtr CreateCall(IntPtr self, IntPtr url, AudioType type);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate IntPtr ClipCall(IntPtr self);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void StreamCall(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value);

		private static readonly CreateCall CreateAudio = IL2CPP.ResolveICall<CreateCall>("UnityEngine.Networking.DownloadHandlerAudioClip::Create");

		private static readonly ClipCall GetAudio = IL2CPP.ResolveICall<ClipCall>("UnityEngine.Networking.DownloadHandlerAudioClip::get_audioClip");

		private static readonly StreamCall SetStream = IL2CPP.ResolveICall<StreamCall>("UnityEngine.Networking.DownloadHandlerAudioClip::set_streamAudio");

		public AudioFileHandler(IntPtr pointer)
			: base(pointer)
		{
		}

		public AudioFileHandler()
			: base(ClassInjector.DerivedConstructorPointer<AudioFileHandler>())
		{
			ClassInjector.DerivedConstructorBody((Il2CppObjectBase)(object)this);
		}

		[HideFromIl2Cpp]
		internal void Initialize(string uri, AudioType type, bool streaming)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			((DownloadHandler)this).m_Ptr = CreateAudio(((Il2CppObjectBase)this).Pointer, IL2CPP.ManagedStringToIl2Cpp(uri), type);
			if (((DownloadHandler)this).m_Ptr == IntPtr.Zero)
			{
				throw new IOException("Unity could not create its audio download handler");
			}
			SetStream(((Il2CppObjectBase)this).Pointer, streaming);
		}

		[HideFromIl2Cpp]
		internal AudioClip? GetClip()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			IntPtr intPtr = GetAudio(((Il2CppObjectBase)this).Pointer);
			if (!(intPtr == IntPtr.Zero))
			{
				return new AudioClip(intPtr);
			}
			return null;
		}
	}
	public sealed class AudioMap
	{
		public Dictionary<string, string[]> Music { get; set; } = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);

		public Dictionary<string, string[]> Sfx { get; set; } = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);

		public Dictionary<string, string> ClipEvents { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		public Dictionary<string, string> ResourceEvents { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		public Dictionary<string, string> BuildingEvents { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		public static AudioMap Read(string path)
		{
			AudioMap audioMap = (File.Exists(path) ? (JsonSerializer.Deserialize<AudioMap>(File.ReadAllText(path), new JsonSerializerOptions
			{
				PropertyNameCaseInsensitive = true,
				ReadCommentHandling = JsonCommentHandling.Skip
			}) ?? throw new InvalidDataException("Empty audio map")) : new AudioMap());
			if (audioMap.Music == null || audioMap.Sfx == null || audioMap.ClipEvents == null || audioMap.ResourceEvents == null || audioMap.BuildingEvents == null)
			{
				throw new InvalidDataException("Map sections cannot be null");
			}
			audioMap.Music = new Dictionary<string, string[]>(audioMap.Music, StringComparer.OrdinalIgnoreCase);
			audioMap.Sfx = new Dictionary<string, string[]>(audioMap.Sfx, StringComparer.OrdinalIgnoreCase);
			audioMap.ClipEvents = new Dictionary<string, string>(audioMap.ClipEvents, StringComparer.OrdinalIgnoreCase);
			audioMap.ResourceEvents = new Dictionary<string, string>(audioMap.ResourceEvents, StringComparer.OrdinalIgnoreCase);
			audioMap.BuildingEvents = new Dictionary<string, string>(audioMap.BuildingEvents, StringComparer.OrdinalIgnoreCase);
			return audioMap;
		}

		public static string? SafeFile(string root, string relative)
		{
			if (string.IsNullOrWhiteSpace(relative) || Path.IsPathRooted(relative))
			{
				return null;
			}
			string text = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
			string fullPath = Path.GetFullPath(Path.Combine(text, relative));
			if (!fullPath.StartsWith(text, StringComparison.OrdinalIgnoreCase))
			{
				return null;
			}
			FileSystemInfo fileSystemInfo = new FileInfo(fullPath);
			while (fileSystemInfo != null && fileSystemInfo.FullName.Length >= text.Length)
			{
				if (fileSystemInfo.Exists && (fileSystemInfo.Attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					return null;
				}
				fileSystemInfo = ((fileSystemInfo is FileInfo fileInfo) ? fileInfo.Directory : ((DirectoryInfo)fileSystemInfo).Parent);
			}
			if (!Supported(fullPath))
			{
				return null;
			}
			return fullPath;
		}

		public static bool Supported(string path)
		{
			string text = Path.GetExtension(path).ToLowerInvariant();
			return text == ".wav" || text == ".ogg" || text == ".mp3";
		}

		public static IEnumerable<string> Files(string root, string key, Dictionary<string, string[]> entries)
		{
			if (entries.TryGetValue(key, out string[] value))
			{
				return (from p in value ?? Array.Empty<string>()
					select SafeFile(root, p) into p
					where p != null && File.Exists(p)
					select p).Cast<string>().Distinct<string>(StringComparer.OrdinalIgnoreCase);
			}
			List<string> list = new List<string>();
			string[] array = new string[3] { ".ogg", ".wav", ".mp3" };
			foreach (string text in array)
			{
				string text2 = SafeFile(root, key + text);
				if (text2 != null && File.Exists(text2))
				{
					list.Add(text2);
				}
			}
			string text3 = SafeFile(root, key + "/placeholder.ogg");
			if (text3 != null && Directory.Exists(Path.GetDirectoryName(text3)))
			{
				list.AddRange((from p in Directory.EnumerateFiles(Path.GetDirectoryName(text3)).Where(Supported)
					select SafeFile(root, Path.GetRelativePath(root, p)) into p
					where p != null
					select p).Cast<string>());
			}
			return list.Distinct<string>(StringComparer.OrdinalIgnoreCase).OrderBy<string, string>((string p) => p, StringComparer.OrdinalIgnoreCase);
		}
	}
	public sealed class ShuffleBag
	{
		private readonly Random random;

		private readonly Queue<int> remaining = new Queue<int>();

		private int last = -1;

		private int size = -1;

		public ShuffleBag(int? seed = null)
		{
			random = (seed.HasValue ? new Random(seed.Value) : new Random());
		}

		public int Next(int count)
		{
			if (count < 1)
			{
				throw new ArgumentOutOfRangeException("count");
			}
			if (size != count)
			{
				remaining.Clear();
				size = count;
				last = -1;
			}
			if (remaining.Count == 0)
			{
				int[] array = (from _ in Enumerable.Range(0, count)
					orderby random.Next()
					select _).ToArray();
				if (count > 1 && array[0] == last)
				{
					ref int reference = ref array[0];
					ref int reference2 = ref array[1];
					int num = array[1];
					int num2 = array[0];
					reference = num;
					reference2 = num2;
				}
				int[] array2 = array;
				foreach (int item in array2)
				{
					remaining.Enqueue(item);
				}
			}
			return last = remaining.Dequeue();
		}
	}
	public sealed class AudioRuntime : MonoBehaviour
	{
		private sealed class Snapshot
		{
			internal Sound Sound;

			internal AudioClip[] Clips = Array.Empty<AudioClip>();

			internal float Min;

			internal float Max;

			internal float MinPitch;

			internal float MaxPitch;

			internal bool Loop;

			internal bool Changed;

			internal string Event = "";

			internal bool FieldAssigned;

			internal bool Ambiguous;

			internal void Restore()
			{
				if (Object.op_Implicit((Object)(object)Sound) && Changed)
				{
					Sound.clips = new Il2CppReferenceArray<AudioClip>(Clips);
					Sound.minVolume = Min;
					Sound.maxVolume = Max;
					Sound.minPitch = MinPitch;
					Sound.maxPitch = MaxPitch;
					Sound.loop = Loop;
					Changed = false;
				}
			}
		}

		private sealed class Selection
		{
			internal Music Music;

			internal float OriginalDuration;

			internal string Context = "";

			internal Sound? Selected;

			internal readonly Dictionary<string, Sound> Sounds = new Dictionary<string, Sound>();

			internal bool JingleDone;

			internal bool HasPlayed;
		}

		private sealed record Plan(AudioMap Map, Dictionary<string, string[]> Music, Dictionary<string, string[]> Sfx);

		private Task<Plan>? planning;

		private Plan? plan;

		private readonly Dictionary<string, AudioClip> clips = new Dictionary<string, AudioClip>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, AudioClip[]> music = new Dictionary<string, AudioClip[]>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, AudioClip[]> sfx = new Dictionary<string, AudioClip[]>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, ShuffleBag> bags = new Dictionary<string, ShuffleBag>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<int, Snapshot> snapshots = new Dictionary<int, Snapshot>();

		private readonly Dictionary<int, Selection> selections = new Dictionary<int, Selection>();

		private readonly HashSet<int> ownedSounds = new HashSet<int>();

		private readonly HashSet<int> seen = new HashSet<int>();

		private readonly List<Sound> created = new List<Sound>();

		private readonly List<AudioSources> pools = new List<AudioSources>();

		private readonly Dictionary<int, AudioSource> muted = new Dictionary<int, AudioSource>();

		private Queue<string> pending = new Queue<string>();

		private HashSet<string> streamingPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private UnityWebRequest? request;

		private AudioFileHandler? handler;

		private string loading = "";

		private bool loadingStream;

		private long decodedBytes;

		private bool stopped;

		private int scene = int.MinValue;

		private int scans;

		private float nextScan;

		private MainMenu? activeMenu;

		private AudioSources? menuPool;

		private PooledSource? menuSlot;

		private long menuTicket;

		private Snapshot? menuSnapshot;

		private readonly Dictionary<string, Sound> effectSounds = new Dictionary<string, Sound>();

		private readonly HashSet<int> effectSoundIds = new HashSet<int>();

		private readonly HashSet<IntPtr> effectClipPointers = new HashSet<IntPtr>();

		private readonly Dictionary<string, float> resourcePlayedAt = new Dictionary<string, float>();

		private readonly Dictionary<int, (BuildingVisual Visual, float Health)> buildingHealth = new Dictionary<int, (BuildingVisual, float)>();

		private Resource? resourceContext;

		private string? buildingContext;

		private string? unitContext;

		private int resourceFrame = -1;

		private int buildingFrame = -1;

		private int unitFrame = -1;

		public bool Ready { get; private set; }

		public int LoadedClipCount => clips.Count;

		public int ReplacementSoundCount => snapshots.Values.Count((Snapshot s) => s.Changed);

		public string LastContext { get; private set; } = "";

		public IntPtr LastStockTrackPointer { get; private set; }

		public string LastEffectEvent { get; private set; } = "";

		public string LastEffectClip { get; private set; } = "";

		public int SuppressedResourceRepeats { get; private set; }

		public AudioRuntime(IntPtr pointer)
			: base(pointer)
		{
		}

		internal bool OwnsSound(Sound? sound)
		{
			if ((Object)(object)sound != (Object)null && Object.op_Implicit((Object)(object)sound))
			{
				return ownedSounds.Contains(((Object)sound).GetInstanceID());
			}
			return false;
		}

		internal float OriginalDuration(Music item)
		{
			if (!selections.TryGetValue(((Object)item).GetInstanceID(), out Selection value))
			{
				return item.transitionDuration;
			}
			return value.OriginalDuration;
		}

		public void Start()
		{
			string root = Path.Combine(Plugin.Folder, "Audio");
			planning = Task.Run(delegate
			{
				AudioMap audioMap = AudioMap.Read(Path.Combine(Plugin.Folder, "audio-map.json"));
				Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
				Dictionary<string, string[]> dictionary2 = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
				foreach (string item in new string[10] { "menu", "lobby", "start", "harvest", "disaster", "building", "planning", "combat", "victory", "defeat" }.Concat(audioMap.Music.Keys).Distinct())
				{
					dictionary[item] = AudioMap.Files(root, item, audioMap.Music).Take(32).ToArray();
				}
				string[] second = new string[8] { "ui_click", "ui_hover", "building_place", "building_rotate", "dice_move", "dice_skull", "harvest_resource", "dice_pop" };
				foreach (string item2 in audioMap.ClipEvents.Values.Where((string v) => !v.StartsWith("music:")).Concat(audioMap.Sfx.Keys).Concat(second)
					.Distinct())
				{
					dictionary2[item2] = AudioMap.Files(root, item2, audioMap.Sfx).Take(32).ToArray();
				}
				return new Plan(audioMap, dictionary, dictionary2);
			});
		}

		public void Update()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			if (stopped)
			{
				return;
			}
			try
			{
				if (!Ready)
				{
					LoadNext();
				}
				UpdateMenuPlaylist();
				if (Plugin.Dirty)
				{
					Plugin.Dirty = false;
					RefreshSettings();
				}
				Scene activeScene = SceneManager.GetActiveScene();
				int handle = ((Scene)(ref activeScene)).handle;
				if (handle != scene)
				{
					scene = handle;
					scans = 0;
					nextScan = Time.realtimeSinceStartup + 0.5f;
					Prune();
				}
				if (Ready && scans < 3 && Time.realtimeSinceStartup >= nextScan)
				{
					Scan();
					scans++;
					nextScan = Time.realtimeSinceStartup + 2f;
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		private void LoadNext()
		{
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Invalid comparison between Unknown and I4
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0301: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_035b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0365: Expected O, but got Unknown
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b2: Expected O, but got Unknown
			if (plan == null)
			{
				if (planning == null || !planning.IsCompleted)
				{
					return;
				}
				try
				{
					plan = planning.GetAwaiter().GetResult();
				}
				catch (Exception ex)
				{
					Plugin.Instance.Error(ex);
					Ready = true;
					return;
				}
				pending = new Queue<string>(plan.Music.Values.Concat<string[]>(plan.Sfx.Values).SelectMany((string[] x) => x).Distinct<string>(StringComparer.OrdinalIgnoreCase)
					.Take(256));
				streamingPaths = new HashSet<string>(plan.Music.Values.SelectMany((string[] paths) => paths), StringComparer.OrdinalIgnoreCase);
			}
			if (request != null)
			{
				if (!request.isDone)
				{
					return;
				}
				try
				{
					if ((int)request.result != 1)
					{
						throw new IOException(Path.GetFileName(loading) + ": " + request.error);
					}
					AudioClip clip = handler.GetClip();
					if ((Object)(object)clip == (Object)null || !Object.op_Implicit((Object)(object)clip) || clip.samples <= 0 || clip.channels <= 0 || clip.length <= 0.01f)
					{
						if (Object.op_Implicit((Object)(object)clip))
						{
							Object.Destroy((Object)(object)clip);
						}
						throw new InvalidDataException("Invalid audio: " + Path.GetFileName(loading));
					}
					long num = (loadingStream ? (new FileInfo(loading).Length + 2L * (long)clip.frequency * clip.channels * 4) : ((long)clip.samples * (long)clip.channels * 4));
					if (decodedBytes + num > 268435456)
					{
						Object.Destroy((Object)(object)clip);
						throw new InvalidDataException("256 MiB decoded audio budget reached: " + Path.GetFileName(loading));
					}
					decodedBytes += num;
					((Object)clip).name = "VillageAudio/" + Path.GetFileName(loading);
					((Object)clip).hideFlags = (HideFlags)61;
					clips.Add(loading, clip);
				}
				catch (Exception ex2)
				{
					Plugin.Instance.Error(ex2);
				}
				finally
				{
					request.Dispose();
					request = null;
					handler = null;
				}
			}
			if (pending.Count > 0)
			{
				loading = pending.Dequeue();
				string text = Path.GetExtension(loading).ToLowerInvariant();
				AudioType val = ((text == ".wav") ? ((AudioType)20) : ((!(text == ".ogg")) ? ((AudioType)13) : ((AudioType)14)));
				AudioType type = val;
				loadingStream = streamingPaths.Contains(loading);
				handler = new AudioFileHandler();
				try
				{
					string absoluteUri = new Uri(loading).AbsoluteUri;
					handler.Initialize(absoluteUri, type, loadingStream);
					request = new UnityWebRequest(absoluteUri, "GET", (DownloadHandler)(object)handler, (UploadHandler)null);
				}
				catch
				{
					((DownloadHandler)handler).Dispose();
					handler = null;
					throw;
				}
				request.timeout = 20;
				request.SendWebRequest();
				return;
			}
			foreach (KeyValuePair<string, string[]> item in plan.Music)
			{
				music[item.Key] = (from p in item.Value.Where(clips.ContainsKey)
					select clips[p]).ToArray();
			}
			foreach (KeyValuePair<string, string[]> item2 in plan.Sfx)
			{
				sfx[item2.Key] = (from p in item2.Value.Where(clips.ContainsKey)
					select clips[p]).ToArray();
			}
			Ready = true;
			scans = 0;
			ManualLogSource log = ((BasePlugin)Plugin.Instance).Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(103, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Audio pack ready: ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(clips.Count);
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" clips, ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<float>((float)decodedBytes / 1048576f, "F1");
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" MiB budgeted (music streams). Missing or invalid entries retain stock audio.");
			}
			log.LogInfo(val2);
		}

		private void Prune()
		{
			int[] array = (from p in snapshots
				where !Object.op_Implicit((Object)(object)p.Value.Sound)
				select p.Key).ToArray();
			foreach (int key in array)
			{
				snapshots.Remove(key);
			}
			array = (from p in selections
				where !Object.op_Implicit((Object)(object)p.Value.Music)
				select p.Key).ToArray();
			foreach (int key2 in array)
			{
				DestroySelection(selections[key2]);
				selections.Remove(key2);
			}
			pools.RemoveAll((AudioSources p) => !Object.op_Implicit((Object)(object)p));
			array = (from p in muted
				where !Object.op_Implicit((Object)(object)p.Value)
				select p.Key).ToArray();
			foreach (int key3 in array)
			{
				muted.Remove(key3);
			}
			PruneContextSounds();
		}

		private void Scan()
		{
			Prune();
			foreach (AudioSources pool in Resources.FindObjectsOfTypeAll<AudioSources>())
			{
				if (Object.op_Implicit((Object)(object)pool) && !pools.Any((AudioSources p) => ((Il2CppObjectBase)p).Pointer == ((Il2CppObjectBase)pool).Pointer))
				{
					pools.Add(pool);
				}
			}
			foreach (MainMenu item in Resources.FindObjectsOfTypeAll<MainMenu>())
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					RegisterMenu(item);
				}
			}
			foreach (Music item2 in Resources.FindObjectsOfTypeAll<Music>())
			{
				if (Object.op_Implicit((Object)(object)item2))
				{
					RegisterMusic(item2);
				}
			}
			RegisterFields<BaseButton>(new(string, string)[2]
			{
				("clickSound", "ui_click"),
				("hoverSound", "ui_hover")
			});
			RegisterFields<Toggle>(new(string, string)[1] { ("clickSound", "ui_click") });
			RegisterFields<BuildingPlacer>(new(string, string)[2]
			{
				("placeSound", "building_place"),
				("rotateSound", "building_rotate")
			});
			RegisterFields<BuildingBulkPlacer>(new(string, string)[2]
			{
				("placeSound", "building_place"),
				("rotateSound", "building_rotate")
			});
			RegisterFields<HarvestInterface>(new(string, string)[4]
			{
				("moveDiceSound", "dice_move"),
				("skullDiceSound", "dice_skull"),
				("harvestResourceSound", "harvest_resource"),
				("dicePopSound", "dice_pop")
			});
			RegisterFields<ResourceStorageInterface>(new(string, string)[1] { ("incrementSound", "harvest_resource") });
			RegisterFields<ResourceParticle>(new(string, string)[1] { ("resourceSound", "harvest_resource") });
			foreach (Sound item3 in Resources.FindObjectsOfTypeAll<Sound>())
			{
				if (Object.op_Implicit((Object)(object)item3))
				{
					ApplySound(item3);
				}
			}
		}

		[HideFromIl2Cpp]
		private void RegisterFields<T>(params (string Property, string Event)[] fields) where T : Object
		{
			(PropertyInfo, string)[] array = fields.Select(((string Property, string Event) f) => (Property: typeof(T).GetProperty(f.Property), Event: f.Event)).ToArray();
			foreach (T item in Resources.FindObjectsOfTypeAll<T>())
			{
				(PropertyInfo, string)[] array2 = array;
				for (int num = 0; num < array2.Length; num++)
				{
					(PropertyInfo, string) tuple = array2[num];
					object? obj = tuple.Item1?.GetValue(item);
					Sound val = (Sound)((obj is Sound) ? obj : null);
					if (val == null || !Object.op_Implicit((Object)(object)val))
					{
						continue;
					}
					Snapshot snapshot = Remember(val);
					if (snapshot.Ambiguous)
					{
						continue;
					}
					if (snapshot.FieldAssigned && snapshot.Event != tuple.Item2)
					{
						if (snapshot.Changed)
						{
							RebindPlaying(snapshot, snapshot.Clips, replacing: false);
							snapshot.Restore();
						}
						snapshot.Ambiguous = true;
						Plugin.Instance.Debug("Shared Sound '" + ((Object)val).name + "' has different semantic roles; keeping stock audio.");
					}
					else
					{
						if (snapshot.Event != tuple.Item2 && snapshot.Changed)
						{
							RebindPlaying(snapshot, snapshot.Clips, replacing: false);
							snapshot.Restore();
						}
						snapshot.Event = tuple.Item2;
						snapshot.FieldAssigned = true;
					}
				}
			}
		}

		public void RegisterMenu(MainMenu menu)
		{
			activeMenu = menu;
			if (Object.op_Implicit((Object)(object)menu.menuMusic))
			{
				Remember(menu.menuMusic).Event = "music:menu";
				ApplySound(menu.menuMusic);
			}
		}

		internal void DisableMenu(MainMenu menu)
		{
			if (!Object.op_Implicit((Object)(object)activeMenu) || ((Il2CppObjectBase)activeMenu).Pointer != ((Il2CppObjectBase)menu).Pointer)
			{
				return;
			}
			if (menuSlot != null && menuSlot.CurrentTicket == menuTicket)
			{
				Snapshot? snapshot = menuSnapshot;
				if (snapshot != null && snapshot.Changed && Object.op_Implicit((Object)(object)menuSlot.audioSource))
				{
					menuSlot.audioSource.Stop();
				}
			}
			activeMenu = null;
			menuSlot = null;
			menuPool = null;
		}

		internal void PreparePlay(Sound sound)
		{
			ApplySound(sound);
			if (Object.op_Implicit((Object)(object)sound) && snapshots.TryGetValue(((Object)sound).GetInstanceID(), out Snapshot value) && value.Changed && !(value.Event != "music:menu"))
			{
				AudioClip[] array = Playlist("menu");
				if (array != null && array.Length != 0)
				{
					sound.clips = new Il2CppReferenceArray<AudioClip>((AudioClip[])(object)new AudioClip[1] { array[Bag("menu").Next(array.Length)] });
					sound.loop = false;
				}
			}
		}

		internal void CaptureMenuSource(AudioSources pool, Sound sound, AllocatedSource source)
		{
			object obj;
			if (source == null)
			{
				obj = null;
			}
			else
			{
				PooledSource pooledSource = source.pooledSource;
				obj = ((pooledSource != null) ? pooledSource.audioSource : null);
			}
			AudioSource val = (AudioSource)obj;
			if (val != null && Object.op_Implicit((Object)(object)val))
			{
				SetMuted(val, ((Object.op_Implicit((Object)(object)sound) && effectSoundIds.Contains(((Object)sound).GetInstanceID())) ? new float?(Plugin.SfxVolume.Value) : ((Object.op_Implicit((Object)(object)sound) && ownedSounds.Contains(((Object)sound).GetInstanceID())) ? new float?(Plugin.MusicVolume.Value) : ((Object.op_Implicit((Object)(object)sound) && snapshots.TryGetValue(((Object)sound).GetInstanceID(), out Snapshot value) && value.Changed) ? new float?(value.Event.StartsWith("music:") ? Plugin.MusicVolume.Value : Plugin.SfxVolume.Value) : ((float?)null)))) == 0f);
			}
			if (Object.op_Implicit((Object)(object)sound) && source != null && snapshots.TryGetValue(((Object)sound).GetInstanceID(), out Snapshot value2) && value2.Changed && !(value2.Event != "music:menu"))
			{
				menuPool = pool;
				menuSlot = source.pooledSource;
				menuTicket = source.ticket;
				menuSnapshot = value2;
			}
		}

		private void SetMuted(AudioSource source, bool mute)
		{
			int instanceID = ((Object)source).GetInstanceID();
			if (mute)
			{
				if (!source.mute)
				{
					source.mute = true;
					muted[instanceID] = source;
				}
			}
			else if (muted.Remove(instanceID))
			{
				source.mute = false;
			}
		}

		private void UpdateMenuPlaylist()
		{
			if (!Ready || !Plugin.MusicEnabled.Value || !Object.op_Implicit((Object)(object)activeMenu) || !((Behaviour)activeMenu).isActiveAndEnabled)
			{
				return;
			}
			Snapshot? snapshot = menuSnapshot;
			if (snapshot == null || !snapshot.Changed || menuSlot == null || !Object.op_Implicit((Object)(object)menuPool) || AudioListener.pause)
			{
				return;
			}
			if (menuSlot.CurrentTicket != menuTicket)
			{
				menuPool.Play(menuSnapshot.Sound, new Nullable<Vector3>());
				return;
			}
			AudioSource audioSource = menuSlot.audioSource;
			if (Object.op_Implicit((Object)(object)audioSource) && !audioSource.isPlaying)
			{
				AudioClip[] array = Playlist("menu");
				if (array != null && array.Length != 0)
				{
					audioSource.clip = array[Bag("menu").Next(array.Length)];
					audioSource.loop = false;
					audioSource.Play();
				}
			}
		}

		public void RegisterMusic(Music item)
		{
			int instanceID = ((Object)item).GetInstanceID();
			if (!selections.ContainsKey(instanceID))
			{
				selections.Add(instanceID, new Selection
				{
					Music = item,
					OriginalDuration = item.transitionDuration
				});
			}
		}

		public void ForgetMusic(Music item)
		{
			if (!selections.Remove(((Object)item).GetInstanceID(), out Selection value))
			{
				return;
			}
			if (OwnsSound(item.currentTrack))
			{
				StopIfAlive(item.currentSource);
			}
			int num = 0;
			while (item.dyingSources != null && num < item.dyingSources.Count)
			{
				if (OwnedAllocation(item.dyingSources[num]))
				{
					StopIfAlive(item.dyingSources[num]);
				}
				num++;
			}
			item.dyingSources?.Clear();
			item.transitionDuration = value.OriginalDuration;
			DestroySelection(value);
		}

		[HideFromIl2Cpp]
		private Snapshot Remember(Sound sound)
		{
			int instanceID = ((Object)sound).GetInstanceID();
			if (snapshots.TryGetValue(instanceID, out Snapshot value))
			{
				return value;
			}
			Snapshot snapshot = new Snapshot
			{
				Sound = sound,
				Clips = (((IEnumerable<AudioClip>)sound.clips)?.ToArray() ?? Array.Empty<AudioClip>()),
				Min = sound.minVolume,
				Max = sound.maxVolume,
				MinPitch = sound.minPitch,
				MaxPitch = sound.maxPitch,
				Loop = sound.loop
			};
			snapshots.Add(instanceID, snapshot);
			return snapshot;
		}

		public void ApplySound(Sound sound)
		{
			if (stopped || !Object.op_Implicit((Object)(object)sound) || ownedSounds.Contains(((Object)sound).GetInstanceID()) || effectSoundIds.Contains(((Object)sound).GetInstanceID()))
			{
				return;
			}
			if (Plugin.DebugLog.Value && seen.Add(((Object)sound).GetInstanceID()))
			{
				Plugin.Instance.Debug("Sound '" + ((Object)sound).name + "': " + string.Join(", ", ((IEnumerable<AudioClip>)sound.clips)?.Select((AudioClip c) => (!Object.op_Implicit((Object)(object)c)) ? "null" : ((Object)c).name) ?? Array.Empty<string>()));
			}
			if (!Ready || plan == null)
			{
				return;
			}
			Snapshot snapshot = Remember(sound);
			if (snapshot.Ambiguous)
			{
				return;
			}
			if (snapshot.Event == "")
			{
				string[] array = (from c in snapshot.Clips
					where Object.op_Implicit((Object)(object)c)
					select plan.Map.ClipEvents.GetValueOrDefault(((Object)c).name)).Distinct().ToArray();
				if (array.Length == 1 && array[0] != null && !array[0].StartsWith("music:"))
				{
					snapshot.Event = array[0];
				}
			}
			bool flag = snapshot.Event.StartsWith("music:");
			AudioClip[] array2 = (flag ? Playlist(snapshot.Event.Substring(6)) : sfx.GetValueOrDefault(snapshot.Event));
			if (!(flag ? Plugin.MusicEnabled.Value : Plugin.SfxEnabled.Value) || array2 == null || array2.Length == 0)
			{
				if (snapshot.Changed)
				{
					RebindPlaying(snapshot, snapshot.Clips, replacing: false);
					snapshot.Restore();
				}
			}
			else if (!snapshot.Changed)
			{
				sound.clips = new Il2CppReferenceArray<AudioClip>(array2);
				float num = Math.Max(0.0001f, flag ? Plugin.MusicVolume.Value : Plugin.SfxVolume.Value);
				sound.minVolume = snapshot.Min * num;
				sound.maxVolume = snapshot.Max * num;
				if (flag)
				{
					sound.minPitch = 1f;
					sound.maxPitch = 1f;
				}
				snapshot.Changed = true;
				RebindPlaying(snapshot, array2, replacing: true);
				Plugin.Instance.Debug($"Mapped '{((Object)sound).name}' => {snapshot.Event} ({array2.Length} variants)");
			}
		}

		[HideFromIl2Cpp]
		private void RebindPlaying(Snapshot snap, AudioClip[] target, bool replacing)
		{
			if (target.Length == 0)
			{
				return;
			}
			AudioClip[] source = (AudioClip[])(replacing ? snap.Clips : ((snap.Event == "music:menu") ? ((Array)(Playlist("menu") ?? ((IEnumerable<AudioClip>)snap.Sound.clips).ToArray())) : ((Array)((IEnumerable<AudioClip>)snap.Sound.clips).ToArray())));
			foreach (AudioSources pool in pools)
			{
				if (!Object.op_Implicit((Object)(object)pool) || pool.pool == null)
				{
					continue;
				}
				foreach (PooledSource item in (Il2CppArrayBase<PooledSource>)(object)pool.pool)
				{
					AudioSource source2 = ((item != null) ? item.audioSource : null);
					if (!((Object)(object)source2 == (Object)null) && Object.op_Implicit((Object)(object)source2) && source2.isPlaying && Object.op_Implicit((Object)(object)source2.clip) && source.Any((AudioClip c) => Object.op_Implicit((Object)(object)c) && ((Il2CppObjectBase)c).Pointer == ((Il2CppObjectBase)source2.clip).Pointer))
					{
						bool flag = snap.Event == "music:menu";
						source2.Stop();
						source2.clip = target[(replacing && flag) ? Bag("menu").Next(target.Length) : 0];
						source2.loop = !(replacing && flag) && snap.Loop;
						if (replacing && flag)
						{
							menuPool = pool;
							menuSlot = item;
							menuTicket = item.CurrentTicket;
							menuSnapshot = snap;
						}
						float num = (snap.Event.StartsWith("music:") ? Plugin.MusicVolume.Value : Plugin.SfxVolume.Value);
						source2.volume = (replacing ? (snap.Min * Math.Max(0.0001f, num)) : snap.Min);
						SetMuted(source2, replacing && num == 0f);
						source2.Play();
					}
				}
			}
		}

		[HideFromIl2Cpp]
		private AudioClip[]? Playlist(string context)
		{
			return music.GetValueOrDefault(context);
		}

		[HideFromIl2Cpp]
		private ShuffleBag Bag(string context)
		{
			if (!bags.TryGetValue(context, out ShuffleBag value))
			{
				bags.Add(context, value = new ShuffleBag());
			}
			return value;
		}

		private static bool Same(Sound? a, Sound? b)
		{
			if (Object.op_Implicit((Object)(object)a) && Object.op_Implicit((Object)(object)b))
			{
				return ((Il2CppObjectBase)a).Pointer == ((Il2CppObjectBase)b).Pointer;
			}
			return false;
		}

		private static bool SamePhase(ITurnPhase? phase, Component? other)
		{
			if (phase != null && Object.op_Implicit((Object)(object)other))
			{
				return ((Il2CppObjectBase)phase).Pointer == ((Il2CppObjectBase)other).Pointer;
			}
			return false;
		}

		public Sound Track(Music item, Sound stock)
		{
			if (Object.op_Implicit((Object)(object)stock))
			{
				LastStockTrackPointer = ((Il2CppObjectBase)stock).Pointer;
			}
			if (stopped || !Ready || !Plugin.MusicEnabled.Value || !Object.op_Implicit((Object)(object)stock))
			{
				return stock;
			}
			RegisterMusic(item);
			Selection selection = selections[((Object)item).GetInstanceID()];
			GameState gameState = item.GameState;
			string text;
			if (!Plugin.Dynamic.Value)
			{
				text = "menu";
			}
			else if (Same(stock, item.winningTrack))
			{
				text = "victory";
			}
			else if (Same(stock, item.loosingTrack))
			{
				text = "defeat";
			}
			else if (!Object.op_Implicit((Object)(object)gameState) || !gameState.GameStarted)
			{
				text = "lobby";
			}
			else
			{
				ITurnPhase turnPhase = gameState.TurnPhase;
				if (SamePhase(turnPhase, (Component?)(object)gameState.harvestPhase))
				{
					text = "harvest";
				}
				else if (!SamePhase(turnPhase, (Component?)(object)gameState.disastersPhase))
				{
					text = (SamePhase(turnPhase, (Component?)(object)gameState.buildPhase) ? "building" : (SamePhase(turnPhase, (Component?)(object)gameState.startPhase) ? "start" : ((!SamePhase(turnPhase, (Component?)(object)gameState.combatPhase)) ? "harvest" : ((Same(stock, item.combatTrack) || Same(stock, item.combatDecisiveTrack)) ? "combat" : "planning"))));
				}
				else
				{
					object obj;
					switch (selection.Context)
					{
					default:
						obj = "harvest";
						break;
					case "menu":
					case "lobby":
					case "harvest":
					case "building":
					case "start":
						obj = selection.Context;
						break;
					}
					text = (string)obj;
				}
			}
			if (selection.Context != text)
			{
				selection.Context = text;
				selection.Selected = null;
				selection.JingleDone = false;
				selection.HasPlayed = false;
				LastContext = text;
				Plugin.Instance.Debug("Music context: " + text);
			}
			bool flag = text == "start" || text == "victory" || text == "defeat";
			if (Object.op_Implicit((Object)(object)selection.Selected) && selection.HasPlayed && !item.currentSource.IsActive && flag)
			{
				selection.JingleDone = true;
			}
			string text2 = ((flag && selection.JingleDone) ? "harvest" : text);
			AudioClip[] array = Playlist(text2);
			if (array == null || array.Length == 0)
			{
				return stock;
			}
			if (Object.op_Implicit((Object)(object)selection.Selected) && !selection.HasPlayed)
			{
				return selection.Selected;
			}
			if (Object.op_Implicit((Object)(object)selection.Selected) && item.currentSource.IsActive && Same(item.currentTrack, selection.Selected))
			{
				return selection.Selected;
			}
			string key = text2 + ":" + ((Object)stock).GetInstanceID();
			if (!selection.Sounds.TryGetValue(key, out Sound value))
			{
				value = Object.Instantiate<Sound>(stock);
				((Object)value).name = "VillageAudio/" + text2;
				((Object)value).hideFlags = (HideFlags)61;
				selection.Sounds.Add(key, value);
				created.Add(value);
				ownedSounds.Add(((Object)value).GetInstanceID());
			}
			value.clips = new Il2CppReferenceArray<AudioClip>((AudioClip[])(object)new AudioClip[1] { array[Bag(text2).Next(array.Length)] });
			value.minVolume = stock.minVolume * Math.Max(0.0001f, Plugin.MusicVolume.Value);
			value.maxVolume = stock.maxVolume * Math.Max(0.0001f, Plugin.MusicVolume.Value);
			value.minPitch = 1f;
			value.maxPitch = 1f;
			value.loop = false;
			selection.Selected = value;
			return value;
		}

		internal void Played(Music item, Sound track)
		{
			if (selections.TryGetValue(((Object)item).GetInstanceID(), out Selection value) && Same(value.Selected, track))
			{
				value.HasPlayed = true;
			}
		}

		private static bool OwnedAllocation(AllocatedSource source)
		{
			object obj;
			if (source == null)
			{
				obj = null;
			}
			else
			{
				PooledSource pooledSource = source.pooledSource;
				obj = ((pooledSource != null) ? pooledSource.audioSource : null);
			}
			AudioSource val = (AudioSource)obj;
			if ((Object)(object)val == (Object)null || !Object.op_Implicit((Object)(object)val))
			{
				return false;
			}
			AudioClip clip = val.clip;
			if ((Object)(object)clip != (Object)null && Object.op_Implicit((Object)(object)clip))
			{
				return ((Object)clip).name.StartsWith("VillageAudio/", StringComparison.Ordinal);
			}
			return false;
		}

		private static void StopIfAlive(AllocatedSource source)
		{
			object obj;
			if (source == null)
			{
				obj = null;
			}
			else
			{
				PooledSource pooledSource = source.pooledSource;
				obj = ((pooledSource != null) ? pooledSource.audioSource : null);
			}
			AudioSource val = (AudioSource)obj;
			if ((Object)(object)val != (Object)null && Object.op_Implicit((Object)(object)val))
			{
				source.Stop();
			}
		}

		private void RefreshSettings()
		{
			if (!Ready)
			{
				return;
			}
			Snapshot[] array = snapshots.Values.ToArray();
			foreach (Snapshot snapshot in array)
			{
				if (Object.op_Implicit((Object)(object)snapshot.Sound))
				{
					if (snapshot.Changed)
					{
						RebindPlaying(snapshot, snapshot.Clips, replacing: false);
						snapshot.Restore();
					}
					ApplySound(snapshot.Sound);
				}
			}
			foreach (Selection value in selections.Values)
			{
				if (Object.op_Implicit((Object)(object)value.Music))
				{
					value.Selected = null;
					value.HasPlayed = false;
					Sound currentTrack = value.Music.currentTrack;
					if ((Object)(object)currentTrack != (Object)null && Object.op_Implicit((Object)(object)currentTrack) && ownedSounds.Contains(((Object)currentTrack).GetInstanceID()))
					{
						value.Music.currentTrack = null;
					}
					value.Music.transitionDuration = value.OriginalDuration;
					value.Music.UpdateTrack();
				}
			}
			RefreshContextSounds();
		}

		[HideFromIl2Cpp]
		private void DestroySelection(Selection state)
		{
			foreach (Sound sound in state.Sounds.Values)
			{
				if (Object.op_Implicit((Object)(object)sound))
				{
					ownedSounds.Remove(((Object)sound).GetInstanceID());
					created.RemoveAll((Sound c) => !Object.op_Implicit((Object)(object)c) || ((Il2CppObjectBase)c).Pointer == ((Il2CppObjectBase)sound).Pointer);
					Object.Destroy((Object)(object)sound);
				}
			}
			state.Sounds.Clear();
		}

		public void Shutdown()
		{
			if (stopped)
			{
				return;
			}
			stopped = true;
			StopContextAllocations();
			UnityWebRequest? obj = request;
			if (obj != null)
			{
				obj.Abort();
			}
			UnityWebRequest? obj2 = request;
			if (obj2 != null)
			{
				obj2.Dispose();
			}
			request = null;
			foreach (Snapshot value in snapshots.Values)
			{
				if (Object.op_Implicit((Object)(object)value.Sound) && value.Changed)
				{
					RebindPlaying(value, value.Clips, replacing: false);
					value.Restore();
				}
			}
			foreach (Selection value2 in selections.Values)
			{
				if (!Object.op_Implicit((Object)(object)value2.Music))
				{
					continue;
				}
				bool flag = OwnsSound(value2.Music.currentTrack);
				if (flag)
				{
					StopIfAlive(value2.Music.currentSource);
				}
				int num = 0;
				while (value2.Music.dyingSources != null && num < value2.Music.dyingSources.Count)
				{
					if (OwnedAllocation(value2.Music.dyingSources[num]))
					{
						StopIfAlive(value2.Music.dyingSources[num]);
					}
					num++;
				}
				value2.Music.dyingSources?.Clear();
				value2.Music.transitionDuration = value2.OriginalDuration;
				if (flag)
				{
					value2.Music.currentTrack = null;
					value2.Music.UpdateTrack();
				}
			}
			foreach (Sound item in created)
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					Object.Destroy((Object)(object)item);
				}
			}
			foreach (AudioClip value3 in clips.Values)
			{
				if (Object.op_Implicit((Object)(object)value3))
				{
					value3.UnloadAudioData();
					Object.Destroy((Object)(object)value3);
				}
			}
			created.Clear();
			clips.Clear();
			snapshots.Clear();
			selections.Clear();
			ownedSounds.Clear();
			foreach (AudioSource value4 in muted.Values)
			{
				if (Object.op_Implicit((Object)(object)value4))
				{
					value4.mute = false;
				}
			}
			muted.Clear();
			ClearContextSounds();
			Ready = false;
		}

		public void OnDestroy()
		{
			Shutdown();
			if (Object.ReferenceEquals((Object)(object)Plugin.Runtime, (Object)(object)this))
			{
				Plugin.Runtime = null;
			}
		}

		internal void RecordResource(Resource resource)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			resourceContext = resource;
			resourceFrame = Time.frameCount;
		}

		internal void RecordBuilding(string type)
		{
			buildingContext = type;
			buildingFrame = Time.frameCount;
		}

		internal void RecordUnit(string type)
		{
			unitContext = type;
			unitFrame = Time.frameCount;
		}

		internal void ForgetBuildingVisual(BuildingVisual visual)
		{
			if (Object.op_Implicit((Object)(object)visual))
			{
				buildingHealth.Remove(((Object)visual).GetInstanceID());
			}
		}

		internal bool RouteContextSound(ref Sound sound)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			if (stopped || !Ready || !Plugin.SfxEnabled.Value || plan == null || !Object.op_Implicit((Object)(object)sound) || effectSoundIds.Contains(((Object)sound).GetInstanceID()))
			{
				return true;
			}
			if (!snapshots.TryGetValue(((Object)sound).GetInstanceID(), out Snapshot value))
			{
				return true;
			}
			string text = null;
			if (value.Event == "harvest_resource" && resourceContext.HasValue && resourceFrame == Time.frameCount)
			{
				text = plan.Map.ResourceEvents.GetValueOrDefault(((object)resourceContext.Value/*cast due to .constrained prefix*/).ToString());
				resourceContext = null;
				if (text != null && sfx.TryGetValue(text, out AudioClip[] value2) && value2.Length != 0)
				{
					if (resourcePlayedAt.TryGetValue(text, out var value3) && Time.unscaledTime - value3 < 0.35f)
					{
						SuppressedResourceRepeats++;
						return false;
					}
					resourcePlayedAt[text] = Time.unscaledTime;
				}
			}
			else if (value.Event == "building_place" && buildingContext != null && buildingFrame == Time.frameCount)
			{
				text = plan.Map.BuildingEvents.GetValueOrDefault(buildingContext);
				buildingContext = null;
			}
			else if (value.Event == "unit_die" && unitContext != null && unitFrame == Time.frameCount)
			{
				object obj;
				if (!(unitContext == "ARCHER"))
				{
					string text2 = unitContext;
					obj = ((text2 == "SORCERER" || text2 == "WARLOCK") ? "unit_die_wizard" : "unit_die");
				}
				else
				{
					obj = "unit_die_archer";
				}
				text = (string)obj;
				unitContext = null;
			}
			if (text == null)
			{
				return true;
			}
			Sound val = EffectSound(sound, text);
			if (Object.op_Implicit((Object)(object)val))
			{
				sound = val;
			}
			return true;
		}

		[HideFromIl2Cpp]
		private Sound? EffectSound(Sound stock, string key)
		{
			if (!sfx.TryGetValue(key, out AudioClip[] value) || value.Length == 0)
			{
				return null;
			}
			AudioClip[] array = value;
			foreach (AudioClip val in array)
			{
				effectClipPointers.Add(((Il2CppObjectBase)val).Pointer);
			}
			Snapshot snapshot = Remember(stock);
			string key2 = ((Object)stock).GetInstanceID() + ":" + key;
			if (!effectSounds.TryGetValue(key2, out Sound value2))
			{
				value2 = Object.Instantiate<Sound>(stock);
				((Object)value2).name = "VillageAudio/SFX/" + key;
				((Object)value2).hideFlags = (HideFlags)61;
				effectSounds.Add(key2, value2);
				effectSoundIds.Add(((Object)value2).GetInstanceID());
				created.Add(value2);
			}
			value2.clips = new Il2CppReferenceArray<AudioClip>((AudioClip[])(object)new AudioClip[1] { value[Bag("sfx:" + key).Next(value.Length)] });
			value2.minPitch = 1f;
			value2.maxPitch = 1f;
			value2.loop = false;
			value2.minVolume = snapshot.Min * Math.Max(0.0001f, Plugin.SfxVolume.Value);
			value2.maxVolume = snapshot.Max * Math.Max(0.0001f, Plugin.SfxVolume.Value);
			LastEffectEvent = key;
			LastEffectClip = ((Object)((Il2CppArrayBase<AudioClip>)(object)value2.clips)[0]).name;
			return value2;
		}

		internal void ObserveBuildingHealth(BuildingVisual visual, float health)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			if (stopped || !Ready || !Object.op_Implicit((Object)(object)visual))
			{
				return;
			}
			Scene val = ((Component)visual).gameObject.scene;
			if (!((Scene)(ref val)).IsValid())
			{
				return;
			}
			int instanceID = ((Object)visual).GetInstanceID();
			(BuildingVisual, float) value;
			bool num = buildingHealth.TryGetValue(instanceID, out value) && value.Item2 > 0f && health <= 0f;
			buildingHealth[instanceID] = (visual, health);
			if (num && Plugin.SfxEnabled.Value && ((Behaviour)visual).isActiveAndEnabled && Object.op_Implicit((Object)(object)visual.audioSources) && Object.op_Implicit((Object)(object)visual.hitSound))
			{
				Sound val2 = EffectSound(visual.hitSound, "building_destroyed");
				if (Object.op_Implicit((Object)(object)val2))
				{
					visual.audioSources.Play(val2, new Nullable<Vector3>(((Component)visual).transform.position));
				}
			}
		}

		private void PruneContextSounds()
		{
			int[] array = (from p in buildingHealth
				where !Object.op_Implicit((Object)(object)p.Value.Visual)
				select p.Key).ToArray();
			foreach (int key in array)
			{
				buildingHealth.Remove(key);
			}
			string[] array2 = effectSounds.Keys.Where((string k) => !snapshots.ContainsKey(int.Parse(k.Split(':')[0]))).ToArray();
			foreach (string key2 in array2)
			{
				Sound sound = effectSounds[key2];
				effectSounds.Remove(key2);
				if (Object.op_Implicit((Object)(object)sound))
				{
					effectSoundIds.Remove(((Object)sound).GetInstanceID());
					created.RemoveAll((Sound c) => !Object.op_Implicit((Object)(object)c) || ((Il2CppObjectBase)c).Pointer == ((Il2CppObjectBase)sound).Pointer);
					Object.Destroy((Object)(object)sound);
				}
			}
		}

		private void RefreshContextSounds()
		{
			if (!Plugin.SfxEnabled.Value)
			{
				StopContextAllocations();
				resourcePlayedAt.Clear();
			}
		}

		private void StopContextAllocations()
		{
			foreach (AudioSources pool in pools)
			{
				if (!Object.op_Implicit((Object)(object)pool) || pool.pool == null)
				{
					continue;
				}
				foreach (PooledSource item in (Il2CppArrayBase<PooledSource>)(object)pool.pool)
				{
					AudioSource val = ((item != null) ? item.audioSource : null);
					if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.clip) && effectClipPointers.Contains(((Il2CppObjectBase)val.clip).Pointer))
					{
						val.Stop();
					}
				}
			}
		}

		private void ClearContextSounds()
		{
			effectSounds.Clear();
			effectSoundIds.Clear();
			effectClipPointers.Clear();
			resourcePlayedAt.Clear();
			buildingHealth.Clear();
		}
	}
	[BepInPlugin("io.github.mikhasan.dicekingdoms.villageaudio", "Village Audio Overhaul", "1.1.0")]
	[BepInProcess("Dice-Kingdoms.exe")]
	public sealed class Plugin : BasePlugin
	{
		public const string Id = "io.github.mikhasan.dicekingdoms.villageaudio";

		internal static Plugin Instance = null;

		internal static ConfigEntry<bool> MusicEnabled = null;

		internal static ConfigEntry<bool> SfxEnabled = null;

		internal static ConfigEntry<bool> Dynamic = null;

		internal static ConfigEntry<bool> Fade = null;

		internal static ConfigEntry<bool> DebugLog = null;

		internal static ConfigEntry<float> MusicVolume = null;

		internal static ConfigEntry<float> SfxVolume = null;

		internal static ConfigEntry<float> FadeSeconds = null;

		internal static AudioRuntime? Runtime;

		internal static string Folder = "";

		internal static volatile bool Dirty;

		private Harmony? harmony;

		private AudioRuntime? component;

		private readonly HashSet<string> errors = new HashSet<string>();

		public override void Load()
		{
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Expected O, but got Unknown
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Expected O, but got Unknown
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Expected O, but got Unknown
			Instance = this;
			Folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			MusicEnabled = ((BasePlugin)this).Config.Bind<bool>("General", "EnableMusicReplacement", true, "Replace music when a valid local track exists.");
			SfxEnabled = ((BasePlugin)this).Config.Bind<bool>("General", "EnableSFXReplacement", true, "Replace only mapped effects with valid local files.");
			Dynamic = ((BasePlugin)this).Config.Bind<bool>("General", "EnableDynamicMusic", true, "Select music by the actual game phase. When false use the menu playlist throughout a match.");
			Fade = ((BasePlugin)this).Config.Bind<bool>("Music", "EnableCrossfade", true, "Use the game's existing transition and pooled sources.");
			FadeSeconds = ((BasePlugin)this).Config.Bind<float>("Music", "CrossfadeSeconds", 1.5f, new ConfigDescription("Transition duration in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 10f), Array.Empty<object>()));
			MusicVolume = ((BasePlugin)this).Config.Bind<float>("Volume", "MusicVolume", 0.75f, new ConfigDescription("Music multiplier; also respects the game's Music and Master mixer settings.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			SfxVolume = ((BasePlugin)this).Config.Bind<float>("Volume", "SFXVolume", 0.85f, new ConfigDescription("Effect multiplier; also respects the game's mixer settings.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			DebugLog = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "DebugLogging", false, "Log first-seen sound IDs and phase changes.");
			((BasePlugin)this).Config.SettingChanged += delegate
			{
				Dirty = true;
			};
			ClassInjector.RegisterTypeInIl2Cpp<AudioFileHandler>();
			component = ((BasePlugin)this).AddComponent<AudioRuntime>();
			Runtime = component;
			harmony = new Harmony("io.github.mikhasan.dicekingdoms.villageaudio");
			Patch(typeof(Music), "get_Track", "Track", prefix: false);
			Patch(typeof(Music), "Transition", "TransitionPrefix", prefix: true);
			Patch(typeof(Music), "Transition", "TransitionPostfix", prefix: false);
			Patch(typeof(Music), "OnEnable", "MusicEnable", prefix: true);
			Patch(typeof(Music), "OnDisable", "MusicDisable", prefix: false);
			Patch(typeof(MainMenu), "Awake", "Menu", prefix: true);
			Patch(typeof(MainMenu), "OnEnable", "Menu", prefix: true);
			Patch(typeof(MainMenu), "OnDisable", "MenuDisable", prefix: false);
			Patch(typeof(AudioSources), "Play", "Play", prefix: true);
			Patch(typeof(AudioSources), "Play", "Played", prefix: false);
			Patch(typeof(ResourceStorageInterface), "Increment", "ResourceIncrement", prefix: false);
			Patch(typeof(ResourceParticle), "OnEnd", "ResourceParticle", prefix: true);
			Patch(typeof(BuildingPlacer), "Place", "Placement", prefix: true);
			Patch(typeof(BuildingBulkPlacer), "Place", "BulkPlacement", prefix: true);
			Patch(typeof(UnitVisual), "OnDie", "UnitDeath", prefix: true);
			Patch(typeof(BuildingVisual), "SetHealth", "BuildingHealth", prefix: false);
			Patch(typeof(BuildingVisual), "OnDisable", "BuildingDisabled", prefix: false);
			Patch(typeof(AllocatedSource), "set_Pitch", "CollectorPitch", prefix: true);
			((BasePlugin)this).Log.LogInfo((object)"Village Audio Overhaul 1.1.0: resource/type-specific native SFX routing; disasters preserve calm music.");
		}

		private void Patch(Type type, string method, string handler, bool prefix)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null) ?? throw new MissingMethodException(type.FullName, method);
			HarmonyMethod val = new HarmonyMethod(typeof(Hooks), handler, (Type[])null);
			harmony.Patch((MethodBase)methodInfo, prefix ? val : null, prefix ? null : val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			ManualLogSource log = ((BasePlugin)this).Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(8, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Hooked ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(type.Name);
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(".");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(method);
			}
			log.LogInfo(val2);
		}

		internal void Error(Exception ex)
		{
			string text = ex.GetType().Name + ": " + ex.Message;
			if (errors.Add(text))
			{
				((BasePlugin)this).Log.LogWarning((object)(DebugLog.Value ? ex.ToString() : text));
			}
		}

		internal void Debug(string message)
		{
			if (DebugLog.Value)
			{
				((BasePlugin)this).Log.LogInfo((object)message);
			}
		}

		public override bool Unload()
		{
			Runtime?.Shutdown();
			Runtime = null;
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			return true;
		}
	}
	internal static class Hooks
	{
		internal static void Track(Music __instance, ref Sound __result)
		{
			try
			{
				if ((Object)(object)Plugin.Runtime != (Object)null)
				{
					__result = Plugin.Runtime.Track(__instance, __result);
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void Menu(MainMenu __instance)
		{
			try
			{
				Plugin.Runtime?.RegisterMenu(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void MusicEnable(Music __instance)
		{
			try
			{
				Plugin.Runtime?.RegisterMusic(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void MusicDisable(Music __instance)
		{
			try
			{
				Plugin.Runtime?.ForgetMusic(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void MenuDisable(MainMenu __instance)
		{
			try
			{
				Plugin.Runtime?.DisableMenu(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static bool Play(ref Sound sound, ref AllocatedSource __result)
		{
			try
			{
				Plugin.Runtime?.PreparePlay(sound);
				if ((Object)(object)Plugin.Runtime != (Object)null && !Plugin.Runtime.RouteContextSound(ref sound))
				{
					__result = AllocatedSource.noop;
					return false;
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
			return true;
		}

		internal static void ResourceIncrement(ResourceStorageInterface __instance, int amount)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (amount > 0)
				{
					Plugin.Runtime?.RecordResource(__instance.resource);
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void ResourceParticle(ResourceParticle __instance)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance.isForLocalPlayer && (int)__instance.resource != 7)
				{
					Plugin.Runtime?.RecordResource(__instance.resource);
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void Placement(BuildingPlacer __instance)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance.CurrentBuildingType != null)
				{
					Plugin.Runtime?.RecordBuilding(((object)__instance.CurrentBuildingType.name/*cast due to .constrained prefix*/).ToString());
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void BulkPlacement(BuildingBulkPlacer __instance)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance.CurrentBuildingType != null)
				{
					Plugin.Runtime?.RecordBuilding(((object)__instance.CurrentBuildingType.name/*cast due to .constrained prefix*/).ToString());
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void UnitDeath(UnitVisual __instance)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Plugin.Runtime?.RecordUnit(((object)__instance.unitType/*cast due to .constrained prefix*/).ToString());
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void BuildingHealth(BuildingVisual __instance, float health)
		{
			try
			{
				Plugin.Runtime?.ObserveBuildingHealth(__instance, health);
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void BuildingDisabled(BuildingVisual __instance)
		{
			try
			{
				Plugin.Runtime?.ForgetBuildingVisual(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void CollectorPitch(AllocatedSource __instance, ref float value)
		{
			try
			{
				AudioRuntime? runtime = Plugin.Runtime;
				if (runtime == null || !runtime.Ready || !Plugin.SfxEnabled.Value)
				{
					return;
				}
				object obj;
				if (__instance == null)
				{
					obj = null;
				}
				else
				{
					PooledSource pooledSource = __instance.pooledSource;
					obj = ((pooledSource != null) ? pooledSource.audioSource : null);
				}
				AudioSource val = (AudioSource)obj;
				if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.clip))
				{
					string name = ((Object)val.clip).name;
					if (name == "VillageAudio/coins_collect_01.ogg" || name == "VillageAudio/elixir_collect_02.ogg")
					{
						value = 1f;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void Played(AudioSources __instance, Sound sound, AllocatedSource __result)
		{
			try
			{
				Plugin.Runtime?.CaptureMenuSource(__instance, sound, __result);
			}
			catch (Exception ex)
			{
				Plugin.Instance.Error(ex);
			}
		}

		internal static void TransitionPrefix(Music __instance, Sound track, out float __state)
		{
			__state = __instance.transitionDuration;
			AudioRuntime? runtime = Plugin.Runtime;
			if (runtime == null || !runtime.Ready || !Plugin.MusicEnabled.Value || (!Plugin.Runtime.OwnsSound(track) && !Plugin.Runtime.OwnsSound(__instance.currentTrack)))
			{
				return;
			}
			if (Plugin.Fade.Value)
			{
				__instance.transitionDuration = Plugin.FadeSeconds.Value;
				return;
			}
			__instance.currentSource.Stop();
			List<AllocatedSource> dyingSources = __instance.dyingSources;
			int num = 0;
			while (dyingSources != null && num < dyingSources.Count)
			{
				dyingSources[num].Stop();
				num++;
			}
			dyingSources?.Clear();
			__instance.transitionDuration = 0.001f;
		}

		internal static void TransitionPostfix(Music __instance, Sound track, float __state)
		{
			Plugin.Runtime?.Played(__instance, track);
			AudioRuntime? runtime = Plugin.Runtime;
			__instance.transitionDuration = ((runtime == null || !runtime.Ready || !Plugin.MusicEnabled.Value || !Plugin.Runtime.OwnsSound(track)) ? (Plugin.Runtime?.OriginalDuration(__instance) ?? __state) : (Plugin.Fade.Value ? Plugin.FadeSeconds.Value : 0.001f));
		}
	}
}