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();
}
}
}
}