Decompiled source of BingBongTwitch v1.0.3

plugins/BingBongTwitch.dll

Decompiled 3 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Photon.Pun;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace BingBongTwitch;

public static class Babble
{
	private const int OutputFrequency = 22050;

	private const float BlipOverlap = 1.45f;

	private const float EdgeFade = 0.004f;

	private static float[] _sourceSamples;

	private static int _sourceFrequency;

	private static readonly List<int> _blipStarts = new List<int>();

	private static bool _preparing;

	public static bool Ready
	{
		get
		{
			if (_sourceSamples != null)
			{
				return _blipStarts.Count > 0;
			}
			return false;
		}
	}

	public static void Reset()
	{
		_sourceSamples = null;
		_blipStarts.Clear();
	}

	public static AudioClip Build(string text, Action_AskBingBong action, float maxSeconds)
	{
		if (!Ready)
		{
			return null;
		}
		float num = Mathf.Max(0.02f, Plugin.CfgSyllableSeconds.Value);
		List<float> list = PitchPerSyllable(text);
		if (list.Count == 0)
		{
			return null;
		}
		int num2 = Mathf.Max(1, Mathf.FloorToInt(maxSeconds / num));
		if (list.Count > num2)
		{
			list.RemoveRange(num2, list.Count - num2);
		}
		float num3 = num * 1.45f;
		int num4 = Mathf.CeilToInt(num3 * 22050f);
		int num5 = Mathf.CeilToInt(num * 22050f);
		int num6 = num5 * list.Count + num4;
		float[] array = new float[num6];
		for (int i = 0; i < list.Count; i++)
		{
			float num7 = list[i];
			if (!(num7 <= 0f))
			{
				RenderBlip(array, i * num5, num4, num7);
			}
		}
		Normalize(array);
		AudioClip val = AudioClip.Create("BingBongBabble", num6, 1, 22050, false);
		val.SetData(array, 0);
		return val;
	}

	private static void RenderBlip(float[] output, int outStart, int blipSamples, float pitch)
	{
		if (_blipStarts.Count == 0)
		{
			return;
		}
		int num = _blipStarts[Random.Range(0, _blipStarts.Count)];
		float num2 = pitch * (float)_sourceFrequency / 22050f;
		int num3 = Mathf.Max(1, Mathf.CeilToInt(88.200005f));
		float num4 = num;
		for (int i = 0; i < blipSamples; i++)
		{
			int num5 = outStart + i;
			if (num5 >= output.Length)
			{
				break;
			}
			int num6 = (int)num4;
			if (num6 + 1 >= _sourceSamples.Length)
			{
				break;
			}
			float num7 = num4 - (float)num6;
			float num8 = _sourceSamples[num6] * (1f - num7) + _sourceSamples[num6 + 1] * num7;
			float num9 = 1f;
			if (i < num3)
			{
				num9 = (float)i / (float)num3;
			}
			else if (i > blipSamples - num3)
			{
				num9 = (float)(blipSamples - i) / (float)num3;
			}
			output[num5] += num8 * num9;
			num4 += num2;
		}
	}

	private static List<float> PitchPerSyllable(string text)
	{
		List<float> list = new List<float>();
		float num = Plugin.CfgPitchMin.Value;
		float num2 = Plugin.CfgPitchMax.Value;
		if (num2 < num)
		{
			float num3 = num;
			num = num2;
			num2 = num3;
		}
		bool flag = false;
		for (int i = 0; i < text.Length; i++)
		{
			char c = char.ToLowerInvariant(text[i]);
			switch (c)
			{
			case ' ':
				flag = false;
				continue;
			case '!':
			case ',':
			case '.':
			case ':':
			case ';':
			case '?':
				list.Add(0f);
				flag = false;
				continue;
			}
			if (char.IsLetterOrDigit(c))
			{
				bool flag2 = IsVowel(c);
				bool flag3 = (flag2 ? (!flag) : (list.Count == 0 || i == text.Length - 1));
				flag = flag2;
				if (flag3)
				{
					float num4 = (float)(c * 7 % 11) / 10f;
					float item = Mathf.Lerp(num, num2, num4) * Random.Range(0.97f, 1.03f);
					list.Add(item);
				}
			}
		}
		if (list.Count == 0)
		{
			list.Add(Mathf.Lerp(num, num2, 0.5f));
		}
		return list;
	}

	private static bool IsVowel(char c)
	{
		switch (c)
		{
		case 'a':
		case 'e':
		case 'i':
		case 'o':
		case 'u':
		case 'y':
		case 'à':
		case 'á':
		case 'â':
		case 'ã':
		case 'é':
		case 'ê':
		case 'í':
		case 'ó':
		case 'ô':
		case 'õ':
		case 'ú':
			return true;
		default:
			return false;
		}
	}

	public static IEnumerator Prepare(Action_AskBingBong action)
	{
		if (Ready || _preparing || (Object)(object)action == (Object)null || action.responses == null)
		{
			yield break;
		}
		_preparing = true;
		List<AudioClip> clips = CollectClips(action);
		if (clips.Count == 0)
		{
			Plugin.Log.LogWarning((object)"O Bing Bong nao tem nenhum audio nas respostas.");
			_preparing = false;
			yield break;
		}
		for (int i = 0; i < clips.Count; i++)
		{
			if ((int)clips[i].loadState != 2)
			{
				clips[i].LoadAudioData();
			}
		}
		float waited = 0f;
		while (waited < 8f)
		{
			bool stillLoading = false;
			for (int j = 0; j < clips.Count; j++)
			{
				if ((int)clips[j].loadState == 1)
				{
					stillLoading = true;
					break;
				}
			}
			if (!stillLoading)
			{
				break;
			}
			waited += Time.deltaTime;
			yield return null;
		}
		BuildBank(clips);
		_preparing = false;
	}

	private static List<AudioClip> CollectClips(Action_AskBingBong action)
	{
		List<AudioClip> list = new List<AudioClip>();
		for (int i = 0; i < action.responses.Length; i++)
		{
			BingBongResponse val = action.responses[i];
			if (val == null || (Object)(object)val.sfx == (Object)null || val.sfx.clips == null)
			{
				continue;
			}
			for (int j = 0; j < val.sfx.clips.Length; j++)
			{
				AudioClip val2 = val.sfx.clips[j];
				if ((Object)(object)val2 != (Object)null && !list.Contains(val2))
				{
					list.Add(val2);
				}
			}
		}
		return list;
	}

	private static void BuildBank(List<AudioClip> clips)
	{
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		List<float> list = new List<float>();
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		for (int i = 0; i < clips.Count; i++)
		{
			AudioClip val = clips[i];
			float[] array;
			try
			{
				array = new float[val.samples * val.channels];
				if (!val.GetData(array, 0))
				{
					num3++;
					continue;
				}
			}
			catch (Exception ex)
			{
				num3++;
				if (num3 == 1)
				{
					Plugin.Log.LogWarning((object)string.Concat("Falha lendo '", ((Object)val).name, "' (", val.loadType, ", estado ", val.loadState, "): ", ex.Message));
				}
				continue;
			}
			if (num == 0)
			{
				num = val.frequency;
			}
			num2++;
			int num4 = Mathf.Max(1, val.channels);
			for (int j = 0; j + num4 <= array.Length; j += num4)
			{
				float num5 = 0f;
				for (int k = 0; k < num4; k++)
				{
					num5 += array[j + k];
				}
				list.Add(num5 / (float)num4);
			}
		}
		if (list.Count == 0 || num == 0)
		{
			Plugin.Log.LogWarning((object)("Nao consegui ler os audios do Bing Bong: " + clips.Count + " clipes encontrados, " + num3 + " falharam."));
			return;
		}
		_sourceSamples = list.ToArray();
		_sourceFrequency = num;
		IndexLoudRegions();
		Plugin.Log.LogInfo((object)("Banco de voz montado: " + num2 + "/" + clips.Count + " clipes, " + _sourceSamples.Length + " amostras, " + _blipStarts.Count + " trechos usaveis a " + _sourceFrequency + "Hz."));
	}

	private static void IndexLoudRegions()
	{
		_blipStarts.Clear();
		int num = Mathf.CeilToInt(0.12f * (float)_sourceFrequency);
		if (num <= 0 || _sourceSamples.Length <= num)
		{
			return;
		}
		double num2 = 0.0;
		for (int i = 0; i < _sourceSamples.Length; i++)
		{
			num2 += (double)Mathf.Abs(_sourceSamples[i]);
		}
		float num3 = (float)(num2 / (double)_sourceSamples.Length);
		float num4 = num3 * 1.3f;
		int num5 = Mathf.Max(1, num / 2);
		for (int j = 0; j + num < _sourceSamples.Length; j += num5)
		{
			float num6 = 0f;
			for (int k = 0; k < num; k += 4)
			{
				num6 += Mathf.Abs(_sourceSamples[j + k]);
			}
			float num7 = num6 / ((float)num / 4f);
			if (num7 >= num4)
			{
				_blipStarts.Add(j);
			}
		}
		if (_blipStarts.Count == 0)
		{
			for (int l = 0; l + num < _sourceSamples.Length; l += num)
			{
				_blipStarts.Add(l);
			}
		}
	}

	private static void Normalize(float[] buffer)
	{
		float num = 0f;
		for (int i = 0; i < buffer.Length; i++)
		{
			float num2 = Mathf.Abs(buffer[i]);
			if (num2 > num)
			{
				num = num2;
			}
		}
		if (!(num <= 0.0001f))
		{
			float num3 = 0.85f / num;
			for (int j = 0; j < buffer.Length; j++)
			{
				buffer[j] *= num3;
			}
		}
	}
}
public class BingBongVoice : MonoBehaviour
{
	public static readonly List<BingBongVoice> Active = new List<BingBongVoice>();

	private Action_AskBingBong _action;

	private BingBongMouth _mouth;

	private Item _item;

	private PhotonView _view;

	private Coroutine _subtitleRoutine;

	private static Camera _cachedCamera;

	public Item Item => _item;

	private void Awake()
	{
		_action = ((Component)this).GetComponent<Action_AskBingBong>();
		_mouth = ((Component)this).GetComponentInChildren<BingBongMouth>(true);
		_item = ((Component)this).GetComponent<Item>();
		if ((Object)(object)_item == (Object)null)
		{
			_item = ((Component)this).GetComponentInParent<Item>();
		}
		if ((Object)(object)_item == (Object)null)
		{
			_item = ((Component)this).GetComponentInChildren<Item>(true);
		}
		_view = ((Component)this).GetComponent<PhotonView>();
		if ((Object)(object)_view == (Object)null)
		{
			_view = ((Component)this).GetComponentInParent<PhotonView>();
		}
		if ((Object)(object)_action != (Object)null && !Babble.Ready)
		{
			((MonoBehaviour)this).StartCoroutine(Babble.Prepare(_action));
		}
		Plugin.Log.LogInfo((object)("Bing Bong encontrado -> action=" + ((Object)(object)_action != (Object)null) + " item=" + ((Object)(object)_item != (Object)null) + " mouth=" + ((Object)(object)_mouth != (Object)null) + " view=" + ((Object)(object)_view != (Object)null) + " responses=" + (((Object)(object)_action != (Object)null && _action.responses != null) ? _action.responses.Length : 0)));
	}

	private void OnEnable()
	{
		if (!Active.Contains(this))
		{
			Active.Add(this);
		}
	}

	private void OnDisable()
	{
		Active.Remove(this);
	}

	public bool HeldByLocalPlayer()
	{
		if ((Object)(object)_item == (Object)null)
		{
			return false;
		}
		Character holderCharacter = _item.holderCharacter;
		if ((Object)(object)holderCharacter == (Object)null)
		{
			return false;
		}
		return (Object)(object)holderCharacter == (Object)(object)Character.localCharacter;
	}

	public string DescribeHolder()
	{
		if ((Object)(object)_item == (Object)null)
		{
			return "(item == null)";
		}
		Character holderCharacter = _item.holderCharacter;
		string text = (((Object)(object)holderCharacter == (Object)null) ? "ninguem" : ((Object)holderCharacter).name);
		string text2 = (((Object)(object)Character.localCharacter == (Object)null) ? "null" : ((Object)Character.localCharacter).name);
		return "segurando=" + text + " local=" + text2;
	}

	public void Say(string author, string text)
	{
		if (Plugin.CfgSyncToOthers.Value && (Object)(object)_view != (Object)null && PhotonNetwork.InRoom)
		{
			Plugin.Log.LogInfo((object)("Enviando fala para a sala (" + PhotonNetwork.CurrentRoom.PlayerCount + " jogadores) via RPC."));
			_view.RPC("BBTwitchSay", (RpcTarget)0, new object[2] { author, text });
			return;
		}
		Plugin.Log.LogInfo((object)("Falando so aqui (sync=" + Plugin.CfgSyncToOthers.Value + " view=" + ((Object)(object)_view != (Object)null) + " naSala=" + PhotonNetwork.InRoom + ")."));
		SayLocal(author, text);
	}

	[PunRPC]
	public void BBTwitchSay(string author, string text)
	{
		SayLocal(author, text);
	}

	private void SayLocal(string author, string text)
	{
		if ((Object)(object)_action == (Object)null || string.IsNullOrEmpty(text))
		{
			return;
		}
		string text2 = Sanitize(text, Plugin.CfgMaxLength.Value);
		if (!string.IsNullOrEmpty(text2))
		{
			string text3 = text2;
			if (Plugin.CfgShowAuthor.Value && !string.IsNullOrEmpty(author))
			{
				text3 = Sanitize(author, 25) + ": " + text2;
			}
			Plugin.Log.LogInfo((object)("Bing Bong falando: " + text3));
			if (_subtitleRoutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_subtitleRoutine);
			}
			if ((Object)(object)_action.source != (Object)null)
			{
				_action.source.Stop();
			}
			if ((Object)(object)_action.squishAnim != (Object)null)
			{
				_action.squishAnim.SetTrigger("Squish");
			}
			VoiceMode value = Plugin.CfgVoiceMode.Value;
			if ((value == VoiceMode.ElevenLabs || value == VoiceMode.Ambos) && ElevenLabs.Configured)
			{
				((MonoBehaviour)this).StartCoroutine(SpeakWithTts(text2, text3, value == VoiceMode.Ambos));
			}
			else if (value == VoiceMode.ElevenLabs)
			{
				Plugin.Log.LogWarning((object)"Modo ElevenLabs sem chave configurada (F10 para colocar). Mostrando so a legenda.");
				_subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(text3, EstimateDuration(text2)));
			}
			else
			{
				SpeakBabble(text2, text3);
			}
		}
	}

	private void SpeakBabble(string body, string display)
	{
		if (!Babble.Ready)
		{
			((MonoBehaviour)this).StartCoroutine(Babble.Prepare(_action));
		}
		AudioClip val = Babble.Build(body, _action, Plugin.CfgMaxSeconds.Value);
		float duration;
		if ((Object)(object)val != (Object)null)
		{
			duration = Mathf.Max(val.length, Plugin.CfgMinSeconds.Value);
			if ((Object)(object)_action.source != (Object)null)
			{
				_action.source.pitch = 1f;
				_action.source.PlayOneShot(val, Plugin.CfgVolume.Value);
			}
			if ((Object)(object)_mouth != (Object)null)
			{
				_mouth.SampleAudioClip(val);
			}
		}
		else
		{
			Plugin.Log.LogWarning((object)"Nao consegui gerar a voz; mostrando so a legenda.");
			duration = EstimateDuration(body);
		}
		if (_subtitleRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(_subtitleRoutine);
		}
		_subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(display, duration));
	}

	private IEnumerator SpeakWithTts(string body, string display, bool babbleOnFailure)
	{
		_subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(display, EstimateDuration(body)));
		AudioClip clip = null;
		yield return ElevenLabs.Synthesize(body, delegate(AudioClip c)
		{
			clip = c;
		});
		if ((Object)(object)clip == (Object)null)
		{
			if (babbleOnFailure)
			{
				Plugin.Log.LogInfo((object)"ElevenLabs falhou; o Bing Bong assume com a tagarelice.");
				SpeakBabble(body, display);
			}
			else
			{
				Plugin.Log.LogWarning((object)"ElevenLabs falhou. Sem tagarelice porque o modo e 'ElevenLabs' puro -- use 'Ambos' para ele assumir nesses casos.");
			}
			yield break;
		}
		float pitch = Plugin.CfgElevenPitch.Value;
		if ((Object)(object)_action.source != (Object)null)
		{
			_action.source.pitch = pitch;
			_action.source.PlayOneShot(clip, Plugin.CfgVolume.Value);
		}
		if ((Object)(object)_mouth != (Object)null)
		{
			_mouth.SampleAudioClip(clip);
		}
		float spoken = clip.length / Mathf.Max(0.1f, pitch);
		if (_subtitleRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(_subtitleRoutine);
		}
		_subtitleRoutine = ((MonoBehaviour)this).StartCoroutine(SubtitleRoutine(display, spoken));
	}

	private static float EstimateDuration(string body)
	{
		return Mathf.Clamp((float)body.Length * Plugin.CfgSecondsPerChar.Value, Plugin.CfgMinSeconds.Value, Plugin.CfgMaxSeconds.Value);
	}

	private IEnumerator SubtitleRoutine(string display, float duration)
	{
		TextMeshPro subtitles = _action.subtitles;
		if ((Object)(object)subtitles == (Object)null)
		{
			yield break;
		}
		((Component)subtitles).gameObject.SetActive(true);
		((TMP_Text)subtitles).text = display;
		float t = 0f;
		while (t < duration)
		{
			t += Time.deltaTime;
			float fadeIn = t * 12f;
			float fadeOut = (duration - t) * 6f;
			((TMP_Text)subtitles).alpha = Mathf.Clamp01(Mathf.Min(fadeIn, fadeOut));
			Camera cam = GetCamera();
			if ((Object)(object)cam != (Object)null)
			{
				Transform transform = subtitles.transform;
				float num = Vector3.Distance(transform.position, ((Component)cam).transform.position);
				if (_action.scaleCurve != null)
				{
					transform.localScale = Vector3.one * _action.scaleCurve.Evaluate(num);
				}
				transform.forward = ((Component)cam).transform.forward;
			}
			yield return null;
		}
		((Component)subtitles).gameObject.SetActive(false);
		_subtitleRoutine = null;
	}

	private static Camera GetCamera()
	{
		if ((Object)(object)_cachedCamera != (Object)null)
		{
			return _cachedCamera;
		}
		if ((Object)(object)MainCamera.instance != (Object)null)
		{
			_cachedCamera = ((Component)MainCamera.instance).GetComponentInChildren<Camera>(true);
		}
		if ((Object)(object)_cachedCamera == (Object)null)
		{
			_cachedCamera = Camera.main;
		}
		return _cachedCamera;
	}

	public static string Sanitize(string input, int maxLength)
	{
		if (string.IsNullOrEmpty(input))
		{
			return "";
		}
		StringBuilder stringBuilder = new StringBuilder(input.Length);
		foreach (char c in input)
		{
			switch (c)
			{
			case '<':
				stringBuilder.Append('‹');
				continue;
			case '>':
				stringBuilder.Append('›');
				continue;
			case '\t':
			case '\n':
			case '\r':
				stringBuilder.Append(' ');
				continue;
			}
			if (!char.IsControl(c))
			{
				stringBuilder.Append(c);
			}
		}
		string text = stringBuilder.ToString().Trim();
		if (maxLength > 0 && text.Length > maxLength)
		{
			text = text.Substring(0, maxLength).TrimEnd() + "...";
		}
		return text;
	}
}
public class ConfigWindow : MonoBehaviour
{
	private const int WindowId = 731204;

	private bool _open;

	private Rect _window = new Rect(0f, 0f, 470f, 300f);

	private bool _centred;

	private static readonly string[] ModeLabels = new string[3] { "Bing Bong", "ElevenLabs", "Os dois" };

	private static readonly VoiceMode[] Modes = new VoiceMode[3]
	{
		VoiceMode.Babble,
		VoiceMode.ElevenLabs,
		VoiceMode.Ambos
	};

	private static readonly string[] ModelLabels = new string[2] { "Multi-idioma", "Turbo (rapido)" };

	private static readonly string[] ModelIds = new string[2] { "eleven_multilingual_v2", "eleven_turbo_v2_5" };

	private string _channel = "";

	private string _apiKey = "";

	private bool _showKey;

	private int _modeIndex;

	private int _modelIndex;

	private int _voiceIndex = -1;

	private bool _loadingVoices;

	private bool _onlyFreeVoices = true;

	private bool _voiceListOpen;

	private Vector2 _voiceScroll;

	private string _status = "";

	private float _statusUntil;

	private CursorLockMode _previousLock;

	private bool _previousVisible;

	private float _autoOpenAt;

	private bool _firstRun;

	private void Start()
	{
		LoadFromConfig();
		if (!Plugin.CfgSetupShown.Value)
		{
			_autoOpenAt = Time.realtimeSinceStartup + 5f;
		}
	}

	private void LoadFromConfig()
	{
		_channel = Plugin.CfgChannel.Value;
		_apiKey = Plugin.CfgElevenApiKey.Value;
		_modeIndex = 0;
		for (int i = 0; i < Modes.Length; i++)
		{
			if (Modes[i] == Plugin.CfgVoiceMode.Value)
			{
				_modeIndex = i;
			}
		}
		_modelIndex = 0;
		for (int j = 0; j < ModelIds.Length; j++)
		{
			if (ModelIds[j] == Plugin.CfgElevenModel.Value)
			{
				_modelIndex = j;
			}
		}
		_voiceIndex = -1;
		string text = Plugin.CfgElevenVoiceId.Value.Trim();
		for (int k = 0; k < ElevenLabs.Voices.Length; k++)
		{
			if (ElevenLabs.Voices[k].Id == text)
			{
				_voiceIndex = k;
			}
		}
	}

	private void Update()
	{
		if (_autoOpenAt > 0f && Time.realtimeSinceStartup >= _autoOpenAt)
		{
			_autoOpenAt = 0f;
			_firstRun = true;
			if (!_open)
			{
				Open();
			}
			Plugin.Log.LogInfo((object)"Primeira vez: abrindo a janela de configuracao. Depois disso, use F10.");
		}
		if (ToggleKeyPressed())
		{
			if (_open)
			{
				Close();
			}
			else
			{
				Open();
			}
		}
	}

	private void ResetListState()
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		_voiceListOpen = false;
		_voiceScroll = Vector2.zero;
	}

	private void Open()
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		LoadFromConfig();
		_open = true;
		_previousLock = Cursor.lockState;
		_previousVisible = Cursor.visible;
		ForceCursor();
		if (ElevenLabs.Voices.Length == 0 && ElevenLabs.Configured && !_loadingVoices)
		{
			FetchVoices();
		}
	}

	private void ForceCursor()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		if ((int)Cursor.lockState != 0)
		{
			Cursor.lockState = (CursorLockMode)0;
		}
		if (!Cursor.visible)
		{
			Cursor.visible = true;
		}
	}

	private void LateUpdate()
	{
		if (_open)
		{
			ForceCursor();
		}
	}

	private void Close()
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		_open = false;
		ResetListState();
		Cursor.lockState = _previousLock;
		Cursor.visible = _previousVisible;
		if (!Plugin.CfgSetupShown.Value)
		{
			Plugin.CfgSetupShown.Value = true;
		}
		_firstRun = false;
	}

	private static bool ToggleKeyPressed()
	{
		try
		{
			Keyboard current = Keyboard.current;
			if (current != null)
			{
				return ((ButtonControl)current[(Key)103]).wasPressedThisFrame;
			}
		}
		catch
		{
		}
		try
		{
			return Input.GetKeyDown((KeyCode)291);
		}
		catch
		{
		}
		return false;
	}

	private void OnGUI()
	{
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Expected O, but got Unknown
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		if (_open)
		{
			ForceCursor();
			if (!_centred)
			{
				((Rect)(ref _window)).x = ((float)Screen.width - ((Rect)(ref _window)).width) / 2f;
				((Rect)(ref _window)).y = ((float)Screen.height - ((Rect)(ref _window)).height) / 2f;
				_centred = true;
			}
			GUI.depth = 0;
			_window = GUILayout.Window(731204, _window, new WindowFunction(DrawWindow), "BingBongTwitch   (F10 abre e fecha)", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
	}

	private void DrawWindow(int id)
	{
		//IL_02b5: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Space(4f);
		if (_firstRun)
		{
			GUILayout.Label("Bem-vindo! Configure aqui e o Bing Bong passa a falar o seu chat.\nDepois, o F10 abre esta janela quando quiser.", (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Space(8f);
		}
		GUILayout.Label("Canal da Twitch", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		_channel = GUILayout.TextField((_channel == null) ? "" : _channel, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("<size=10>So o nome, sem o https://twitch.tv/</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Space(10f);
		GUILayout.Label("Voz", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		_modeIndex = GUILayout.Toolbar(_modeIndex, ModeLabels, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (Modes[_modeIndex] == VoiceMode.Babble)
		{
			GUILayout.Label("<size=10>Tagarelice feita com os sons do proprio Bing Bong. De graca, sem internet, instantaneo. A mensagem aparece na legenda.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		else if (Modes[_modeIndex] == VoiceMode.ElevenLabs)
		{
			GUILayout.Label("<size=10>So ElevenLabs. Se falhar ou acabarem os creditos, sai apenas a legenda.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		else
		{
			GUILayout.Label("<size=10>Tenta o ElevenLabs; quando ele falha, o Bing Bong assume com a tagarelice. Nunca fica mudo.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		if (Modes[_modeIndex] != VoiceMode.Babble)
		{
			GUILayout.Space(6f);
			GUILayout.Label("Chave do ElevenLabs", (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
			if (_showKey)
			{
				_apiKey = GUILayout.TextField((_apiKey == null) ? "" : _apiKey, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			else
			{
				_apiKey = GUILayout.PasswordField((_apiKey == null) ? "" : _apiKey, '*', (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
			_showKey = GUILayout.Toggle(_showKey, " ver", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) });
			GUILayout.EndHorizontal();
			GUILayout.Label("<size=10>Fica so neste PC. Gasta creditos da sua conta.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Space(8f);
			DrawVoicePicker();
			GUILayout.Space(6f);
			GUILayout.Label("Modelo", (GUILayoutOption[])(object)new GUILayoutOption[0]);
			_modelIndex = GUILayout.Toolbar(_modelIndex, ModelLabels, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			GUILayout.Label((_modelIndex == 0) ? "<size=10>Fala portugues e ingles bem. Um pouco mais lento.</size>" : "<size=10>Responde mais rapido e gasta menos creditos.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		GUILayout.Space(10f);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("Salvar", (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			Save();
		}
		if (GUILayout.Button("Fechar", (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			Close();
		}
		GUILayout.EndHorizontal();
		if (!string.IsNullOrEmpty(_status) && Time.realtimeSinceStartup < _statusUntil)
		{
			GUILayout.Label(_status, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f));
	}

	private void DrawVoicePicker()
	{
		//IL_0140: 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)
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Voz:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(35f) });
		if (ElevenLabs.Voices.Length == 0)
		{
			GUILayout.Label(_loadingVoices ? "buscando..." : "(clique em Buscar)", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		else
		{
			string text = ((_voiceIndex < 0) ? "(primeira da conta)" : ElevenLabs.Voices[_voiceIndex].ToString());
			if (GUILayout.Button(text + "    " + (_voiceListOpen ? "▲" : "▼"), (GUILayoutOption[])(object)new GUILayoutOption[0]))
			{
				_voiceListOpen = !_voiceListOpen;
			}
		}
		GUI.enabled = !_loadingVoices;
		if (GUILayout.Button("Buscar", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
		{
			FetchVoices();
		}
		GUI.enabled = true;
		GUILayout.EndHorizontal();
		if (_voiceListOpen && ElevenLabs.Voices.Length > 0)
		{
			DrawVoiceList();
		}
		if (ElevenLabs.Voices.Length > 0)
		{
			bool onlyFreeVoices = _onlyFreeVoices;
			_onlyFreeVoices = GUILayout.Toggle(_onlyFreeVoices, " Esconder vozes que exigem plano pago", (GUILayoutOption[])(object)new GUILayoutOption[0]);
			if (onlyFreeVoices != _onlyFreeVoices)
			{
				_voiceScroll = Vector2.zero;
			}
			if (_voiceIndex >= 0 && !ElevenLabs.Voices[_voiceIndex].FreePlanFriendly)
			{
				GUILayout.Label("<size=10>Esta voz e da biblioteca. No plano gratuito a API recusa ela e o Bing Bong nao fala.</size>", (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
		}
	}

	private void DrawVoiceList()
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		_voiceScroll = GUILayout.BeginScrollView(_voiceScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(160f) });
		int num = 0;
		for (int i = 0; i < ElevenLabs.Voices.Length; i++)
		{
			ElevenLabs.VoiceInfo voiceInfo = ElevenLabs.Voices[i];
			if (!_onlyFreeVoices || voiceInfo.FreePlanFriendly)
			{
				num++;
				string text = ((i == _voiceIndex) ? "• " : "   ");
				if (GUILayout.Button(text + voiceInfo.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[0]))
				{
					_voiceIndex = i;
					_voiceListOpen = false;
				}
			}
		}
		if (num == 0)
		{
			GUILayout.Label("Nenhuma voz do plano gratuito nesta conta.\nDesmarque a opcao abaixo para ver as outras.", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		GUILayout.EndScrollView();
	}

	private void FetchVoices()
	{
		Plugin.CfgElevenApiKey.Value = ((_apiKey == null) ? "" : _apiKey).Trim();
		_loadingVoices = true;
		((MonoBehaviour)this).StartCoroutine(ElevenLabs.FetchVoices(OnVoicesLoaded));
	}

	private void OnVoicesLoaded(string error)
	{
		_loadingVoices = false;
		if (!string.IsNullOrEmpty(error))
		{
			SetStatus(error);
			return;
		}
		string text = Plugin.CfgElevenVoiceId.Value.Trim();
		_voiceIndex = -1;
		for (int i = 0; i < ElevenLabs.Voices.Length; i++)
		{
			if (ElevenLabs.Voices[i].Id == text)
			{
				_voiceIndex = i;
			}
		}
		if (_voiceIndex < 0)
		{
			ElevenLabs.VoiceInfo voiceInfo = ElevenLabs.PickDefaultVoice();
			for (int j = 0; j < ElevenLabs.Voices.Length; j++)
			{
				if (ElevenLabs.Voices[j] == voiceInfo)
				{
					_voiceIndex = j;
				}
			}
		}
		int num = 0;
		for (int k = 0; k < ElevenLabs.Voices.Length; k++)
		{
			if (ElevenLabs.Voices[k].FreePlanFriendly)
			{
				num++;
			}
		}
		SetStatus(ElevenLabs.Voices.Length + " vozes (" + num + " funcionam no plano gratuito). Use < e > para escolher.");
	}

	private void Save()
	{
		string text = ((_channel == null) ? "" : _channel).Trim();
		string value = ((_apiKey == null) ? "" : _apiKey).Trim();
		VoiceMode voiceMode = Modes[_modeIndex];
		if (voiceMode != VoiceMode.Babble && string.IsNullOrEmpty(value))
		{
			SetStatus("Escreva a chave, ou escolha 'Bing Bong'.");
			return;
		}
		Plugin.CfgElevenApiKey.Value = value;
		Plugin.CfgVoiceMode.Value = voiceMode;
		Plugin.CfgElevenModel.Value = ModelIds[_modelIndex];
		if (_voiceIndex >= 0 && _voiceIndex < ElevenLabs.Voices.Length)
		{
			Plugin.CfgElevenVoiceId.Value = ElevenLabs.Voices[_voiceIndex].Id;
		}
		Plugin.CfgChannel.Value = text;
		ElevenLabs.Reset();
		if (!Plugin.CfgSetupShown.Value)
		{
			Plugin.CfgSetupShown.Value = true;
		}
		_firstRun = false;
		Plugin.Log.LogInfo((object)("Configuracao salva pela janela do jogo. Canal: " + (string.IsNullOrEmpty(text) ? "(vazio)" : text) + " | voz: " + Plugin.CfgVoiceMode.Value));
		SetStatus("Salvo! Conectando em #" + text + "...");
	}

	private void SetStatus(string message)
	{
		_status = message;
		_statusUntil = Time.realtimeSinceStartup + 6f;
	}
}
public static class ElevenLabs
{
	public class VoiceInfo
	{
		public string Id;

		public string Name;

		public string Description;

		public bool FreePlanFriendly;

		public override string ToString()
		{
			string text = Name;
			if (!string.IsNullOrEmpty(Description))
			{
				text = text + " - " + Description;
			}
			if (!FreePlanFriendly)
			{
				text += "  [plano pago]";
			}
			return text;
		}
	}

	private const string BaseUrl = "https://api.elevenlabs.io/v1";

	private static string _resolvedVoiceId;

	private static bool _voiceLookupDone;

	private static float _lastRequestTime = -999f;

	private static int _fileCounter;

	public static VoiceInfo[] Voices = new VoiceInfo[0];

	public static bool Configured
	{
		get
		{
			if (Plugin.CfgElevenApiKey != null)
			{
				return !string.IsNullOrEmpty(Plugin.CfgElevenApiKey.Value.Trim());
			}
			return false;
		}
	}

	public static void Reset()
	{
		_resolvedVoiceId = null;
		_voiceLookupDone = false;
	}

	public static IEnumerator Synthesize(string text, Action<AudioClip> onReady)
	{
		if (!Configured)
		{
			onReady(null);
			yield break;
		}
		float since = Time.realtimeSinceStartup - _lastRequestTime;
		float minGap = Plugin.CfgElevenMinGap.Value;
		if (since < minGap)
		{
			Plugin.Log.LogInfo((object)("ElevenLabs: ignorando (faltam " + (minGap - since).ToString("0.0") + "s para a proxima chamada permitida)."));
			onReady(null);
			yield break;
		}
		_lastRequestTime = Time.realtimeSinceStartup;
		if (!_voiceLookupDone)
		{
			yield return ResolveVoiceId();
			_voiceLookupDone = true;
		}
		string voiceId = _resolvedVoiceId;
		if (string.IsNullOrEmpty(voiceId))
		{
			Plugin.Log.LogWarning((object)"ElevenLabs: nenhuma voz disponivel. Preencha VoiceId no config.");
			onReady(null);
			yield break;
		}
		JObject body = new JObject();
		body["text"] = JToken.op_Implicit(text);
		body["model_id"] = JToken.op_Implicit(Plugin.CfgElevenModel.Value);
		string url = "https://api.elevenlabs.io/v1/text-to-speech/" + voiceId + "?output_format=mp3_44100_128";
		byte[] payload = Encoding.UTF8.GetBytes(((JToken)body).ToString((Formatting)0, (JsonConverter[])(object)new JsonConverter[0]));
		byte[] audio = null;
		UnityWebRequest request = new UnityWebRequest(url, "POST");
		try
		{
			request.uploadHandler = (UploadHandler)new UploadHandlerRaw(payload);
			request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
			request.SetRequestHeader("Content-Type", "application/json");
			request.SetRequestHeader("xi-api-key", Plugin.CfgElevenApiKey.Value.Trim());
			request.timeout = Mathf.CeilToInt(Plugin.CfgElevenTimeout.Value);
			float started = Time.realtimeSinceStartup;
			yield return request.SendWebRequest();
			if ((int)request.result != 1)
			{
				string text2 = Describe(request);
				if (request.responseCode == 402 && text2.IndexOf("library voices", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					Plugin.Log.LogWarning((object)"ElevenLabs: essa voz e da biblioteca e o plano gratuito nao deixa usa-la pela API. Aperte F10 e escolha uma voz SEM a marca [plano pago].");
				}
				else if (request.responseCode == 401)
				{
					Plugin.Log.LogWarning((object)"ElevenLabs: chave invalida. Aperte F10 e confira.");
				}
				else if (request.responseCode == 429)
				{
					Plugin.Log.LogWarning((object)"ElevenLabs: muitas chamadas seguidas ou creditos esgotados.");
				}
				else
				{
					Plugin.Log.LogWarning((object)("ElevenLabs falhou (" + request.responseCode + "): " + request.error + " " + text2));
				}
				onReady(null);
				yield break;
			}
			audio = request.downloadHandler.data;
			Plugin.Log.LogInfo((object)("ElevenLabs respondeu em " + (Time.realtimeSinceStartup - started).ToString("0.00") + "s (" + ((audio != null) ? audio.Length : 0) + " bytes)."));
		}
		finally
		{
			((IDisposable)request)?.Dispose();
		}
		if (audio == null || audio.Length == 0)
		{
			onReady(null);
			yield break;
		}
		string path = Path.Combine(Application.temporaryCachePath, "bbtwitch_" + _fileCounter++ % 4 + ".mp3");
		bool written = false;
		try
		{
			File.WriteAllBytes(path, audio);
			written = true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("ElevenLabs: nao consegui salvar o audio: " + ex.Message));
		}
		if (!written)
		{
			onReady(null);
			yield break;
		}
		UnityWebRequest load = UnityWebRequestMultimedia.GetAudioClip("file://" + path.Replace("\\", "/"), (AudioType)13);
		try
		{
			yield return load.SendWebRequest();
			if ((int)load.result != 1)
			{
				Plugin.Log.LogWarning((object)("ElevenLabs: nao consegui decodificar o MP3: " + load.error));
				onReady(null);
			}
			else
			{
				AudioClip clip = DownloadHandlerAudioClip.GetContent(load);
				onReady(clip);
			}
		}
		finally
		{
			((IDisposable)load)?.Dispose();
		}
	}

	private static IEnumerator ResolveVoiceId()
	{
		string configured = Plugin.CfgElevenVoiceId.Value.Trim();
		if (!string.IsNullOrEmpty(configured))
		{
			_resolvedVoiceId = configured;
			yield break;
		}
		yield return FetchVoices(null);
		VoiceInfo pick = PickDefaultVoice();
		if (pick != null)
		{
			_resolvedVoiceId = pick.Id;
			Plugin.Log.LogInfo((object)("ElevenLabs: usando a voz '" + pick.Name + "'. Aperte F10 para escolher outra."));
		}
	}

	public static VoiceInfo PickDefaultVoice()
	{
		VoiceInfo voiceInfo = null;
		for (int i = 0; i < Voices.Length; i++)
		{
			VoiceInfo voiceInfo2 = Voices[i];
			if (voiceInfo2.FreePlanFriendly)
			{
				if (voiceInfo == null)
				{
					voiceInfo = voiceInfo2;
				}
				string text = (voiceInfo2.Name + " " + voiceInfo2.Description).ToLowerInvariant();
				if (text.Contains("portug") || text.Contains("brazil") || text.Contains("brasil"))
				{
					return voiceInfo2;
				}
			}
		}
		if (voiceInfo != null)
		{
			return voiceInfo;
		}
		if (Voices.Length <= 0)
		{
			return null;
		}
		return Voices[0];
	}

	public static IEnumerator FetchVoices(Action<string> onDone)
	{
		if (!Configured)
		{
			onDone?.Invoke("Escreva a chave primeiro.");
			yield break;
		}
		UnityWebRequest request = UnityWebRequest.Get("https://api.elevenlabs.io/v1/voices");
		try
		{
			request.SetRequestHeader("xi-api-key", Plugin.CfgElevenApiKey.Value.Trim());
			request.timeout = 20;
			yield return request.SendWebRequest();
			if ((int)request.result != 1)
			{
				string text = "Nao consegui buscar as vozes (" + request.responseCode + ")";
				if (request.responseCode == 401)
				{
					text = "Chave invalida.";
				}
				Plugin.Log.LogWarning((object)("ElevenLabs: " + text + " " + request.error));
				onDone?.Invoke(text);
				yield break;
			}
			List<VoiceInfo> found = new List<VoiceInfo>();
			string failure = null;
			try
			{
				JObject val = JObject.Parse(request.downloadHandler.text);
				JToken obj = val["voices"];
				JArray val2 = (JArray)(object)((obj is JArray) ? obj : null);
				if (val2 != null)
				{
					for (int i = 0; i < ((JContainer)val2).Count; i++)
					{
						VoiceInfo voiceInfo = new VoiceInfo();
						voiceInfo.Id = (string)val2[i][(object)"voice_id"];
						voiceInfo.Name = (string)val2[i][(object)"name"];
						JToken val3 = val2[i][(object)"labels"];
						if (val3 != null)
						{
							string text2 = (string)val3[(object)"accent"];
							string text3 = (string)val3[(object)"gender"];
							string text4 = (string)val3[(object)"language"];
							voiceInfo.Description = string.Join(" ", text3, text2, text4).Trim();
						}
						string text5 = (string)val2[i][(object)"category"];
						voiceInfo.FreePlanFriendly = text5 == "premade";
						if (!string.IsNullOrEmpty(voiceInfo.Id))
						{
							found.Add(voiceInfo);
						}
					}
				}
			}
			catch (Exception ex)
			{
				failure = "Resposta ilegivel: " + ex.Message;
				Plugin.Log.LogWarning((object)("ElevenLabs: " + failure));
			}
			if (failure != null)
			{
				onDone?.Invoke(failure);
				yield break;
			}
			Voices = found.ToArray();
			Plugin.Log.LogInfo((object)("ElevenLabs: " + Voices.Length + " vozes na conta."));
			onDone?.Invoke((Voices.Length == 0) ? "A conta nao tem nenhuma voz." : null);
		}
		finally
		{
			((IDisposable)request)?.Dispose();
		}
	}

	private static string Describe(UnityWebRequest request)
	{
		if (request.downloadHandler == null)
		{
			return "";
		}
		string text = request.downloadHandler.text;
		if (string.IsNullOrEmpty(text))
		{
			return "";
		}
		if (text.Length > 300)
		{
			text = text.Substring(0, 300);
		}
		return text;
	}
}
public static class Patches
{
	[HarmonyPostfix]
	[HarmonyPatch(typeof(ItemActionBase), "Start")]
	public static void AttachVoice(ItemActionBase __instance)
	{
		Action_AskBingBong val = (Action_AskBingBong)(object)((__instance is Action_AskBingBong) ? __instance : null);
		if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject.GetComponent<BingBongVoice>() != (Object)null))
		{
			((Component)val).gameObject.AddComponent<BingBongVoice>();
			PhotonView component = ((Component)val).gameObject.GetComponent<PhotonView>();
			if ((Object)(object)component != (Object)null)
			{
				component.RefreshRpcMonoBehaviourCache();
			}
		}
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(Action_AskBingBong), "RunAction")]
	public static bool SpeakChatOnUse(Action_AskBingBong __instance)
	{
		if (!Plugin.CfgEnabled.Value)
		{
			return true;
		}
		if (Plugin.CfgMode.Value == SpeakMode.Auto)
		{
			return true;
		}
		ChatMessage lastMessage = Plugin.LastMessage;
		if (lastMessage == null)
		{
			Plugin.Log.LogWarning((object)"Usou o Bing Bong, mas nenhuma mensagem do chat chegou ainda -> fala normal.");
			return true;
		}
		BingBongVoice bingBongVoice = ((Component)__instance).gameObject.GetComponent<BingBongVoice>();
		if ((Object)(object)bingBongVoice == (Object)null)
		{
			Plugin.Log.LogWarning((object)"BingBongVoice nao estava anexado -> anexando agora.");
			bingBongVoice = ((Component)__instance).gameObject.AddComponent<BingBongVoice>();
		}
		if (!bingBongVoice.HeldByLocalPlayer())
		{
			Plugin.Log.LogWarning((object)("Bing Bong nao esta na mao do jogador local -> fala normal. " + bingBongVoice.DescribeHolder()));
			return true;
		}
		bingBongVoice.Say(lastMessage.Author, lastMessage.Text);
		return false;
	}
}
public enum VoiceMode
{
	Babble,
	ElevenLabs,
	Ambos
}
public enum SpeakMode
{
	OnUse,
	Auto,
	Both
}
[BepInPlugin("rodr313.bingbongtwitch", "BingBongTwitch", "1.0.3")]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "rodr313.bingbongtwitch";

	public const string Name = "BingBongTwitch";

	public const string Version = "1.0.3";

	public static Plugin Instance;

	public static ManualLogSource Log;

	public static ConfigEntry<string> CfgChannel;

	public static ConfigEntry<bool> CfgEnabled;

	public static ConfigEntry<SpeakMode> CfgMode;

	public static ConfigEntry<string> CfgCommandPrefix;

	public static ConfigEntry<bool> CfgOnlySubs;

	public static ConfigEntry<float> CfgAutoCooldown;

	public static ConfigEntry<int> CfgMaxLength;

	public static ConfigEntry<bool> CfgShowAuthor;

	public static ConfigEntry<bool> CfgSyncToOthers;

	public static ConfigEntry<float> CfgVolume;

	public static ConfigEntry<float> CfgPitchMin;

	public static ConfigEntry<float> CfgPitchMax;

	public static ConfigEntry<float> CfgSyllableSeconds;

	public static ConfigEntry<VoiceMode> CfgVoiceMode;

	public static ConfigEntry<string> CfgElevenApiKey;

	public static ConfigEntry<string> CfgElevenVoiceId;

	public static ConfigEntry<string> CfgElevenModel;

	public static ConfigEntry<float> CfgElevenPitch;

	public static ConfigEntry<float> CfgElevenMinGap;

	public static ConfigEntry<float> CfgElevenTimeout;

	public static ConfigEntry<float> CfgSecondsPerChar;

	public static ConfigEntry<float> CfgMinSeconds;

	public static ConfigEntry<float> CfgMaxSeconds;

	public static ChatMessage LastMessage;

	public static ConfigEntry<bool> CfgSetupShown;

	public static ConfigEntry<bool> CfgVerbose;

	public static ConfigEntry<bool> CfgTestOnConnect;

	private TwitchIrc _irc;

	private Harmony _harmony;

	private float _nextAutoSpeak;

	private bool _announcedConnection;

	private void Awake()
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfig();
		if (!CfgEnabled.Value)
		{
			Log.LogInfo((object)"Desligado no config (Enabled = false). Nenhum patch aplicado.");
			return;
		}
		_harmony = new Harmony("rodr313.bingbongtwitch");
		_harmony.PatchAll(typeof(Patches));
		((Component)this).gameObject.AddComponent<ConfigWindow>();
		Log.LogInfo((object)"Aperte F10 dentro do jogo para escolher o canal da Twitch e a voz.");
		Connect();
	}

	private void BindConfig()
	{
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Expected O, but got Unknown
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		//IL_011c: Expected O, but got Unknown
		//IL_0190: Unknown result type (might be due to invalid IL or missing references)
		//IL_019a: Expected O, but got Unknown
		//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d8: Expected O, but got Unknown
		//IL_020c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0216: Expected O, but got Unknown
		//IL_024a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0254: Expected O, but got Unknown
		//IL_0314: Unknown result type (might be due to invalid IL or missing references)
		//IL_031e: Expected O, but got Unknown
		//IL_0352: Unknown result type (might be due to invalid IL or missing references)
		//IL_035c: Expected O, but got Unknown
		//IL_0390: Unknown result type (might be due to invalid IL or missing references)
		//IL_039a: Expected O, but got Unknown
		//IL_03ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_03d8: Expected O, but got Unknown
		//IL_040c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0416: Expected O, but got Unknown
		//IL_044a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0454: Expected O, but got Unknown
		CfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Twitch", "Enabled", true, "Liga o mod.");
		CfgChannel = ((BaseUnityPlugin)this).Config.Bind<string>("1. Twitch", "Channel", "mrfalll", "Nome do seu canal da Twitch (so o nome, sem o https://twitch.tv/). Vem preenchido com mrfalll -- troque pelo seu canal. A leitura do chat e anonima: nao precisa de token, senha nem API.");
		CfgMode = ((BaseUnityPlugin)this).Config.Bind<SpeakMode>("2. Comportamento", "Mode", SpeakMode.OnUse, "OnUse = ele fala a mensagem mais recente quando voce usa o Bing Bong. Auto = ele fala sozinho cada mensagem nova. Both = os dois.");
		CfgCommandPrefix = ((BaseUnityPlugin)this).Config.Bind<string>("2. Comportamento", "CommandPrefix", "", "Se preenchido (ex: !bb), so mensagens que comecam com isso contam, e o prefixo e removido antes de falar. Vazio = qualquer mensagem.");
		CfgOnlySubs = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Comportamento", "OnlySubsAndMods", false, "So aceita mensagens de subs, VIPs, mods e do dono do canal.");
		CfgAutoCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("2. Comportamento", "AutoCooldownSeconds", 6f, new ConfigDescription("No modo Auto, tempo minimo entre uma fala e outra.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), new object[0]));
		CfgMaxLength = ((BaseUnityPlugin)this).Config.Bind<int>("2. Comportamento", "MaxLength", 140, new ConfigDescription("Mensagens maiores que isso sao cortadas.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 400), new object[0]));
		CfgShowAuthor = ((BaseUnityPlugin)this).Config.Bind<bool>("2. Comportamento", "ShowAuthor", true, "Mostra o nome de quem escreveu antes da mensagem.");
		CfgSyncToOthers = ((BaseUnityPlugin)this).Config.Bind<bool>("3. Multiplayer", "SyncToOthers", true, "Manda a fala pra todo mundo na sala via Photon. Quem nao tiver o mod instalado nao vai ver (e vai logar um aviso no console dele).");
		CfgVolume = ((BaseUnityPlugin)this).Config.Bind<float>("4. Voz", "Volume", 1f, new ConfigDescription("Volume da voz do Bing Bong.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), new object[0]));
		CfgPitchMin = ((BaseUnityPlugin)this).Config.Bind<float>("4. Voz", "PitchMin", 0.85f, new ConfigDescription("Pitch mais grave da tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), new object[0]));
		CfgPitchMax = ((BaseUnityPlugin)this).Config.Bind<float>("4. Voz", "PitchMax", 1.35f, new ConfigDescription("Pitch mais agudo da tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), new object[0]));
		CfgSyllableSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("4. Voz", "SyllableSeconds", 0.085f, new ConfigDescription("Duracao de cada silaba. Menor = ele fala mais rapido.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.03f, 0.3f), new object[0]));
		CfgVoiceMode = ((BaseUnityPlugin)this).Config.Bind<VoiceMode>("4. Voz", "VoiceMode", VoiceMode.Ambos, "Babble = tagarelice feita com os sons do proprio Bing Bong (offline e de graca). ElevenLabs = fala de verdade, lendo a mensagem (precisa da ApiKey e gasta creditos); se falhar, sai so a legenda. Ambos = tenta o ElevenLabs e, quando ele falha, o Babble assume.");
		CfgElevenApiKey = ((BaseUnityPlugin)this).Config.Bind<string>("7. ElevenLabs", "ApiKey", "", "Sua chave da API do ElevenLabs. Fica so nesta maquina. NUNCA mande este arquivo para outra pessoa: quem tiver a chave gasta os seus creditos.");
		CfgElevenVoiceId = ((BaseUnityPlugin)this).Config.Bind<string>("7. ElevenLabs", "VoiceId", "", "Id da voz. Deixe vazio para usar a primeira voz da sua conta (o log mostra a lista completa com os ids para voce escolher).");
		CfgElevenModel = ((BaseUnityPlugin)this).Config.Bind<string>("7. ElevenLabs", "ModelId", "eleven_multilingual_v2", "Modelo usado. 'eleven_multilingual_v2' fala portugues bem. 'eleven_turbo_v2_5' responde mais rapido e custa menos.");
		CfgElevenPitch = ((BaseUnityPlugin)this).Config.Bind<float>("7. ElevenLabs", "Pitch", 1.35f, new ConfigDescription("Acelera a voz para soar agudo tipo Bing Bong. 1 = voz normal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2.5f), new object[0]));
		CfgElevenMinGap = ((BaseUnityPlugin)this).Config.Bind<float>("7. ElevenLabs", "MinSecondsBetweenRequests", 5f, new ConfigDescription("Protecao de creditos: tempo minimo entre duas chamadas na API.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 120f), new object[0]));
		CfgElevenTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("7. ElevenLabs", "TimeoutSeconds", 15f, new ConfigDescription("Desiste da chamada depois desse tempo e usa a tagarelice.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 60f), new object[0]));
		CfgSecondsPerChar = ((BaseUnityPlugin)this).Config.Bind<float>("5. Legenda", "SecondsPerChar", 0.075f, new ConfigDescription("Quanto tempo de fala cada caractere vale.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.3f), new object[0]));
		CfgMinSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("5. Legenda", "MinSeconds", 1.8f, new ConfigDescription("Duracao minima da fala.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), new object[0]));
		CfgMaxSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("5. Legenda", "MaxSeconds", 9f, new ConfigDescription("Duracao maxima da fala.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), new object[0]));
		CfgSetupShown = ((BaseUnityPlugin)this).Config.Bind<bool>("1. Twitch", "SetupShown", false, "Controla a janela de configuracao que abre sozinha na primeira vez. Volte para false se quiser que ela apareca de novo.");
		CfgVerbose = ((BaseUnityPlugin)this).Config.Bind<bool>("6. Diagnostico", "Verbose", true, "Escreve no log cada mensagem do chat que chega, e o motivo quando o Bing Bong solta uma fala normal em vez da mensagem.");
		CfgTestOnConnect = ((BaseUnityPlugin)this).Config.Bind<bool>("6. Diagnostico", "TestMessageOnConnect", true, "Ao conectar no chat, ja deixa uma mensagem de teste engatilhada, pra voce conseguir testar o mod sem esperar alguem escrever.");
		TwitchIrc.Verbose = CfgVerbose.Value;
		CfgVerbose.SettingChanged += delegate
		{
			TwitchIrc.Verbose = CfgVerbose.Value;
		};
		CfgChannel.SettingChanged += delegate
		{
			Reconnect();
		};
		CfgEnabled.SettingChanged += delegate
		{
			Reconnect();
		};
	}

	private void Connect()
	{
		if (string.IsNullOrEmpty(CfgChannel.Value))
		{
			Log.LogWarning((object)"Nenhum canal escolhido. APERTE F10 dentro do jogo e escreva o nome do seu canal da Twitch.");
			return;
		}
		_irc = new TwitchIrc(CfgChannel.Value, delegate(string m)
		{
			Log.LogInfo((object)m);
		}, delegate(string m)
		{
			Log.LogWarning((object)m);
		});
		_irc.Start();
	}

	private void Reconnect()
	{
		if (_irc != null)
		{
			_irc.Stop();
			_irc = null;
		}
		if (CfgEnabled.Value)
		{
			Connect();
		}
	}

	private void Update()
	{
		if (_irc == null)
		{
			return;
		}
		if (_irc.Connected && !_announcedConnection)
		{
			_announcedConnection = true;
			if (CfgTestOnConnect.Value && LastMessage == null)
			{
				ChatMessage chatMessage = new ChatMessage();
				chatMessage.Author = "BingBongTwitch";
				chatMessage.Text = "mod conectado! use o Bing Bong pra me ouvir";
				LastMessage = chatMessage;
				Log.LogInfo((object)"Mensagem de teste engatilhada. Pegue o Bing Bong e use.");
			}
		}
		ChatMessage message;
		while (_irc.TryDequeue(out message))
		{
			ChatMessage chatMessage2 = Filter(message);
			if (chatMessage2 == null)
			{
				if (CfgVerbose.Value)
				{
					Log.LogInfo((object)("Mensagem descartada pelos filtros: " + message.Author + ": " + message.Text));
				}
				continue;
			}
			LastMessage = chatMessage2;
			if (CfgVerbose.Value)
			{
				Log.LogInfo((object)("Proxima fala do Bing Bong -> " + chatMessage2.Author + ": " + chatMessage2.Text));
			}
			if (CfgMode.Value == SpeakMode.Auto || CfgMode.Value == SpeakMode.Both)
			{
				TryAutoSpeak(chatMessage2);
			}
		}
	}

	private static ChatMessage Filter(ChatMessage msg)
	{
		if (msg == null || string.IsNullOrEmpty(msg.Text))
		{
			return null;
		}
		if (CfgOnlySubs.Value && !msg.IsSubscriber && !msg.IsModerator && !msg.IsVip && !msg.IsBroadcaster)
		{
			return null;
		}
		string value = CfgCommandPrefix.Value;
		if (!string.IsNullOrEmpty(value))
		{
			value = value.Trim();
			if (!msg.Text.StartsWith(value, StringComparison.OrdinalIgnoreCase))
			{
				return null;
			}
			msg.Text = msg.Text.Substring(value.Length).Trim();
			if (msg.Text.Length == 0)
			{
				return null;
			}
		}
		return msg;
	}

	private void TryAutoSpeak(ChatMessage msg)
	{
		if (Time.time < _nextAutoSpeak)
		{
			return;
		}
		BingBongVoice bingBongVoice = FindSpeaker();
		if ((Object)(object)bingBongVoice == (Object)null)
		{
			if (CfgVerbose.Value)
			{
				Log.LogInfo((object)("Modo Auto: ninguem esta segurando o Bing Bong (" + BingBongVoice.Active.Count + " encontrados na cena)."));
			}
		}
		else
		{
			_nextAutoSpeak = Time.time + CfgAutoCooldown.Value;
			bingBongVoice.Say(msg.Author, msg.Text);
		}
	}

	public static BingBongVoice FindSpeaker()
	{
		for (int i = 0; i < BingBongVoice.Active.Count; i++)
		{
			BingBongVoice bingBongVoice = BingBongVoice.Active[i];
			if ((Object)(object)bingBongVoice != (Object)null && bingBongVoice.HeldByLocalPlayer())
			{
				return bingBongVoice;
			}
		}
		return null;
	}

	private void OnDestroy()
	{
		if (_irc != null)
		{
			_irc.Stop();
		}
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
		Babble.Reset();
	}
}
public class ChatMessage
{
	public string Author;

	public string Text;

	public bool IsBroadcaster;

	public bool IsModerator;

	public bool IsSubscriber;

	public bool IsVip;
}
public class TwitchIrc
{
	private const string Host = "irc.chat.twitch.tv";

	private const int Port = 6667;

	private readonly string _channel;

	private readonly ConcurrentQueue<ChatMessage> _queue = new ConcurrentQueue<ChatMessage>();

	private readonly Action<string> _log;

	private readonly Action<string> _logError;

	private Thread _thread;

	private volatile bool _running;

	private TcpClient _client;

	public volatile bool Connected;

	public static bool Verbose;

	public string Channel => _channel;

	public TwitchIrc(string channel, Action<string> log, Action<string> logError)
	{
		_channel = ((channel == null) ? "" : channel).Trim().TrimStart('#').ToLowerInvariant();
		_log = log;
		_logError = logError;
	}

	public bool TryDequeue(out ChatMessage message)
	{
		return _queue.TryDequeue(out message);
	}

	public void Start()
	{
		if (!_running)
		{
			if (string.IsNullOrEmpty(_channel))
			{
				_logError("Nenhum canal configurado. Preencha Channel no arquivo de config.");
				return;
			}
			_running = true;
			_thread = new Thread(Loop);
			_thread.IsBackground = true;
			_thread.Name = "BingBongTwitchIRC";
			_thread.Start();
		}
	}

	public void Stop()
	{
		_running = false;
		Connected = false;
		try
		{
			if (_client != null)
			{
				_client.Close();
			}
		}
		catch
		{
		}
		_client = null;
	}

	private void Loop()
	{
		int num = 2;
		while (_running)
		{
			try
			{
				RunSession();
				num = 2;
			}
			catch (Exception ex)
			{
				if (_running)
				{
					_logError("Conexao com a Twitch caiu: " + ex.Message);
				}
			}
			Connected = false;
			if (!_running)
			{
				break;
			}
			for (int i = 0; i < num * 10; i++)
			{
				if (!_running)
				{
					break;
				}
				Thread.Sleep(100);
			}
			num = Math.Min(num * 2, 60);
		}
	}

	private void RunSession()
	{
		using TcpClient tcpClient = new TcpClient();
		_client = tcpClient;
		tcpClient.Connect("irc.chat.twitch.tv", 6667);
		using NetworkStream stream = tcpClient.GetStream();
		using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
		using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		streamWriter.NewLine = "\r\n";
		streamWriter.AutoFlush = true;
		streamWriter.WriteLine("PASS SCHMOOPIIE");
		streamWriter.WriteLine("NICK justinfan" + new Random().Next(10000, 99999));
		streamWriter.WriteLine("CAP REQ :twitch.tv/tags");
		streamWriter.WriteLine("JOIN #" + _channel);
		_log("Conectando ao chat de #" + _channel + " (anonimo, sem token)...");
		string text;
		while (_running && (text = streamReader.ReadLine()) != null)
		{
			if (text.StartsWith("PING", StringComparison.Ordinal))
			{
				streamWriter.WriteLine("PONG :tmi.twitch.tv");
				continue;
			}
			if (!Connected && text.IndexOf(" 001 ", StringComparison.Ordinal) >= 0)
			{
				Connected = true;
				_log("Conectado ao chat de #" + _channel + ".");
			}
			ChatMessage chatMessage = Parse(text);
			if (chatMessage != null)
			{
				_queue.Enqueue(chatMessage);
				if (Verbose)
				{
					_log("IRC recebeu de " + chatMessage.Author + ": " + chatMessage.Text);
				}
			}
			else if (Verbose && text.IndexOf("PRIVMSG", StringComparison.Ordinal) >= 0)
			{
				_log("IRC nao entendeu esta linha: " + text);
			}
		}
	}

	private static ChatMessage Parse(string line)
	{
		string text = null;
		string text2 = line;
		if (text2.Length > 0 && text2[0] == '@')
		{
			int num = text2.IndexOf(' ');
			if (num < 0)
			{
				return null;
			}
			text = text2.Substring(1, num - 1);
			text2 = text2.Substring(num + 1);
		}
		string author = null;
		if (text2.Length > 0 && text2[0] == ':')
		{
			int num2 = text2.IndexOf(' ');
			if (num2 < 0)
			{
				return null;
			}
			string text3 = text2.Substring(1, num2 - 1);
			int num3 = text3.IndexOf('!');
			author = ((num3 > 0) ? text3.Substring(0, num3) : text3);
			text2 = text2.Substring(num2 + 1);
		}
		if (!text2.StartsWith("PRIVMSG ", StringComparison.Ordinal))
		{
			return null;
		}
		int num4 = text2.IndexOf(" :", StringComparison.Ordinal);
		if (num4 < 0)
		{
			return null;
		}
		string text4 = text2.Substring(num4 + 2);
		ChatMessage chatMessage = new ChatMessage();
		chatMessage.Author = author;
		chatMessage.Text = text4;
		if (text != null)
		{
			string tag = GetTag(text, "display-name");
			if (!string.IsNullOrEmpty(tag))
			{
				chatMessage.Author = tag;
			}
			string text5 = GetTag(text, "badges");
			if (text5 == null)
			{
				text5 = "";
			}
			chatMessage.IsBroadcaster = text5.IndexOf("broadcaster/", StringComparison.Ordinal) >= 0;
			chatMessage.IsVip = text5.IndexOf("vip/", StringComparison.Ordinal) >= 0;
			chatMessage.IsModerator = GetTag(text, "mod") == "1" || chatMessage.IsBroadcaster;
			chatMessage.IsSubscriber = GetTag(text, "subscriber") == "1";
		}
		if (string.IsNullOrEmpty(chatMessage.Author))
		{
			chatMessage.Author = "chat";
		}
		return chatMessage;
	}

	private static string GetTag(string tags, string key)
	{
		string[] array = tags.Split(';');
		for (int i = 0; i < array.Length; i++)
		{
			int num = array[i].IndexOf('=');
			if (num > 0 && string.Equals(array[i].Substring(0, num), key, StringComparison.Ordinal))
			{
				return array[i].Substring(num + 1);
			}
		}
		return null;
	}
}