Decompiled source of MirageVoces v0.6.2

MirageVoces.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance.Audio.Playback;
using GameNetcodeStuff;
using HarmonyLib;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("Lloreria")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Txauuuul 2026")]
[assembly: AssemblyDescription("Add-on de Mirage para Lethal Company: voces imitadas, cadaveres que susurran, walkies y puertas con voces.")]
[assembly: AssemblyFileVersion("0.6.2.0")]
[assembly: AssemblyInformationalVersion("0.6.2")]
[assembly: AssemblyProduct("MirageVoces")]
[assembly: AssemblyTitle("MirageVoces - mod de Lethal Company (BepInEx)")]
[assembly: NeutralResourcesLanguage("es-ES")]
[assembly: AssemblyVersion("0.6.2.0")]
namespace MirageVoces;

internal static class Effects
{
	private enum Kind : byte
	{
		Door,
		Body,
		Walkie,
		Monster,
		Lonely,
		Ship
	}

	internal sealed class Clip
	{
		public int Rate;

		public byte[] Pcm;
	}

	internal static class Answers
	{
		internal sealed class Entry
		{
			public string Type;

			public string Wav;

			public string Opus;
		}

		private static Dictionary<string, List<Entry>> byPlayer = new Dictionary<string, List<Entry>>();

		private static readonly Dictionary<string, string> lastPicked = new Dictionary<string, string>();

		private static float nextRefresh;

		private static void RefreshIfStale()
		{
			if (Time.unscaledTime < nextRefresh)
			{
				return;
			}
			nextRefresh = Time.unscaledTime + 10f;
			string path = Path.Combine(Plugin.MirageDir, "Centralizado", "Respuestas");
			Dictionary<string, List<Entry>> dictionary = new Dictionary<string, List<Entry>>();
			if (Directory.Exists(path))
			{
				string[] directories = Directory.GetDirectories(path);
				foreach (string path2 in directories)
				{
					List<Entry> list = (from f in Directory.GetFiles(path2, "*.wav")
						where !f.EndsWith(".tmp48.wav", StringComparison.OrdinalIgnoreCase)
						select new Entry
						{
							Type = Path.GetFileName(f).Split(new string[1] { "__" }, 2, StringSplitOptions.None)[0],
							Wav = f,
							Opus = Path.ChangeExtension(f, ".opus")
						} into e
						where File.Exists(e.Opus)
						select e).ToList();
					if (list.Count > 0)
					{
						dictionary[Path.GetFileName(path2)] = list;
					}
				}
			}
			byPlayer = dictionary;
		}

		internal static string BestType(StartOfRound round, PlayerControllerB p, string[] types)
		{
			RefreshIfStale();
			if (!byPlayer.TryGetValue(KeyFor(round, p), out var all))
			{
				return null;
			}
			return types.FirstOrDefault((string t) => all.Any((Entry e) => e.Type == t));
		}

		internal static Entry Pick(StartOfRound round, PlayerControllerB p, string type)
		{
			RefreshIfStale();
			string key = KeyFor(round, p);
			if (type == null || !byPlayer.TryGetValue(key, out var value))
			{
				return null;
			}
			List<Entry> list = value.Where((Entry e) => e.Type == type).ToList();
			if (list.Count == 0)
			{
				return null;
			}
			lastPicked.TryGetValue(key, out var last);
			List<Entry> list2 = ((list.Count > 1) ? list.Where((Entry e) => e.Wav != last).ToList() : list);
			Entry entry = list2[random.Next(list2.Count)];
			lastPicked[key] = entry.Wav;
			return entry;
		}
	}

	private static class Library
	{
		private static Dictionary<string, List<(string cat, string path)>> byPlayer = new Dictionary<string, List<(string, string)>>();

		private static readonly Dictionary<string, string> lastPicked = new Dictionary<string, string>();

		public static bool IsEmpty => byPlayer.Count == 0;

		public static bool Has(string key)
		{
			if (byPlayer.TryGetValue(key, out List<(string, string)> value))
			{
				return value.Count > 0;
			}
			return false;
		}

		public static void Refresh()
		{
			string path = Path.Combine(Plugin.MirageDir, "Centralizado", "Efectos");
			Dictionary<string, List<(string, string)>> dictionary = new Dictionary<string, List<(string, string)>>();
			if (Directory.Exists(path))
			{
				string[] directories = Directory.GetDirectories(path);
				foreach (string path2 in directories)
				{
					List<(string, string)> list = (from f in Directory.GetFiles(path2, "*.wav")
						select (Path.GetFileName(f).Split(new string[1] { "__" }, 2, StringSplitOptions.None)[0], f: f)).ToList();
					if (list.Count > 0)
					{
						dictionary[Path.GetFileName(path2)] = list;
					}
				}
			}
			byPlayer = dictionary;
		}

		public static string PickPath(string key, string[] cats)
		{
			if (!byPlayer.TryGetValue(key, out List<(string, string)> value) || value.Count == 0)
			{
				return null;
			}
			List<(string, string)> list = value;
			foreach (string cat in cats)
			{
				List<(string, string)> list2 = value.Where<(string, string)>(((string cat, string path) c) => c.cat == cat).ToList();
				if (list2.Count > 0)
				{
					list = list2;
					break;
				}
			}
			lastPicked.TryGetValue(key, out var last);
			List<(string, string)> list3 = ((list.Count > 1) ? list.Where<(string, string)>(((string cat, string path) c) => c.path != last).ToList() : list);
			string item = list3[random.Next(list3.Count)].Item2;
			lastPicked[key] = item;
			return item;
		}

		public static string PickAnyPath(string key, string[] cats, bool peek = false)
		{
			if (!byPlayer.TryGetValue(key, out List<(string, string)> value))
			{
				return null;
			}
			List<(string, string)> list = value.Where<(string, string)>(((string cat, string path) c) => cats.Contains(c.cat)).ToList();
			if (list.Count == 0)
			{
				return null;
			}
			if (peek)
			{
				return list[0].Item2;
			}
			lastPicked.TryGetValue(key, out var last);
			List<(string, string)> list2 = ((list.Count > 1) ? list.Where<(string, string)>(((string cat, string path) c) => c.path != last).ToList() : list);
			string item = list2[random.Next(list2.Count)].Item2;
			lastPicked[key] = item;
			return item;
		}

		public static Clip Pick(string key, string[] cats)
		{
			string text = PickPath(key, cats);
			if (text == null)
			{
				return null;
			}
			try
			{
				return ReadWav(text);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("No se pudo leer " + Path.GetFileName(text) + ": " + ex.Message));
				return null;
			}
		}

		internal static Clip ReadWav(string path)
		{
			using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
			{
				binaryReader.ReadBytes(12);
				int rate = 16000;
				while (binaryReader.BaseStream.Position + 8 <= binaryReader.BaseStream.Length)
				{
					string text = new string(binaryReader.ReadChars(4));
					int num = binaryReader.ReadInt32();
					if (text == "fmt ")
					{
						rate = BitConverter.ToInt32(binaryReader.ReadBytes(num), 4);
						continue;
					}
					if (text == "data")
					{
						num = (int)Math.Min(num, binaryReader.BaseStream.Length - binaryReader.BaseStream.Position);
						return new Clip
						{
							Rate = rate,
							Pcm = binaryReader.ReadBytes(num & -2)
						};
					}
					binaryReader.BaseStream.Seek(num + (num & 1), SeekOrigin.Current);
				}
			}
			return null;
		}
	}

	private const string MessageName = "MirageVoces.Efecto";

	private static readonly string[] BodyCats = new string[5] { "ayuda", "grito", "llamada", "nombre", "frase" };

	private static readonly string[] WalkieCats = new string[6] { "ayuda", "grito", "llamada", "seguir", "nave", "aviso" };

	private static readonly string[] DoorCats = new string[6] { "llamada", "nombre", "seguir", "ayuda", "botin", "grito" };

	private static CustomMessagingManager registeredOn;

	private static readonly Random random = new Random();

	private static readonly Dictionary<ulong, float> nextBody = new Dictionary<ulong, float>();

	private static float nextWalkie;

	private static float nextDoor;

	private static float nextLibraryRefresh;

	private static float nextDoorRefresh;

	private static DoorLock[] doors = (DoorLock[])(object)new DoorLock[0];

	private static readonly FieldInfo doorOpenedField = AccessTools.Field(typeof(DoorLock), "isDoorOpened");

	private static float nextNamesRefresh;

	private static List<(string key, string wav, string steam, bool shortName)> names = new List<(string, string, string, bool)>();

	internal static void EnsureRegistered(NetworkManager net)
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Expected O, but got Unknown
		CustomMessagingManager val = (((Object)(object)net != (Object)null && net.IsListening) ? net.CustomMessagingManager : null);
		if (val == null)
		{
			registeredOn = null;
		}
		else if (val != registeredOn)
		{
			val.RegisterNamedMessageHandler("MirageVoces.Efecto", new HandleNamedMessageDelegate(OnMessage));
			registeredOn = val;
		}
	}

	private unsafe static void Send(Kind kind, Vector3 pos, ulong playerId, int footsteps, Clip clip)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		NetworkManager singleton = NetworkManager.Singleton;
		int num = clip.Pcm.Length + 64;
		FastBufferWriter val = default(FastBufferWriter);
		((FastBufferWriter)(ref val))..ctor(num, (Allocator)2, num);
		try
		{
			byte b = (byte)kind;
			((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteValueSafe(ref pos);
			((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref playerId, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref footsteps, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref clip.Rate, default(ForPrimitives));
			int num2 = clip.Pcm.Length;
			((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num2, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteBytesSafe(clip.Pcm, clip.Pcm.Length, 0);
			singleton.CustomMessagingManager.SendNamedMessageToAll("MirageVoces.Efecto", val, (NetworkDelivery)4);
		}
		finally
		{
			((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
		}
		Play(kind, pos, playerId, footsteps, clip.Rate, clip.Pcm);
	}

	private static void OnMessage(ulong sender, FastBufferReader reader)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!NetworkManager.Singleton.IsHost)
			{
				byte kind = default(byte);
				((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref kind, default(ForPrimitives));
				Vector3 pos = default(Vector3);
				((FastBufferReader)(ref reader)).ReadValueSafe(ref pos);
				ulong playerId = default(ulong);
				((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref playerId, default(ForPrimitives));
				int footsteps = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref footsteps, default(ForPrimitives));
				int rate = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref rate, default(ForPrimitives));
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				if (num > 0 && num <= 1000000)
				{
					byte[] pcm = new byte[num];
					((FastBufferReader)(ref reader)).ReadBytesSafe(ref pcm, num, 0);
					Play((Kind)kind, pos, playerId, footsteps, rate, pcm);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Efecto recibido con error: " + ex.Message));
		}
	}

	private static void Play(Kind kind, Vector3 pos, ulong playerId, int footsteps, int rate, byte[] pcm)
	{
		//IL_0421: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
		StartOfRound instance = StartOfRound.Instance;
		PlayerControllerB val = (((Object)(object)instance != (Object)null) ? instance.localPlayerController : null);
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		float[] array = new float[pcm.Length / 2];
		for (int i = 0; i < array.Length; i++)
		{
			array[i] = (float)BitConverter.ToInt16(pcm, i * 2) / 32768f;
		}
		AudioClip val2 = AudioClip.Create("MirageVoces", array.Length, 1, rate, false);
		val2.SetData(array, 0);
		switch (kind)
		{
		case Kind.Body:
			if (playerId < (ulong)instance.allPlayerScripts.Length)
			{
				DeadBodyInfo deadBody = instance.allPlayerScripts[playerId].deadBody;
				if (!((Object)(object)deadBody == (Object)null) && !deadBody.deactivated && ((Component)deadBody).gameObject.activeInHierarchy)
				{
					Transform obj2 = ((deadBody.bodyParts != null && deadBody.bodyParts.Length != 0 && (Object)(object)deadBody.bodyParts[0] != (Object)null) ? ((Component)deadBody.bodyParts[0]).transform : ((Component)deadBody).transform);
					PlayAndDestroy(MakeSource(obj2, obj2.position, Plugin.BodyVolume.Value, 1f, 9f), val2, 0f);
				}
			}
			break;
		case Kind.Walkie:
		{
			PlayerControllerB val6 = (val.isPlayerDead ? val.spectatedPlayerScript : val);
			if (!((Object)(object)val6 == (Object)null) && !val6.isPlayerDead)
			{
				WalkieTalkie val7 = val6.ItemSlots.OfType<WalkieTalkie>().FirstOrDefault((Func<WalkieTalkie, bool>)((WalkieTalkie w) => (Object)(object)w != (Object)null && ((GrabbableObject)w).isBeingUsed));
				if (!((Object)(object)val7 == (Object)null))
				{
					AudioSource val8 = MakeSource(((Component)val7).transform, ((Component)val7).transform.position, Plugin.WalkieVolume.Value, 1f, 8f);
					((Component)val8).gameObject.AddComponent<AudioHighPassFilter>().cutoffFrequency = 500f;
					((Component)val8).gameObject.AddComponent<AudioLowPassFilter>().cutoffFrequency = 3200f;
					((Component)val8).gameObject.AddComponent<AudioDistortionFilter>().distortionLevel = 0.45f;
					((MonoBehaviour)Runner.Instance).StartCoroutine(WalkieRoutine(val7, val8, val2));
				}
			}
			break;
		}
		case Kind.Lonely:
		{
			bool flag2 = val.playerClientId == playerId && !val.isPlayerDead;
			bool flag3 = val.isPlayerDead && (Object)(object)val.spectatedPlayerScript != (Object)null && val.spectatedPlayerScript.playerClientId == playerId;
			if (flag2 || flag3)
			{
				PlayerControllerB val4 = (flag2 ? val : val.spectatedPlayerScript);
				Transform val5 = (((Object)(object)val4.gameplayCamera != (Object)null) ? ((Component)val4.gameplayCamera).transform : ((Component)val4).transform);
				float num = ((random.NextDouble() < 0.5) ? (-1f) : 1f);
				PlayAndDestroy(MakeSource(val5, val5.position - val5.forward * 1.2f + val5.right * num * 0.8f, Plugin.LonelyVolume.Value, 0.5f, flag3 ? 12f : 5f), val2, 0f);
			}
			break;
		}
		case Kind.Ship:
		{
			AudioSource speakerAudioSource = instance.speakerAudioSource;
			if (!((Object)(object)speakerAudioSource == (Object)null))
			{
				speakerAudioSource.PlayOneShot(val2, Plugin.ShipVolume.Value);
			}
			break;
		}
		case Kind.Monster:
		{
			NetworkManager singleton = NetworkManager.Singleton;
			Dictionary<ulong, NetworkObject> dictionary = ((singleton == null) ? null : singleton.SpawnManager?.SpawnedObjects);
			if (dictionary != null && dictionary.TryGetValue(playerId, out var value) && !((Object)(object)value == (Object)null))
			{
				bool flag = footsteps == 1;
				AudioSource obj = MakeSource(((Component)value).transform, ((Component)value).transform.position + Vector3.up * 2f, Plugin.BrackenVolume.Value * (flag ? 0.85f : 1f), flag ? 3f : 1.5f, flag ? 24f : 28f);
				((Component)obj).gameObject.AddComponent<AudioLowPassFilter>();
				((Component)obj).gameObject.AddComponent<OccludeAudio>();
				PlayAndDestroy(obj, val2, 0f);
			}
			break;
		}
		default:
		{
			AudioSource val3 = MakeSource(null, pos, Plugin.DoorVolume.Value, 1.5f, 22f);
			((Component)val3).gameObject.AddComponent<AudioLowPassFilter>();
			((Component)val3).gameObject.AddComponent<OccludeAudio>();
			((MonoBehaviour)Runner.Instance).StartCoroutine(DoorRoutine(val3, val2, footsteps));
			break;
		}
		}
	}

	private static AudioSource MakeSource(Transform parent, Vector3 pos, float volume, float minDist, float maxDist)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject("MirageVoces.Efecto");
		val.transform.position = pos;
		if ((Object)(object)parent != (Object)null)
		{
			val.transform.SetParent(parent, true);
		}
		AudioSource obj = val.AddComponent<AudioSource>();
		obj.spatialBlend = 1f;
		obj.rolloffMode = (AudioRolloffMode)1;
		obj.minDistance = minDist;
		obj.maxDistance = maxDist;
		obj.dopplerLevel = 0f;
		obj.volume = volume;
		obj.playOnAwake = false;
		return obj;
	}

	private static void PlayAndDestroy(AudioSource src, AudioClip clip, float delay)
	{
		src.clip = clip;
		src.PlayDelayed(delay);
		Object.Destroy((Object)(object)((Component)src).gameObject, delay + clip.length / Mathf.Max(0.1f, src.pitch) + 1f);
	}

	private static IEnumerator WalkieRoutine(WalkieTalkie walkie, AudioSource src, AudioClip clip)
	{
		if (walkie.startTransmissionSFX != null && walkie.startTransmissionSFX.Length != 0 && (Object)(object)walkie.thisAudio != (Object)null)
		{
			walkie.thisAudio.PlayOneShot(walkie.startTransmissionSFX[random.Next(walkie.startTransmissionSFX.Length)]);
		}
		yield return (object)new WaitForSeconds(0.25f);
		if (!((Object)(object)src == (Object)null))
		{
			PlayAndDestroy(src, clip, 0f);
			yield return (object)new WaitForSeconds(clip.length + 0.1f);
			if ((Object)(object)walkie != (Object)null && walkie.stopTransmissionSFX != null && walkie.stopTransmissionSFX.Length != 0 && (Object)(object)walkie.thisAudio != (Object)null)
			{
				walkie.thisAudio.PlayOneShot(walkie.stopTransmissionSFX[random.Next(walkie.stopTransmissionSFX.Length)]);
			}
		}
	}

	private static IEnumerator DoorRoutine(AudioSource src, AudioClip clip, int footsteps)
	{
		FootstepSurface[] footstepSurfaces = StartOfRound.Instance.footstepSurfaces;
		AudioClip[] steps = ((footstepSurfaces != null && footstepSurfaces.Length != 0) ? footstepSurfaces[0].clips : null);
		for (int i = 0; i < footsteps; i++)
		{
			if (steps == null)
			{
				break;
			}
			if (steps.Length == 0)
			{
				break;
			}
			if ((Object)(object)src == (Object)null)
			{
				yield break;
			}
			src.PlayOneShot(steps[random.Next(steps.Length)], 0.8f);
			yield return (object)new WaitForSeconds(0.38f + (float)random.NextDouble() * 0.12f);
		}
		if (!((Object)(object)src == (Object)null))
		{
			yield return (object)new WaitForSeconds(0.4f);
			PlayAndDestroy(src, clip, 0f);
		}
	}

	internal static void Tick(StartOfRound round)
	{
		float time = Time.time;
		if (!round.shipHasLanded || round.inShipPhase)
		{
			nextBody.Clear();
			nextWalkie = time + Range(Plugin.WalkieMinSeconds.Value, Plugin.WalkieMaxSeconds.Value);
			nextDoor = time + Range(Plugin.DoorMinSeconds.Value, Plugin.DoorMaxSeconds.Value);
			return;
		}
		if (time >= nextLibraryRefresh)
		{
			nextLibraryRefresh = time + 10f;
			Library.Refresh();
		}
		if (!Library.IsEmpty)
		{
			List<PlayerControllerB> players = round.allPlayerScripts.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && (p.isPlayerControlled || p.isPlayerDead)).ToList();
			if (Plugin.BodiesEnabled.Value)
			{
				TickBodies(round, players, time);
			}
			if (Plugin.WalkieEnabled.Value && time >= nextWalkie)
			{
				TickWalkie(round, players, time);
			}
			if (Plugin.DoorsEnabled.Value && time >= nextDoor)
			{
				TickDoor(round, players, time);
			}
		}
	}

	private static void TickBodies(StartOfRound round, List<PlayerControllerB> players, float now)
	{
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		foreach (PlayerControllerB player in players)
		{
			DeadBodyInfo deadBody = player.deadBody;
			float value;
			if (!player.isPlayerDead || (Object)(object)deadBody == (Object)null || deadBody.deactivated || !((Component)deadBody).gameObject.activeInHierarchy)
			{
				nextBody.Remove(player.playerClientId);
			}
			else if (!nextBody.TryGetValue(player.playerClientId, out value))
			{
				nextBody[player.playerClientId] = now + Range(15f, 35f);
			}
			else if (!(now < value))
			{
				nextBody[player.playerClientId] = now + Range(Plugin.BodyMinSeconds.Value, Plugin.BodyMaxSeconds.Value);
				Clip clip = Library.Pick(KeyFor(round, player), BodyCats);
				if (clip != null)
				{
					Send(Kind.Body, ((Component)deadBody).transform.position, player.playerClientId, 0, clip);
					Plugin.Log.LogInfo((object)("[efecto] el cadaver de " + player.playerUsername + " susurra."));
					Events.Log("cadaver", player.playerUsername);
				}
			}
		}
	}

	private static void TickWalkie(StartOfRound round, List<PlayerControllerB> players, float now)
	{
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		nextWalkie = now + Range(Plugin.WalkieMinSeconds.Value, Plugin.WalkieMaxSeconds.Value);
		if (!players.Any((PlayerControllerB p) => !p.isPlayerDead && p.ItemSlots.OfType<WalkieTalkie>().Any((WalkieTalkie w) => (Object)(object)w != (Object)null && ((GrabbableObject)w).isBeingUsed)))
		{
			return;
		}
		List<PlayerControllerB> list = players.Where((PlayerControllerB p) => p.isPlayerDead && Library.Has(KeyFor(round, p))).ToList();
		if (list.Count == 0)
		{
			list = players.Where((PlayerControllerB p) => Library.Has(KeyFor(round, p))).ToList();
		}
		if (list.Count != 0)
		{
			PlayerControllerB val = list[random.Next(list.Count)];
			Clip clip = Library.Pick(KeyFor(round, val), WalkieCats);
			if (clip != null)
			{
				Send(Kind.Walkie, Vector3.zero, val.playerClientId, 0, clip);
				Plugin.Log.LogInfo((object)("[efecto] walkie falso con la voz de " + val.playerUsername + "."));
				Events.Log("walkie", val.playerUsername);
			}
		}
	}

	private static void TickDoor(StartOfRound round, List<PlayerControllerB> players, float now)
	{
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		nextDoor = now + Range(Plugin.DoorMinSeconds.Value, Plugin.DoorMaxSeconds.Value);
		if (now >= nextDoorRefresh)
		{
			nextDoorRefresh = now + 20f;
			doors = Object.FindObjectsOfType<DoorLock>();
		}
		List<PlayerControllerB> list = players.Where((PlayerControllerB p) => !p.isPlayerDead && p.isInsideFactory).ToList();
		if (list.Count == 0)
		{
			return;
		}
		PlayerControllerB target = list[random.Next(list.Count)];
		Vector3 pos = ((Component)target).transform.position;
		List<PlayerControllerB> list2 = players.Where((PlayerControllerB p) => (Object)(object)p != (Object)(object)target && Library.Has(KeyFor(round, p)) && (p.isPlayerDead || Vector3.Distance(((Component)p).transform.position, pos) > 25f)).ToList();
		if (list2.Count != 0)
		{
			PlayerControllerB val = list2[random.Next(list2.Count)];
			if (DoorNear(round, target, val, DoorCats))
			{
				Plugin.Log.LogInfo((object)("[efecto] voz de " + val.playerUsername + " tras una puerta cerca de " + target.playerUsername + "."));
			}
			Events.Log("puerta", val.playerUsername, target.playerUsername);
		}
	}

	internal static bool DoorNear(StartOfRound round, PlayerControllerB listener, PlayerControllerB speaker, string[] cats)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		DoorLock val = FindDoor(listener);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		Clip clip = Library.Pick(KeyFor(round, speaker), cats);
		if (clip == null)
		{
			return false;
		}
		Send(Kind.Door, ((Component)val).transform.position + Vector3.up, speaker.playerClientId, random.Next(2, 5), clip);
		return true;
	}

	internal static bool DoorClip(PlayerControllerB listener, PlayerControllerB speaker, Clip clip, int footsteps)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		DoorLock val = FindDoor(listener);
		if ((Object)(object)val == (Object)null || clip == null)
		{
			return false;
		}
		Send(Kind.Door, ((Component)val).transform.position + Vector3.up, speaker.playerClientId, footsteps, clip);
		return true;
	}

	internal static bool MonsterClip(EnemyAI enemy, Clip clip, bool whisper)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)enemy == (Object)null || clip == null || (Object)(object)((NetworkBehaviour)enemy).NetworkObject == (Object)null)
		{
			return false;
		}
		Send(Kind.Monster, ((Component)enemy).transform.position, ((NetworkBehaviour)enemy).NetworkObjectId, whisper ? 1 : 0, clip);
		return true;
	}

	internal static bool WalkieClip(PlayerControllerB speaker, Clip clip)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		if (clip == null)
		{
			return false;
		}
		Send(Kind.Walkie, Vector3.zero, speaker.playerClientId, 0, clip);
		return true;
	}

	internal static bool HiddenSpotClip(PlayerControllerB listener, PlayerControllerB speaker, Clip clip)
	{
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		RoundManager instance = RoundManager.Instance;
		if (clip == null || (Object)(object)instance == (Object)null)
		{
			return false;
		}
		GameObject[] array = (listener.isInsideFactory ? instance.insideAINodes : instance.outsideAINodes);
		if (array == null || array.Length == 0)
		{
			return false;
		}
		Vector3 eye = (((Object)(object)listener.gameplayCamera != (Object)null) ? ((Component)listener.gameplayCamera).transform.position : (((Component)listener).transform.position + Vector3.up * 1.6f));
		int mask = StartOfRound.Instance.collidersAndRoomMaskAndDefault;
		List<Vector3> list = (from n in array
			where (Object)(object)n != (Object)null
			select n.transform.position + Vector3.up * 1.2f).Where(delegate(Vector3 pos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			float num = Vector3.Distance(pos, eye);
			return num > 8f && num < 20f;
		}).ToList();
		if (list.Count == 0)
		{
			return false;
		}
		List<Vector3> list2 = list.Where((Vector3 pos) => Physics.Linecast(eye, pos, mask, (QueryTriggerInteraction)1)).ToList();
		List<Vector3> list3 = ((list2.Count > 0) ? list2 : list);
		Send(Kind.Door, list3[random.Next(list3.Count)], speaker.playerClientId, random.Next(0, 3), clip);
		return true;
	}

	private static DoorLock FindDoor(PlayerControllerB listener)
	{
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		if (!listener.isInsideFactory)
		{
			return null;
		}
		if (Time.time >= nextDoorRefresh)
		{
			nextDoorRefresh = Time.time + 20f;
			doors = Object.FindObjectsOfType<DoorLock>();
		}
		Vector3 pos = ((Component)listener).transform.position;
		List<DoorLock> list = doors.Where((DoorLock d) => (Object)(object)d != (Object)null && !IsOpen(d)).Where(delegate(DoorLock d)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			float num = Vector3.Distance(((Component)d).transform.position, pos);
			return num > 4f && num < 14f;
		}).ToList();
		if (list.Count != 0)
		{
			return list[random.Next(list.Count)];
		}
		return null;
	}

	internal static bool PlayAt(StartOfRound round, Vector3 pos, PlayerControllerB speaker, string[] cats)
	{
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		if (Time.time >= nextLibraryRefresh)
		{
			nextLibraryRefresh = Time.time + 10f;
			Library.Refresh();
		}
		Clip clip = Library.Pick(KeyFor(round, speaker), cats);
		if (clip == null)
		{
			return false;
		}
		Send(Kind.Door, pos, speaker.playerClientId, 0, clip);
		return true;
	}

	internal static bool WalkieFrom(StartOfRound round, PlayerControllerB speaker, string[] cats)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		Clip clip = Library.Pick(KeyFor(round, speaker), cats);
		if (clip == null)
		{
			return false;
		}
		Send(Kind.Walkie, Vector3.zero, speaker.playerClientId, 0, clip);
		return true;
	}

	internal static bool PointClip(Vector3 pos, PlayerControllerB speaker, Clip clip)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		if (clip == null)
		{
			return false;
		}
		Send(Kind.Door, pos, speaker.playerClientId, 0, clip);
		return true;
	}

	internal static bool LonelyClip(PlayerControllerB target, PlayerControllerB owner, Clip clip)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		if (clip == null)
		{
			return false;
		}
		Send(Kind.Lonely, Vector3.zero, target.playerClientId, 0, clip);
		return true;
	}

	internal static bool ShipClip(PlayerControllerB owner, Clip clip)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		if (clip == null)
		{
			return false;
		}
		Send(Kind.Ship, Vector3.zero, owner.playerClientId, 0, clip);
		return true;
	}

	internal static (string path, Clip clip)? PickAnyClip(StartOfRound round, PlayerControllerB p, string[] cats)
	{
		if (Time.time >= nextLibraryRefresh)
		{
			nextLibraryRefresh = Time.time + 10f;
			Library.Refresh();
		}
		string text = Library.PickAnyPath(KeyFor(round, p), cats);
		if (text == null)
		{
			return null;
		}
		Clip clip = ReadWavFile(text);
		if (clip != null)
		{
			return (text, clip);
		}
		return null;
	}

	internal static bool HasAnyClip(StartOfRound round, PlayerControllerB p, string[] cats)
	{
		if (Time.time >= nextLibraryRefresh)
		{
			nextLibraryRefresh = Time.time + 10f;
			Library.Refresh();
		}
		return Library.PickAnyPath(KeyFor(round, p), cats, peek: true) != null;
	}

	internal static Clip PickClip(StartOfRound round, PlayerControllerB p, string[] cats)
	{
		if (Time.time >= nextLibraryRefresh)
		{
			nextLibraryRefresh = Time.time + 10f;
			Library.Refresh();
		}
		return Library.Pick(KeyFor(round, p), cats);
	}

	internal static string LatestClip(string key, DateTime from, DateTime to)
	{
		string path = Path.Combine(Plugin.MirageDir, "Centralizado", "Efectos", key);
		if (!Directory.Exists(path))
		{
			return null;
		}
		return (from f in Directory.GetFiles(path, "*.wav")
			select (f: f, t: File.GetLastWriteTime(f)) into x
			where x.t >= @from && x.t <= to
			orderby x.t descending
			select x.f).FirstOrDefault();
	}

	internal static string OpusFor(string key, string wav)
	{
		string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(wav);
		int num = fileNameWithoutExtension.IndexOf("__", StringComparison.Ordinal);
		string path = ((num >= 0) ? fileNameWithoutExtension.Substring(num + 2) : fileNameWithoutExtension) + ".opus";
		string text = ((key == "yo") ? Path.Combine(Plugin.MirageDir, "Recording", path) : Path.Combine(Plugin.ActiveDir, key, path));
		if (!File.Exists(text))
		{
			return null;
		}
		return text;
	}

	internal static (PlayerControllerB owner, string wav)? NameClip(StartOfRound round, PlayerControllerB target, List<PlayerControllerB> players)
	{
		if (Time.unscaledTime >= nextNamesRefresh)
		{
			nextNamesRefresh = Time.unscaledTime + 15f;
			names = ReadNames();
		}
		string who = LiveSpeech.Normalize(target.playerUsername);
		string targetKey = KeyFor(round, target);
		Dictionary<string, PlayerControllerB> owners = players.Where((PlayerControllerB p) => (Object)(object)p != (Object)(object)target).ToDictionary((PlayerControllerB p) => KeyFor(round, p));
		List<(string, string, string, bool)> list = names.Where(((string key, string wav, string steam, bool shortName) n) => LiveSpeech.Normalize(n.steam) == who && n.key != targetKey && owners.ContainsKey(n.key) && File.Exists(n.wav)).ToList();
		if (list.Count == 0)
		{
			return null;
		}
		List<(string, string, string, bool)> list2 = list.Where<(string, string, string, bool)>(((string key, string wav, string steam, bool shortName) n) => n.shortName).ToList();
		List<(string, string, string, bool)> list3 = ((list2.Count > 0 && random.NextDouble() < 0.75) ? list2 : list);
		(string, string, string, bool) tuple = list3[random.Next(list3.Count)];
		return (owners[tuple.Item1], tuple.Item2);
	}

	private static List<(string, string, string, bool)> ReadNames()
	{
		List<(string, string, string, bool)> list = new List<(string, string, string, bool)>();
		string path = Path.Combine(Plugin.MirageDir, "Centralizado");
		Read(Path.Combine(path, "Respuestas", "nombres.txt"), Path.Combine(path, "Respuestas"), ".wav", shortName: true);
		Read(Path.Combine(path, "Efectos", "nombres.txt"), Path.Combine(path, "Efectos"), "", shortName: false);
		return list;
		void Read(string file, string root, string ext, bool shortName)
		{
			if (!File.Exists(file))
			{
				return;
			}
			try
			{
				string[] array = File.ReadAllLines(file);
				foreach (string text in array)
				{
					int num = text.LastIndexOf('|');
					int num2 = text.IndexOf('/');
					if (num > 0 && num2 > 0 && num2 <= num)
					{
						string text2 = text.Substring(0, num2);
						string text3 = text.Substring(num2 + 1, num - num2 - 1);
						list.Add((text2, Path.Combine(root, text2, text3 + ext), text.Substring(num + 1).Trim(), shortName));
					}
				}
			}
			catch (IOException)
			{
			}
		}
	}

	internal static string FindOpus(StartOfRound round, PlayerControllerB p, string[] cats)
	{
		if (Time.time >= nextLibraryRefresh)
		{
			nextLibraryRefresh = Time.time + 10f;
			Library.Refresh();
		}
		string text = KeyFor(round, p);
		string text2 = Library.PickPath(text, cats);
		if (text2 == null)
		{
			return null;
		}
		string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2);
		int num = fileNameWithoutExtension.IndexOf("__", StringComparison.Ordinal);
		string path = ((num >= 0) ? fileNameWithoutExtension.Substring(num + 2) : fileNameWithoutExtension) + ".opus";
		string text3 = ((text == "yo") ? Path.Combine(Plugin.MirageDir, "Recording", path) : Path.Combine(Plugin.ActiveDir, text, path));
		if (!File.Exists(text3))
		{
			return null;
		}
		return text3;
	}

	internal static bool HasPhrases(StartOfRound round, PlayerControllerB p)
	{
		if (Library.IsEmpty || Time.time >= nextLibraryRefresh)
		{
			nextLibraryRefresh = Time.time + 10f;
			Library.Refresh();
		}
		return Library.Has(KeyFor(round, p));
	}

	private static bool IsOpen(DoorLock d)
	{
		if (doorOpenedField != null)
		{
			return (bool)doorOpenedField.GetValue(d);
		}
		return false;
	}

	internal static string KeyFor(StartOfRound round, PlayerControllerB p)
	{
		if (!((Object)(object)p == (Object)(object)round.localPlayerController))
		{
			return p.playerSteamId.ToString();
		}
		return "yo";
	}

	private static float Range(float a, float b)
	{
		return Mathf.Min(a, b) + (float)random.NextDouble() * Mathf.Abs(b - a);
	}

	internal static Clip ReadWavFile(string path)
	{
		try
		{
			return Library.ReadWav(path);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("No se pudo leer " + Path.GetFileName(path) + ": " + ex.Message));
			return null;
		}
	}
}
internal static class Events
{
	private static readonly object fileLock = new object();

	private static string FilePath => Path.Combine(Plugin.MirageDir, "Centralizado", "Vivo", "eventos.jsonl");

	internal static void Log(string type, string player = null, string other = null, string detail = null, string file = null)
	{
		StringBuilder stringBuilder = new StringBuilder("{");
		Field(stringBuilder, "t", ((double)DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0).ToString(CultureInfo.InvariantCulture), quote: false);
		Field(stringBuilder, "tipo", type);
		Field(stringBuilder, "jugador", player);
		Field(stringBuilder, "otro", other);
		Field(stringBuilder, "detalle", detail);
		Field(stringBuilder, "archivo", file);
		stringBuilder.Append("}\n");
		lock (fileLock)
		{
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
				File.AppendAllText(FilePath, stringBuilder.ToString(), Encoding.UTF8);
			}
			catch (IOException)
			{
			}
		}
	}

	private static void Field(StringBuilder sb, string name, string value, bool quote = true)
	{
		if (value == null)
		{
			return;
		}
		if (sb.Length > 1)
		{
			sb.Append(',');
		}
		sb.Append('"').Append(name).Append("\":");
		if (!quote)
		{
			sb.Append(value);
			return;
		}
		sb.Append('"');
		foreach (char c in value)
		{
			if (c == '"' || c == '\\')
			{
				sb.Append('\\').Append(c);
			}
			else if (c < ' ')
			{
				StringBuilder stringBuilder = sb.Append("\\u");
				int num = c;
				stringBuilder.Append(num.ToString("x4"));
			}
			else
			{
				sb.Append(c);
			}
		}
		sb.Append('"');
	}
}
public static class HostBridge
{
	public static Action<string, string, string> Start;

	public static Func<bool> IsRunning;

	public static Action Stop;
}
internal static class LiveSpeech
{
	[Serializable]
	private class Line
	{
		public string clave;

		public string jugador;

		public string texto;

		public string[] categorias;

		public bool guardada;

		public double t;
	}

	private class Heard
	{
		public PlayerControllerB Speaker;

		public string Text;

		public string Norm;

		public float Time;
	}

	private static float nextLaunchTry;

	private static float nextHeartbeat;

	private static float nextSignal;

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

	private static readonly Dictionary<string, int> forbiddenThisMoon = new Dictionary<string, int>();

	private static float lastAnswer = -9999f;

	private static float lastOpenAnswer = -9999f;

	private static int launchFailures;

	private static long readOffset;

	private static string partial = "";

	private static bool wasOnMoon;

	private static readonly List<Heard> recent = new List<Heard>();

	private static readonly Dictionary<ulong, float> lastAnswerFor = new Dictionary<ulong, float>();

	private static readonly List<(float at, Action action)> pending = new List<(float, Action)>();

	private static readonly Random random = new Random();

	private static List<(string steam, string nicks)> nicknameCache = new List<(string, string)>();

	private static float nextNicknameRead;

	private static string VivoDir => Path.Combine(Plugin.MirageDir, "Centralizado", "Vivo");

	internal static void Tick(StartOfRound round)
	{
		if (!Plugin.LiveEnabled.Value)
		{
			return;
		}
		EnsureServer();
		Heartbeat();
		ReadResults(round);
		float time = Time.time;
		for (int num = pending.Count - 1; num >= 0; num--)
		{
			if (!(time < pending[num].at))
			{
				Action item = pending[num].action;
				pending.RemoveAt(num);
				try
				{
					item();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Reaccion en vivo fallida: " + ex.Message));
				}
			}
		}
		int num2;
		if (round.shipHasLanded)
		{
			num2 = ((!round.inShipPhase) ? 1 : 0);
			if (num2 != 0 && !wasOnMoon)
			{
				forbiddenThisMoon.Clear();
				nextSignal = time + Range(Plugin.SignalMinSeconds.Value, Plugin.SignalMaxSeconds.Value);
			}
		}
		else
		{
			num2 = 0;
		}
		wasOnMoon = (byte)num2 != 0;
		if (num2 != 0 && Plugin.SignalEnabled.Value && time >= nextSignal)
		{
			nextSignal = time + Range(Plugin.SignalMinSeconds.Value, Plugin.SignalMaxSeconds.Value);
			TrySignalTranslator(round);
		}
	}

	private static void EnsureServer()
	{
		if (HostBridge.Start == null || (HostBridge.IsRunning != null && HostBridge.IsRunning()) || Time.unscaledTime < nextLaunchTry || launchFailures >= 3)
		{
			return;
		}
		nextLaunchTry = Time.unscaledTime + 60f;
		string text = Plugin.LiveScript.Value.Trim().Trim('"');
		if (string.IsNullOrEmpty(text) || !File.Exists(text))
		{
			if (launchFailures++ == 0)
			{
				Plugin.Log.LogWarning((object)("Whisper en vivo desactivado: no encuentro el script '" + text + "'. Ponlo en la config (Whisper en vivo > Script)."));
			}
			return;
		}
		try
		{
			Directory.CreateDirectory(VivoDir);
			File.WriteAllText(Path.Combine(VivoDir, "latido.txt"), DateTime.Now.ToString("o"));
			string text2 = Path.Combine(VivoDir, "resultados.jsonl");
			readOffset = (File.Exists(text2) ? new FileInfo(text2).Length : 0);
			partial = "";
			HostBridge.Start(Plugin.LivePython.Value, text, VivoDir);
			Plugin.Log.LogInfo((object)("Whisper en vivo arrancado (registro en " + Path.Combine(VivoDir, "servidor.log") + ")."));
		}
		catch (Exception ex)
		{
			launchFailures++;
			Plugin.Log.LogWarning((object)("No se pudo arrancar Whisper en vivo (" + Plugin.LivePython.Value + "): " + ex.Message));
		}
	}

	private static void Heartbeat()
	{
		if (Time.unscaledTime < nextHeartbeat)
		{
			return;
		}
		nextHeartbeat = Time.unscaledTime + 5f;
		try
		{
			File.WriteAllText(Path.Combine(VivoDir, "latido.txt"), DateTime.Now.ToString("o"));
		}
		catch
		{
		}
	}

	internal static void Shutdown()
	{
		try
		{
			HostBridge.Stop?.Invoke();
		}
		catch
		{
		}
	}

	private static void ReadResults(StartOfRound round)
	{
		string path = Path.Combine(VivoDir, "resultados.jsonl");
		if (!File.Exists(path))
		{
			return;
		}
		try
		{
			using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
			if (fileStream.Length < readOffset)
			{
				readOffset = 0L;
				partial = "";
			}
			if (fileStream.Length == readOffset)
			{
				return;
			}
			fileStream.Seek(readOffset, SeekOrigin.Begin);
			byte[] array = new byte[fileStream.Length - readOffset];
			int num = fileStream.Read(array, 0, array.Length);
			readOffset += num;
			partial += Encoding.UTF8.GetString(array, 0, num);
		}
		catch (IOException)
		{
			return;
		}
		int num2;
		while ((num2 = partial.IndexOf('\n')) >= 0)
		{
			string text = partial.Substring(0, num2).Trim();
			partial = partial.Substring(num2 + 1);
			if (text.Length != 0)
			{
				Line line;
				try
				{
					line = JsonUtility.FromJson<Line>(text);
				}
				catch
				{
					continue;
				}
				if (line != null && !string.IsNullOrWhiteSpace(line.texto))
				{
					OnHeard(round, line);
				}
			}
		}
	}

	private static void OnHeard(StartOfRound round, Line line)
	{
		PlayerControllerB val = (PlayerControllerB)((line.clave == "yo") ? ((object)round.localPlayerController) : ((object)((IEnumerable<PlayerControllerB>)round.allPlayerScripts).FirstOrDefault((Func<PlayerControllerB, bool>)((PlayerControllerB p) => (Object)(object)p != (Object)null && p.playerSteamId.ToString() == line.clave))));
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		Heard heard = new Heard
		{
			Speaker = val,
			Text = line.texto.Trim(),
			Norm = Normalize(line.texto),
			Time = Time.time
		};
		recent.Add(heard);
		recent.RemoveAll((Heard h) => Time.time - h.Time > 900f);
		Plugin.Log.LogDebug((object)("[vivo] " + val.playerUsername + ": " + heard.Text));
		if (!round.shipHasLanded || round.inShipPhase)
		{
			return;
		}
		if ((Object)(object)val == (Object)(object)round.localPlayerController && IsTestPhrase(heard.Norm))
		{
			pending.Add((Time.time + 1f, delegate
			{
				TestVoices(round);
			}));
			return;
		}
		if (Plugin.AnswersEnabled.Value)
		{
			TryAnswer(round, heard, line.texto);
		}
		if (Plugin.ForbiddenEnabled.Value)
		{
			TryForbidden(round, heard);
		}
	}

	private static void TryAnswer(StartOfRound round, Heard heard, string raw)
	{
		PlayerControllerB asker = heard.Speaker;
		if (asker.isPlayerDead)
		{
			return;
		}
		List<PlayerControllerB> inGame = round.allPlayerScripts.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && (p.isPlayerControlled || p.isPlayerDead)).ToList();
		Intent intent = Preguntas.Detect(raw, heard.Norm, (string w) => inGame.Any((PlayerControllerB p) => NameWords(p).Any((string n) => Preguntas.WordMatchesName(w, n))));
		if (intent == Intent.None || Time.time - lastAnswer < 6f)
		{
			return;
		}
		string[] types = Preguntas.AnswerTypes(intent);
		List<PlayerControllerB> list = inGame.Where((PlayerControllerB p) => (Object)(object)p != (Object)(object)asker && NameMentioned(p, heard.Norm)).ToList();
		List<PlayerControllerB> voices;
		if (list.Count > 0)
		{
			voices = list.Where((PlayerControllerB p) => !CanReallyAnswer(p, asker)).ToList();
			if (voices.Count == 0)
			{
				Plugin.Log.LogInfo((object)$"[respuesta] {asker.playerUsername} pregunta ({intent}) a quien puede contestar de verdad: no hago nada.");
				return;
			}
		}
		else
		{
			if (Time.time - lastOpenAnswer < 40f || (intent == Intent.Saludo && random.NextDouble() < 0.5))
			{
				return;
			}
			voices = inGame.Where((PlayerControllerB p) => (Object)(object)p != (Object)(object)asker && !CanReallyAnswer(p, asker)).ToList();
			if (inGame.Count == 1)
			{
				voices.Add(asker);
			}
		}
		voices = voices.Where((PlayerControllerB p) => (!lastAnswerFor.TryGetValue(p.playerClientId, out var value) || !(Time.time - value < 15f)) && Effects.Answers.BestType(round, p, types) != null).ToList();
		if (voices.Count == 0)
		{
			Plugin.Log.LogInfo((object)($"[respuesta] {asker.playerUsername}: \"{heard.Text}\" ({intent}), pero nadie que pueda contestar " + "tiene respuestas de tipo " + string.Join("/", types) + " (ver resumen.txt)."));
			return;
		}
		lastAnswer = Time.time;
		bool isNamed = list.Count > 0;
		pending.Add((Time.time + Range(0.4f, 0.9f), delegate
		{
			Answer(round, asker, voices, types, intent, isNamed, heard.Text, 0);
		}));
	}

	private static void Answer(StartOfRound round, PlayerControllerB asker, List<PlayerControllerB> voices, string[] types, Intent intent, bool named, string question, int attempt)
	{
		if ((Object)(object)asker == (Object)null || asker.isPlayerDead)
		{
			return;
		}
		List<(EnemyAI enemy, PlayerControllerB player)> list = (from m in CentralPlayback.MimicsNear(asker, 40f)
			where voices.Contains(m.player)
			select m).ToList();
		bool flag = false;
		foreach (var item3 in list)
		{
			EnemyAI item = item3.enemy;
			PlayerControllerB item2 = item3.player;
			Effects.Answers.Entry entry = Effects.Answers.Pick(round, item2, Effects.Answers.BestType(round, item2, types));
			if (entry != null)
			{
				if (CentralPlayback.PlayNow(item, entry.Opus))
				{
					Done(item2, entry, "contesta " + item.enemyType?.enemyName);
					return;
				}
				flag = true;
			}
		}
		if (flag && attempt < 5)
		{
			pending.Add((Time.time + 0.5f, delegate
			{
				Answer(round, asker, voices, types, intent, named, question, attempt + 1);
			}));
			return;
		}
		List<PlayerControllerB> list2 = voices.Where((PlayerControllerB p) => p.isPlayerDead).ToList();
		PlayerControllerB val = ((list2.Count > 0) ? list2[random.Next(list2.Count)] : voices[random.Next(voices.Count)]);
		Effects.Answers.Entry entry2 = Effects.Answers.Pick(round, val, Effects.Answers.BestType(round, val, types));
		if (entry2 != null)
		{
			if (Plugin.DoorsEnabled.Value && asker.isInsideFactory && (named || random.NextDouble() < 0.6) && Effects.DoorClip(asker, val, Effects.ReadWavFile(entry2.Wav), random.Next(1, 3)))
			{
				Done(val, entry2, "tras una puerta");
			}
			else if (Plugin.WalkieEnabled.Value && HasWalkieOn(asker) && Effects.WalkieClip(val, Effects.ReadWavFile(entry2.Wav)))
			{
				Done(val, entry2, "por el walkie");
			}
			else if (Effects.HiddenSpotClip(asker, val, Effects.ReadWavFile(entry2.Wav)))
			{
				Done(val, entry2, "desde un rincon oculto");
			}
			else
			{
				Plugin.Log.LogInfo((object)$"[respuesta] {asker.playerUsername}: \"{question}\" ({intent}): no hay monstruo que imite, puerta ni walkie cerca.");
			}
		}
		void Done(PlayerControllerB voice, Effects.Answers.Entry entry3, string how)
		{
			lastAnswer = Time.time;
			if (!named)
			{
				lastOpenAnswer = Time.time;
			}
			lastAnswerFor[voice.playerClientId] = Time.time;
			Plugin.Log.LogInfo((object)($"[respuesta] {asker.playerUsername}: \"{question}\" ({intent}) -> {how} con la voz de " + voice.playerUsername + ": " + Path.GetFileNameWithoutExtension(entry3.Wav)));
			Events.Log("respuesta", voice.playerUsername, asker.playerUsername, question + " -> " + how, entry3.Wav);
		}
	}

	private static bool CanReallyAnswer(PlayerControllerB p, PlayerControllerB asker)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		if (p.isPlayerDead || !p.isPlayerControlled)
		{
			return false;
		}
		if (HasWalkieOn(p) && HasWalkieOn(asker))
		{
			return true;
		}
		if (p.isInsideFactory == asker.isInsideFactory)
		{
			return Vector3.Distance(((Component)p).transform.position, ((Component)asker).transform.position) < 25f;
		}
		return false;
	}

	private static bool HasWalkieOn(PlayerControllerB p)
	{
		return p.ItemSlots.OfType<WalkieTalkie>().Any((WalkieTalkie w) => (Object)(object)w != (Object)null && ((GrabbableObject)w).isBeingUsed);
	}

	private static bool IsTestPhrase(string norm)
	{
		if (Plugin.TestPhraseEnabled.Value)
		{
			return Preguntas.IsTestPhrase(norm);
		}
		return false;
	}

	private static void TestVoices(StartOfRound round)
	{
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0146: Unknown result type (might be due to invalid IL or missing references)
		PlayerControllerB me = round.localPlayerController;
		string[] cats = new string[3] { "llamada", "seguir", "ayuda" };
		if (!Effects.HasPhrases(round, me))
		{
			Plugin.Log.LogWarning((object)"[prueba] No hay frases tuyas en Mirage/Centralizado/Efectos/yo: ¿se ejecuto Mirage Voces?");
			HUDManager instance = HUDManager.Instance;
			if (instance != null)
			{
				instance.DisplayTip("MirageVoces", "No hay frases tuyas todavia", true, false, "LC_Tip1");
			}
			return;
		}
		List<string> list = new List<string>();
		if (Effects.PlayAt(round, ((Component)me).transform.position + ((Component)me).transform.forward * 4f + Vector3.up, me, cats))
		{
			list.Add("delante de ti");
		}
		pending.Add((Time.time + 6f, delegate
		{
			if (Effects.DoorNear(round, me, me, cats))
			{
				Plugin.Log.LogInfo((object)"[prueba] ... y tras una puerta");
			}
		}));
		pending.Add((Time.time + 12f, delegate
		{
			if (me.ItemSlots.OfType<WalkieTalkie>().Any((WalkieTalkie w) => (Object)(object)w != (Object)null && ((GrabbableObject)w).isBeingUsed) && Effects.WalkieFrom(round, me, cats))
			{
				Plugin.Log.LogInfo((object)"[prueba] ... y por el walkie");
			}
		}));
		EnemyAI enemy = CentralPlayback.FindMimicking(me, ((Component)me).transform.position, 30f);
		string opus = (((Object)(object)enemy != (Object)null) ? Effects.FindOpus(round, me, cats) : null);
		if (opus != null)
		{
			pending.Add((Time.time + 18f, delegate
			{
				if (CentralPlayback.PlayNow(enemy, opus))
				{
					Plugin.Log.LogInfo((object)("[prueba] ... y desde " + enemy.enemyType?.enemyName));
				}
			}));
		}
		if (opus != null)
		{
			list.Add("luego " + enemy.enemyType?.enemyName);
		}
		string text = ((list.Count > 0) ? string.Join(", ", list) : "nada (sin sitio donde sonar)");
		Plugin.Log.LogInfo((object)("[prueba] Tu voz suena en: " + text));
		HUDManager instance2 = HUDManager.Instance;
		if (instance2 != null)
		{
			instance2.DisplayTip("MirageVoces", "Prueba: " + text, false, false, "LC_Tip1");
		}
	}

	private static void TryForbidden(StartOfRound round, Heard heard)
	{
		//IL_0200: Unknown result type (might be due to invalid IL or missing references)
		//IL_0205: Unknown result type (might be due to invalid IL or missing references)
		//IL_027c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0294: Unknown result type (might be due to invalid IL or missing references)
		PlayerControllerB speaker = heard.Speaker;
		foreach (var item in ParseForbidden())
		{
			if (!Preguntas.IsSummon(heard.Norm, Plugin.SummonPhrase.Value, item.word))
			{
				continue;
			}
			string text = null;
			forbiddenThisMoon.TryGetValue(item.word, out var value);
			float value2;
			if (Plugin.SummonHostOnly.Value && (Object)(object)speaker != (Object)(object)round.localPlayerController)
			{
				text = "solo puede invocar el anfitrion";
			}
			else if (speaker.isPlayerDead)
			{
				text = "esta muerto";
			}
			else if (!speaker.isInsideFactory)
			{
				text = "esta fuera del edificio";
			}
			else if (value >= Plugin.ForbiddenMaxPerMoon.Value)
			{
				text = $"ya salio {value} vez en esta luna";
			}
			else if (lastForbidden.TryGetValue(item.word, out value2) && Time.time - value2 < Plugin.ForbiddenCooldown.Value)
			{
				text = $"faltan {(int)(Plugin.ForbiddenCooldown.Value - (Time.time - value2))} s de espera";
			}
			if (text != null)
			{
				Plugin.Log.LogInfo((object)("[vivo] " + speaker.playerUsername + " dijo '" + item.word + "' (\"" + heard.Text + "\"), pero no aparece: " + text + "."));
				break;
			}
			EnemyType val = FindEnemyType(round, item.enemy);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)("Palabra prohibida '" + item.word + "': no existe el monstruo '" + item.enemy + "'."));
				continue;
			}
			GameObject[] array = RoundManager.Instance?.insideAINodes;
			if (array != null && array.Length != 0)
			{
				Vector3 pos = ((Component)speaker).transform.position;
				List<GameObject> list = array.Where((GameObject n) => (Object)(object)n != (Object)null).Where(delegate(GameObject n)
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					float num = Vector3.Distance(n.transform.position, pos);
					return num > 15f && num < 35f;
				}).ToList();
				if (list.Count != 0)
				{
					GameObject val2 = list[random.Next(list.Count)];
					RoundManager.Instance.SpawnEnemyGameObject(val2.transform.position, (float)random.Next(360), -1, val);
					lastForbidden[item.word] = Time.time;
					forbiddenThisMoon[item.word] = value + 1;
					Plugin.Log.LogInfo((object)("[vivo] " + speaker.playerUsername + " dijo '" + item.word + "': aparece " + val.enemyName + "."));
					Events.Log("invocacion", speaker.playerUsername, null, val.enemyName);
				}
			}
			break;
		}
	}

	private static void TrySignalTranslator(StartOfRound round)
	{
		if ((Object)(object)Object.FindObjectOfType<SignalTranslator>() == (Object)null)
		{
			return;
		}
		HUDManager instance = HUDManager.Instance;
		if (!((Object)(object)instance == (Object)null) && recent.Count != 0)
		{
			List<Heard> list = recent.Where((Heard h) => (Object)(object)h.Speaker != (Object)null && h.Speaker.isPlayerDead).ToList();
			if (list.Count == 0)
			{
				list = recent;
			}
			List<string> list2 = (from h in list
				select ShortMessage(h.Norm) into m
				where m != null
				select m).ToList();
			if (list2.Count != 0)
			{
				string text = list2[random.Next(list2.Count)];
				instance.UseSignalTranslatorServerRpc(text);
				Plugin.Log.LogInfo((object)("[vivo] Signal Translator: " + text));
			}
		}
	}

	private static string ShortMessage(string norm)
	{
		if (string.IsNullOrWhiteSpace(norm))
		{
			return null;
		}
		if (norm.Length <= 10)
		{
			return norm.ToUpperInvariant();
		}
		string[] array = norm.Split(' ');
		string text = "";
		string[] array2 = array;
		foreach (string text2 in array2)
		{
			string text3 = ((text.Length == 0) ? text2 : (text + " " + text2));
			if (text3.Length > 10)
			{
				break;
			}
			text = text3;
		}
		if (text.Length < 3)
		{
			return null;
		}
		return text.ToUpperInvariant();
	}

	private static IEnumerable<(string word, string enemy)> ParseForbidden()
	{
		return from p in Plugin.ForbiddenWords.Value.Split(',')
			select p.Split('=') into p
			where p.Length == 2 && p[0].Trim().Length > 0 && p[1].Trim().Length > 0
			select (Normalize(p[0]), p[1].Trim());
	}

	private static EnemyType FindEnemyType(StartOfRound round, string name)
	{
		SpawnableEnemyWithRarity val = round.currentLevel?.Enemies?.FirstOrDefault((Func<SpawnableEnemyWithRarity, bool>)Same);
		if (val != null)
		{
			return val.enemyType;
		}
		SelectableLevel[] levels = round.levels;
		foreach (SelectableLevel val2 in levels)
		{
			if (!((Object)(object)val2 == (Object)null))
			{
				val = val2.Enemies?.FirstOrDefault((Func<SpawnableEnemyWithRarity, bool>)Same) ?? val2.OutsideEnemies?.FirstOrDefault((Func<SpawnableEnemyWithRarity, bool>)Same);
				if (val != null)
				{
					return val.enemyType;
				}
			}
		}
		return null;
		bool Same(SpawnableEnemyWithRarity e)
		{
			if ((Object)(object)e?.enemyType != (Object)null)
			{
				return string.Equals(e.enemyType.enemyName, name, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}
	}

	private static bool NameMentioned(PlayerControllerB p, string norm)
	{
		string[] source = norm.Split(' ');
		foreach (string n in NameWords(p))
		{
			if (n.Contains(' ') ? ContainsPhrase(norm, n) : source.Any((string w) => Preguntas.WordMatchesName(w, n)))
			{
				return true;
			}
		}
		return false;
	}

	private static List<string> NameWords(PlayerControllerB p)
	{
		List<string> list = (from w in Regex.Split(Normalize(p.playerUsername), "[^a-z0-9ñ]+")
			where w.Length >= 3
			select w).ToList();
		foreach (var (text, text2) in (from e in Plugin.Nicknames.Value.Split(';')
			select e.Split('=') into parts
			where parts.Length == 2
			select (steam: parts[0], nicks: parts[1])).Concat(NicknameFile()))
		{
			if (Normalize(text) == Normalize(p.playerUsername))
			{
				list.AddRange(from n in text2.Split(',').Select(Normalize)
					where n.Length >= 2
					select n);
			}
		}
		return list;
	}

	private static List<(string steam, string nicks)> NicknameFile()
	{
		if (Time.unscaledTime < nextNicknameRead)
		{
			return nicknameCache;
		}
		nextNicknameRead = Time.unscaledTime + 30f;
		List<(string, string)> list = new List<(string, string)>();
		try
		{
			string directoryName = Path.GetDirectoryName(Plugin.LiveScript.Value.Trim().Trim('"'));
			string text = (string.IsNullOrEmpty(directoryName) ? null : Path.Combine(directoryName, "apodos.txt"));
			if (text != null && File.Exists(text))
			{
				string[] array = File.ReadAllLines(text, Encoding.UTF8);
				for (int i = 0; i < array.Length; i++)
				{
					string text2 = array[i].Trim();
					int num = text2.LastIndexOf(':');
					if (text2.Length != 0 && !text2.StartsWith("#") && num > 0)
					{
						list.Add((text2.Substring(0, num).Trim(), text2.Substring(num + 1)));
					}
				}
			}
		}
		catch (Exception)
		{
		}
		nicknameCache = list;
		return list;
	}

	private static bool SoundsLike(string norm, string word)
	{
		return Preguntas.SoundsLike(norm, word);
	}

	private static bool ContainsPhrase(string norm, string phrase)
	{
		return Preguntas.ContainsPhrase(norm, phrase);
	}

	internal static string Normalize(string text)
	{
		return Preguntas.Normalize(text);
	}

	private static float Range(float a, float b)
	{
		return Mathf.Min(a, b) + (float)random.NextDouble() * Mathf.Abs(b - a);
	}
}
internal static class MonsterVoice
{
	private class State
	{
		public int Behaviour = -1;

		public bool Killing;

		public bool Carrying;

		public float BusyUntil;

		public float NextStalk;

		public float NextChase;

		public float NextDrag;

		public int Catches;

		public int LastBehaviourSeen = -1;

		public float HuntingSince = -1f;
	}

	private static readonly Dictionary<FlowermanAI, State> states = new Dictionary<FlowermanAI, State>();

	private static readonly Dictionary<string, List<string>> clips = new Dictionary<string, List<string>>();

	private static readonly Dictionary<string, string> lastClip = new Dictionary<string, string>();

	private static readonly Random random = new Random();

	private static float nextRefresh;

	internal static void Tick(StartOfRound round)
	{
		//IL_0378: Unknown result type (might be due to invalid IL or missing references)
		//IL_0384: Unknown result type (might be due to invalid IL or missing references)
		RoundManager instance = RoundManager.Instance;
		if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null || !round.shipHasLanded)
		{
			states.Clear();
			return;
		}
		foreach (FlowermanAI item in states.Keys.Where((FlowermanAI b) => (Object)(object)b == (Object)null).ToList())
		{
			states.Remove(item);
		}
		float time = Time.time;
		List<FlowermanAI> list = (from b in instance.SpawnedEnemies.OfType<FlowermanAI>()
			where (Object)(object)b != (Object)null && !((EnemyAI)b).isEnemyDead
			select b).ToList();
		foreach (FlowermanAI item2 in list)
		{
			if (!states.ContainsKey(item2))
			{
				states[item2] = new State
				{
					NextStalk = time + Range(4f, 8f)
				};
			}
		}
		if (Plugin.BrackenAggressive.Value)
		{
			foreach (FlowermanAI item3 in list)
			{
				Aggression(item3, states[item3], time);
			}
		}
		if (!Plugin.BrackenEnabled.Value)
		{
			return;
		}
		if (Time.unscaledTime >= nextRefresh)
		{
			nextRefresh = Time.unscaledTime + 10f;
			Refresh();
		}
		if (clips.Count == 0)
		{
			return;
		}
		foreach (FlowermanAI item4 in list)
		{
			State state = states[item4];
			int currentBehaviourStateIndex = ((EnemyAI)item4).currentBehaviourStateIndex;
			bool inKillAnimation = item4.inKillAnimation;
			bool carryingPlayerBody = item4.carryingPlayerBody;
			if (inKillAnimation && !state.Killing)
			{
				Say(item4, state, "muerte", whisper: false, interrupt: true);
			}
			else if (carryingPlayerBody && !state.Carrying)
			{
				state.NextDrag = time + Range(2.5f, 4f);
			}
			else if (currentBehaviourStateIndex == 2 && state.Behaviour != 2)
			{
				Say(item4, state, "persecucion", whisper: false, interrupt: true);
				state.NextChase = time + Range(5f, 9f);
			}
			else if (currentBehaviourStateIndex == 1 && state.Behaviour == 0)
			{
				Say(item4, state, "pillado", whisper: false, interrupt: true);
			}
			else if (carryingPlayerBody && !inKillAnimation && time >= state.NextDrag)
			{
				state.NextDrag = time + Range(7f, 12f);
				Say(item4, state, "arrastre", whisper: false, interrupt: false);
			}
			else if (currentBehaviourStateIndex == 2 && !inKillAnimation && time >= state.NextChase)
			{
				state.NextChase = time + Range(5f, 9f);
				Say(item4, state, "persecucion", whisper: false, interrupt: false);
			}
			else if (currentBehaviourStateIndex == 0 && !carryingPlayerBody && time >= state.NextStalk)
			{
				state.NextStalk = time + Range(Plugin.BrackenStalkMin.Value, Plugin.BrackenStalkMax.Value);
				PlayerControllerB targetPlayer = ((EnemyAI)item4).targetPlayer;
				if ((Object)(object)targetPlayer != (Object)null && !targetPlayer.isPlayerDead && Vector3.Distance(((Component)targetPlayer).transform.position, ((Component)item4).transform.position) < 20f)
				{
					Say(item4, state, "acecho", whisper: true, interrupt: false);
				}
			}
			state.Behaviour = currentBehaviourStateIndex;
			state.Killing = inKillAnimation;
			state.Carrying = carryingPlayerBody;
		}
	}

	private static void Aggression(FlowermanAI bracken, State st, float now)
	{
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		int currentBehaviourStateIndex = ((EnemyAI)bracken).currentBehaviourStateIndex;
		bool flag = currentBehaviourStateIndex == 1 && st.LastBehaviourSeen == 0;
		st.LastBehaviourSeen = currentBehaviourStateIndex;
		if (currentBehaviourStateIndex == 2 || bracken.inKillAnimation || bracken.carryingPlayerBody)
		{
			st.HuntingSince = -1f;
			st.Catches = 0;
			return;
		}
		PlayerControllerB targetPlayer = ((EnemyAI)bracken).targetPlayer;
		bool flag2 = (Object)(object)targetPlayer != (Object)null && !targetPlayer.isPlayerDead && Vector3.Distance(((Component)targetPlayer).transform.position, ((Component)bracken).transform.position) < 30f;
		if (flag2 && st.HuntingSince < 0f)
		{
			st.HuntingSince = now;
		}
		if (flag)
		{
			st.Catches++;
		}
		string text = null;
		if (flag && st.Catches >= Plugin.BrackenCatchesToAnger.Value)
		{
			text = $"le han pillado {st.Catches} veces";
		}
		else if (flag2 && st.HuntingSince >= 0f && now - st.HuntingSince > Plugin.BrackenPatience.Value)
		{
			text = $"lleva {(int)(now - st.HuntingSince)} s acechando";
		}
		if (text != null)
		{
			bracken.EnterAngerModeServerRpc(Plugin.BrackenAngerSeconds.Value);
			st.Catches = 0;
			st.HuntingSince = -1f;
			Plugin.Log.LogInfo((object)("[bracken] se enfada (" + text + ")."));
			Events.Log("bracken_enfado", null, null, text);
		}
	}

	private static void Say(FlowermanAI bracken, State st, string moment, bool whisper, bool interrupt)
	{
		if (!interrupt && Time.time < st.BusyUntil)
		{
			return;
		}
		string text = Pick(moment);
		if (text != null)
		{
			Effects.Clip clip = Effects.ReadWavFile(text);
			if (clip != null && Effects.MonsterClip((EnemyAI)(object)bracken, clip, whisper))
			{
				st.BusyUntil = Time.time + (float)clip.Pcm.Length / 2f / (float)Mathf.Max(1, clip.Rate) + 0.5f;
				Plugin.Log.LogInfo((object)("[bracken] " + moment + ": " + Path.GetFileNameWithoutExtension(text)));
				Events.Log("bracken", null, null, moment, text);
			}
		}
	}

	private static string Pick(string moment)
	{
		if (!clips.TryGetValue(moment, out var value) || value.Count == 0)
		{
			return null;
		}
		lastClip.TryGetValue(moment, out var last);
		List<string> list = ((value.Count > 1) ? value.Where((string c) => c != last).ToList() : value);
		string text = list[random.Next(list.Count)];
		lastClip[moment] = text;
		return text;
	}

	private static void Refresh()
	{
		clips.Clear();
		string path = Path.Combine(Plugin.MirageDir, "Centralizado", "Monstruos", "Bracken");
		if (!Directory.Exists(path))
		{
			return;
		}
		string[] directories = Directory.GetDirectories(path);
		foreach (string path2 in directories)
		{
			List<string> list = Directory.GetFiles(path2, "*.wav").ToList();
			if (list.Count > 0)
			{
				clips[Path.GetFileName(path2).ToLowerInvariant()] = list;
			}
		}
	}

	private static float Range(float a, float b)
	{
		return Mathf.Min(a, b) + (float)random.NextDouble() * Mathf.Abs(b - a);
	}
}
internal static class NightEvents
{
	private class Death
	{
		public PlayerControllerB Player;

		public DateTime When;

		public Vector3 Where;

		public bool Inside;

		public float PlayAt;
	}

	private static readonly string[] ShipCats = new string[4] { "ayuda", "llamada", "grito", "nombre" };

	private static readonly string[] LonelyCats = new string[7] { "llamada", "ayuda", "seguir", "aviso", "nombre", "grito", "risa" };

	private static readonly Random random = new Random();

	private static readonly Dictionary<ulong, bool> wasDead = new Dictionary<ulong, bool>();

	private static readonly List<Death> deaths = new List<Death>();

	private static readonly Dictionary<ulong, float> aloneSince = new Dictionary<ulong, float>();

	private static readonly Dictionary<ulong, float> nextWhisper = new Dictionary<ulong, float>();

	private static readonly HashSet<ulong> warnedNoNames = new HashSet<ulong>();

	private static readonly List<PlayerControllerB> diedOnMoon = new List<PlayerControllerB>();

	private static bool wasOnMoon;

	private static bool wasLeaving;

	private static float nextShipVoice = float.MaxValue;

	private static int shipVoicesThisMoon;

	internal static void Tick(StartOfRound round)
	{
		float time = Time.time;
		bool flag = round.shipHasLanded && !round.inShipPhase;
		List<PlayerControllerB> list = round.allPlayerScripts.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && (p.isPlayerControlled || p.isPlayerDead)).ToList();
		if (flag && !wasOnMoon)
		{
			deaths.Clear();
			diedOnMoon.Clear();
			aloneSince.Clear();
			nextWhisper.Clear();
			warnedNoNames.Clear();
			shipVoicesThisMoon = 0;
			nextShipVoice = float.MaxValue;
			foreach (PlayerControllerB item in list)
			{
				wasDead[item.playerClientId] = item.isPlayerDead;
			}
			Events.Log("luna", null, null, ((Object)(object)round.currentLevel != (Object)null) ? round.currentLevel.PlanetName : "?");
		}
		if (round.shipIsLeaving && !wasLeaving)
		{
			Events.Log("despegue", null, null, $"{list.Count((PlayerControllerB p) => !p.isPlayerDead)} vivos de {list.Count}");
			deaths.Clear();
			nextShipVoice = float.MaxValue;
		}
		wasLeaving = round.shipIsLeaving;
		wasOnMoon = flag;
		if (flag && !round.shipIsLeaving)
		{
			TickLastWords(round, list, time);
			if (Plugin.LonelyEnabled.Value)
			{
				TickLonely(round, list, time);
			}
			if (Plugin.ShipSpeakerEnabled.Value)
			{
				TickShip(round, list, time);
			}
		}
	}

	private static void TickLastWords(StartOfRound round, List<PlayerControllerB> players, float now)
	{
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		foreach (PlayerControllerB player in players)
		{
			wasDead.TryGetValue(player.playerClientId, out var value);
			if (player.isPlayerDead && !value)
			{
				Events.Log("muerte", player.playerUsername, KillerNear(player), ((object)Unsafe.As<CauseOfDeath, CauseOfDeath>(ref player.causeOfDeath)/*cast due to .constrained prefix*/).ToString());
				if (!diedOnMoon.Contains(player))
				{
					diedOnMoon.Add(player);
				}
				if (nextShipVoice == float.MaxValue && shipVoicesThisMoon < Plugin.ShipMaxPerMoon.Value && random.NextDouble() < (double)Plugin.ShipChance.Value)
				{
					nextShipVoice = now + Range(Plugin.ShipMinSeconds.Value, Plugin.ShipMaxSeconds.Value);
				}
			}
			if (player.isPlayerDead && !value && Plugin.LastWordsEnabled.Value)
			{
				deaths.Add(new Death
				{
					Player = player,
					When = DateTime.Now,
					Where = ((Component)player).transform.position,
					Inside = player.isInsideFactory,
					PlayAt = now + Range(25f, 45f)
				});
			}
			wasDead[player.playerClientId] = player.isPlayerDead;
		}
		for (int num = deaths.Count - 1; num >= 0; num--)
		{
			Death death = deaths[num];
			if (!(now < death.PlayAt))
			{
				deaths.RemoveAt(num);
				SayLastWords(round, death);
			}
		}
	}

	private static string KillerNear(PlayerControllerB p)
	{
		RoundManager instance = RoundManager.Instance;
		if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null)
		{
			return null;
		}
		return (from e in instance.SpawnedEnemies
			where (Object)(object)e != (Object)null && (Object)(object)e.enemyType != (Object)null
			select (e: e, d: Vector3.Distance(((Component)e).transform.position, ((Component)p).transform.position)) into x
			where x.d < 10f
			orderby x.d
			select x.e).FirstOrDefault()?.enemyType.enemyName;
	}

	private static void SayLastWords(StartOfRound round, Death d)
	{
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0171: Unknown result type (might be due to invalid IL or missing references)
		string key = Effects.KeyFor(round, d.Player);
		string text = Effects.LatestClip(key, d.When.AddSeconds(-90.0), d.When.AddSeconds(20.0));
		if (text == null)
		{
			Plugin.Log.LogInfo((object)("[ultimas] " + d.Player.playerUsername + " murio sin decir nada que se guardara: no hay ultimas palabras."));
			return;
		}
		EnemyAI val = CentralPlayback.AnyMimicNear(d.Where, d.Inside, 30f);
		string text2 = Effects.OpusFor(key, text);
		if ((Object)(object)val != (Object)null && text2 != null && CentralPlayback.PlayNow(val, text2))
		{
			Plugin.Log.LogInfo((object)("[ultimas] " + val.enemyType?.enemyName + " repite lo ultimo que dijo " + d.Player.playerUsername + ": " + Path.GetFileName(text)));
			Events.Log("ultimas", d.Player.playerUsername, null, val.enemyType?.enemyName, text);
		}
		else
		{
			DeadBodyInfo deadBody = d.Player.deadBody;
			if (Effects.PointClip(((Object)(object)deadBody != (Object)null && !deadBody.deactivated) ? (((Component)deadBody).transform.position + Vector3.up * 0.5f) : (d.Where + Vector3.up), d.Player, Effects.ReadWavFile(text)))
			{
				Plugin.Log.LogInfo((object)("[ultimas] el cadaver de " + d.Player.playerUsername + " repite lo ultimo que dijo: " + Path.GetFileName(text)));
			}
			Events.Log("ultimas", d.Player.playerUsername, null, "su cadaver", text);
		}
	}

	private static void TickLonely(StartOfRound round, List<PlayerControllerB> players, float now)
	{
		List<PlayerControllerB> list = players.Where((PlayerControllerB val2) => !val2.isPlayerDead && val2.isPlayerControlled).ToList();
		foreach (PlayerControllerB p in list)
		{
			float value;
			if (p.isInHangarShipRoom)
			{
				aloneSince.Remove(p.playerClientId);
				nextWhisper.Remove(p.playerClientId);
			}
			else if (list.Any((PlayerControllerB o) => (Object)(object)o != (Object)(object)p && Vector3.Distance(((Component)o).transform.position, ((Component)p).transform.position) < Plugin.LonelyDistance.Value))
			{
				aloneSince.Remove(p.playerClientId);
			}
			else if (!aloneSince.TryGetValue(p.playerClientId, out value))
			{
				aloneSince[p.playerClientId] = now;
			}
			else
			{
				if (now - value < Plugin.LonelySeconds.Value)
				{
					continue;
				}
				if (!nextWhisper.TryGetValue(p.playerClientId, out var value2))
				{
					nextWhisper[p.playerClientId] = now + Range(0f, Plugin.LonelyMinSeconds.Value * 0.5f);
				}
				else
				{
					if (now < value2)
					{
						continue;
					}
					nextWhisper[p.playerClientId] = now + Range(Plugin.LonelyMinSeconds.Value, Plugin.LonelyMaxSeconds.Value);
					string text = null;
					PlayerControllerB val = null;
					Effects.Clip clip = null;
					(PlayerControllerB, string)? tuple = ((random.NextDouble() < 0.35) ? Effects.NameClip(round, p, players) : (((PlayerControllerB, string)?)null));
					if (tuple.HasValue)
					{
						val = tuple.Value.Item1;
						text = tuple.Value.Item2;
						clip = Effects.ReadWavFile(text);
					}
					else
					{
						List<PlayerControllerB> list2 = players.Where((PlayerControllerB o) => (Object)(object)o != (Object)(object)p && Effects.HasAnyClip(round, o, LonelyCats)).ToList();
						if (list2.Count > 0)
						{
							val = list2[random.Next(list2.Count)];
							(string, Effects.Clip)? tuple2 = Effects.PickAnyClip(round, val, LonelyCats);
							if (tuple2.HasValue)
							{
								text = tuple2.Value.Item1;
								clip = tuple2.Value.Item2;
							}
						}
					}
					if (clip == null)
					{
						if (warnedNoNames.Add(p.playerClientId))
						{
							Plugin.Log.LogInfo((object)("[solo] " + p.playerUsername + " esta solo, pero no hay frases de otros jugadores para susurrarle."));
						}
					}
					else if (Effects.LonelyClip(p, val, clip))
					{
						Plugin.Log.LogInfo((object)$"[solo] {p.playerUsername} lleva {(int)(now - value)} s solo: oye a {val.playerUsername} ({Path.GetFileName(text)})");
						Events.Log("solo", p.playerUsername, val.playerUsername, null, text);
					}
				}
			}
		}
	}

	private static void TickShip(StartOfRound round, List<PlayerControllerB> players, float now)
	{
		if (now < nextShipVoice || shipVoicesThisMoon >= Plugin.ShipMaxPerMoon.Value)
		{
			return;
		}
		if (!players.Any((PlayerControllerB p) => !p.isPlayerDead && p.isPlayerControlled && p.isInHangarShipRoom))
		{
			nextShipVoice = now + 20f;
			return;
		}
		nextShipVoice = float.MaxValue;
		List<PlayerControllerB> list = diedOnMoon.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && p.isPlayerDead).ToList();
		if (list.Count != 0)
		{
			PlayerControllerB val = list[random.Next(list.Count)];
			Effects.Clip clip = Effects.PickClip(round, val, ShipCats);
			if (clip != null && Effects.ShipClip(val, clip))
			{
				shipVoicesThisMoon++;
				Plugin.Log.LogInfo((object)("[altavoz] suena por el altavoz de la nave la voz de " + val.playerUsername + ", que ha muerto en esta luna."));
				Events.Log("altavoz", val.playerUsername);
			}
		}
	}

	private static float Range(float a, float b)
	{
		return Mathf.Min(a, b) + (float)random.NextDouble() * Mathf.Abs(b - a);
	}
}
[BepInPlugin("lloreria.miragevoces", "MirageVoces", "0.6.2")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "lloreria.miragevoces";

	public const string Name = "MirageVoces";

	public const string Version = "0.6.2";

	internal static ManualLogSource Log;

	internal static string MirageDir;

	internal static string IncomingDir;

	internal static string ActiveDir;

	internal static ConfigEntry<bool> RecordFriends;

	internal static ConfigEntry<bool> RecordDeadPlayers;

	internal static ConfigEntry<float> VoiceThreshold;

	internal static ConfigEntry<float> MinClipSeconds;

	internal static ConfigEntry<float> MaxClipSeconds;

	internal static ConfigEntry<bool> CentralPlayback;

	internal static ConfigEntry<int> MinDelayMasked;

	internal static ConfigEntry<int> MaxDelayMasked;

	internal static ConfigEntry<int> MinDelayOther;

	internal static ConfigEntry<int> MaxDelayOther;

	internal static ConfigEntry<bool> BodiesEnabled;

	internal static ConfigEntry<float> BodyMinSeconds;

	internal static ConfigEntry<float> BodyMaxSeconds;

	internal static ConfigEntry<float> BodyVolume;

	internal static ConfigEntry<bool> WalkieEnabled;

	internal static ConfigEntry<float> WalkieMinSeconds;

	internal static ConfigEntry<float> WalkieMaxSeconds;

	internal static ConfigEntry<float> WalkieVolume;

	internal static ConfigEntry<bool> DoorsEnabled;

	internal static ConfigEntry<float> DoorMinSeconds;

	internal static ConfigEntry<float> DoorMaxSeconds;

	internal static ConfigEntry<float> DoorVolume;

	internal static ConfigEntry<bool> BrackenEnabled;

	internal static ConfigEntry<float> BrackenVolume;

	internal static ConfigEntry<float> BrackenStalkMin;

	internal static ConfigEntry<float> BrackenStalkMax;

	internal static ConfigEntry<bool> BrackenAggressive;

	internal static ConfigEntry<int> BrackenCatchesToAnger;

	internal static ConfigEntry<float> BrackenPatience;

	internal static ConfigEntry<float> BrackenAngerSeconds;

	internal static ConfigEntry<bool> HideNames;

	internal static ConfigEntry<bool> LastWordsEnabled;

	internal static ConfigEntry<bool> LonelyEnabled;

	internal static ConfigEntry<float> LonelySeconds;

	internal static ConfigEntry<float> LonelyDistance;

	internal static ConfigEntry<float> LonelyMinSeconds;

	internal static ConfigEntry<float> LonelyMaxSeconds;

	internal static ConfigEntry<float> LonelyVolume;

	internal static ConfigEntry<bool> ShipSpeakerEnabled;

	internal static ConfigEntry<float> ShipMinSeconds;

	internal static ConfigEntry<float> ShipMaxSeconds;

	internal static ConfigEntry<int> ShipMaxPerMoon;

	internal static ConfigEntry<float> ShipChance;

	internal static ConfigEntry<float> ShipVolume;

	internal static ConfigEntry<bool> LiveEnabled;

	internal static ConfigEntry<string> LivePython;

	internal static ConfigEntry<string> LiveScript;

	internal static ConfigEntry<bool> AnswersEnabled;

	internal static ConfigEntry<string> Nicknames;

	internal static ConfigEntry<bool> TestPhraseEnabled;

	internal static ConfigEntry<bool> ForbiddenEnabled;

	internal static ConfigEntry<string> SummonPhrase;

	internal static ConfigEntry<bool> SummonHostOnly;

	internal static ConfigEntry<string> ForbiddenWords;

	internal static ConfigEntry<float> ForbiddenCooldown;

	internal static ConfigEntry<int> ForbiddenMaxPerMoon;

	internal static ConfigEntry<bool> SignalEnabled;

	internal static ConfigEntry<float> SignalMinSeconds;

	internal static ConfigEntry<float> SignalMaxSeconds;

	private void Awake()
	{
		//IL_0853: Unknown result type (might be due to invalid IL or missing references)
		//IL_0858: Unknown result type (might be due to invalid IL or missing references)
		//IL_086d: Expected O, but got Unknown
		//IL_0872: Unknown result type (might be due to invalid IL or missing references)
		//IL_0877: Unknown result type (might be due to invalid IL or missing references)
		//IL_087f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0885: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		MirageDir = Path.Combine(Paths.GameRootPath, "Mirage");
		IncomingDir = Path.Combine(MirageDir, "Centralizado", "Entrantes");
		ActiveDir = Path.Combine(MirageDir, "Centralizado", "Activas");
		RecordFriends = ((BaseUnityPlugin)this).Config.Bind<bool>("Grabacion", "Grabar voces de amigos", true, "Si eres el anfitrion, guarda las frases de cada amigo que llegan por el chat de voz.");
		RecordDeadPlayers = ((BaseUnityPlugin)this).Config.Bind<bool>("Grabacion", "Grabar a jugadores muertos", true, "Tambien graba a los amigos mientras estan muertos (espectando).");
		VoiceThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Grabacion", "Umbral de voz", 0.008f, "Volumen minimo (RMS) para considerar que alguien habla. Subelo si se cuela ruido.");
		MinClipSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Grabacion", "Duracion minima (s)", 0.3f, "Frases mas cortas se ignoran (un \"¡Ah!\" dura menos de medio segundo).");
		MaxClipSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Grabacion", "Duracion maxima (s)", 12f, "Las frases se cortan a esta duracion.");
		CentralPlayback = ((BaseUnityPlugin)this).Config.Bind<bool>("Reproduccion", "Reproducir frases curadas", true, "Si eres el anfitrion y tienes frases curadas de un amigo, los monstruos que lo imiten usan esas.");
		MinDelayMasked = ((BaseUnityPlugin)this).Config.Bind<int>("Reproduccion", "Espera minima enmascarados (ms)", 4000, "");
		MaxDelayMasked = ((BaseUnityPlugin)this).Config.Bind<int>("Reproduccion", "Espera maxima enmascarados (ms)", 9000, "");
		MinDelayOther = ((BaseUnityPlugin)this).Config.Bind<int>("Reproduccion", "Espera minima otros monstruos (ms)", 20000, "");
		MaxDelayOther = ((BaseUnityPlugin)this).Config.Bind<int>("Reproduccion", "Espera maxima otros monstruos (ms)", 45000, "");
		BodiesEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Efectos - Cadaveres", "Activado", true, "Los cadaveres susurran frases del jugador muerto.");
		BodyMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Cadaveres", "Espera minima (s)", 35f, "");
		BodyMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Cadaveres", "Espera maxima (s)", 80f, "");
		BodyVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Cadaveres", "Volumen", 0.45f, "0 a 1");
		WalkieEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Efectos - Walkie", "Activado", true, "Transmisiones falsas por los walkies encendidos (mejor si quien habla esta muerto).");
		WalkieMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Walkie", "Espera minima (s)", 120f, "");
		WalkieMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Walkie", "Espera maxima (s)", 240f, "");
		WalkieVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Walkie", "Volumen", 0.8f, "0 a 1");
		DoorsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Efectos - Puertas", "Activado", true, "Pasos y la voz de un compañero que esta lejos, al otro lado de una puerta cercana.");
		DoorMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Puertas", "Espera minima (s)", 90f, "");
		DoorMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Puertas", "Espera maxima (s)", 200f, "");
		DoorVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Efectos - Puertas", "Volumen", 0.9f, "0 a 1");
		BrackenEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Bracken con tu voz", "Activado", true, "El Bracken habla con la voz del anfitrion segun lo que hace (acecho, pillado, persecucion, muerte, arrastre). Frases en Mirage/Importar/Bracken/<momento>/.");
		BrackenVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Bracken con tu voz", "Volumen", 1f, "0 a 1");
		BrackenStalkMin = ((BaseUnityPlugin)this).Config.Bind<float>("Bracken con tu voz", "Susurro de acecho espera minima (s)", 10f, "");
		BrackenStalkMax = ((BaseUnityPlugin)this).Config.Bind<float>("Bracken con tu voz", "Susurro de acecho espera maxima (s)", 20f, "");
		BrackenAggressive = ((BaseUnityPlugin)this).Config.Bind<bool>("Bracken agresivo", "Activado", true, "En el juego base, si miras al Bracken a menudo nunca ataca. Con esto se cansa y va a por ti.");
		BrackenCatchesToAnger = ((BaseUnityPlugin)this).Config.Bind<int>("Bracken agresivo", "Veces pillado para enfadarse", 3, "Cuantas veces se le puede pillar mirandolo antes de que se enfade y persiga.");
		BrackenPatience = ((BaseUnityPlugin)this).Config.Bind<float>("Bracken agresivo", "Paciencia maxima (s)", 120f, "Si lleva este tiempo acechando a alguien sin matar, se enfada igualmente.");
		BrackenAngerSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Bracken agresivo", "Duracion del enfado (s)", 20f, "Cuanto dura la persecucion (si te pilla, te mata).");
		HideNames = ((BaseUnityPlugin)this).Config.Bind<bool>("Noche", "Ocultar nombres", true, "Quita los nombres de encima de los jugadores, para no distinguir a un enmascarado de un compañero.");
		LastWordsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Noche", "Ultimas palabras", true, "Un rato despues de morir alguien, un monstruo cercano (o su cadaver) repite lo ultimo que dijo.");
		LonelyEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Noche", "Susurros al estar solo", true, "Quien se queda solo oye muy de vez en cuando una voz de otro jugador (su nombre o algo que llame la atencion). Lo oye el y quien le espectee.");
		LonelySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Segundos solo antes de las voces", 45f, "");
		LonelyDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Distancia para estar solo (m)", 20f, "Si nadie vivo esta mas cerca que esto, cuenta como solo.");
		LonelyMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Voz al estar solo espera minima (s)", 150f, "");
		LonelyMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Voz al estar solo espera maxima (s)", 300f, "");
		LonelyVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Volumen de los susurros", 0.6f, "0 a 1");
		ShipSpeakerEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Noche", "Muertos por el altavoz", true, "En tierra, como mucho una vez por luna, el altavoz de la nave suelta la voz de alguien que ha muerto.");
		ShipMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Altavoz segundos tras la muerte (min)", 60f, "");
		ShipMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Altavoz segundos tras la muerte (max)", 180f, "");
		ShipMaxPerMoon = ((BaseUnityPlugin)this).Config.Bind<int>("Noche", "Altavoz maximo por luna", 1, "");
		ShipChance = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Altavoz probabilidad", 0.5f, "Probabilidad (0 a 1) de que suene en una luna en la que ha muerto alguien.");
		ShipVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Noche", "Volumen del altavoz", 0.9f, "0 a 1");
		LiveEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Whisper en vivo", "Activado", true, "Arranca Mirage Voces en vivo al hacer de anfitrion: cura las frases durante la partida y reacciona a lo que se dice.");
		LivePython = ((BaseUnityPlugin)this).Config.Bind<string>("Whisper en vivo", "Python", "python", "Comando o ruta de python.exe.");
		LiveScript = ((BaseUnityPlugin)this).Config.Bind<string>("Whisper en vivo", "Script", "", "Ruta completa de mirage_vivo.py (carpeta de Mirage Voces). Vacio = desactivado.");
		AnswersEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Whisper en vivo", "Respuestas", true, "Si alguien pregunta por un compañero que no puede contestar, responde un monstruo, una puerta o el walkie con su voz.");
		Nicknames = ((BaseUnityPlugin)this).Config.Bind<string>("Whisper en vivo", "Apodos", "", "Mejor usa apodos.txt (carpeta de Mirage Voces). Aqui tambien vale: NombreSteam=Apodo1,Apodo2; Otro=Apodo");
		TestPhraseEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Whisper en vivo", "Frase de prueba", true, "Si el anfitrion dice \"activar prueba de voces\" en una luna, suena una frase suya (monstruo, puerta, walkie o delante) para comprobar que todo funciona.");
		ForbiddenEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Whisper en vivo", "Palabras prohibidas", true, "Decir una palabra prohibida dentro del edificio hace aparecer ese monstruo cerca.");
		ForbiddenWords = ((BaseUnityPlugin)this).Config.Bind<string>("Whisper en vivo", "Lista de palabras prohibidas", "bracken=Flowerman, jester=Jester", "palabra=NombreInternoDelMonstruo, separadas por comas (Flowerman=Bracken, Jester, Spring=Coilhead, Crawler=Thumper...).");
		SummonPhrase = ((BaseUnityPlugin)this).Config.Bind<string>("Whisper en vivo", "Frase para invocar", "que venga el", "Lo que hay que decir antes del nombre del monstruo para invocarlo (\"que venga el bracken\"). Nombrarlo sin mas no hace nada.");
		SummonHostOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("Whisper en vivo", "Solo invoca el anfitrion", true, "Si es true, solo el anfitrion puede invocar monstruos con la frase.");
		ForbiddenCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Whisper en vivo", "Espera entre palabras prohibidas (s)", 300f, "");
		ForbiddenMaxPerMoon = ((BaseUnityPlugin)this).Config.Bind<int>("Whisper en vivo", "Maximo de palabras prohibidas por luna", 1, "");
		SignalEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Whisper en vivo", "Signal Translator", true, "Si teneis el Signal Translator, de vez en cuando muestra algo que alguien dijo de verdad.");
		SignalMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Whisper en vivo", "Signal Translator espera minima (s)", 240f, "");
		SignalMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Whisper en vivo", "Signal Translator espera maxima (s)", 480f, "");
		VoiceCapture.Init();
		Harmony val = new Harmony("lloreria.miragevoces");
		val.PatchAll(typeof(VoiceCapture));
		VoiceCapture.PatchVolume(val);
		GameObject val2 = new GameObject("MirageVoces")
		{
			hideFlags = (HideFlags)61
		};
		Object.DontDestroyOnLoad((Object)val2);
		val2.AddComponent<Runner>();
		Log.LogInfo((object)("MirageVoces 0.6.2 cargado. Carpeta: " + MirageDir));
	}
}
internal enum Intent
{
	None,
	Donde,
	Presencia,
	Oir,
	Estado,
	Venir,
	Contesta,
	Saludo,
	Llamada
}
internal static class Preguntas
{
	private static readonly (Intent intent, string[] patterns)[] Patterns = new(Intent, string[])[6]
	{
		(Intent.Donde, new string[1] { "donde (\\w+ ){0,2}(estas|estais|andas|andais|te has metido|os habeis metido|te metes|os meteis)" }),
		(Intent.Oir, new string[2] { "(me|nos) (oyes|ois|oye|escuchas|escuchais|escucha|copias|copiais|recibes|recibis)", "se (me|nos) (oye|escucha)" }),
		(Intent.Estado, new string[7] { "(estas|estais|sigues|seguis) (bien|vivos?|herid[oa]s?)", "(estas|has) muert[oa]", "te has muerto", "te ha pasado algo", "que te ha pasado", "todo bien", "que tal estas" }),
		(Intent.Venir, new string[4] { "(ya )?(vienes|venis|vuelves|volveis)", "(te|os) (vienes|venis)", "(vas|vais) a venir", "me (sigues|seguis)" }),
		(Intent.Presencia, new string[6] { "hay alguien", "alguien (ahi|ahy|aqui|por ahi)", "(estas|estais|sigues|seguis) (ahi|por ahi|por aqui|ahy)", "quien (anda|esta|hay)( ahi)?", "eres tu", "sois vosotros" }),
		(Intent.Contesta, new string[4] { "contesta(d|me)?", "responde(d|me)?", "di algo", "decid algo" })
	};

	private static readonly string[] Greetings = new string[8] { "hola", "hey", "ey", "buenas", "eh", "oye", "oi", "hello" };

	private static readonly string[] Fillers = new string[13]
	{
		"eh", "oye", "ey", "hey", "tio", "tia", "hola", "a", "y", "que",
		"pero", "chicos", "chavales"
	};

	internal static Intent Detect(string raw, string norm, Func<string, bool> isName)
	{
		if (string.IsNullOrWhiteSpace(norm))
		{
			return Intent.None;
		}
		bool flag = raw.Contains("?") || raw.Contains("¿");
		string[] array = norm.Split(' ');
		if (array.Length > 25)
		{
			return Intent.None;
		}
		if (norm == "alguien" || norm == "alguien ahi")
		{
			return Intent.Presencia;
		}
		(Intent, string[])[] patterns = Patterns;
		for (int i = 0; i < patterns.Length; i++)
		{
			(Intent, string[]) tuple = patterns[i];
			var (intent, _) = tuple;
			if (tuple.Item2.Any((string p) => Regex.IsMatch(" " + norm + " ", "[^a-z0-9ñ]" + p + "[^a-z0-9ñ]")))
			{
				bool flag2 = intent == Intent.Donde || intent == Intent.Oir || intent == Intent.Presencia || intent == Intent.Contesta;
				if (flag || flag2 || array.Length <= 8)
				{
					return intent;
				}
			}
		}
		if (!flag && array.Length > 8)
		{
			return Intent.None;
		}
		List<string> list = array.Where((string w) => !isName(w)).ToList();
		bool flag3 = list.Count < array.Length;
		bool flag4 = list.All((string w) => Fillers.Contains(w));
		if (flag3 && flag4)
		{
			return Intent.Llamada;
		}
		if (!flag3 && array.Length <= 3 && array.Any((string w) => Greetings.Contains(w)) && flag4)
		{
			return Intent.Saludo;
		}
		return Intent.None;
	}

	internal static string[] AnswerTypes(Intent intent)
	{
		return intent switch
		{
			Intent.Donde => new string[2] { "aqui", "hola" }, 
			Intent.Presencia => new string[3] { "aqui", "si", "hola" }, 
			Intent.Oir => new string[3] { "oigo", "si", "aqui" }, 
			Intent.Estado => new string[2] { "bien", "si" }, 
			Intent.Venir => new string[2] { "voy", "si" }, 
			Intent.Contesta => new string[4] { "aqui", "oigo", "hola", "si" }, 
			Intent.Saludo => new string[2] { "hola", "aqui" }, 
			Intent.Llamada => new string[3] { "aqui", "hola", "si" }, 
			_ => new string[0], 
		};
	}

	internal static string Normalize(string text)
	{
		StringBuilder stringBuilder = new StringBuilder();
		string text2 = (text ?? "").ToLowerInvariant().Normalize(NormalizationForm.FormD);
		foreach (char c in text2)
		{
			if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
			{
				stringBuilder.Append(char.IsLetterOrDigit(c) ? c : ' ');
			}
		}
		return Regex.Replace(stringBuilder.ToString(), "\\s+", " ").Trim();
	}

	internal static bool ContainsPhrase(string norm, string phrase)
	{
		return Regex.IsMatch(" " + norm + " ", "[^a-z0-9ñ]" + Regex.Escape(Normalize(phrase)) + "[^a-z0-9ñ]");
	}

	internal static bool SoundsLike(string norm, string word)
	{
		word = Normalize(word);
		if (word.Contains(' ') || word.Length < 4)
		{
			return false;
		}
		int allowed = ((word.Length < 6) ? 1 : 2);
		string skeleton = Skeleton(word);
		return norm.Split(' ').Any((string w) => (w.Length > 0 && w[0] == word[0] && Math.Abs(w.Length - word.Length) <= 1 && Distance(w, word) <= allowed) || (w.Length >= 4 && skeleton.Length >= 3 && Skeleton(w) == skeleton));
	}

	internal static string Skeleton(string w)
	{
		string text = w.Replace("qu", "k").Replace("ck", "k").Replace("c", "k")
			.Replace("q", "k")
			.Replace("l", "r")
			.Replace("v", "b")
			.Replace("z", "s")
			.Replace("h", "")
			.Replace("y", "i");
		StringBuilder stringBuilder = new StringBuilder();
		string text2 = text;
		foreach (char c in text2)
		{
			if ("aeiou".IndexOf(c) < 0 && (stringBuilder.Length == 0 || stringBuilder[stringBuilder.Length - 1] != c))
			{
				stringBuilder.Append(c);
			}
		}
		return stringBuilder.ToString();
	}

	internal static bool IsTestPhrase(string norm)
	{
		string[] array = (from w in norm.Split(' ')
			where w.Length > 0
			select w).ToArray();
		if (array.Length < 3 || array.Length > 6)
		{
			return false;
		}
		int num = Array.FindIndex(array, (string w) => w.StartsWith("activ") || w.StartsWith("aktiv") || (w.Length >= 6 && Distance(w, "activar") <= 2));
		int num2 = Array.FindLastIndex(array, delegate(string w)
		{
			switch (w)
			{
			default:
				return w == "boz";
			case "voces":
			case "boces":
			case "voses":
			case "voz":
				return true;
			}
		});
		if (num >= 0 && num2 > num + 1)
		{
			return num2 >= array.Length - 2;
		}
		return false;
	}

	internal static bool IsSummon(string norm, string trigger, string monster)
	{
		string[] array = (from w in norm.Split(' ')
			where w.Length > 0
			select w).ToArray();
		string[] array2 = (from w in Normalize(trigger).Split(' ')
			where w.Length >= 4
			select w).ToArray();
		monster = Normalize(monster);
		int num = 0;
		string[] array3 = array2;
		foreach (string text in array3)
		{
			for (; num < array.Length && !(array[num] == text) && (array[num].Length < 4 || Distance(array[num], text) > 1) && !(Skeleton(array[num]) == Skeleton(text)); num++)
			{
			}
			if (num >= array.Length)
			{
				return false;
			}
			num++;
		}
		for (int num3 = num; num3 < array.Length && num3 < num + 3; num3++)
		{
			if (array[num3] == monster || SoundsLike(array[num3], monster))
			{
				return true;
			}
		}
		return false;
	}

	private static int Distance(string a, string b)
	{
		int[,] array = new int[a.Length + 1, b.Length + 1];
		for (int i = 0; i <= a.Length; i++)
		{
			array[i, 0] = i;
		}
		for (int j = 0; j <= b.Length; j++)
		{
			array[0, j] = j;
		}
		for (int k = 1; k <= a.Length; k++)
		{
			for (int l = 1; l <= b.Length; l++)
			{
				array[k, l] = Math.Min(Math.Min(array[k - 1, l] + 1, array[k, l - 1] + 1), array[k - 1, l - 1] + ((a[k - 1] != b[l - 1]) ? 1 : 0));
			}
		}
		ret