Decompiled source of Mimic v1.1.6

BepInEx/plugins/Mimics.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mimics.patches;
using Photon.Pun;
using Photon.Realtime;
using Photon.Voice;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Mimics")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.1.6.0")]
[assembly: AssemblyInformationalVersion("1.1.6")]
[assembly: AssemblyProduct("My first plugin")]
[assembly: AssemblyTitle("Mimics")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.6.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 Mimics
{
	public class Mimics : MonoBehaviour
	{
		[CompilerGenerated]
		private sealed class <DestroyAfterDelay>d__34 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public AudioSource audioSource;

			public float delay;

			public Mimics <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DestroyAfterDelay>d__34(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0027: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(delay);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					Object.Destroy((Object)(object)audioSource);
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <RecordAtRandomIntervals>d__16 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Mimics <>4__this;

			private int <clipCount>5__1;

			private float <randomDelay>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <RecordAtRandomIntervals>d__16(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c3: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					goto IL_00e0;
				case 1:
					<>1__state = -1;
					break;
				case 2:
					{
						<>1__state = -1;
						<>4__this.StartRecording();
						goto IL_00e0;
					}
					IL_00e0:
					<clipCount>5__1 = Directory.GetFiles(<>4__this.audioFolderPath, "*.wav").Length;
					if (<clipCount>5__1 >= <>4__this.maxClipCount)
					{
						<>2__current = <>4__this.ClearAudioFolderAsync().AsCoroutine();
						<>1__state = 1;
						return true;
					}
					break;
				}
				<randomDelay>5__2 = Random.Range(Plugin.configMinDelay.Value, Plugin.configMaxDelay.Value);
				<>2__current = (object)new WaitForSeconds(<randomDelay>5__2);
				<>1__state = 2;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <WaitForVoiceChat>d__15 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerAvatar playerAvatar;

			public Mimics <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <WaitForVoiceChat>d__15(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_003c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Expected O, but got Unknown
				//IL_007c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0086: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>4__this.playerVoiceChat = (PlayerVoiceChat)<>4__this.voiceChatField.GetValue(playerAvatar);
					break;
				case 1:
					<>1__state = -1;
					<>4__this.playerVoiceChat = (PlayerVoiceChat)<>4__this.voiceChatField.GetValue(playerAvatar);
					break;
				}
				if ((Object)(object)<>4__this.playerVoiceChat == (Object)null)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				log.LogInfo((object)"PlayerVoiceChat successfully initialized.");
				if (<>4__this.photonView.IsMine && SemiFunc.RunIsLevel())
				{
					<>4__this.audioFolderPath = Path.Combine(Application.dataPath, "AudioFiles");
					Directory.CreateDirectory(<>4__this.audioFolderPath);
					((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.RecordAtRandomIntervals());
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly ManualLogSource log = Logger.CreateLogSource("Mimics");

		public PhotonView photonView;

		private PlayerVoiceChat playerVoiceChat;

		private FieldInfo voiceChatField;

		private FieldInfo recorderField;

		private int sampleRate;

		private float[] audioBuffer;

		private int bufferPosition = 0;

		public bool isRecording = false;

		private bool capturingSpeech = false;

		private string audioFolderPath;

		private int maxClipCount = 10;

		private bool fileSaved = false;

		private Dictionary<string, bool> filter;

		private HashSet<int> sentChunks = new HashSet<int>();

		private List<byte[]> receivedChunks = new List<byte[]>();

		private int expectedChunkCount = 0;

		private void Awake()
		{
			photonView = ((Component)this).GetComponent<PhotonView>();
			if ((Object)(object)photonView == (Object)null)
			{
				log.LogError((object)"PhotonView not found on Mimics.");
				return;
			}
			PlayerAvatar component = ((Component)this).GetComponent<PlayerAvatar>();
			if ((Object)(object)component == (Object)null)
			{
				log.LogError((object)"PlayerAvatar not found on Mimics.");
				return;
			}
			voiceChatField = typeof(PlayerAvatar).GetField("voiceChat", BindingFlags.Instance | BindingFlags.NonPublic);
			if (voiceChatField == null)
			{
				log.LogError((object)"Could not find 'voiceChat' field in PlayerAvatar.");
				return;
			}
			recorderField = typeof(PlayerVoiceChat).GetField("recorder", BindingFlags.Instance | BindingFlags.NonPublic);
			if (recorderField == null)
			{
				log.LogError((object)"Could not find 'recorder' field in PlayerVoiceChat.");
				return;
			}
			sampleRate = Plugin.configSamplingRate.Value;
			if (Plugin.configFilterEnabled.Value)
			{
				filter = new Dictionary<string, bool>();
				SetEnemyFilter();
			}
			else
			{
				log.LogInfo((object)"Filter not enabled. All enemies (custom included) will mimic voices.");
			}
			((MonoBehaviour)this).StartCoroutine(WaitForVoiceChat(component));
		}

		[IteratorStateMachine(typeof(<WaitForVoiceChat>d__15))]
		private IEnumerator WaitForVoiceChat(PlayerAvatar playerAvatar)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <WaitForVoiceChat>d__15(0)
			{
				<>4__this = this,
				playerAvatar = playerAvatar
			};
		}

		[IteratorStateMachine(typeof(<RecordAtRandomIntervals>d__16))]
		private IEnumerator RecordAtRandomIntervals()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <RecordAtRandomIntervals>d__16(0)
			{
				<>4__this = this
			};
		}

		private void StartRecording()
		{
			if (!isRecording)
			{
				audioBuffer = new float[sampleRate * 3];
				bufferPosition = 0;
				isRecording = true;
				capturingSpeech = false;
				fileSaved = false;
				log.LogInfo((object)"Recording started.");
			}
		}

		public void ProcessVoiceData(short[] voiceData)
		{
			if (!isRecording || !photonView.IsMine)
			{
				return;
			}
			if ((bool)typeof(PlayerVoiceChat).GetField("isTalking", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(playerVoiceChat) && !capturingSpeech)
			{
				capturingSpeech = true;
				bufferPosition = 0;
				log.LogInfo((object)"Speech detected, capturing audio.");
			}
			if (capturingSpeech)
			{
				int num = Mathf.Min(voiceData.Length, audioBuffer.Length - bufferPosition);
				for (int i = 0; i < num; i++)
				{
					audioBuffer[bufferPosition + i] = (float)voiceData[i] / 32768f;
				}
				bufferPosition += num;
				if (bufferPosition >= audioBuffer.Length && !fileSaved)
				{
					isRecording = false;
					capturingSpeech = false;
					fileSaved = true;
					log.LogInfo((object)"Buffer full, saving audio.");
					SaveAudioToFileAsync(audioBuffer);
				}
			}
		}

		private async Task SaveAudioToFileAsync(float[] audioData)
		{
			string filePath = Path.Combine(audioFolderPath, $"audio_{Guid.NewGuid()}.wav");
			byte[] audioBytes = ConvertFloatArrayToByteArray(audioData);
			using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
			{
				using BinaryWriter writer = new BinaryWriter(fileStream);
				WriteWavHeader(writer, audioData.Length, sampleRate);
				writer.Write(audioBytes);
			}
			log.LogInfo((object)("Audio saved to: " + filePath));
			await PlayRandomAudioFile();
		}

		private void WriteWavHeader(BinaryWriter writer, int sampleCount, int sampleRate)
		{
			writer.Write("RIFF".ToCharArray());
			writer.Write(36 + sampleCount * 2);
			writer.Write("WAVE".ToCharArray());
			writer.Write("fmt ".ToCharArray());
			writer.Write(16);
			writer.Write((short)1);
			writer.Write((short)1);
			writer.Write(sampleRate);
			writer.Write(sampleRate * 2);
			writer.Write((short)2);
			writer.Write((short)16);
			writer.Write("data".ToCharArray());
			writer.Write(sampleCount * 2);
		}

		private byte[] ConvertFloatArrayToByteArray(float[] audioData)
		{
			byte[] array = new byte[audioData.Length * 2];
			for (int i = 0; i < audioData.Length; i++)
			{
				short value = (short)(audioData[i] * 32767f);
				BitConverter.GetBytes(value).CopyTo(array, i * 2);
			}
			return array;
		}

		private async Task PlayRandomAudioFile()
		{
			string[] files = Directory.GetFiles(audioFolderPath, "*.wav");
			if (files.Length != 0)
			{
				string selectedFile = files[Random.Range(0, files.Length)];
				await SendAudioInChunksAsync(await File.ReadAllBytesAsync(selectedFile));
			}
		}

		private async Task SendAudioInChunksAsync(byte[] audioData)
		{
			List<byte[]> chunks = ChunkAudioData(audioData, 8192);
			sentChunks.Clear();
			for (int i = 0; i < chunks.Count; i++)
			{
				if (!PhotonNetwork.IsConnectedAndReady)
				{
					log.LogWarning((object)"Photon disconnected, aborting send.");
					return;
				}
				bool applyVoiceFilter = Random.value > 0.9f;
				if (Plugin.configHearYourself.Value)
				{
					photonView.RPC("ReceiveAudioChunk", (RpcTarget)0, new object[5]
					{
						chunks[i],
						i,
						chunks.Count,
						applyVoiceFilter,
						sampleRate
					});
				}
				else
				{
					photonView.RPC("ReceiveAudioChunk", (RpcTarget)1, new object[5]
					{
						chunks[i],
						i,
						chunks.Count,
						applyVoiceFilter,
						sampleRate
					});
				}
				sentChunks.Add(i);
				await Task.Delay(125);
			}
			log.LogInfo((object)"All chunks sent.");
		}

		[PunRPC]
		public void ReceiveAudioChunk(byte[] chunk, int chunkIndex, int totalChunks, bool applyFilter, int senderSampleRate)
		{
			if (chunkIndex == 0)
			{
				receivedChunks.Clear();
				expectedChunkCount = totalChunks;
				log.LogInfo((object)$"New audio transmission started, expecting {totalChunks} chunks at {senderSampleRate} Hz.");
			}
			if (chunkIndex >= expectedChunkCount)
			{
				log.LogWarning((object)$"Received chunk index {chunkIndex} exceeds expected {expectedChunkCount}.");
				return;
			}
			if (chunkIndex >= receivedChunks.Count)
			{
				receivedChunks.AddRange(Enumerable.Repeat<byte[]>(null, chunkIndex - receivedChunks.Count + 1));
			}
			receivedChunks[chunkIndex] = chunk;
			if (receivedChunks.Count >= expectedChunkCount && receivedChunks.All((byte[] c) => c != null))
			{
				log.LogInfo((object)"All chunks received, playing audio.");
				byte[] audioData = CombineChunks(receivedChunks);
				PlayReceivedAudio(audioData, applyFilter, senderSampleRate);
				receivedChunks.Clear();
				expectedChunkCount = 0;
			}
		}

		private byte[] CombineChunks(List<byte[]> chunks)
		{
			int num = chunks.Sum((byte[] chunk) => chunk.Length);
			byte[] array = new byte[num];
			int num2 = 0;
			foreach (byte[] chunk in chunks)
			{
				Array.Copy(chunk, 0, array, num2, chunk.Length);
				num2 += chunk.Length;
			}
			return array;
		}

		private void PlayReceivedAudio(byte[] audioData, bool applyVoiceFilter, int senderSampleRate)
		{
			if (applyVoiceFilter)
			{
				log.LogInfo((object)"Expecting filter.");
			}
			float[] array = ConvertByteArrayToFloatArray(audioData, applyVoiceFilter, senderSampleRate);
			AudioClip val = AudioClip.Create("ReceivedClip", array.Length, 1, senderSampleRate, false);
			val.SetData(array, 0);
			foreach (GameObject item in from e in GetEnemiesList()
				where (Object)(object)e != (Object)null && !((Object)e).name.Contains("Gnome")
				select e)
			{
				if (Plugin.configFilterEnabled.Value)
				{
					string text = ((Object)item).name.Replace("(Clone)", "");
					if (!filter.TryGetValue(text, out var value) || !value)
					{
						log.LogInfo((object)("Skipped " + text + ": disabled in config"));
						continue;
					}
				}
				Transform obj = item.transform.Find("Enable/Controller");
				GameObject val2 = ((obj != null) ? ((Component)obj).gameObject : null);
				if (!((Object)(object)val2 == (Object)null))
				{
					AudioSource val3 = val2.GetComponent<AudioSource>() ?? val2.AddComponent<AudioSource>();
					val3.clip = val;
					val3.volume = Plugin.configVoiceVolume.Value;
					val3.spatialBlend = 1f;
					val3.dopplerLevel = 0.5f;
					val3.minDistance = 1f;
					val3.maxDistance = 20f;
					val3.rolloffMode = (AudioRolloffMode)1;
					val3.outputAudioMixerGroup = playerVoiceChat.mixerMicrophoneSound;
					val3.Play();
					((MonoBehaviour)this).StartCoroutine(DestroyAfterDelay(val3, val.length + 0.1f));
				}
			}
		}

		private void SetEnemyFilter()
		{
			filter.Clear();
			if (filter == null)
			{
				log.LogError((object)"Enemy filter is null");
			}
			foreach (KeyValuePair<string, ConfigEntry<bool>> enemyConfigEntry in Plugin.enemyConfigEntries)
			{
				filter.Add(enemyConfigEntry.Key ?? "", enemyConfigEntry.Value.Value);
			}
			log.LogInfo((object)"Config loaded and filter set.");
		}

		private List<GameObject> GetEnemiesList()
		{
			GameObject obj = GameObject.Find("Level Generator");
			Transform val = ((obj != null) ? obj.transform.Find("Enemies") : null);
			return ((Object)(object)val != (Object)null) ? (from Transform t in (IEnumerable)val
				select ((Component)t).gameObject).ToList() : new List<GameObject>();
		}

		private float[] ConvertByteArrayToFloatArray(byte[] byteArray, bool applyVoiceFilter, int senderSampleRate)
		{
			int num = (int)((float)senderSampleRate * 0.5f);
			int num2 = byteArray.Length / 2;
			int num3 = (int)((float)senderSampleRate * 0.02f);
			float[] array = new float[num2];
			for (int i = 0; i < num2; i++)
			{
				array[i] = (float)BitConverter.ToInt16(byteArray, i * 2) / 32768f;
			}
			array = ApplyLowPassFilter(array, 4500f);
			if (applyVoiceFilter)
			{
				int num4 = Random.Range(0, 3);
				if (num4 == 0)
				{
					array = ApplyPitchShift(array, 0.5f);
				}
				if (num4 == 1)
				{
					array = ApplyPitchShift(array, 1.2f);
				}
				if (num4 == 2)
				{
					array = ApplyAlienFilter(array);
				}
			}
			float[] array2 = new float[array.Length + 2 * num];
			for (int j = 0; j < num; j++)
			{
				array2[j] = 0f;
			}
			for (int k = 0; k < array.Length; k++)
			{
				float num5 = array[k];
				float num6 = 1f;
				if (k < num3)
				{
					num6 = (float)k / (float)num3;
				}
				else if (k >= array.Length - num3)
				{
					num6 = (float)(array.Length - k) / (float)num3;
				}
				array2[k + num] = num5 * num6;
			}
			for (int l = array.Length + num; l < array2.Length; l++)
			{
				array2[l] = 0f;
			}
			return array2;
		}

		private async Task ClearAudioFolderAsync()
		{
			string[] files = Directory.GetFiles(audioFolderPath, "*.wav");
			foreach (string file in files)
			{
				await Task.Run(delegate
				{
					File.Delete(file);
				});
			}
			log.LogInfo((object)"Audio folder cleared.");
		}

		[IteratorStateMachine(typeof(<DestroyAfterDelay>d__34))]
		private IEnumerator DestroyAfterDelay(AudioSource audioSource, float delay)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DestroyAfterDelay>d__34(0)
			{
				<>4__this = this,
				audioSource = audioSource,
				delay = delay
			};
		}

		private List<byte[]> ChunkAudioData(byte[] audioData, int chunkSize)
		{
			List<byte[]> list = new List<byte[]>();
			for (int i = 0; i < audioData.Length; i += chunkSize)
			{
				int num = Mathf.Min(chunkSize, audioData.Length - i);
				byte[] array = new byte[num];
				Array.Copy(audioData, i, array, 0, num);
				list.Add(array);
			}
			return list;
		}

		private float[] ApplyPitchShift(float[] samples, float pitchFactor)
		{
			log.LogInfo((object)"Pitch shift applied.");
			int num = (int)((float)samples.Length / pitchFactor);
			float[] array = new float[num];
			for (int i = 0; i < num; i++)
			{
				float num2 = (float)i * pitchFactor;
				int num3 = (int)num2;
				float num4 = num2 - (float)num3;
				if (num3 + 1 < samples.Length)
				{
					array[i] = samples[num3] * (1f - num4) + samples[num3 + 1] * num4;
				}
				else if (num3 < samples.Length)
				{
					array[i] = samples[num3];
				}
			}
			return array;
		}

		private float[] ApplyAlienFilter(float[] samples)
		{
			float[] array = new float[samples.Length];
			float num = 5f;
			float num2 = 0.05f;
			float num3 = 200f;
			float num4 = 0.3f;
			for (int i = 0; i < samples.Length; i++)
			{
				float num5 = (float)i / (float)sampleRate;
				float num6 = Mathf.Sin(MathF.PI * 2f * num * num5) * num2;
				float num7 = 1f + num6;
				float num8 = (float)i * num7;
				int num9 = (int)num8;
				float num10 = num8 - (float)num9;
				float num11 = 0f;
				if (num9 + 1 < samples.Length)
				{
					num11 = samples[num9] * (1f - num10) + samples[num9 + 1] * num10;
				}
				else if (num9 < samples.Length)
				{
					num11 = samples[num9];
				}
				float num12 = Mathf.Sin(MathF.PI * 2f * num3 * num5);
				float num13 = num11 * num12 * num4;
				array[i] = num11 * (1f - num4) + num13;
				array[i] = Mathf.Clamp(array[i], -1f, 1f);
			}
			return array;
		}

		private float[] ApplyLowPassFilter(float[] samples, float cutoffFreq)
		{
			float[] array = new float[samples.Length];
			float num = 1f / (MathF.PI * 2f * cutoffFreq);
			float num2 = 1f / (float)sampleRate;
			float num3 = num2 / (num + num2);
			array[0] = samples[0];
			for (int i = 1; i < samples.Length; i++)
			{
				array[i] = array[i - 1] + num3 * (samples[i] - array[i - 1]);
			}
			float[] array2 = new float[samples.Length];
			array2[samples.Length - 1] = array[samples.Length - 1];
			for (int num4 = samples.Length - 2; num4 >= 0; num4--)
			{
				array2[num4] = array2[num4 + 1] + num3 * (array[num4] - array2[num4 + 1]);
			}
			return array2;
		}
	}
	public static class TaskExtensions
	{
		[CompilerGenerated]
		private sealed class <AsCoroutine>d__0 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Task task;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <AsCoroutine>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if (!task.IsCompleted)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				if (task.Exception != null)
				{
					throw task.Exception;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[IteratorStateMachine(typeof(<AsCoroutine>d__0))]
		public static IEnumerator AsCoroutine(this Task task)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <AsCoroutine>d__0(0)
			{
				task = task
			};
		}
	}
	[BepInPlugin("Mimics", "Mimics", "1.1.6")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Logger;

		private static Harmony _harmony;

		public static ConfigEntry<float> configVoiceVolume;

		public static ConfigEntry<float> configMinDelay;

		public static ConfigEntry<float> configMaxDelay;

		public static ConfigEntry<bool> configHearYourself;

		public static ConfigEntry<bool> configFilterEnabled;

		public static Dictionary<string, ConfigEntry<bool>> enemyConfigEntries = new Dictionary<string, ConfigEntry<bool>>();

		public static ConfigEntry<int> configSamplingRate;

		private void Awake()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Expected O, but got Unknown
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Expected O, but got Unknown
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			Logger.LogInfo((object)"Plugin Mimics is loaded!");
			configVoiceVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Volume", 0.75f, new ConfigDescription("Volume of the mimic voices.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			configMinDelay = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MinDelay", 30f, new ConfigDescription("Minimum time before an audio clip is recorded and played.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 120f), Array.Empty<object>()));
			configMaxDelay = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MaxDelay", 120f, new ConfigDescription("Maximum time before an audio clip is recorded and played.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(60f, 240f), Array.Empty<object>()));
			configHearYourself = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Hear Yourself?", true, new ConfigDescription("Turning this off will make it so you won't hear your own voice played by mimics.", (AcceptableValueBase)null, Array.Empty<object>()));
			configSamplingRate = ((BaseUnityPlugin)this).Config.Bind<int>("Experimental", "Sampling Rate", 48000, new ConfigDescription("Only change this value if the console gives you a warning about your microphone frequency not being supported.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16000, 48000), Array.Empty<object>()));
			configFilterEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Filter", "Filter Enabled?", false, "Turning this on allows you to customize which enemies can mimic voices. (Keep as 'false' if you want to allow custom enemies to mimic voices)");
			_harmony = new Harmony("Mimics");
			_harmony.PatchAll();
			EnemyDirectorStartPatch.Initialize(((BaseUnityPlugin)this).Config);
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "Mimics";

		public const string PLUGIN_NAME = "My first plugin";

		public const string PLUGIN_VERSION = "1.1.6";
	}
}
namespace Mimics.patches
{
	[HarmonyPatch(typeof(EnemyDirector))]
	internal class EnemyDirectorStartPatch
	{
		private static readonly ManualLogSource log = Logger.CreateLogSource("Mimics");

		public static HashSet<string> filterEnemies;

		private static bool setupComplete = false;

		private static ConfigFile configFile;

		public static void Initialize(ConfigFile config)
		{
			configFile = config;
			log.LogInfo((object)"EnemyDirectorStartPatch initialized with ConfigFile.");
		}

		[HarmonyPatch("Start")]
		[HarmonyPostfix]
		public static void SetupEnemies(EnemyDirector __instance)
		{
			if (setupComplete)
			{
				return;
			}
			List<EnemySetup>[] array = new List<EnemySetup>[3] { __instance.enemiesDifficulty1, __instance.enemiesDifficulty2, __instance.enemiesDifficulty3 };
			filterEnemies = new HashSet<string>();
			List<EnemySetup>[] array2 = array;
			foreach (List<EnemySetup> list in array2)
			{
				foreach (EnemySetup item in list)
				{
					if (((Object)item.spawnObjects[0]).name.Contains("Director"))
					{
						filterEnemies.Add(((Object)item.spawnObjects[1]).name);
					}
					else
					{
						filterEnemies.Add(((Object)item.spawnObjects[0]).name);
					}
				}
			}
			setupComplete = true;
			SetupEnemyConfig();
		}

		private static void SetupEnemyConfig()
		{
			log.LogInfo((object)"Setting up enemy config...");
			foreach (string filterEnemy in filterEnemies)
			{
				string text = filterEnemy.Replace("Enemy - ", "");
				Plugin.enemyConfigEntries[filterEnemy] = configFile.Bind<bool>("Enemies", filterEnemy, true, "Enables/disables ability for " + filterEnemy + " to mimic player voices.");
				log.LogInfo((object)("Added config entry for enemy: " + filterEnemy));
			}
		}
	}
	public class MimicsFinder : MonoBehaviour
	{
		private static MimicsFinder instance;

		private static readonly ManualLogSource log = Logger.CreateLogSource("Mimics");

		public static Mimics LocalMimics { get; set; }

		public static void EnsureInitialized()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)instance == (Object)null)
			{
				instance = new GameObject("MimicsFinder").AddComponent<MimicsFinder>();
				Object.DontDestroyOnLoad((Object)(object)((Component)instance).gameObject);
				ManualLogSource obj = log;
				Player localPlayer = PhotonNetwork.LocalPlayer;
				obj.LogInfo((object)$"MimicsFinder initialized for Player {((localPlayer != null) ? localPlayer.ActorNumber : (-1))}");
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)instance == (Object)(object)this)
			{
				LocalMimics = null;
				instance = null;
				log.LogInfo((object)"MimicsFinder destroyed, clearing cache.");
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "Awake")]
	internal class PlayerAvatarPatch
	{
		private static readonly ManualLogSource log = Logger.CreateLogSource("Mimics");

		private static void Postfix(PlayerAvatar __instance)
		{
			if (PhotonNetwork.IsConnectedAndReady)
			{
				Mimics mimics = ((Component)__instance).GetComponent<Mimics>();
				if ((Object)(object)mimics == (Object)null)
				{
					mimics = ((Component)__instance).gameObject.AddComponent<Mimics>();
					log.LogInfo((object)("Added Mimics component to PlayerAvatar: " + ((Object)__instance).name));
				}
				PhotonView component = ((Component)__instance).GetComponent<PhotonView>();
				if ((Object)(object)component != (Object)null && component.IsMine)
				{
					MimicsFinder.LocalMimics = mimics;
					log.LogInfo((object)("Set LocalMimics for local PlayerAvatar: " + ((Object)__instance).name));
				}
			}
		}
	}
	[HarmonyPatch(typeof(LocalVoiceFramed<short>), "PushDataAsync")]
	internal class LocalVoiceFramedPatch
	{
		private static void Prefix(short[] buf, LocalVoiceFramed<short> __instance)
		{
			MimicsFinder.EnsureInitialized();
			if (PhotonNetwork.IsConnectedAndReady && !((Object)(object)MimicsFinder.LocalMimics == (Object)null) && !((Object)(object)((Component)MimicsFinder.LocalMimics).gameObject == (Object)null) && MimicsFinder.LocalMimics.photonView.IsMine)
			{
				MimicsFinder.LocalMimics.ProcessVoiceData(buf);
			}
		}
	}
}