Decompiled source of Narrator v1.0.2

Narrator/Narrator.dll

Decompiled 9 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("Narrator")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("My first plugin")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Narrator")]
[assembly: AssemblyTitle("Narrator")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Narrator
{
	internal static class AssetLoader
	{
		private static string _assetFolder = "assets";

		public static Sprite LoadEmbeddedSprite(string resourceName)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			resourceName = "Narrator." + _assetFolder + "." + resourceName;
			Logger.Log("Finding the asset at " + resourceName, (string)null, ConsoleColor.White);
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			using (Stream stream = executingAssembly.GetManifestResourceStream(resourceName))
			{
				if (stream == null)
				{
					return null;
				}
				byte[] array = new byte[stream.Length];
				stream.Read(array, 0, array.Length);
				Texture2D val = new Texture2D(2, 2);
				if (ImageConversion.LoadImage(val, array))
				{
					return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
				}
			}
			Logger.Log("Asset could not be found", (string)null, ConsoleColor.White);
			return null;
		}
	}
	public class Historian
	{
		private enum EventType
		{
			Death,
			PostMortem,
			Victory
		}

		public static Dictionary<int, Animals> _playerNumberAnimal = new Dictionary<int, Animals>();

		public static List<string> _events = new List<string>();

		public static List<string> _deathList = new List<string>();

		public static List<string> _victoryList = new List<string>();

		private static List<string> _worldAlternatives = new List<string>
		{
			"stupidity", "magic", "the call of the void", "a holy hand grenade", "superheated air", "entropy", "that one trap", "ghosts", "the intro to Skyrim", "the world itself",
			"ascending to heaven", "over-exposure to sunlight"
		};

		private static int _chosenWorldAlternative = Random.Range(0, _worldAlternatives.Count);

		public static void ClearEvents()
		{
			_events.Clear();
			_deathList.Clear();
			_victoryList.Clear();
		}

		public static string GetAndResetEvents(bool randomiseWorldString = true)
		{
			string text = "";
			text += GetRandomEvents();
			_events.Clear();
			_deathList.Clear();
			_victoryList.Clear();
			if (randomiseWorldString)
			{
				_chosenWorldAlternative = Random.Range(0, _worldAlternatives.Count);
			}
			return text;
		}

		private static string GetRandomEvents()
		{
			int num = Random.Range(1, 2);
			string text = "";
			Logger.Log($"{num} events will be narrated", (string)null, ConsoleColor.White);
			for (int i = 0; i < num; i++)
			{
				switch ((EventType)Random.Range(0, 1))
				{
				case EventType.Death:
					text += GetRandomStringFromList(_deathList, remove: true);
					break;
				case EventType.Victory:
					text += GetRandomStringFromList(_victoryList, remove: true);
					break;
				default:
					Logger.Log("Event list does not exist", (string)null, ConsoleColor.White);
					break;
				}
			}
			return text;
		}

		private static string GetRandomStringFromList(List<string> list, bool remove = false)
		{
			if (list.Count <= 0)
			{
				Logger.Log("List is empty, no narration will take place.", (string)null, ConsoleColor.White);
				return "";
			}
			int index = Random.Range(0, list.Count);
			string text = list[index];
			if (remove)
			{
				list.RemoveAt(index);
			}
			Logger.Log("Narrating '" + text + "'", (string)null, ConsoleColor.White);
			return text;
		}

		public static void AddDeath(Character deadCharacter, string cause, int causedByPlayerNumber)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			string text = ((cause == "World") ? _worldAlternatives[_chosenWorldAlternative] : cause);
			string text2 = ((object)(Animals)(ref deadCharacter.CharacterSprite)).ToString().ToLower() + " died by " + text;
			if (causedByPlayerNumber != 0 && _playerNumberAnimal.TryGetValue(causedByPlayerNumber, out var value))
			{
				text2 = text2 + ", caused by " + ((object)(Animals)(ref value)).ToString().ToLower();
			}
			_deathList.Add(text2);
			if (_playerNumberAnimal.ContainsKey(deadCharacter.PlayerNumber))
			{
				_playerNumberAnimal.Remove(deadCharacter.PlayerNumber);
			}
			_playerNumberAnimal.Add(deadCharacter.PlayerNumber, deadCharacter.CharacterSprite);
			Logger.Log($"{deadCharacter.CharacterSprite} was added to the list", (string)null, ConsoleColor.White);
		}

		public static void PointBlockGained(PointBlock block)
		{
			//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)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: 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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			string text = "";
			pointBlockType type = block.type;
			if (!_playerNumberAnimal.TryGetValue(block.playerNumber, out var _))
			{
				return;
			}
			pointBlockType val = type;
			pointBlockType val2 = val;
			Animals val3;
			if ((int)val2 != 0)
			{
				if ((int)val2 == 1)
				{
					string text2 = text;
					val3 = _playerNumberAnimal[block.playerNumber];
					text = text2 + ((object)(Animals)(ref val3)).ToString().ToLower() + " got to the finish line after dying.";
					_victoryList.Add(text);
				}
			}
			else
			{
				string text3 = text;
				val3 = _playerNumberAnimal[block.playerNumber];
				text = text3 + ((object)(Animals)(ref val3)).ToString().ToLower() + " got to the finish line.";
				_victoryList.Add(text);
			}
			_events.Add(text);
		}
	}
	[HarmonyPatch(typeof(Character), "setupDeath")]
	public class DeathTypeTracker
	{
		[HarmonyPostfix]
		public static void PostFix(Character __instance, string cause, int causedByPlayerNumber)
		{
			Historian.AddDeath(__instance, cause, causedByPlayerNumber);
		}
	}
	[HarmonyPatch(typeof(ScoreKeeper), "AwardPoint")]
	public class PostMortemHistorian
	{
		[HarmonyPostfix]
		public static void PostFix(PointBlock pointBlock)
		{
			Historian.PointBlockGained(pointBlock);
		}
	}
	[HarmonyPatch(typeof(VersusControl), "RpcSetWinner")]
	public class VersusControlWinnerPatch
	{
		[HarmonyPostfix]
		public static void PostFix(GameObject winner)
		{
			Historian.ClearEvents();
		}
	}
	public class Narrator : MonoBehaviour
	{
		private struct NarratedText
		{
			public string text;

			public AudioClip clip;
		}

		[Serializable]
		public class TextToSpeechRequest
		{
			public string text;

			public string model_id;

			public VoiceSettings voice_settings;
		}

		[Serializable]
		public class VoiceSettings
		{
			public int stability;

			public int similarity_boost;

			public float style;

			public bool use_speaker_boost;
		}

		private class ChatGPTResponse
		{
			public Choice[] Choices { get; set; }
		}

		private class Choice
		{
			public Message Message { get; set; }
		}

		private class Message
		{
			public string Content { get; set; }
		}

		private static Narrator _instance;

		private AudioSource _audioSource = null;

		private string _openAIApiKey;

		private const string _openAIBaseUrl = "https://api.openai.com/v1/chat/completions";

		private string _defaultSystemPrompt = "You are a narrator for a game of Ultimate Chicken Horse.You give the players a task each round, which they must strive to complete.This task should be simple, short, straightforward and achievable. But most importantly, it must be funny and spark joy.When creating a task, keep these rules in mind:1. Be short and concise.2. Keep it achievable.3. Avoid requiring specific blocks or items.4. Encourage player interaction and collaboration.5. Use nonsensical tasks rarely.6. Do not wish the players good luck.7. Do not mention high fives or dancing floors.Always address the players as 'you', as if you're speaking to them directly.Most importantly, keep your responses extremely short and to the point, within 20 words.";

		private string _defaultRoundEventsNarrationPrompt = "In a game of Ultimate Chicken Horse, a round just ended.We need you to creatively describe the events of the previous rounds. You will be given the records of what happened, and are free to interpret why and how they did.Be dramatic and creative, but most extremely short and to the point.Your sentences should be short and funny. Make sure to mention what happened and who was involvedWhen multiple people are involvedin the same event, mention them all in the same sentence.Do not mention 'Ultimate Chicken Horse'";

		private string _elevenLabsApiKey;

		private string _apiUrl = "https://api.elevenlabs.io";

		private string _voiceId = "pNInz6obpgDQGcFmaJgB";

		public int LatencyOptimization = 0;

		private List<NarratedText> _narratedTextList = new List<NarratedText>();

		private float _clipDelay = 10f;

		private float _defaultDelay = 3f;

		private string _audioFolder = "GeneratedAudio";

		private string _fileName = "GeneratedAudio.mp3";

		private bool _keepGeneratedFiles = true;

		public bool _isVoiceOn = true;

		private Historian _deathTracker = new Historian();

		private Transcriber _transcriber;

		public static Narrator Instance => _instance;

		public void AddNarratedText(string text, AudioClip clip)
		{
			NarratedText item = default(NarratedText);
			item.text = text;
			item.clip = clip;
			_narratedTextList.Add(item);
		}

		private void Awake()
		{
			if ((Object)(object)_instance != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
				return;
			}
			string path = Path.Combine(Application.dataPath, _audioFolder);
			DirectoryInfo directoryInfo = Directory.CreateDirectory(path);
			if (!directoryInfo.Exists)
			{
				Logger.Log("Created a folder to hold generated audio", (string)null, ConsoleColor.White);
			}
			else
			{
				Logger.Log("Audio folder exists", (string)null, ConsoleColor.White);
			}
			_instance = this;
			_audioSource = ((Component)this).gameObject.AddComponent<AudioSource>();
			((Component)this).gameObject.AddComponent<AudioListener>();
			_transcriber = ((Component)this).gameObject.AddComponent<Transcriber>();
			LoadConfigs();
			Logger.Log("Narrator created", (string)null, ConsoleColor.White);
		}

		private void GetAllResources()
		{
			Object[] array = Resources.FindObjectsOfTypeAll(typeof(Object));
			Object[] array2 = array;
			foreach (Object val in array2)
			{
				Logger.Log(val.name, (string)null, ConsoleColor.White);
			}
		}

		private void Application_quitting()
		{
			if (!_keepGeneratedFiles)
			{
				try
				{
					Logger.Log("Removing generated files", (string)null, ConsoleColor.White);
					Directory.Delete(Path.Combine(Application.dataPath, _audioFolder), recursive: true);
				}
				catch (Exception ex)
				{
					Logger.Log("Failed to delete generated files: " + ex.Message, (string)null, ConsoleColor.White);
				}
			}
		}

		private void OnEnable()
		{
			Application.quitting += Application_quitting;
		}

		private void OnDisable()
		{
			Application.quitting -= Application_quitting;
		}

		private void LoadConfigs()
		{
			Plugin instance = Plugin.Instance;
			_elevenLabsApiKey = instance.ElevenLabsAPIKey;
			_voiceId = instance.VoiceID;
			_isVoiceOn = instance.UseTTS;
			_openAIApiKey = instance.OpenAIAPIKey;
			_keepGeneratedFiles = instance.KeepGeneratedFiles;
			if (instance.TaskPrompt != "")
			{
				_defaultSystemPrompt = "In a game of Ultimate Chicken Horse, you are a narrator. Remember to keep your responses extremely short. " + instance.TaskPrompt;
			}
			if (instance.RoundNarrationPrompt != "")
			{
				_defaultRoundEventsNarrationPrompt = "In a game of Ultimate Chicken Horse, you are a narrator. Remember to keep your responses extremely short. " + instance.RoundNarrationPrompt;
			}
		}

		private void Update()
		{
			if (_narratedTextList.Count != 0 && _clipDelay <= 0f)
			{
				PlayNextClipAndSetText();
			}
			else if (_clipDelay > 0f)
			{
				_clipDelay -= Time.deltaTime;
			}
			if (Input.GetKeyDown((KeyCode)105))
			{
				GenerateRandomTask();
			}
			if (Input.GetKeyDown((KeyCode)108))
			{
				Logger.Log("Generating audio", (string)null, ConsoleColor.White);
				GenerateAudio("Testing testing");
			}
			if (Input.GetKeyDown((KeyCode)112))
			{
				Logger.Log("Removing generated files", (string)null, ConsoleColor.White);
				Directory.Delete(Path.Combine(Application.dataPath, _audioFolder), recursive: true);
			}
		}

		private void PlayNextClipAndSetText()
		{
			Logger.Log("Playing clip", (string)null, ConsoleColor.White);
			NarratedText narratedText = _narratedTextList[0];
			float num = 7f;
			if ((Object)(object)narratedText.clip != (Object)null)
			{
				num = narratedText.clip.length;
				_audioSource.PlayOneShot(narratedText.clip);
			}
			_clipDelay = num + _defaultDelay;
			if (narratedText.text != "" && narratedText.text != null && Object.op_Implicit((Object)(object)_transcriber))
			{
				_transcriber.SetText(narratedText.text);
				((MonoBehaviour)_transcriber).CancelInvoke();
				_transcriber.DisableText(num + 2f);
			}
			_narratedTextList.RemoveAt(0);
		}

		private async Task<string> PromptToText(string prompt, string systemPrompt = "")
		{
			return await AskChatGPT(prompt, systemPrompt);
		}

		public async void GenerateRandomTask(string prompt = "")
		{
			string task = await PromptToText((prompt == "") ? "What is our task?" : prompt);
			if (_isVoiceOn)
			{
				await GenerateAudio(task);
			}
			else
			{
				AddNarratedText(task, null);
			}
		}

		public async Task<bool> GenerateRandomTask(int chancePercentage, string prompt = "")
		{
			Logger.Log("Checking if a task will be assigned", (string)null, ConsoleColor.White);
			if (Random.Range(0, 100) < chancePercentage)
			{
				Logger.Log("Task generating", (string)null, ConsoleColor.White);
				GenerateRandomTask(prompt);
				return true;
			}
			Logger.Log("No task generated", (string)null, ConsoleColor.White);
			return false;
		}

		public async void DescribeRoundEvents(string prompt = "")
		{
			string deaths = Historian.GetAndResetEvents();
			if (!Util_String.NullOrEmpty(deaths))
			{
				string task = await PromptToText(deaths, (prompt == "") ? _defaultRoundEventsNarrationPrompt : prompt);
				if (_isVoiceOn)
				{
					await GenerateAudio(task);
				}
				else
				{
					AddNarratedText(task, null);
				}
			}
		}

		private void PlayAudioOneShot(AudioClip clip)
		{
			_audioSource.PlayOneShot(clip);
		}

		public async Task<bool> GenerateAudio(string text, bool useElevenLabs = true)
		{
			if (!Object.op_Implicit((Object)(object)_audioSource))
			{
				return false;
			}
			if (useElevenLabs)
			{
				Logger.Log("Generating via ElevenLabs", (string)null, ConsoleColor.White);
				((MonoBehaviour)this).StartCoroutine(DoRequest(text));
			}
			return true;
		}

		private IEnumerator DoRequest(string message)
		{
			if (!((Object)(object)_audioSource != (Object)null))
			{
				yield break;
			}
			string fileName = SetupFileName(message);
			string filePath = Path.Combine(path2: _audioFolder + "\\" + fileName, path1: Application.dataPath);
			if (!File.Exists(filePath))
			{
				Logger.Log("Requesting voice", (string)null, ConsoleColor.White);
				TextToSpeechRequest postData = new TextToSpeechRequest
				{
					text = message,
					model_id = "eleven_monolingual_v1"
				};
				VoiceSettings voiceSetting = new VoiceSettings
				{
					stability = 0,
					similarity_boost = 0,
					style = 0.5f,
					use_speaker_boost = true
				};
				postData.voice_settings = voiceSetting;
				string json = JsonConvert.SerializeObject((object)postData);
				UploadHandlerRaw uH = new UploadHandlerRaw(Encoding.ASCII.GetBytes(json));
				string url = $"{_apiUrl}/v1/text-to-speech/{_voiceId}?optimize_streaming_latency={LatencyOptimization}";
				UnityWebRequest request = UnityWebRequest.Post(url, json);
				DownloadHandlerAudioClip downloadHandler = new DownloadHandlerAudioClip(url, (AudioType)13);
				request.uploadHandler = (UploadHandler)(object)uH;
				request.downloadHandler = (DownloadHandler)(object)downloadHandler;
				request.SetRequestHeader("Content-Type", "application/json");
				request.SetRequestHeader("xi-api-key", _elevenLabsApiKey);
				request.SetRequestHeader("Accept", "audio/mpeg");
				yield return request.SendWebRequest();
				Logger.Log("Web request received", (string)null, ConsoleColor.DarkRed);
				if ((int)request.result != 1)
				{
					Debug.LogError((object)("Error downloading audio: " + request.error));
					yield break;
				}
				_ = downloadHandler.audioClip;
				byte[] audioData = ((DownloadHandler)downloadHandler).data;
				SaveAudioToFile(audioData, fileName);
				request.Dispose();
			}
			((MonoBehaviour)this).StartCoroutine(LoadAudioClip(fileName, message));
		}

		private void SaveAudioToFile(byte[] audioData, string filePath)
		{
			try
			{
				string path = _audioFolder + "\\" + filePath;
				filePath = Path.Combine(Application.dataPath, path);
				string text = Path.Combine(Application.dataPath, filePath);
				File.WriteAllBytes(text, audioData);
				Debug.Log((object)("Audio saved to: " + text));
			}
			catch (Exception ex)
			{
				Debug.LogError((object)("Error saving audio: " + ex.Message));
			}
		}

		private IEnumerator LoadAudioClip(string name = "", string text = "")
		{
			if (name == "")
			{
				name = _fileName;
			}
			string filePath = Path.Combine(path2: _audioFolder + "\\" + name, path1: Application.dataPath);
			Logger.Log("Getting file at path " + filePath, (string)null, ConsoleColor.White);
			UnityWebRequest web = UnityWebRequestMultimedia.GetAudioClip(filePath, (AudioType)13);
			try
			{
				yield return web.SendWebRequest();
				if ((int)web.result == 1)
				{
					AudioClip clip = DownloadHandlerAudioClip.GetContent(web);
					if ((Object)(object)clip != (Object)null)
					{
						Logger.Log("Loaded audio clip", (string)null, ConsoleColor.White);
						((Object)clip).name = name;
						AddNarratedText(text, clip);
					}
					else
					{
						Logger.Log("Audio clip is null", (string)null, ConsoleColor.White);
					}
				}
				else
				{
					Logger.Log("Could not find file: " + web.error, (string)null, ConsoleColor.White);
				}
			}
			finally
			{
				((IDisposable)web)?.Dispose();
			}
		}

		public static string RemoveWhiteSpace(string message)
		{
			return new string((from c in message.ToCharArray()
				where !char.IsWhiteSpace(c)
				select c).ToArray());
		}

		public static string ShortenSentence(string message, int maxCharacters = 50)
		{
			return message.Substring(0, Math.Min(message.Length, maxCharacters));
		}

		public static string SetupFileName(string message)
		{
			return ShortenSentence(RemoveWhiteSpace(message)) + ".mp3";
		}

		public async Task<string> AskChatGPT(string prompt, string systemPrompt = "")
		{
			using HttpClient httpClient = new HttpClient();
			httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + _openAIApiKey);
			var requestBody = new
			{
				model = "gpt-3.5-turbo",
				messages = new[]
				{
					new
					{
						role = "system",
						content = ((systemPrompt == "") ? _defaultSystemPrompt : systemPrompt)
					},
					new
					{
						role = "user",
						content = prompt
					}
				}
			};
			string json = JsonConvert.SerializeObject((object)requestBody);
			StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
			HttpResponseMessage response = await httpClient.PostAsync("https://api.openai.com/v1/chat/completions", content);
			if (response.IsSuccessStatusCode)
			{
				ChatGPTResponse result = JsonConvert.DeserializeObject<ChatGPTResponse>(await response.Content.ReadAsStringAsync());
				Logger.Log(((result != null) ? result.Choices[0].Message.Content : null) ?? "No response", (string)null, ConsoleColor.White);
				return ((result != null) ? result.Choices[0].Message.Content : null) ?? "No response";
			}
			return $"Error: {response.StatusCode}";
		}
	}
	[HarmonyPatch(typeof(VersusControl), "ToPlaceMode")]
	public class VersusControlPlaceModePatch
	{
		[HarmonyPostfix]
		public static void PostFix(VersusControl __instance)
		{
			Narrator instance = Narrator.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.DescribeRoundEvents();
				instance.GenerateRandomTask(50);
			}
		}
	}
	[BepInPlugin("Narrator", "Narrator", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private static Plugin _instance;

		private ConfigEntry<bool> _cfgUseTTS;

		private ConfigEntry<bool> _cfgKeepGeneratedFiles;

		private ConfigEntry<string> _cfgTaskPrompt;

		private ConfigEntry<string> _cfgRoundNarrationPrompt;

		private ConfigEntry<string> _cfgElevenLabsAPIKey;

		private ConfigEntry<string> _cfgElevenLabsVoiceID;

		private ConfigEntry<string> _cfgOpenAIAPIKey;

		public static Plugin Instance => _instance;

		public bool UseTTS => _cfgUseTTS.Value;

		public bool KeepGeneratedFiles => _cfgKeepGeneratedFiles.Value;

		public string TaskPrompt => _cfgTaskPrompt.Value;

		public string RoundNarrationPrompt => _cfgRoundNarrationPrompt.Value;

		public string ElevenLabsAPIKey => _cfgElevenLabsAPIKey.Value;

		public string VoiceID => _cfgElevenLabsVoiceID.Value;

		public string OpenAIAPIKey => _cfgOpenAIAPIKey.Value;

		private void Awake()
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			_instance = this;
			SetupConfig();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin Narrator is loaded!");
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Creating Narrator");
			if (OpenAIAPIKey != "change_this_to_your_key")
			{
				GameObject val = new GameObject();
				val.AddComponent<Narrator>();
				((Object)val).name = "_Narrator";
				Object.DontDestroyOnLoad((Object)(object)val);
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"OpenAI API key not set, narrator not created");
			}
			if (ElevenLabsAPIKey == "change_this_to_your_key")
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"ElevenLabs key not set, text-to-speech will not be used");
			}
		}

		private void SetupConfig()
		{
			_cfgUseTTS = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UseTTS", false, "Allows you to enable TTS for the narrator.");
			_cfgKeepGeneratedFiles = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "KeepGeneratedFiles", false, "Keeps the generated files and reuses them when applicable.");
			_cfgTaskPrompt = ((BaseUnityPlugin)this).Config.Bind<string>("General", "TaskPrompt", "", "The prompt used to request tasks at the start of a round. Leave empty to use the built-in standard.");
			_cfgRoundNarrationPrompt = ((BaseUnityPlugin)this).Config.Bind<string>("General", "DeathPrompt", "", "The prompt used to request narration when a round ends. Leave empty to use the built-in standard.");
			_cfgElevenLabsAPIKey = ((BaseUnityPlugin)this).Config.Bind<string>("API", "ElevenLabsAPIKey", "change_this_to_your_key", "Your API key to access TTS, without this, no voices will be generated.\nCan be found at https://elevenlabs.io/, under Profile Settings.");
			_cfgElevenLabsVoiceID = ((BaseUnityPlugin)this).Config.Bind<string>("API", "ElevenLabsVoiceID", "pNInz6obpgDQGcFmaJgB", "The ID of the voice you want to use for narration.\nCan be found at https://elevenlabs.io/docs/voicelab/pre-made-voices or any custom voices you add to your account.\nIf changed, make sure the voice is actually available for your API key.");
			_cfgOpenAIAPIKey = ((BaseUnityPlugin)this).Config.Bind<string>("API", "OpenAIAPIKey", "change_this_to_your_key", "Your API key to access ChatGPT, without this, there will be no narration.\nCan be found at https://platform.openai.com/api-keys.");
		}
	}
	public class Transcriber : MonoBehaviour
	{
		private Text _text;

		private Camera _attachedCamera;

		private GameObject _textHolderObject;

		private float _offset = 0.1f;

		public Text Text => _text;

		private void Awake()
		{
			_attachedCamera = Camera.main;
		}

		private void Update()
		{
			if ((Object.op_Implicit((Object)(object)_attachedCamera) && ((Behaviour)_attachedCamera).isActiveAndEnabled) || !Object.op_Implicit((Object)(object)LobbyManager.instance))
			{
				return;
			}
			Camera val = LobbyManager.instance.CurrentGameController?.UICamera;
			if ((Object)(object)val != (Object)null)
			{
				if (Object.op_Implicit((Object)(object)_textHolderObject))
				{
					Object.Destroy((Object)(object)_textHolderObject);
				}
				_attachedCamera = val;
				SpawnText();
			}
		}

		private void SpawnText()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Expected O, but got Unknown
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			_textHolderObject = new GameObject("_TranscriberTextHolder");
			Canvas val = _textHolderObject.AddComponent<Canvas>();
			val.renderMode = (RenderMode)0;
			val.sortingOrder = 999;
			CanvasScaler val2 = _textHolderObject.AddComponent<CanvasScaler>();
			val2.uiScaleMode = (ScaleMode)1;
			val2.referenceResolution = new Vector2(2560f, 1440f);
			GameObject val3 = new GameObject("_TranscriberText");
			val3.transform.parent = _textHolderObject.transform;
			_text = val3.AddComponent<Text>();
			_text.text = "Yo wtf this actually worked";
			_text.alignment = (TextAnchor)1;
			RectTransform component = val3.GetComponent<RectTransform>();
			component.sizeDelta = new Vector2(2560f, 1440f);
			((Transform)component).position = new Vector3((float)(Screen.width / 2), (float)(Screen.height / 2), 0f);
			Logger.Log("Loading font", (string)null, ConsoleColor.White);
			Font val4 = null;
			Object[] array = Resources.FindObjectsOfTypeAll(typeof(Font));
			Object[] array2 = array;
			foreach (Object val5 in array2)
			{
				Logger.Log(val5.name, (string)null, ConsoleColor.White);
				Font val6 = (Font)val5;
				if (((Object)val6).name == "KGBlankSpaceSolid_EditedOne")
				{
					Logger.Log("Applied font " + ((Object)val6).name, (string)null, ConsoleColor.White);
					val4 = val6;
					_text.font = val6;
				}
			}
			if ((Object)(object)val4 == (Object)null)
			{
				Logger.Log("Falling back to Arial font", (string)null, ConsoleColor.White);
				_text.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			}
			_text.fontSize = 72;
			((Graphic)_text).color = Color.white;
			val3.AddComponent<Shadow>().effectDistance = new Vector2(5f, -5f);
			((Component)_text).gameObject.SetActive(false);
			_textHolderObject.transform.parent = ((Component)_attachedCamera).transform;
			((Component)_text).transform.localPosition = new Vector3(0f, (float)(-Screen.height) * _offset, 0f);
		}

		public void SetText(string text, float timeUntilDisable = 0f)
		{
			if (!(text == "") && text != null)
			{
				if (Object.op_Implicit((Object)(object)_text))
				{
					((Component)_text).gameObject.SetActive(true);
					_text.text = text;
				}
				if (timeUntilDisable > 0f)
				{
					((MonoBehaviour)this).Invoke("DisableText", timeUntilDisable);
				}
			}
		}

		public void DisableText(float timeUntilDisable)
		{
			((MonoBehaviour)this).Invoke("DisableText", timeUntilDisable);
		}

		public void DisableText()
		{
			((Component)_text).gameObject.SetActive(false);
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "Narrator";

		public const string PLUGIN_NAME = "Narrator";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}