Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Mimic v1.1.6
BepInEx/plugins/Mimics.dll
Decompiled a year agousing 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); } } } }