Decompiled source of VoiceMimicry v1.0.0

BepInEx/plugins/VoiceMimicry.dll

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

[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("Mimicry")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Mimicry")]
[assembly: AssemblyTitle("Mimicry")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 SmartMimic
{
	public class MimicRecorder : MonoBehaviourPunCallbacks
	{
		private PlayerVoiceChat _pvc;

		private static FieldInfo _isTalkingField;

		private static FieldInfo _audioSourceField;

		private PhotonView _view;

		private List<short> _tempBuffer = new List<short>();

		private bool _isRecording = false;

		private float _silenceTimer = 0f;

		private Dictionary<int, List<short>> _incomingChunks = new Dictionary<int, List<short>>();

		private int _recordedRate = 48000;

		private void Awake()
		{
			_pvc = ((Component)this).GetComponent<PlayerVoiceChat>();
			_isTalkingField = AccessTools.Field(typeof(PlayerVoiceChat), "isTalking");
			_audioSourceField = AccessTools.Field(typeof(PlayerVoiceChat), "audioSource");
			_view = ((Component)this).GetComponent<PhotonView>();
			_recordedRate = AudioSettings.outputSampleRate;
		}

		public void ProcessRawFrame(short[] frame)
		{
			if (!_isRecording || !PhotonNetwork.InRoom)
			{
				return;
			}
			lock (_tempBuffer)
			{
				_tempBuffer.AddRange(frame);
			}
		}

		private void Update()
		{
			if ((Object)(object)LevelGenerator.Instance == (Object)null || !LevelGenerator.Instance.Generated || !PhotonNetwork.InRoom || (Object)(object)_view == (Object)null || !_view.IsMine)
			{
				return;
			}
			if ((bool)_isTalkingField.GetValue(_pvc))
			{
				if (!_isRecording)
				{
					_isRecording = true;
					lock (_tempBuffer)
					{
						_tempBuffer.Clear();
					}
				}
				_silenceTimer = 0f;
			}
			else if (_isRecording)
			{
				_silenceTimer += Time.deltaTime;
				if (_silenceTimer > 0.6f)
				{
					StopAndSave();
				}
			}
		}

		private void StopAndSave()
		{
			_isRecording = false;
			short[] array;
			lock (_tempBuffer)
			{
				if (_tempBuffer.Count < 10000)
				{
					return;
				}
				array = _tempBuffer.ToArray();
				_tempBuffer.Clear();
			}
			VoiceManager.Instance.StoreClip(PhotonNetwork.LocalPlayer.ActorNumber, array, _recordedRate);
			if (PhotonNetwork.IsConnectedAndReady)
			{
				((MonoBehaviour)this).StartCoroutine(SendChunksCoroutine(array, _recordedRate));
			}
		}

		private IEnumerator SendChunksCoroutine(short[] fullClip, int rate)
		{
			int chunkSize = 15000;
			int totalChunks = Mathf.CeilToInt((float)fullClip.Length / (float)chunkSize);
			for (int i = 0; i < totalChunks; i++)
			{
				if (!PhotonNetwork.InRoom)
				{
					yield break;
				}
				short[] chunk = fullClip.Skip(i * chunkSize).Take(chunkSize).ToArray();
				bool isLast = i == totalChunks - 1;
				_view.RPC("RPC_ReceiveMimicChunk", (RpcTarget)1, new object[3] { chunk, isLast, rate });
				yield return null;
			}
			Plugin.Log.LogInfo((object)$"[SmartMimic] Sent clip ({fullClip.Length} samples) in {totalChunks} chunks.");
		}

		[PunRPC]
		public void RPC_ReceiveMimicChunk(short[] chunk, bool isLast, int rate, PhotonMessageInfo info)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			int actorNumber = info.Sender.ActorNumber;
			if (!_incomingChunks.ContainsKey(actorNumber))
			{
				_incomingChunks[actorNumber] = new List<short>();
			}
			_incomingChunks[actorNumber].AddRange(chunk);
			if (isLast)
			{
				VoiceManager.Instance.StoreClip(actorNumber, _incomingChunks[actorNumber].ToArray(), rate);
				_incomingChunks[actorNumber].Clear();
			}
		}

		[PunRPC]
		public void RPC_EnemyPlayVoice(int enemyViewID, int actorNum)
		{
			if (PhotonNetwork.InRoom)
			{
				PhotonView val = PhotonView.Find(enemyViewID);
				if ((Object)(object)val != (Object)null)
				{
					((Component)val).GetComponent<MimicSpeaker>()?.PlayLocal(actorNum);
				}
			}
		}
	}
	public class MimicSpeaker : MonoBehaviour
	{
		private AudioSource _source;

		private float _timer;

		private PhotonView _view;

		private void Start()
		{
			_view = ((Component)this).GetComponent<PhotonView>();
			_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			_source.spatialBlend = 1f;
			_source.minDistance = 5f;
			_source.maxDistance = 35f;
			_source.outputAudioMixerGroup = VoiceManager.Instance.GetGameMixer();
			_timer = Random.Range(20f, 45f);
		}

		private void Update()
		{
			if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && !((Object)(object)LevelGenerator.Instance == (Object)null) && LevelGenerator.Instance.Generated)
			{
				_timer -= Time.deltaTime;
				if (_timer <= 0f)
				{
					_timer = Random.Range(Plugin.Instance.MinDelay.Value, Plugin.Instance.MaxDelay.Value);
					ExecuteSmartMimic();
				}
			}
		}

		private void ExecuteSmartMimic()
		{
			if ((Object)(object)GameDirector.instance == (Object)null || (Object)(object)PlayerVoiceChat.instance == (Object)null)
			{
				return;
			}
			List<PlayerAvatar> list = (from p in GameDirector.instance.PlayerList
				where (Object)(object)p != (Object)null && (Object)(object)p.photonView != (Object)null
				orderby Vector3.Distance(((Component)this).transform.position, ((Component)p).transform.position)
				select p).ToList();
			if (list.Count == 0)
			{
				return;
			}
			int num = -1;
			foreach (PlayerAvatar item in list)
			{
				int actorNumber = item.photonView.Owner.ActorNumber;
				if ((Object)(object)VoiceManager.Instance.GetRandomClip(actorNumber) != (Object)null)
				{
					num = actorNumber;
					break;
				}
			}
			if (num == -1)
			{
				if ((Object)(object)VoiceManager.Instance.GetRandomClip() == (Object)null)
				{
					return;
				}
				num = list[Random.Range(0, list.Count)].photonView.Owner.ActorNumber;
			}
			MimicRecorder component = ((Component)PlayerVoiceChat.instance).GetComponent<MimicRecorder>();
			if ((Object)(object)component != (Object)null)
			{
				((MonoBehaviourPun)component).photonView.RPC("RPC_EnemyPlayVoice", (RpcTarget)0, new object[2] { _view.ViewID, num });
			}
		}

		public void PlayLocal(int actor)
		{
			if (!((Object)(object)_source == (Object)null) && !_source.isPlaying)
			{
				AudioClip randomClip = VoiceManager.Instance.GetRandomClip(actor);
				if ((Object)(object)randomClip != (Object)null)
				{
					_source.volume = Plugin.Instance.Volume.Value;
					_source.PlayOneShot(randomClip);
					Object.Destroy((Object)(object)randomClip, randomClip.length + 0.1f);
				}
			}
		}
	}
	[BepInPlugin("com.randomlygenerated.voicemimicry", "VoiceMimicry", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "com.randomlygenerated.voicemimicry";

		public const string NAME = "VoiceMimicry";

		public const string VERSION = "1.0.0";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		public ConfigEntry<float> Volume;

		public ConfigEntry<float> MinDelay;

		public ConfigEntry<float> MaxDelay;

		public ConfigEntry<int> MemoryLimit;

		public ConfigEntry<bool> HearYourself;

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

		public static Plugin Instance { get; private set; }

		private void Awake()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			InitConfig();
			_harmony = new Harmony("com.randomlygenerated.voicemimicry");
			_harmony.PatchAll();
			GameObject val = new GameObject("SmartMimic_VoiceManager");
			Object.DontDestroyOnLoad((Object)(object)val);
			val.AddComponent<VoiceManager>();
			Log.LogInfo((object)"SmartMimic: Initialized (Clean Architecture).");
		}

		private void InitConfig()
		{
			Volume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Volume", 1f, "Master volume for mimic voices.");
			MinDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "MinDelay", 30f, "Minimum seconds between mimicking.");
			MaxDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "MaxDelay", 120f, "Maximum seconds between mimicking.");
			MemoryLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Optimization", "MemoryClipLimit", 10, "How many voice clips to keep in RAM per player. Higher = more variety but more RAM usage.");
			HearYourself = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HearYourself", false, "[Broken, you still will hear your own voice.]If true, you will hear your own voice from enemies.");
		}

		public bool IsEnemyAllowed(string enemyName)
		{
			string text = enemyName.Replace("(Clone)", "").Replace("Enemy_", "").Trim();
			if (!EnemyConfigs.ContainsKey(text))
			{
				EnemyConfigs[text] = ((BaseUnityPlugin)this).Config.Bind<bool>("Enemies", text, true, "Allow " + text + " to mimic voices?");
			}
			return EnemyConfigs[text].Value;
		}
	}
	public class MimicClip
	{
		public short[] Data;

		public int SamplingRate;

		public float RecordTime;
	}
	public class VoiceManager : MonoBehaviour
	{
		public static VoiceManager Instance;

		private Dictionary<int, List<MimicClip>> _voiceBank = new Dictionary<int, List<MimicClip>>();

		private int _maxClips = 5;

		private void Awake()
		{
			Instance = this;
			_maxClips = Plugin.Instance.MemoryLimit.Value;
		}

		public void ClearBank()
		{
			_voiceBank.Clear();
			Plugin.Log.LogInfo((object)"[SmartMimic] Voice bank cleared for new level.");
		}

		public void StoreClip(int actorNumber, short[] data, int rate)
		{
			if (!_voiceBank.ContainsKey(actorNumber))
			{
				_voiceBank[actorNumber] = new List<MimicClip>();
			}
			List<MimicClip> list = _voiceBank[actorNumber];
			if (list.Count >= _maxClips)
			{
				list.RemoveAt(0);
			}
			list.Add(new MimicClip
			{
				Data = data,
				SamplingRate = rate,
				RecordTime = Time.time
			});
		}

		public AudioClip GetRandomClip(int actorNumber = -1, float minAge = 10f)
		{
			List<MimicClip> list = new List<MimicClip>();
			if (actorNumber != -1)
			{
				if (_voiceBank.ContainsKey(actorNumber))
				{
					list.AddRange(_voiceBank[actorNumber].Where((MimicClip c) => Time.time - c.RecordTime > minAge));
				}
			}
			else
			{
				foreach (KeyValuePair<int, List<MimicClip>> item in _voiceBank)
				{
					list.AddRange(item.Value.Where((MimicClip c) => Time.time - c.RecordTime > minAge));
				}
			}
			if (list.Count == 0)
			{
				return null;
			}
			MimicClip mimicClip = list[Random.Range(0, list.Count)];
			float[] array = new float[mimicClip.Data.Length];
			float num = 0f;
			for (int i = 0; i < mimicClip.Data.Length; i++)
			{
				array[i] = (float)mimicClip.Data[i] / 32768f;
				if (Mathf.Abs(array[i]) > num)
				{
					num = Mathf.Abs(array[i]);
				}
			}
			if (num > 0f && num < 0.7f)
			{
				float num2 = 0.7f / num;
				for (int j = 0; j < array.Length; j++)
				{
					array[j] *= num2;
				}
			}
			AudioClip val = AudioClip.Create("Mimic_Voice", array.Length, 1, mimicClip.SamplingRate, false);
			val.SetData(array, 0);
			return val;
		}

		public AudioMixerGroup GetGameMixer()
		{
			if ((Object)(object)AudioManager.instance == (Object)null)
			{
				return null;
			}
			return AudioManager.instance.MicrophoneSoundGroup;
		}
	}
}
namespace SmartMimic.Patches
{
	[HarmonyPatch(typeof(Enemy), "Start")]
	public static class EnemyPatch
	{
		[HarmonyPostfix]
		public static void Postfix(Enemy __instance)
		{
			GameObject gameObject = ((Component)__instance).gameObject;
			string name = ((Object)gameObject).name;
			EnemyParent componentInParent = gameObject.GetComponentInParent<EnemyParent>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				name = ((Object)((Component)componentInParent).gameObject).name;
			}
			name = name.Replace("(Clone)", "");
			name = name.Replace("Enemy - ", "");
			name = name.Trim();
			if (string.IsNullOrEmpty(name))
			{
				return;
			}
			switch (name)
			{
			case "Controller":
				return;
			case "Enemies":
				return;
			}
			if (!(name == "Level Generator") && Plugin.Instance.IsEnemyAllowed(name) && (Object)(object)gameObject.GetComponent<MimicSpeaker>() == (Object)null)
			{
				Plugin.Log.LogInfo((object)("[SmartMimic] Attached Speaker to monster: " + name));
				gameObject.AddComponent<MimicSpeaker>();
			}
		}
	}
	[HarmonyPatch(typeof(PlayerVoiceChat), "Start")]
	public static class PlayerVoiceChatPatch
	{
		private static FieldInfo _photonViewField;

		[HarmonyPostfix]
		public static void Postfix(PlayerVoiceChat __instance)
		{
			if (_photonViewField == null)
			{
				_photonViewField = AccessTools.Field(typeof(PlayerVoiceChat), "photonView");
			}
			object? value = _photonViewField.GetValue(__instance);
			PhotonView val = (PhotonView)((value is PhotonView) ? value : null);
			if ((Object)(object)val != (Object)null && val.IsMine && (Object)(object)((Component)__instance).GetComponent<MimicRecorder>() == (Object)null)
			{
				Plugin.Log.LogInfo((object)"SmartMimic: Attaching Recorder to Local Player.");
				((Component)__instance).gameObject.AddComponent<MimicRecorder>();
			}
		}
	}
	[HarmonyPatch(typeof(LocalVoiceFramed<short>), "PushDataAsync")]
	public static class VoiceDataPatch
	{
		[HarmonyPrefix]
		public static void Prefix(short[] buf)
		{
			if ((Object)(object)PlayerVoiceChat.instance != (Object)null)
			{
				MimicRecorder component = ((Component)PlayerVoiceChat.instance).GetComponent<MimicRecorder>();
				if ((Object)(object)component != (Object)null)
				{
					component.ProcessRawFrame(buf);
				}
			}
		}
	}
	[HarmonyPatch(typeof(LevelGenerator), "Generate")]
	public static class LevelCleanupPatch
	{
		[HarmonyPrefix]
		public static void Prefix()
		{
			if ((Object)(object)VoiceManager.Instance != (Object)null)
			{
				VoiceManager.Instance.ClearBank();
			}
		}
	}
}