Decompiled source of CipherPeak BingBongTTS v1.0.1

BepInEx/plugins/CipherPeak-BingBongTTS/CipherPeak.BingBongTTS.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CipherPeak.Core.BingBong;
using CipherPeak.Core.Commands;
using CipherPeak.Core.Config;
using CipherPeak.Core.Filtering;
using CipherPeak.Core.Ingestion;
using CipherPeak.Core.Logging;
using CipherPeak.Core.Net;
using CipherPeak.Core.Queueing;
using CipherPeak.Core.Speech;
using CipherPeak.Core.Tts;
using CipherPeak.Core.Twitch;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Zorro.Core;
using Zorro.Settings;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Mrzeapple")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+ff720e6ad4c9eb80aee667592390f4cda551bd07")]
[assembly: AssemblyProduct("CipherPeak.BingBongTTS")]
[assembly: AssemblyTitle("CipherPeak.BingBongTTS")]
[assembly: AssemblyVersion("1.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 CipherPeak.Plugin
{
	internal static class BackpackHotkey
	{
		internal static Func<bool> KeyHeld = () => false;

		internal static void Open()
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Invalid comparison between Unknown and I4
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.player == (Object)null)
			{
				return;
			}
			CharacterData data = localCharacter.data;
			if ((Object)(object)data == (Object)null || !data.fullyConscious || data.dead || data.isClimbing || data.isRopeClimbing || data.usingBackpackWheel)
			{
				return;
			}
			BackpackSlot backpackSlot = localCharacter.player.backpackSlot;
			if (backpackSlot == null || ((ItemSlot)backpackSlot).IsEmpty())
			{
				BingBongSlot.Trace("backpack key ignored: nothing on your back.");
				return;
			}
			if ((int)backpackSlot.backpackType == 4)
			{
				BingBongSlot.Trace("backpack key ignored: a rocketpack has no wheel.");
				return;
			}
			try
			{
				BackpackOnBackVisuals activeBackpackVisuals = localCharacter.refs.backpackHandler.activeBackpackVisuals;
				if (!((Object)(object)activeBackpackVisuals == (Object)null))
				{
					GUIManager instance = GUIManager.instance;
					if (!((Object)(object)instance == (Object)null))
					{
						instance.OpenBackpackWheel(BackpackReference.GetFromEquippedBackpack(localCharacter), activeBackpackVisuals.slotCount, activeBackpackVisuals.backpackType);
					}
				}
			}
			catch (Exception ex)
			{
				if (BingBongSlot.Log != null)
				{
					BingBongSlot.Log.Warn("Could not open the backpack wheel: " + ex.Message);
				}
			}
		}
	}
	[HarmonyPatch(typeof(BackpackWheel), "Update")]
	internal static class BackpackWheelHoldPatch
	{
		private static bool _spoofed;

		private static void Prefix()
		{
			_spoofed = false;
			Character localCharacter = Character.localCharacter;
			if (!((Object)(object)localCharacter == (Object)null) && !((Object)(object)localCharacter.input == (Object)null) && !localCharacter.input.interactIsPressed && BackpackHotkey.KeyHeld())
			{
				localCharacter.input.interactIsPressed = true;
				_spoofed = true;
			}
		}

		private static void Postfix()
		{
			if (_spoofed)
			{
				_spoofed = false;
				Character localCharacter = Character.localCharacter;
				if ((Object)(object)localCharacter != (Object)null && (Object)(object)localCharacter.input != (Object)null)
				{
					localCharacter.input.interactIsPressed = false;
				}
			}
		}
	}
	[HarmonyPatch(typeof(CharacterItems), "DropItemFromSlotRPC")]
	internal static class BingBongDropPatch
	{
		private static readonly MethodInfo UpdateWeight = AccessTools.Method(typeof(CharacterAfflictions), "UpdateWeight", (Type[])null, (Type[])null);

		private static bool Prefix(byte slotID, Vector3 spawnPosition, Character ___character)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			if (!PhotonNetwork.IsMasterClient)
			{
				return true;
			}
			UnityBingBongWorld current = UnityBingBongWorld.Current;
			if (current == null || (Object)(object)___character == (Object)null || (Object)(object)___character.player == (Object)null)
			{
				return true;
			}
			ItemSlot itemSlot = ___character.player.GetItemSlot(slotID);
			if (itemSlot == null || itemSlot.IsEmpty())
			{
				return true;
			}
			if (!BingBongSlot.IsBingBong(itemSlot.prefab))
			{
				return true;
			}
			try
			{
				if (current.SpawnAt(spawnPosition) == 0)
				{
					return true;
				}
				___character.player.EmptySlot(Optionable<byte>.Some(slotID));
				if (UpdateWeight != null)
				{
					UpdateWeight.Invoke(___character.refs.afflictions, null);
				}
				BingBongSlot.Trace("dropped Bing Bong re-spawned as a mod one so it stays in the pool.");
				return false;
			}
			catch (Exception ex)
			{
				if (BingBongSlot.Log != null)
				{
					BingBongSlot.Log.Warn("Could not re-spawn a dropped Bing Bong: " + ex.Message);
				}
				return true;
			}
		}
	}
	internal static class BingBongSlot
	{
		internal const byte SlotId = 249;

		private static readonly ConditionalWeakTable<Player, ItemSlot> Slots = new ConditionalWeakTable<Player, ItemSlot>();

		internal static Func<ModSettings> Settings = () => (ModSettings)null;

		internal static ILog Log;

		internal static Func<string> KeyLabel = () => "";

		internal static bool Enabled => Settings()?.BingBong.DedicatedSlot ?? false;

		internal static ItemSlot For(Player player)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			if (Slots.TryGetValue(player, out var value))
			{
				return value;
			}
			value = new ItemSlot((byte)249, player);
			Slots.Add(player, value);
			return value;
		}

		internal static bool IsBingBong(Item prefab)
		{
			if ((Object)(object)prefab != (Object)null)
			{
				return (Object)(object)((Component)prefab).GetComponentInChildren<Action_AskBingBong>(true) != (Object)null;
			}
			return false;
		}

		internal static void Trace(string message)
		{
			if (Log != null)
			{
				Log.Info("[slot] " + message);
			}
		}

		internal static void ToggleEquip()
		{
			//IL_0091: 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)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.player == (Object)null)
			{
				return;
			}
			if (!Enabled)
			{
				Trace("hotkey ignored: DedicatedSlot is off.");
				return;
			}
			ItemSlot val = For(localCharacter.player);
			if (val == null || val.IsEmpty())
			{
				Trace("hotkey ignored: the Bing Bong slot is empty.");
				return;
			}
			CharacterItems items = localCharacter.refs.items;
			bool flag = items.currentSelectedSlot.IsSome && items.currentSelectedSlot.Value == 249;
			items.EquipSlot(flag ? Optionable<byte>.None : Optionable<byte>.Some((byte)249));
		}
	}
	[HarmonyPatch(typeof(Player), "GetItemSlot")]
	internal static class PlayerGetItemSlotPatch
	{
		private static bool Prefix(Player __instance, byte slotID, ref ItemSlot __result)
		{
			if (slotID != 249)
			{
				return true;
			}
			__result = BingBongSlot.For(__instance);
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "AddItem")]
	internal static class PlayerAddItemPatch
	{
		private static bool Prefix(Player __instance, ushort itemID, ItemInstanceData instanceData, ref ItemSlot slot, ref bool __result)
		{
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			if (!BingBongSlot.Enabled)
			{
				return true;
			}
			if (!PhotonNetwork.IsMasterClient)
			{
				return true;
			}
			Item val = default(Item);
			if (!ItemDatabase.TryGetItem(itemID, ref val))
			{
				return true;
			}
			if (!BingBongSlot.IsBingBong(val))
			{
				return true;
			}
			if (!BingBongSlotSync.HasMod(__instance))
			{
				BingBongSlot.Trace(((Object)__instance).name + " has no mod; leaving their Bing Bong in a normal slot.");
				return true;
			}
			ItemSlot val2 = BingBongSlot.For(__instance);
			if (val2 == null || !val2.IsEmpty())
			{
				BingBongSlot.Trace("Bing Bong picked up by " + ((Object)__instance).name + " but its slot is already full; falling back to a normal slot.");
				return true;
			}
			if (instanceData == null)
			{
				instanceData = new ItemInstanceData(Guid.NewGuid());
				ItemInstanceDataHandler.AddInstanceData(instanceData);
			}
			val2.SetItem(val, instanceData);
			slot = val2;
			__result = true;
			BingBongSlot.Trace("Bing Bong routed to the dedicated slot for " + ((Object)__instance).name + ".");
			BingBongSlotSync.Broadcast(__instance, val2);
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "HasEmptySlot")]
	internal static class PlayerHasEmptySlotPatch
	{
		private static void Postfix(Player __instance, ushort itemID, ref bool __result)
		{
			Item prefab = default(Item);
			if (!__result && BingBongSlot.Enabled && ItemDatabase.TryGetItem(itemID, ref prefab) && BingBongSlot.IsBingBong(prefab))
			{
				ItemSlot val = BingBongSlot.For(__instance);
				if (val != null && val.IsEmpty())
				{
					__result = true;
				}
			}
		}
	}
	[HarmonyPatch(typeof(CharacterItems), "DropAllItems")]
	internal static class DropAllItemsPatch
	{
		private static void Postfix(CharacterItems __instance, Character ___character)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			if (!BingBongSlot.Enabled)
			{
				return;
			}
			if ((Object)(object)___character == (Object)null || !___character.IsLocal || (Object)(object)___character.player == (Object)null)
			{
				return;
			}
			ItemSlot val = BingBongSlot.For(___character.player);
			if (val == null || val.IsEmpty())
			{
				return;
			}
			Vector3 val2 = ___character.Center + Vector3.up * 0.5f;
			try
			{
				((MonoBehaviourPun)__instance).photonView.RPC("DropItemFromSlotRPC", (RpcTarget)0, new object[2]
				{
					(byte)249,
					val2
				});
			}
			catch (Exception ex)
			{
				if (BingBongSlot.Log != null)
				{
					BingBongSlot.Log.Warn("Could not drop the Bing Bong slot: " + ex.Message);
				}
			}
		}
	}
	internal sealed class BingBongSlotSync : MonoBehaviour, IOnEventCallback
	{
		internal const byte EventCode = 178;

		internal const byte PresenceEventCode = 179;

		private const ushort Empty = ushort.MaxValue;

		private const float TickSeconds = 0.5f;

		private const float PresenceSeconds = 5f;

		private static BingBongSlotSync _instance;

		private readonly Dictionary<int, ushort> _lastSent = new Dictionary<int, ushort>();

		private readonly HashSet<int> _modded = new HashSet<int>();

		private ILog _log;

		private float _nextTick;

		private float _nextPresence;

		private int _lastPlayerCount;

		private string _room;

		internal void Initialize(ILog log)
		{
			_log = log;
			_instance = this;
			PhotonNetwork.AddCallbackTarget((object)this);
		}

		private void OnDestroy()
		{
			PhotonNetwork.RemoveCallbackTarget((object)this);
			if ((Object)(object)_instance == (Object)(object)this)
			{
				_instance = null;
			}
		}

		private void Update()
		{
			if (!PhotonNetwork.InRoom)
			{
				_room = null;
				return;
			}
			string text = ((PhotonNetwork.CurrentRoom == null) ? null : PhotonNetwork.CurrentRoom.Name);
			if (text != _room)
			{
				_room = text;
				_modded.Clear();
				_lastSent.Clear();
				_lastPlayerCount = 0;
				_nextPresence = 0f;
			}
			AnnouncePresence();
			if (!BingBongSlot.Enabled || !PhotonNetwork.IsMasterClient || Time.unscaledTime < _nextTick)
			{
				return;
			}
			_nextTick = Time.unscaledTime + 0.5f;
			int num = ((PhotonNetwork.CurrentRoom != null) ? PhotonNetwork.CurrentRoom.PlayerCount : 0);
			if (num > _lastPlayerCount)
			{
				_lastSent.Clear();
			}
			_lastPlayerCount = num;
			List<Character> allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters();
			if (allPlayerCharacters == null)
			{
				return;
			}
			for (int i = 0; i < allPlayerCharacters.Count; i++)
			{
				Character val = allPlayerCharacters[i];
				if ((Object)(object)val == (Object)null || (Object)(object)val.player == (Object)null)
				{
					continue;
				}
				ItemSlot val2 = BingBongSlot.For(val.player);
				if (val2 == null)
				{
					continue;
				}
				int num2 = ViewIdOf(val.player);
				if (num2 != 0)
				{
					ushort num3 = (((Object)(object)val2.prefab == (Object)null) ? ushort.MaxValue : val2.prefab.itemID);
					if (!_lastSent.TryGetValue(num2, out var value) || value != num3)
					{
						_lastSent[num2] = num3;
						Send(num2, val2);
					}
				}
			}
		}

		private void AnnouncePresence()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//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_003b: Expected O, but got Unknown
			if (Time.unscaledTime < _nextPresence)
			{
				return;
			}
			_nextPresence = Time.unscaledTime + 5f;
			try
			{
				PhotonNetwork.RaiseEvent((byte)179, (object)null, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable);
			}
			catch (Exception ex)
			{
				if (_log != null)
				{
					_log.Warn("Could not announce the mod to the lobby: " + ex.Message);
				}
			}
		}

		internal static bool HasMod(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			PhotonView photonView = ((MonoBehaviourPun)player).photonView;
			if ((Object)(object)photonView == (Object)null || photonView.Owner == null)
			{
				return false;
			}
			if (photonView.Owner.IsLocal)
			{
				return true;
			}
			if ((Object)(object)_instance == (Object)null)
			{
				return false;
			}
			return _instance._modded.Contains(photonView.Owner.ActorNumber);
		}

		internal static void Broadcast(Player player, ItemSlot slot)
		{
			if (!((Object)(object)_instance == (Object)null) && !((Object)(object)player == (Object)null) && slot != null && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
			{
				int num = ViewIdOf(player);
				if (num != 0)
				{
					_instance._lastSent[num] = (((Object)(object)slot.prefab == (Object)null) ? ushort.MaxValue : slot.prefab.itemID);
					_instance.Send(num, slot);
				}
			}
		}

		private void Send(int viewId, ItemSlot slot)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: 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)
			//IL_0079: Expected O, but got Unknown
			ushort num = (((Object)(object)slot.prefab == (Object)null) ? ushort.MaxValue : slot.prefab.itemID);
			byte[] array = ((slot.data == null) ? new byte[0] : slot.data.guid.ToByteArray());
			try
			{
				PhotonNetwork.RaiseEvent((byte)178, (object)new object[3]
				{
					viewId,
					(int)num,
					array
				}, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable);
			}
			catch (Exception ex)
			{
				if (_log != null)
				{
					_log.Warn("Bing Bong slot sync failed: " + ex.Message);
				}
			}
		}

		public void OnEvent(EventData photonEvent)
		{
			if (photonEvent.Code == 179)
			{
				if (_modded.Add(photonEvent.Sender))
				{
					BingBongSlot.Trace("player " + photonEvent.Sender + " has the mod.");
				}
			}
			else
			{
				if (photonEvent.Code != 178 || !(photonEvent.CustomData is object[] array) || array.Length < 3)
				{
					return;
				}
				try
				{
					int num = (int)array[0];
					ushort num2 = (ushort)(int)array[1];
					byte[] guidBytes = array[2] as byte[];
					PhotonView val = PhotonView.Find(num);
					if ((Object)(object)val == (Object)null)
					{
						return;
					}
					Player component = ((Component)val).GetComponent<Player>();
					if ((Object)(object)component == (Object)null)
					{
						return;
					}
					ItemSlot val2 = BingBongSlot.For(component);
					if (val2 != null)
					{
						Item val3 = default(Item);
						if (num2 == ushort.MaxValue)
						{
							val2.EmptyOut();
							BingBongSlot.Trace("received: slot cleared for " + ((Object)component).name + ".");
						}
						else if (ItemDatabase.TryGetItem(num2, ref val3))
						{
							val2.SetItem(val3, ResolveData(guidBytes));
							BingBongSlot.Trace("received: " + ((Object)val3).name + " in the slot for " + ((Object)component).name + ".");
						}
					}
				}
				catch (Exception ex)
				{
					if (_log != null)
					{
						_log.Warn("Malformed Bing Bong slot event ignored: " + ex.Message);
					}
				}
			}
		}

		private static ItemInstanceData ResolveData(byte[] guidBytes)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			if (guidBytes == null || guidBytes.Length != 16)
			{
				return new ItemInstanceData(Guid.NewGuid());
			}
			Guid guid = new Guid(guidBytes);
			ItemInstanceData result = default(ItemInstanceData);
			if (ItemInstanceDataHandler.TryGetInstanceData(guid, ref result))
			{
				return result;
			}
			result = new ItemInstanceData(guid);
			ItemInstanceDataHandler.AddInstanceData(result);
			return result;
		}

		private static int ViewIdOf(Player player)
		{
			PhotonView component = ((Component)player).GetComponent<PhotonView>();
			if (!((Object)(object)component == (Object)null))
			{
				return component.ViewID;
			}
			return 0;
		}
	}
	[HarmonyPatch(typeof(GUIManager), "UpdateItems")]
	internal static class BingBongSlotUi
	{
		private static InventoryItemUI _widget;

		private static bool _failed;

		private static Item _lastDrawn;

		private static void Postfix(GUIManager __instance)
		{
			if (_failed)
			{
				return;
			}
			ModSettings val = BingBongSlot.Settings();
			if (val == null)
			{
				return;
			}
			if (!val.BingBong.DedicatedSlot)
			{
				if ((Object)(object)_widget != (Object)null)
				{
					((Component)_widget).gameObject.SetActive(false);
				}
				return;
			}
			Character observedCharacter = Character.observedCharacter;
			if ((Object)(object)observedCharacter == (Object)null || (Object)(object)observedCharacter.player == (Object)null)
			{
				return;
			}
			ItemSlot val2 = BingBongSlot.For(observedCharacter.player);
			if (val2 == null)
			{
				return;
			}
			InventoryItemUI val3 = Widget(__instance, (float)val.BingBong.SlotUiOffsetX);
			if ((Object)(object)val3 == (Object)null)
			{
				return;
			}
			if (val2.IsEmpty())
			{
				if (((Component)val3).gameObject.activeSelf)
				{
					((Component)val3).gameObject.SetActive(false);
				}
				return;
			}
			if (!((Component)val3).gameObject.activeSelf)
			{
				((Component)val3).gameObject.SetActive(true);
			}
			val3.SetItem(val2);
			if ((Object)(object)_lastDrawn != (Object)(object)val2.prefab)
			{
				_lastDrawn = val2.prefab;
				BingBongSlot.Trace("widget drawing '" + val2.GetPrefabName() + "'.");
			}
		}

		private static InventoryItemUI Widget(GUIManager gui, float offsetX)
		{
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_widget != (Object)null)
			{
				return _widget;
			}
			InventoryItemUI val = ((gui.items != null && gui.items.Length != 0 && (Object)(object)gui.items[gui.items.Length - 1] != (Object)null) ? gui.items[gui.items.Length - 1] : gui.temporaryItem);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			try
			{
				GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent);
				((Object)val2).name = "CipherPeak.BingBongSlot";
				_widget = val2.GetComponent<InventoryItemUI>();
				if ((Object)(object)_widget == (Object)null)
				{
					Object.Destroy((Object)(object)val2);
					_failed = true;
					return null;
				}
				_widget.isTemporarySlot = true;
				_widget.isBackpack = false;
				Relabel(val2, (TMP_Text)(object)_widget.nameText);
				Place(val2, ((Component)val).gameObject, offsetX);
				val2.SetActive(false);
				RectTransform component = val2.GetComponent<RectTransform>();
				RectTransform component2 = ((Component)val).GetComponent<RectTransform>();
				object[] obj = new object[6]
				{
					((Object)val).name,
					((Object)(object)((Component)val).transform.parent == (Object)null) ? "<none>" : ((Object)((Component)val).transform.parent).name,
					HasLayoutGroup(((Component)val).transform.parent),
					((Object)(object)component2 == (Object)null) ? "?" : ((object)component2.anchoredPosition/*cast due to .constrained prefix*/).ToString(),
					null,
					null
				};
				object obj2;
				if (!((Object)(object)component2 == (Object)null))
				{
					Rect rect = component2.rect;
					obj2 = ((object)((Rect)(ref rect)).size/*cast due to .constrained prefix*/).ToString();
				}
				else
				{
					obj2 = "?";
				}
				obj[4] = obj2;
				obj[5] = (((Object)(object)component == (Object)null) ? "?" : ((object)component.anchoredPosition/*cast due to .constrained prefix*/).ToString());
				BingBongSlot.Trace(string.Format("widget cloned from '{0}' under '{1}' (layout group: {2}); template at {3} size {4}, widget at {5}", obj));
				return _widget;
			}
			catch (Exception ex)
			{
				_failed = true;
				if (BingBongSlot.Log != null)
				{
					BingBongSlot.Log.Warn("Could not create the Bing Bong slot widget: " + ex.Message);
				}
				return null;
			}
		}

		private static void Place(GameObject clone, GameObject template, float offsetX)
		{
			//IL_0020: 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_0030: 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_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			RectTransform component = clone.GetComponent<RectTransform>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			if (Math.Abs(offsetX) > 0.01f)
			{
				component.anchoredPosition += new Vector2(offsetX, 0f);
			}
			else if (!HasLayoutGroup(template.transform.parent))
			{
				RectTransform component2 = template.GetComponent<RectTransform>();
				float num;
				if (!((Object)(object)component2 == (Object)null))
				{
					Rect rect = component2.rect;
					num = ((Rect)(ref rect)).width;
				}
				else
				{
					num = 90f;
				}
				float num2 = num;
				component.anchoredPosition += new Vector2(0f - num2 * 1.1f, 0f);
			}
		}

		private static void Relabel(GameObject clone, TMP_Text nameText)
		{
			string text = BingBongSlot.KeyLabel();
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			InputIcon[] componentsInChildren = clone.GetComponentsInChildren<InputIcon>(true);
			foreach (InputIcon val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					TMP_Text component = ((Component)val).GetComponent<TMP_Text>();
					Object.DestroyImmediate((Object)(object)val);
					if (!((Object)(object)component == (Object)null) && !((Object)(object)component == (Object)(object)nameText))
					{
						component.text = text;
						((Behaviour)component).enabled = true;
						BingBongSlot.Trace("relabelled the slot key icon to '" + text + "'.");
					}
				}
			}
		}

		private static bool HasLayoutGroup(Transform parent)
		{
			if ((Object)(object)parent != (Object)null)
			{
				return (Object)(object)((Component)parent).GetComponent<LayoutGroup>() != (Object)null;
			}
			return false;
		}

		internal static void Reset()
		{
			if ((Object)(object)_widget != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_widget).gameObject);
			}
			_widget = null;
			_lastDrawn = null;
			_failed = false;
		}
	}
	public sealed class BingBongVolumeSetting : FloatSetting, IExposedSetting
	{
		internal static Func<float> ReadConfig = () => 1f;

		internal static Action<float> WriteConfig = delegate
		{
		};

		public override void ApplyValue()
		{
			WriteConfig(((FloatSetting)this).Value);
		}

		protected override float GetDefaultValue()
		{
			return ReadConfig();
		}

		protected override float2 GetMinMaxValue()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			return new float2(0f, 3f);
		}

		public string GetDisplayName()
		{
			return "Bing Bong TTS Volume";
		}

		public string GetCategory()
		{
			return "Audio";
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class SettingsHandlerPatch
	{
		private static void Postfix(SettingsHandler __instance)
		{
			try
			{
				__instance.AddSetting((Setting)(object)new BingBongVolumeSetting());
				BingBongSlot.Trace("registered the TTS volume slider under Audio.");
			}
			catch (Exception ex)
			{
				if (BingBongSlot.Log != null)
				{
					BingBongSlot.Log.Warn("Could not add the volume slider to the options menu: " + ex.Message);
				}
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class BingBongWeightPatch
	{
		internal static Func<bool> IsEnabled = () => false;

		private static readonly Dictionary<int, bool> IsBingBongCache = new Dictionary<int, bool>();

		private static void Postfix(Item __instance, ref int __result)
		{
			if (__result != 0 && !((Object)(object)__instance == (Object)null))
			{
				bool flag;
				try
				{
					flag = IsEnabled();
				}
				catch
				{
					return;
				}
				if (flag && IsBingBong(__instance))
				{
					__result = 0;
				}
			}
		}

		private static bool IsBingBong(Item item)
		{
			int instanceID = ((Object)item).GetInstanceID();
			if (IsBingBongCache.TryGetValue(instanceID, out var value))
			{
				return value;
			}
			value = (Object)(object)((Component)item).GetComponentInChildren<Action_AskBingBong>(true) != (Object)null;
			IsBingBongCache[instanceID] = value;
			return value;
		}

		internal static void Forget()
		{
			IsBingBongCache.Clear();
		}
	}
	internal sealed class CipherPeakRunner : MonoBehaviour
	{
		private PluginConfig _config;

		private ILog _log;

		private TwitchIrcClient _twitch;

		private MessageFilter _filter;

		private SpeechQueue _queue;

		private VoiceRegistry _voices;

		private ChatCommandProcessor _commands;

		private ChatPipeline _pipeline;

		private TtsRouter _router;

		private HttpClientTransport _transport;

		private FileAudioCache _cache;

		private BingBongDirector _director;

		private UnityBingBongWorld _world;

		private TtsPlaybackManager _playback;

		private NetworkAudioBus _bus;

		private CancellationTokenSource _ttsCancellation;

		private Coroutine _pump;

		private float _nextLifecycleTick;

		private bool _wasInRun;

		private bool _wasHost;

		private bool _wasDriving;

		private ModSettings Settings => _config.Settings;

		private static bool IsHost
		{
			get
			{
				if (PhotonNetwork.InRoom)
				{
					return PhotonNetwork.IsMasterClient;
				}
				return false;
			}
		}

		private static bool InPlayableRun
		{
			get
			{
				if ((Object)(object)GameHandler.Instance == (Object)null)
				{
					return false;
				}
				if (!GameHandler.IsInGameplayScene)
				{
					return false;
				}
				if (!MapHandler.Exists)
				{
					return false;
				}
				Character localCharacter = Character.localCharacter;
				if ((Object)(object)localCharacter != (Object)null)
				{
					return !localCharacter.inAirport;
				}
				return false;
			}
		}

		private bool DrivingChat
		{
			get
			{
				if (Settings.Enabled && InPlayableRun)
				{
					if (!IsHost)
					{
						return Settings.Twitch.UseMyOwnChat;
					}
					return true;
				}
				return false;
			}
		}

		private bool BroadcastsSpeech => IsHost;

		public void Initialize(PluginConfig config, ILog log, string cacheDirectory, string playbackDirectory)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Expected O, but got Unknown
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Expected O, but got Unknown
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Expected O, but got Unknown
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Expected O, but got Unknown
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Expected O, but got Unknown
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Expected O, but got Unknown
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Expected O, but got Unknown
			_config = config;
			_log = log;
			_playback = ((Component)this).gameObject.AddComponent<TtsPlaybackManager>();
			_playback.Initialize(() => Settings, _log, playbackDirectory);
			_bus = ((Component)this).gameObject.AddComponent<NetworkAudioBus>();
			_bus.Initialize(_log, _playback);
			_transport = new HttpClientTransport();
			_cache = new FileAudioCache(cacheDirectory, (Func<long>)(() => Math.Max(0L, (long)Settings.Tts.CacheMaxMegabytes * 1024L * 1024)), _log);
			_voices = new VoiceRegistry((Func<ModSettings>)(() => Settings));
			_filter = new MessageFilter((Func<ModSettings>)(() => Settings));
			_queue = new SpeechQueue((Func<ModSettings>)(() => Settings));
			_commands = new ChatCommandProcessor((Func<ModSettings>)(() => Settings), _voices, _log);
			_commands.SkipRequested += Skip;
			_commands.ClearRequested += ClearQueue;
			_commands.EnabledChangeRequested += SetEnabled;
			_pipeline = new ChatPipeline((Func<ModSettings>)(() => Settings), _commands, _filter, _queue, _voices, _log);
			List<ITtsProvider> list = new List<ITtsProvider>
			{
				(ITtsProvider)new ElevenLabsTtsProvider((IHttpTransport)(object)_transport, (Func<ModSettings>)(() => Settings)),
				(ITtsProvider)new TikTokTtsProvider((IHttpTransport)(object)_transport, (Func<ModSettings>)(() => Settings))
			};
			_router = new TtsRouter((Func<ModSettings>)(() => Settings), _voices, (IReadOnlyList<ITtsProvider>)list, (IAudioCache)(object)_cache, _log);
			_world = new UnityBingBongWorld(() => Settings, () => InPlayableRun, (int viewId) => _playback.IsSpeaking(viewId), _log);
			_director = new BingBongDirector((IBingBongWorld)(object)_world, _log);
			_twitch = new TwitchIrcClient((Func<ModSettings>)(() => Settings), _log);
			_ttsCancellation = new CancellationTokenSource();
			_pump = ((MonoBehaviour)this).StartCoroutine(SpeakPump());
		}

		private void Update()
		{
			if (_config == null)
			{
				return;
			}
			HandleHotkeys();
			bool flag = Settings.Enabled && InPlayableRun;
			bool flag2 = flag && IsHost;
			bool flag3 = flag && (IsHost || Settings.Twitch.UseMyOwnChat);
			if (_wasInRun && !flag)
			{
				OnRunEnded();
			}
			if (_wasDriving && !flag3)
			{
				OnStoppedDrivingChat();
			}
			if (_wasHost && !flag2)
			{
				OnLostHost();
			}
			if (!_wasHost && flag2)
			{
				OnBecameHost();
			}
			if (!_wasDriving && flag3)
			{
				OnStartedDrivingChat();
			}
			_wasInRun = flag;
			_wasHost = flag2;
			_wasDriving = flag3;
			if (flag2 && Time.unscaledTime >= _nextLifecycleTick)
			{
				_nextLifecycleTick = Time.unscaledTime + (float)Settings.BingBong.LifecycleTickSeconds;
				try
				{
					_director.Tick();
				}
				catch (Exception ex)
				{
					_log.Error("Bing Bong lifecycle tick failed: " + ex.Message);
				}
			}
			if (flag3)
			{
				DrainChat();
			}
		}

		private void OnStartedDrivingChat()
		{
			_log.Info(IsHost ? "Now hosting; connecting to Twitch for the lobby." : "Reading your own Twitch chat; only you will hear it.");
			_twitch.Start();
		}

		private void OnStoppedDrivingChat()
		{
			_log.Info("No longer reading Twitch; disconnecting.");
			_twitch.Stop();
			_queue.Reset();
			_filter.Reset();
		}

		private void OnBecameHost()
		{
			_log.Info("Taking over the Bing Bongs.");
			_nextLifecycleTick = 0f;
		}

		private void OnLostHost()
		{
			_log.Info("No longer hosting; Bing Bongs stay with the new host.");
			_director.Forget();
		}

		private void OnRunEnded()
		{
			_log.Info("Run ended; removing mod Bing Bongs and clearing the queue.");
			if (IsHost)
			{
				try
				{
					_director.ReleaseAll();
				}
				catch (Exception ex)
				{
					_log.Warn("Cleanup on run end failed: " + ex.Message);
				}
			}
			_queue.Reset();
			_filter.Reset();
			if (BroadcastsSpeech)
			{
				_bus.BroadcastStop();
			}
			else
			{
				_playback.StopAll();
			}
		}

		private void HandleHotkeys()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			if (Hotkey.Pressed(_config.SkipKey))
			{
				Skip();
			}
			if (Hotkey.Pressed(_config.ClearKey))
			{
				ClearQueue();
			}
			if (Hotkey.Pressed(_config.ToggleKey))
			{
				SetEnabled(!Settings.Enabled);
			}
			if (Hotkey.Pressed(_config.BingBongSlotKey))
			{
				BingBongSlot.ToggleEquip();
			}
			if (Hotkey.Pressed(_config.BackpackKey))
			{
				BackpackHotkey.Open();
			}
		}

		private void DrainChat()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			DateTimeOffset utcNow = DateTimeOffset.UtcNow;
			int num = 32;
			ChatMessage val = default(ChatMessage);
			SpeechRequest val2 = default(SpeechRequest);
			while (num-- > 0 && _twitch.TryDequeue(ref val))
			{
				try
				{
					_pipeline.Handle(val, utcNow, ref val2);
				}
				catch (Exception ex)
				{
					_log.Warn("Chat message could not be processed: " + ex.Message);
				}
			}
		}

		private IEnumerator SpeakPump()
		{
			SpeakerRotation rotation = new SpeakerRotation();
			WaitForSecondsRealtime wait = new WaitForSecondsRealtime(0.2f);
			SpeechRequest request = default(SpeechRequest);
			while (true)
			{
				if (_config == null || !DrivingChat || _playback.IsBusy)
				{
					yield return wait;
					continue;
				}
				if (!_queue.TryDequeue(ref request))
				{
					yield return wait;
					continue;
				}
				Task<TtsResult> task = SynthesizeSafely(request);
				while (!task.IsCompleted)
				{
					yield return null;
				}
				TtsResult result = task.Result;
				if (result == null || !result.Success)
				{
					_log.Warn("Skipping message from " + request.Login + ": " + ((result != null) ? result.Error : "unknown TTS failure"));
					continue;
				}
				int num = PickSpeaker(rotation);
				if (num == 0)
				{
					_log.Warn("No Bing Bong available to speak; dropping this message while one is restored.");
					continue;
				}
				_log.Info("Speaking " + request.Login + "'s message through " + _world.Describe(num) + " (" + result.Audio.Length + " bytes" + (BroadcastsSpeech ? "" : ", locally") + ").");
				if (BroadcastsSpeech)
				{
					yield return _bus.Broadcast(request.Id, num, result.Audio);
				}
				else
				{
					_playback.Enqueue(request.Id, num, result.Audio);
				}
				float arrivalDeadline = Time.unscaledTime + 5f;
				while (!_playback.IsPlaying(request.Id) && Time.unscaledTime < arrivalDeadline)
				{
					yield return null;
				}
				float playbackDeadline = Time.unscaledTime + 120f;
				while (_playback.IsPlaying(request.Id) && Time.unscaledTime < playbackDeadline)
				{
					yield return null;
				}
				float num2 = (float)Settings.Queue.GapBetweenMessagesSeconds;
				if (num2 > 0f)
				{
					yield return (object)new WaitForSecondsRealtime(num2);
				}
				request = null;
			}
		}

		private Task<TtsResult> SynthesizeSafely(SpeechRequest request)
		{
			try
			{
				return _router.SynthesizeAsync(request.Text, request.VoiceAlias, _ttsCancellation.Token);
			}
			catch (Exception ex)
			{
				return Task.FromResult<TtsResult>(TtsResult.Fail("TTS dispatch failed: " + ex.Message, 0.0));
			}
		}

		private int PickSpeaker(SpeakerRotation rotation)
		{
			if (!IsHost)
			{
				return _world.LocalSpeaker();
			}
			IReadOnlyList<int> handles = _director.Handles;
			bool[] array = new bool[handles.Count];
			for (int i = 0; i < handles.Count; i++)
			{
				array[i] = _world.IsAlive(handles[i]) && _world.CanBeHeard(handles[i]);
			}
			int num = rotation.Next((IReadOnlyList<bool>)array);
			if (num >= 0)
			{
				return handles[num];
			}
			for (int j = 0; j < handles.Count; j++)
			{
				array[j] = _world.IsAlive(handles[j]);
			}
			num = rotation.Next((IReadOnlyList<bool>)array);
			if (num >= 0)
			{
				return handles[num];
			}
			return 0;
		}

		private void Skip()
		{
			if (IsHost)
			{
				_bus.BroadcastStop();
				_log.Info("Skipped the current message.");
			}
		}

		private void ClearQueue()
		{
			if (IsHost)
			{
				int num = _queue.Clear();
				_bus.BroadcastStop();
				_log.Info("Cleared " + num + " queued message(s).");
			}
		}

		private void SetEnabled(bool value)
		{
			_config.SetEnabled(value);
			_log.Info("TTS " + (value ? "enabled" : "disabled") + ".");
			if (!value)
			{
				_twitch.Stop();
				_queue.Reset();
				_bus.BroadcastStop();
				if (IsHost)
				{
					_director.ReleaseAll();
				}
			}
		}

		public void Shutdown()
		{
			if (_pump != null)
			{
				((MonoBehaviour)this).StopCoroutine(_pump);
				_pump = null;
			}
			try
			{
				_ttsCancellation?.Cancel();
			}
			catch
			{
			}
			if (_twitch != null)
			{
				_twitch.Dispose();
				_twitch = null;
			}
			if (_director != null && IsHost)
			{
				try
				{
					_director.ReleaseAll();
				}
				catch
				{
				}
			}
			if ((Object)(object)_playback != (Object)null)
			{
				_playback.StopAll();
			}
			if (_cache != null && _config != null && !Settings.Tts.CacheEnabled)
			{
				_cache.Purge();
			}
			if (_transport != null)
			{
				_transport.Dispose();
				_transport = null;
			}
			_ttsCancellation?.Dispose();
			_ttsCancellation = null;
		}

		private void OnDestroy()
		{
			Shutdown();
		}
	}
	internal static class Hotkey
	{
		internal static bool Pressed(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//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_0037: Unknown result type (might be due to invalid IL or missing references)
			KeyCode mainKey = ((KeyboardShortcut)(ref shortcut)).MainKey;
			if ((int)mainKey == 0)
			{
				return false;
			}
			if (!UnityInput.Current.GetKeyDown(mainKey))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				if (!UnityInput.Current.GetKey(modifier))
				{
					return false;
				}
			}
			return true;
		}

		internal static bool Held(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			KeyCode mainKey = ((KeyboardShortcut)(ref shortcut)).MainKey;
			if ((int)mainKey != 0)
			{
				return UnityInput.Current.GetKey(mainKey);
			}
			return false;
		}

		internal unsafe static string Label(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			KeyCode mainKey = ((KeyboardShortcut)(ref shortcut)).MainKey;
			if ((int)mainKey == 0)
			{
				return "";
			}
			string text = ((object)(*(KeyCode*)(&mainKey))/*cast due to .constrained prefix*/).ToString();
			if (text.StartsWith("Alpha", StringComparison.Ordinal))
			{
				return text.Substring(5);
			}
			if (text.StartsWith("Keypad", StringComparison.Ordinal))
			{
				return "Num" + text.Substring(6);
			}
			return text;
		}
	}
	internal sealed class NetworkAudioBus : MonoBehaviour, IOnEventCallback
	{
		internal const byte EventCode = 177;

		private const byte OpChunk = 1;

		private const byte OpStop = 2;

		private const int ChunksPerFrame = 4;

		private readonly AudioReassembler _reassembler = new AudioReassembler(4);

		private ILog _log;

		private TtsPlaybackManager _playback;

		private bool _registered;

		public void Initialize(ILog log, TtsPlaybackManager playback)
		{
			_log = log;
			_playback = playback;
			if (!_registered)
			{
				PhotonNetwork.AddCallbackTarget((object)this);
				_registered = true;
			}
		}

		private void OnDestroy()
		{
			if (_registered)
			{
				PhotonNetwork.RemoveCallbackTarget((object)this);
				_registered = false;
			}
		}

		public IEnumerator Broadcast(int messageId, int speakerViewId, byte[] audio)
		{
			if (audio == null || audio.Length == 0)
			{
				yield break;
			}
			if (!PhotonNetwork.InRoom)
			{
				_playback.Enqueue(messageId, speakerViewId, audio);
				yield break;
			}
			List<byte[]> chunks = AudioChunker.Split(audio, 12000);
			RaiseEventOptions options = new RaiseEventOptions
			{
				Receivers = (ReceiverGroup)1
			};
			for (int i = 0; i < chunks.Count; i++)
			{
				bool flag;
				try
				{
					flag = PhotonNetwork.RaiseEvent((byte)177, (object)new object[6]
					{
						(byte)1,
						messageId,
						speakerViewId,
						(short)i,
						(short)chunks.Count,
						chunks[i]
					}, options, SendOptions.SendReliable);
				}
				catch (Exception ex)
				{
					_log.Warn("Failed to broadcast audio chunk " + i + ": " + ex.Message);
					break;
				}
				if (!flag)
				{
					_log.Warn("Photon refused audio chunk " + i + "; dropping the rest of this message.");
					break;
				}
				if ((i + 1) % 4 == 0)
				{
					yield return null;
				}
			}
		}

		public void BroadcastStop()
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			_playback.StopAll();
			_reassembler.Clear();
			if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			try
			{
				PhotonNetwork.RaiseEvent((byte)177, (object)new object[1] { (byte)2 }, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				}, SendOptions.SendReliable);
			}
			catch (Exception ex)
			{
				_log.Warn("Failed to broadcast stop: " + ex.Message);
			}
		}

		public void OnEvent(EventData photonEvent)
		{
			if (photonEvent.Code != 177 || !(photonEvent.CustomData is object[] array) || array.Length == 0)
			{
				return;
			}
			try
			{
				switch (Convert.ToByte(array[0]))
				{
				case 1:
					HandleChunk(array);
					break;
				case 2:
					_playback.StopAll();
					_reassembler.Clear();
					break;
				}
			}
			catch (Exception ex)
			{
				_log.Warn("Ignoring malformed CipherPeak network event: " + ex.Message);
			}
		}

		private void HandleChunk(object[] payload)
		{
			if (payload.Length >= 6)
			{
				int num = Convert.ToInt32(payload[1]);
				int num2 = Convert.ToInt32(payload[2]);
				int num3 = Convert.ToInt32(payload[3]);
				int num4 = Convert.ToInt32(payload[4]);
				byte[] audio = default(byte[]);
				int speakerViewId = default(int);
				if (payload[5] is byte[] array && _reassembler.Accept(num, num2, num3, num4, array, ref audio, ref speakerViewId))
				{
					_playback.Enqueue(num, speakerViewId, audio);
				}
			}
		}
	}
	[BepInPlugin("com.cipherpeak.bingbongtts", "CipherPeak Bing Bong TTS", "1.0.1")]
	[BepInProcess("PEAK.exe")]
	public sealed class BingBongTtsPlugin : BaseUnityPlugin
	{
		public const string Guid = "com.cipherpeak.bingbongtts";

		public const string Name = "CipherPeak Bing Bong TTS";

		public const string Version = "1.0.1";

		private GameObject _host;

		private CipherPeakRunner _runner;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Expected O, but got Unknown
			UnityLog unityLog = new UnityLog(((BaseUnityPlugin)this).Logger);
			PluginConfig config = new PluginConfig(((BaseUnityPlugin)this).Config, (ILog)(object)unityLog);
			BingBongWeightPatch.IsEnabled = () => config.Settings.BingBong.Weightless;
			BingBongSlot.Settings = () => config.Settings;
			BingBongSlot.Log = (ILog)(object)unityLog;
			BingBongSlot.KeyLabel = () => Hotkey.Label(config.BingBongSlotKey);
			BackpackHotkey.KeyHeld = () => Hotkey.Held(config.BackpackKey);
			_harmony = new Harmony("com.cipherpeak.bingbongtts");
			_harmony.PatchAll(typeof(BingBongWeightPatch));
			_harmony.PatchAll(typeof(PlayerGetItemSlotPatch));
			_harmony.PatchAll(typeof(PlayerAddItemPatch));
			_harmony.PatchAll(typeof(PlayerHasEmptySlotPatch));
			_harmony.PatchAll(typeof(DropAllItemsPatch));
			_harmony.PatchAll(typeof(BingBongDropPatch));
			_harmony.PatchAll(typeof(BingBongSlotUi));
			BingBongVolumeSetting.ReadConfig = () => config.Volume;
			BingBongVolumeSetting.WriteConfig = config.SetVolume;
			_harmony.PatchAll(typeof(SettingsHandlerPatch));
			_harmony.PatchAll(typeof(BackpackWheelHoldPatch));
			string path = Path.Combine(Paths.CachePath, "CipherPeak");
			string cacheDirectory = Path.Combine(path, "tts-cache");
			string playbackDirectory = Path.Combine(path, "playback");
			_host = new GameObject("CipherPeak.BingBongTTS");
			Object.DontDestroyOnLoad((Object)(object)_host);
			_runner = _host.AddComponent<CipherPeakRunner>();
			_runner.Initialize(config, (ILog)(object)unityLog, cacheDirectory, playbackDirectory);
			_host.AddComponent<BingBongSlotSync>().Initialize((ILog)(object)unityLog);
			unityLog.Info("CipherPeak Bing Bong TTS 1.0.1 loaded.");
		}

		private void OnDestroy()
		{
			if ((Object)(object)_runner != (Object)null)
			{
				_runner.Shutdown();
			}
			if ((Object)(object)_host != (Object)null)
			{
				Object.Destroy((Object)(object)_host);
			}
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
			BingBongWeightPatch.Forget();
			BingBongSlotUi.Reset();
			SecretScrubber.Clear();
		}
	}
	internal sealed class PluginConfig
	{
		private readonly ConfigFile _file;

		private readonly ILog _log;

		private ConfigEntry<bool> _enabled;

		private ConfigEntry<string> _channel;

		private ConfigEntry<string> _twitchUser;

		private ConfigEntry<string> _twitchToken;

		private ConfigEntry<bool> _allowInsecureTls;

		private ConfigEntry<bool> _useOwnChat;

		private ConfigEntry<int> _minLength;

		private ConfigEntry<int> _maxLength;

		private ConfigEntry<int> _duplicateHistory;

		private ConfigEntry<string> _commandPrefixes;

		private ConfigEntry<string> _ignoredUsers;

		private ConfigEntry<string> _blockedWords;

		private ConfigEntry<bool> _blockLinks;

		private ConfigEntry<bool> _subsOnly;

		private ConfigEntry<bool> _modsOnly;

		private ConfigEntry<bool> _modBypass;

		private ConfigEntry<ProfanityAction> _profanity;

		private ConfigEntry<string> _profanityWords;

		private ConfigEntry<string> _profanityMask;

		private ConfigEntry<int> _maxQueued;

		private ConfigEntry<float> _userCooldown;

		private ConfigEntry<float> _globalCooldown;

		private ConfigEntry<float> _messageGap;

		private ConfigEntry<string> _provider;

		private ConfigEntry<string> _defaultVoice;

		private ConfigEntry<string> _voiceAliases;

		private ConfigEntry<bool> _allowVoiceCommand;

		private ConfigEntry<bool> _fallbackProvider;

		private ConfigEntry<bool> _cacheEnabled;

		private ConfigEntry<string> _elevenKey;

		private ConfigEntry<string> _elevenModel;

		private ConfigEntry<string> _elevenFormat;

		private ConfigEntry<float> _elevenStability;

		private ConfigEntry<float> _elevenSimilarity;

		private ConfigEntry<string> _tiktokEndpoint;

		private ConfigEntry<string> _tiktokSession;

		private ConfigEntry<int> _timeout;

		private ConfigEntry<int> _retries;

		private ConfigEntry<int> _cacheMb;

		private ConfigEntry<int> _maxAudioBytes;

		private ConfigEntry<string> _prefabOverride;

		private ConfigEntry<float> _spawnRadius;

		private ConfigEntry<float> _spawnHeight;

		private ConfigEntry<float> _tickSeconds;

		private ConfigEntry<float> _maxDistance;

		private ConfigEntry<float> _outOfBoundsDrop;

		private ConfigEntry<bool> _weightless;

		private ConfigEntry<bool> _dedicatedSlot;

		private ConfigEntry<float> _slotUiOffsetX;

		private ConfigEntry<float> _volume;

		private ConfigEntry<float> _minDistance;

		private ConfigEntry<float> _audioMaxDistance;

		private ConfigEntry<bool> _animateMouth;

		private ConfigEntry<KeyboardShortcut> _skipKey;

		private ConfigEntry<KeyboardShortcut> _clearKey;

		private ConfigEntry<KeyboardShortcut> _toggleKey;

		private ConfigEntry<KeyboardShortcut> _bingBongSlotKey;

		private ConfigEntry<KeyboardShortcut> _backpackKey;

		private ModSettings _snapshot = new ModSettings();

		public ModSettings Settings => _snapshot;

		public KeyboardShortcut SkipKey => _skipKey.Value;

		public KeyboardShortcut ClearKey => _clearKey.Value;

		public KeyboardShortcut ToggleKey => _toggleKey.Value;

		public KeyboardShortcut BingBongSlotKey => _bingBongSlotKey.Value;

		public KeyboardShortcut BackpackKey => _backpackKey.Value;

		public float Volume
		{
			get
			{
				if (_volume != null)
				{
					return _volume.Value;
				}
				return 1f;
			}
		}

		public PluginConfig(ConfigFile file, ILog log)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			_file = file;
			_log = log;
			Bind();
			Rebuild();
			_file.SettingChanged += delegate
			{
				Rebuild();
			};
		}

		public void SetEnabled(bool value)
		{
			_enabled.Value = value;
			Rebuild();
		}

		public void SetVolume(float value)
		{
			if (_volume != null)
			{
				_volume.Value = Mathf.Clamp(value, 0f, 3f);
				Rebuild();
			}
		}

		private void Bind()
		{
			//IL_0364: Unknown result type (might be due to invalid IL or missing references)
			//IL_036e: Expected O, but got Unknown
			//IL_06db: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e5: Expected O, but got Unknown
			//IL_07b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0814: Unknown result type (might be due to invalid IL or missing references)
			//IL_0840: Unknown result type (might be due to invalid IL or missing references)
			//IL_086c: Unknown result type (might be due to invalid IL or missing references)
			_enabled = _file.Bind<bool>("General", "Enabled", true, "Master switch. When false no Twitch connection is made and no Bing Bongs are spawned.");
			_useOwnChat = _file.Bind<bool>("Twitch", "UseMyOwnChat", false, "Read your own Twitch chat instead of listening to the host's, using your own channel, filters, voices and API key. Only you hear it - your chat is never sent to the lobby - so it needs no agreement from the host. Ignored when you are the host, since the host already reads their own.");
			_channel = _file.Bind<string>("Twitch", "Channel", "", "Twitch channel to read, without the leading '#'.");
			_twitchUser = _file.Bind<string>("Twitch", "Username", "", "Lowercase login of the account the OAuth token belongs to. Leave empty for anonymous read-only chat.");
			_twitchToken = _file.Bind<string>("Twitch", "OAuthToken", "", "OAuth token with the chat:read scope, with or without the 'oauth:' prefix. Leave empty to connect anonymously. This file is not tracked by git; never share it.");
			_allowInsecureTls = _file.Bind<bool>("Twitch", "AllowInsecureTls", true, "Unity's Mono runtime often has no root certificate store, which makes strict TLS validation fail for harmless reasons. When true the client connects anyway and logs a warning. Set false to refuse unverified certificates instead.");
			_minLength = _file.Bind<int>("Filter", "MinMessageLength", 2, "Messages shorter than this are ignored.");
			_maxLength = _file.Bind<int>("Filter", "MaxMessageLength", 200, "Messages longer than this are ignored.");
			_commandPrefixes = _file.Bind<string>("Filter", "CommandPrefixes", "!,/,.,?", "Comma-separated prefixes treated as bot commands and never spoken.");
			_ignoredUsers = _file.Bind<string>("Filter", "IgnoredUsers", "nightbot,streamelements,streamlabs,moobot,fossabot,sery_bot", "Comma-separated logins that are never spoken.");
			_blockedWords = _file.Bind<string>("Filter", "BlockedWords", "", "Comma-separated substrings; a message containing any of them is dropped.");
			_profanity = _file.Bind<ProfanityAction>("Filter", "Profanity", (ProfanityAction)0, "Off = no profanity handling. Mask = replace each swear with ProfanityMask and speak the rest. Block = drop the whole message.");
			_profanityWords = _file.Bind<string>("Filter", "ProfanityWords", "", "Comma-separated extra word stems, added to the built-in list. A stem also matches its common suffixes ('fuck' catches 'fucking') but only as a whole word, so 'grass' stays clean.");
			_profanityMask = _file.Bind<string>("Filter", "ProfanityMask", "beep", "Spoken in place of each swear when Profanity = Mask. Empty removes the word instead.");
			_blockLinks = _file.Bind<bool>("Filter", "BlockLinks", true, "Drop messages containing URLs or bare domains.");
			_duplicateHistory = _file.Bind<int>("Filter", "DuplicateHistorySize", 25, "How many recent messages are remembered for duplicate suppression. 0 disables it.");
			_subsOnly = _file.Bind<bool>("Filter", "SubscribersOnly", false, "Only subscribers may be spoken.");
			_modsOnly = _file.Bind<bool>("Filter", "ModeratorsOnly", false, "Only moderators and the broadcaster may be spoken.");
			_modBypass = _file.Bind<bool>("Filter", "ModeratorsBypassLimits", true, "Moderators and the broadcaster skip length limits, cooldowns and permission gates.");
			_maxQueued = _file.Bind<int>("Queue", "MaxQueuedMessages", 10, "Queue capacity; further messages are dropped.");
			_userCooldown = _file.Bind<float>("Queue", "PerUserCooldownSeconds", 20f, "Minimum gap between one chatter's messages.");
			_globalCooldown = _file.Bind<float>("Queue", "GlobalCooldownSeconds", 0f, "Minimum gap between any two accepted messages.");
			_messageGap = _file.Bind<float>("Queue", "GapBetweenMessagesSeconds", 0.5f, "Silence inserted between spoken messages.");
			_provider = _file.Bind<string>("TTS", "DefaultProvider", "elevenlabs", new ConfigDescription("Provider used when a voice alias does not name one.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "elevenlabs", "tiktok" }), Array.Empty<object>()));
			_defaultVoice = _file.Bind<string>("TTS", "DefaultVoiceAlias", "default", "Alias from VoiceAliases used for chatters who have not picked one.");
			_voiceAliases = _file.Bind<string>("TTS", "VoiceAliases", "default=elevenlabs:21m00Tcm4TlvDq8ikWAM", "Comma-separated 'alias=provider:voiceId'. Chat only ever sees the alias, so provider voice ids stay out of the stream. Only aliases listed here can be selected.");
			_allowVoiceCommand = _file.Bind<bool>("TTS", "AllowVoiceCommand", false, "Let chatters run '!voice <alias>' to pick one of the allowlisted aliases.");
			_elevenKey = _file.Bind<string>("TTS", "ElevenLabsApiKey", "", "ElevenLabs API key. Also readable from the CIPHERPEAK_ELEVENLABS_API_KEY environment variable, which keeps it out of this file entirely.");
			_elevenModel = _file.Bind<string>("TTS", "ElevenLabsModelId", "eleven_multilingual_v2", "ElevenLabs model id.");
			_elevenFormat = _file.Bind<string>("TTS", "ElevenLabsOutputFormat", "mp3_44100_64", "ElevenLabs output_format. Must be an mp3_* value; Unity decodes MP3.");
			_elevenStability = _file.Bind<float>("TTS", "ElevenLabsStability", 0.5f, "ElevenLabs voice_settings.stability.");
			_elevenSimilarity = _file.Bind<float>("TTS", "ElevenLabsSimilarityBoost", 0.75f, "ElevenLabs voice_settings.similarity_boost.");
			_tiktokEndpoint = _file.Bind<string>("TTS", "TikTokEndpoint", "", "TikTok publishes no public, permitted TTS API. The provider stays disabled unless you point this at an endpoint you are allowed to use. See README, 'TikTok limitation'.");
			_tiktokSession = _file.Bind<string>("TTS", "TikTokSessionId", "", "Session cookie for the TikTok endpoint above. Also readable from CIPHERPEAK_TIKTOK_SESSION_ID.");
			_timeout = _file.Bind<int>("TTS", "RequestTimeoutSeconds", 20, "Per-request timeout.");
			_retries = _file.Bind<int>("TTS", "MaxRetries", 2, "Retries per provider before giving up or falling back.");
			_fallbackProvider = _file.Bind<bool>("TTS", "FallbackToOtherProvider", true, "If the selected provider fails, try the other configured one.");
			_cacheEnabled = _file.Bind<bool>("TTS", "CacheEnabled", true, "Cache generated clips on disk.");
			_cacheMb = _file.Bind<int>("TTS", "CacheMaxMegabytes", 64, "Cache size budget; oldest clips are trimmed first.");
			_maxAudioBytes = _file.Bind<int>("TTS", "MaxAudioBytes", 524288, "Reject clips larger than this before they are sent over the network.");
			_prefabOverride = _file.Bind<string>("BingBong", "PrefabNameOverride", "", "Resources name of the Bing Bong item prefab. Empty auto-detects it from the item database.");
			_spawnRadius = _file.Bind<float>("BingBong", "SpawnRadiusMeters", 3f, "Horizontal spread around the anchor point.");
			_spawnHeight = _file.Bind<float>("BingBong", "SpawnHeightOffsetMeters", 1.5f, "Height above the anchor to probe from.");
			_tickSeconds = _file.Bind<float>("BingBong", "LifecycleTickSeconds", 3f, "How often the 'exactly two Bing Bongs' check runs.");
			_maxDistance = _file.Bind<float>("BingBong", "MaxDistanceFromPlayersMeters", 0f, "Past this distance from the nearest living scout a Bing Bong counts as lost and is replaced next to the party. 0 disables it, so Bing Bongs stay exactly where they were left.");
			_outOfBoundsDrop = _file.Bind<float>("BingBong", "OutOfBoundsDropMeters", 0f, "This far below the nearest scout, a Bing Bong counts as out of bounds and is replaced. 0 disables it. Climbing triggers this as readily as falling does, hence the default.");
			_weightless = _file.Bind<bool>("BingBong", "Weightless", false, "Carry a Bing Bong without it counting toward your carry weight, in any slot. Applies to your own character only, so every player who wants it needs the mod. The weight readout refreshes on the next pickup or drop, not the instant you change this.");
			_volume = _file.Bind<float>("Audio", "Volume", 1f, new ConfigDescription("TTS volume, on top of the game's SFX slider. 1 matches the game's own sounds; above that amplifies, and past about 2.5 it starts to clip.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			_minDistance = _file.Bind<float>("Audio", "MinDistance", 10f, "Distance the voice stays at full volume before it starts falling off. Raising this is the most effective way to make Bing Bongs louder at normal ranges.");
			_audioMaxDistance = _file.Bind<float>("Audio", "MaxDistance", 60f, "Distance at which the voice is inaudible.");
			_animateMouth = _file.Bind<bool>("Audio", "AnimateMouth", true, "Animate the Bing Bong's mouth to the speech.");
			_dedicatedSlot = _file.Bind<bool>("BingBong", "DedicatedSlot", true, "Give the Bing Bong its own inventory slot instead of spending one of the normal three. The slot sits outside the game's weight sum, so what is in it never weighs anything. Every player who wants the slot needs the mod; to others the Bing Bong is simply not there.");
			_slotUiOffsetX = _file.Bind<float>("BingBong", "SlotUiOffsetX", 0f, "Horizontal nudge for the slot's HUD widget, in canvas units. 0 auto-places it beside the game's temporary slot.");
			_skipKey = _file.Bind<KeyboardShortcut>("Hotkeys", "Skip", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Skip the message being spoken.");
			_clearKey = _file.Bind<KeyboardShortcut>("Hotkeys", "Clear", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Clear the pending queue.");
			_toggleKey = _file.Bind<KeyboardShortcut>("Hotkeys", "Toggle", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Toggle TTS on or off.");
			_bingBongSlotKey = _file.Bind<KeyboardShortcut>("Hotkeys", "BingBongSlot", new KeyboardShortcut((KeyCode)53, Array.Empty<KeyCode>()), "Equip or stow the Bing Bong slot. The game's scroll cycle only walks slots 0-3, so the dedicated slot needs its own key.");
			_backpackKey = _file.Bind<KeyboardShortcut>("Hotkeys", "OpenBackpack", new KeyboardShortcut((KeyCode)98, Array.Empty<KeyCode>()), "Open the wheel for the backpack on your own back, without dropping it first. Set to None to leave the key alone for another mod.");
		}

		private void Rebuild()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0299: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Unknown result type (might be due to invalid IL or missing references)
			//IL_032d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_0368: Unknown result type (might be due to invalid IL or missing references)
			//IL_0388: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_040c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0430: Unknown result type (might be due to invalid IL or missing references)
			//IL_0447: Unknown result type (might be due to invalid IL or missing references)
			//IL_045e: Unknown result type (might be due to invalid IL or missing references)
			//IL_047f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0496: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0507: Unknown result type (might be due to invalid IL or missing references)
			//IL_051e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0535: Unknown result type (might be due to invalid IL or missing references)
			//IL_054c: Expected O, but got Unknown
			ModSettings val = new ModSettings
			{
				Enabled = _enabled.Value,
				Twitch = 
				{
					Channel = (_channel.Value ?? "").Trim().TrimStart('#')
				},
				Twitch = 
				{
					Username = (_twitchUser.Value ?? "").Trim()
				},
				Twitch = 
				{
					OAuthToken = ResolveSecret(_twitchToken.Value, "CIPHERPEAK_TWITCH_OAUTH")
				},
				Twitch = 
				{
					AllowInsecureTls = _allowInsecureTls.Value
				},
				Twitch = 
				{
					UseMyOwnChat = _useOwnChat.Value
				},
				Filter = 
				{
					MinMessageLength = _minLength.Value
				},
				Filter = 
				{
					MaxMessageLength = _maxLength.Value
				},
				Filter = 
				{
					CommandPrefixes = SplitCsv(_commandPrefixes.Value)
				},
				Filter = 
				{
					IgnoredUsers = SplitCsv(_ignoredUsers.Value)
				},
				Filter = 
				{
					BlockedWords = SplitCsv(_blockedWords.Value)
				},
				Filter = 
				{
					Profanity = _profanity.Value
				},
				Filter = 
				{
					ProfanityWords = SplitCsv(_profanityWords.Value)
				},
				Filter = 
				{
					ProfanityMask = (_profanityMask.Value ?? "").Trim()
				},
				Filter = 
				{
					BlockLinks = _blockLinks.Value
				},
				Filter = 
				{
					DuplicateHistorySize = _duplicateHistory.Value
				},
				Filter = 
				{
					SubscribersOnly = _subsOnly.Value
				},
				Filter = 
				{
					ModeratorsOnly = _modsOnly.Value
				},
				Filter = 
				{
					ModeratorsBypassLimits = _modBypass.Value
				},
				Queue = 
				{
					MaxQueuedMessages = _maxQueued.Value
				},
				Queue = 
				{
					PerUserCooldownSeconds = _userCooldown.Value
				},
				Queue = 
				{
					GlobalCooldownSeconds = _globalCooldown.Value
				},
				Queue = 
				{
					GapBetweenMessagesSeconds = _messageGap.Value
				},
				Tts = 
				{
					DefaultProvider = (_provider.Value ?? "elevenlabs").ToLowerInvariant()
				},
				Tts = 
				{
					DefaultVoiceAlias = (_defaultVoice.Value ?? "").Trim().ToLowerInvariant()
				},
				Tts = 
				{
					AllowVoiceCommand = _allowVoiceCommand.Value
				},
				Tts = 
				{
					VoiceAliases = VoiceAlias.ParseCsv(_voiceAliases.Value)
				},
				Tts = 
				{
					ElevenLabsApiKey = ResolveSecret(_elevenKey.Value, "CIPHERPEAK_ELEVENLABS_API_KEY")
				},
				Tts = 
				{
					ElevenLabsModelId = _elevenModel.Value
				},
				Tts = 
				{
					ElevenLabsOutputFormat = _elevenFormat.Value
				},
				Tts = 
				{
					ElevenLabsStability = _elevenStability.Value
				},
				Tts = 
				{
					ElevenLabsSimilarityBoost = _elevenSimilarity.Value
				},
				Tts = 
				{
					TikTokEndpoint = (_tiktokEndpoint.Value ?? "").Trim()
				},
				Tts = 
				{
					TikTokSessionId = ResolveSecret(_tiktokSession.Value, "CIPHERPEAK_TIKTOK_SESSION_ID")
				},
				Tts = 
				{
					RequestTimeoutSeconds = _timeout.Value
				},
				Tts = 
				{
					MaxRetries = _retries.Value
				},
				Tts = 
				{
					FallbackToOtherProvider = _fallbackProvider.Value
				},
				Tts = 
				{
					CacheEnabled = _cacheEnabled.Value
				},
				Tts = 
				{
					CacheMaxMegabytes = _cacheMb.Value
				},
				Tts = 
				{
					MaxAudioBytes = _maxAudioBytes.Value
				},
				BingBong = 
				{
					PrefabNameOverride = (_prefabOverride.Value ?? "").Trim()
				},
				BingBong = 
				{
					SpawnRadiusMeters = _spawnRadius.Value
				},
				BingBong = 
				{
					SpawnHeightOffsetMeters = _spawnHeight.Value
				},
				BingBong = 
				{
					LifecycleTickSeconds = Mathf.Max(0.5f, _tickSeconds.Value)
				},
				BingBong = 
				{
					MaxDistanceFromPlayersMeters = _maxDistance.Value
				},
				BingBong = 
				{
					OutOfBoundsDropMeters = _outOfBoundsDrop.Value
				},
				BingBong = 
				{
					Weightless = _weightless.Value
				},
				BingBong = 
				{
					DedicatedSlot = _dedicatedSlot.Value
				},
				BingBong = 
				{
					SlotUiOffsetX = _slotUiOffsetX.Value
				},
				Audio = 
				{
					Volume = _volume.Value
				},
				Audio = 
				{
					MinDistance = _minDistance.Value
				},
				Audio = 
				{
					MaxDistance = _audioMaxDistance.Value
				},
				Audio = 
				{
					AnimateMouth = _animateMouth.Value
				}
			};
			if (val.Tts.VoiceAliases.Count == 0)
			{
				_log.Warn("No voice aliases configured; TTS will stay silent until VoiceAliases has an entry.");
			}
			SecretScrubber.Clear();
			SecretScrubber.Register(val.Tts.ElevenLabsApiKey);
			SecretScrubber.Register(val.Tts.TikTokSessionId);
			SecretScrubber.Register(val.Twitch.OAuthToken);
			_snapshot = val;
		}

		private static string ResolveSecret(string configured, string environmentVariable)
		{
			string text = null;
			try
			{
				text = Environment.GetEnvironmentVariable(environmentVariable);
			}
			catch
			{
			}
			if (string.IsNullOrWhiteSpace(text))
			{
				return (configured ?? "").Trim();
			}
			return text.Trim();
		}

		private static List<string> SplitCsv(string value)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrWhiteSpace(value))
			{
				return list;
			}
			string[] array = value.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					list.Add(text);
				}
			}
			return list;
		}
	}
	internal sealed class TtsPlaybackManager : MonoBehaviour
	{
		private sealed class Job
		{
			public int MessageId;

			public int SpeakerViewId;

			public byte[] Audio;
		}

		private const string SourceObjectName = "CipherPeakTtsSource";

		internal const float MaxVolume = 3f;

		private readonly Queue<Job> _pending = new Queue<Job>();

		private Job _current;

		private Coroutine _loop;

		private string _scratchDirectory;

		private Func<ModSettings> _settings;

		private ILog _log;

		public bool IsBusy
		{
			get
			{
				if (_current == null)
				{
					return _pending.Count > 0;
				}
				return true;
			}
		}

		public void Initialize(Func<ModSettings> settings, ILog log, string scratchDirectory)
		{
			_settings = settings;
			_log = log;
			_scratchDirectory = scratchDirectory;
			TryEnsureScratch();
			if (_loop == null)
			{
				_loop = ((MonoBehaviour)this).StartCoroutine(PlayLoop());
			}
		}

		public bool IsSpeaking(int speakerViewId)
		{
			if (_current != null)
			{
				return _current.SpeakerViewId == speakerViewId;
			}
			return false;
		}

		public bool IsPlaying(int messageId)
		{
			if (_current == null || _current.MessageId != messageId)
			{
				return ContainsPending(messageId);
			}
			return true;
		}

		public void Enqueue(int messageId, int speakerViewId, byte[] audio)
		{
			if (audio != null && audio.Length != 0 && _settings().Enabled)
			{
				_pending.Enqueue(new Job
				{
					MessageId = messageId,
					SpeakerViewId = speakerViewId,
					Audio = audio
				});
			}
		}

		public void StopAll()
		{
			_pending.Clear();
			Job current = _current;
			_current = null;
			if (current != null)
			{
				StopSource(current.SpeakerViewId);
			}
		}

		private void OnDestroy()
		{
			StopAll();
			CleanScratch();
		}

		private bool ContainsPending(int messageId)
		{
			foreach (Job item in _pending)
			{
				if (item.MessageId == messageId)
				{
					return true;
				}
			}
			return false;
		}

		private IEnumerator PlayLoop()
		{
			while (true)
			{
				if (_pending.Count == 0)
				{
					yield return null;
					continue;
				}
				Job job = (_current = _pending.Dequeue());
				yield return PlayOne(job);
				if (_current == job)
				{
					_current = null;
				}
			}
		}

		private IEnumerator PlayOne(Job job)
		{
			AudioClip clip = null;
			string path;
			try
			{
				TryEnsureScratch();
				path = Path.Combine(_scratchDirectory, "msg_" + job.MessageId + ".mp3");
				File.WriteAllBytes(path, job.Audio);
			}
			catch (Exception ex)
			{
				_log.Warn("Could not stage TTS audio for playback: " + ex.Message);
				yield break;
			}
			string absoluteUri;
			try
			{
				absoluteUri = new Uri(path).AbsoluteUri;
			}
			catch (Exception ex2)
			{
				_log.Warn("Bad audio path: " + ex2.Message);
				TryDelete(path);
				yield break;
			}
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(absoluteUri, (AudioType)13);
			try
			{
				DownloadHandler downloadHandler = request.downloadHandler;
				DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null);
				if (val != null)
				{
					val.streamAudio = false;
				}
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					_log.Warn("Could not decode TTS audio: " + request.error);
					TryDelete(path);
					yield break;
				}
				try
				{
					clip = DownloadHandlerAudioClip.GetContent(request);
				}
				catch (Exception ex3)
				{
					_log.Warn("Could not build AudioClip: " + ex3.Message);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
			if ((Object)(object)clip == (Object)null)
			{
				TryDelete(path);
				yield break;
			}
			AudioSource source = GetOrCreateSource(job.SpeakerViewId);
			if ((Object)(object)source == (Object)null)
			{
				_log.Warn("Bing Bong " + job.SpeakerViewId + " vanished before it could speak; dropping the message.");
				Cleanup(clip, path);
				yield break;
			}
			ApplyAudioSettings(source);
			source.clip = clip;
			source.Play();
			if (_settings().Audio.AnimateMouth)
			{
				TryAnimateMouth(source, clip);
			}
			float deadline = Time.unscaledTime + clip.length + 1f;
			while ((Object)(object)source != (Object)null && source.isPlaying && Time.unscaledTime < deadline && _current == job)
			{
				yield return null;
			}
			if ((Object)(object)source != (Object)null && _current != job)
			{
				source.Stop();
			}
			Cleanup(clip, path);
		}

		private void Cleanup(AudioClip clip, string path)
		{
			if ((Object)(object)clip != (Object)null)
			{
				clip.UnloadAudioData();
				Object.Destroy((Object)(object)clip);
			}
			TryDelete(path);
		}

		private void ApplyAudioSettings(AudioSource source)
		{
			AudioSettings audio = _settings().Audio;
			source.volume = Mathf.Clamp((float)audio.Volume, 0f, 3f);
			source.minDistance = Mathf.Max(0.1f, (float)audio.MinDistance);
			source.maxDistance = Mathf.Max(source.minDistance + 1f, (float)audio.MaxDistance);
		}

		private AudioSource GetOrCreateSource(int speakerViewId)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			PhotonView val = PhotonView.Find(speakerViewId);
			if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
			{
				return null;
			}
			Transform val2 = AnchorFor(val);
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			Transform val3 = val2.Find("CipherPeakTtsSource");
			if ((Object)(object)val3 != (Object)null)
			{
				AudioSource component = ((Component)val3).GetComponent<AudioSource>();
				if ((Object)(object)component != (Object)null)
				{
					return component;
				}
			}
			GameObject val4 = new GameObject("CipherPeakTtsSource");
			val4.transform.SetParent(val2, false);
			val4.transform.localPosition = Vector3.zero;
			AudioSource val5 = val4.AddComponent<AudioSource>();
			val5.playOnAwake = false;
			val5.loop = false;
			val5.spatialBlend = 1f;
			val5.rolloffMode = (AudioRolloffMode)0;
			val5.dopplerLevel = 0f;
			SFX_Player instance = SFX_Player.instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.defaultMixerGroup != (Object)null)
			{
				val5.outputAudioMixerGroup = instance.defaultMixerGroup;
			}
			ApplyAudioSettings(val5);
			return val5;
		}

		private static Transform AnchorFor(PhotonView view)
		{
			Character component = ((Component)view).GetComponent<Character>();
			if ((Object)(object)component == (Object)null || component.refs == null)
			{
				return ((Component)view).transform;
			}
			if ((Object)(object)component.refs.head != (Object)null)
			{
				return component.refs.head.transform;
			}
			if ((Object)(object)component.refs.hip != (Object)null)
			{
				return component.refs.hip.transform;
			}
			return ((Component)view).transform;
		}

		private void StopSource(int speakerViewId)
		{
			PhotonView val = PhotonView.Find(speakerViewId);
			if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
			{
				return;
			}
			Transform val2 = AnchorFor(val);
			if ((Object)(object)val2 == (Object)null)
			{
				return;
			}
			Transform val3 = val2.Find("CipherPeakTtsSource");
			if (!((Object)(object)val3 == (Object)null))
			{
				AudioSource component = ((Component)val3).GetComponent<AudioSource>();
				if ((Object)(object)component != (Object)null)
				{
					component.Stop();
				}
			}
		}

		private void TryAnimateMouth(AudioSource source, AudioClip clip)
		{
			try
			{
				BingBongMouth componentInParent = ((Component)source).GetComponentInParent<BingBongMouth>();
				if (!((Object)(object)componentInParent == (Object)null))
				{
					componentInParent.SampleAudioClip(clip);
				}
			}
			catch (Exception ex)
			{
				_log.Info("Mouth animation skipped: " + ex.Message);
			}
		}

		private void TryEnsureScratch()
		{
			try
			{
				Directory.CreateDirectory(_scratchDirectory);
			}
			catch (Exception ex)
			{
				_log.Warn("Could not create playback scratch directory: " + ex.Message);
			}
		}

		private void TryDelete(string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				return;
			}
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}

		private void CleanScratch()
		{
			try
			{
				if (Directory.Exists(_scratchDirectory))
				{
					Directory.Delete(_scratchDirectory, recursive: true);
				}
			}
			catch
			{
			}
		}
	}
	internal sealed class UnityBingBongWorld : IBingBongWorld
	{
		private const string Marker = "CipherPeak.BingBong.v1";

		private readonly Func<ModSettings> _settings;

		private readonly Func<bool> _inPlayableRun;

		private readonly Func<int, bool> _isSpeaking;

		private readonly ILog _log;

		private string _prefabName;

		private bool _prefabErrorLogged;

		private readonly HashSet<int> _ours = new HashSet<int>();

		internal static UnityBingBongWorld Current;

		public bool CanManage
		{
			get
			{
				if (PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient && _inPlayableRun())
				{
					return ResolvePrefabName() != null;
				}
				return false;
			}
		}

		public UnityBingBongWorld(Func<ModSettings> settings, Func<bool> inPlayableRun, Func<int, bool> isSpeaking, ILog log)
		{
			_settings = settings;
			_inPlayableRun = inPlayableRun;
			_isSpeaking = isSpeaking;
			_log = log;
			Current = this;
		}

		public IReadOnlyList<int> FindManaged()
		{
			//IL_0007: 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)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			List<int> list = new List<int>(2);
			ValueIterator<int, PhotonView> enumerator = PhotonNetwork.PhotonViewCollection.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					PhotonView current = enumerator.Current;
					if (!((Object)(object)current == (Object)null) && !((Object)(object)((Component)current).gameObject == (Object)null) && IsMarked(current))
					{
						Item component = ((Component)current).GetComponent<Item>();
						if (!((Object)(object)component != (Object)null) || !((Object)(object)component.holderCharacter != (Object)null))
						{
							list.Add(current.ViewID);
						}
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			List<Character> allPlayerCharacters = PlayerHandler.GetAllPlayerCharacters();
			if (allPlayerCharacters != null)
			{
				for (int i = 0; i < allPlayerCharacters.Count; i++)
				{
					Character val = allPlayerCharacters[i];
					if (!((Object)(object)val == (Object)null) && !((Object)(object)val.player == (Object)null) && CarriedBy(val.player) != 0)
					{
						int num = ViewIdOf(val);
						if (num != 0 && !list.Contains(num))
						{
							list.Add(num);
						}
					}
				}
			}
			return list;
		}

		internal bool CanBeHeard(int handle)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			PhotonView val = PhotonView.Find(handle);
			if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
			{
				return false;
			}
			if (!TryFindNearestCharacter(PositionOf(val), out var _, out var distance))
			{
				return true;
			}
			return (double)distance <= _settings().Audio.MaxDistance;
		}

		private static Vector3 PositionOf(PhotonView view)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Character component = ((Component)view).GetComponent<Character>();
			if (!((Object)(object)component != (Object)null))
			{
				return ((Component)view).transform.position;
			}
			return component.Center;
		}

		internal int LocalSpeaker()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null)
			{
				return 0;
			}
			int result = ViewIdOf(localCharacter);
			if ((Object)(object)localCharacter.player != (Object)null && CarriedBy(localCharacter.player) > 0)
			{
				return result;
			}
			Vector3 center = localCharacter.Center;
			float num = float.MaxValue;
			int num2 = 0;
			ValueIterator<int, PhotonView> enumerator = PhotonNetwork.PhotonViewCollection.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					PhotonView current = enumerator.Current;
					if ((Object)(object)current == (Object)null || (Object)(object)((Component)current).gameObject == (Object)null)
					{
						continue;
					}
					Item component = ((Component)current).GetComponent<Item>();
					if (!((Object)(object)component == (Object)null) && !((Object)(object)component.holderCharacter != (Object)null) && BingBongSlot.IsBingBong(component))
					{
						float num3 = Vector3.Distance(((Component)current).transform.position, center);
						if (!(num3 >= num))
						{
							num = num3;
							num2 = current.ViewID;
						}
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			if (num2 == 0 || !((double)num <= _settings().Audio.MaxDistance))
			{
				return result;
			}
			return num2;
		}

		internal string Describe(int handle)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			PhotonView val = PhotonView.Find(handle);
			if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
			{
				return "view " + handle + " (missing)";
			}
			string text = (((Object)(object)((Component)val).GetComponent<Character>() != (Object)null) ? ("scout " + ((Object)((Component)val).gameObject).name) : ("Bing Bong " + handle));
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter == (Object)null)
			{
				return text;
			}
			return text + ", " + Vector3.Distance(PositionOf(val), localCharacter.Center).ToString("F0") + "m away";
		}

		private static int ViewIdOf(Character character)
		{
			PhotonView component = ((Component)character).GetComponent<PhotonView>();
			if (!((Object)(object)component == (Object)null))
			{
				return component.ViewID;
			}
			return 0;
		}

		private static bool IsMarked(PhotonView view)
		{
			object[] instantiationData = view.InstantiationData;
			if (instantiationData != null && instantiationData.Length != 0)
			{
				return instantiationData[0] as string == "CipherPeak.BingBong.v1";
			}
			return false;
		}

		public int Spawn()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (!TryFindSpawnPosition(out var position))
			{
				return 0;
			}
			return SpawnAt(position);
		}

		internal int SpawnAt(Vector3 position)
		{
			//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)
			string text = ResolvePrefabName();
			if (text == null)
			{
				return 0;
			}
			if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient)
			{
				return 0;
			}
			GameObject val;
			try
			{
				val = PhotonNetwork.InstantiateRoomObject("0_Items/" + text, position, Random.rotation, (byte)0, new object[1] { "CipherPeak.BingBong.v1" });
			}
			catch (Exception ex)
			{
				_log.Error("Failed to spawn Bing Bong: " + ex.Message);
				return 0;
			}
			if ((Object)(object)val == (Object)null)
			{
				return 0;
			}
			PhotonView component = val.GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null)
			{
				_log.Error("Spawned Bing Bong has no PhotonView; destroying it again.");
				PhotonNetwork.Destroy(val);
				return 0;
			}
			_ours.Add(component.ViewID);
			return component.ViewID;
		}

		public void Despawn(int handle)
		{
			if (!_ours.Remove(handle))
			{
				return;
			}
			PhotonView val = PhotonView.Find(handle);
			if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			try
			{
				PhotonNetwork.Destroy(((Component)val).gameObject);
			}
			catch (Exception ex)
			{
				_log.Warn("Failed to despawn Bing Bong " + handle + ": " + ex.Message);
			}
		}

		private int CarriedBy(Player player)
		{
			int num = 0;
			ItemSlot[] itemSlots = player.itemSlots;
			if (itemSlots != null)
			{
				for (int i = 0; i < itemSlots.Length; i++)
				{
					if (IsBingBongIn(itemSlots[i]))
					{
						num++;
					}
				}
			}
			if (IsBingBongIn(player.tempFullSlot))
			{
				num++;
			}
			if (_settings().BingBong.DedicatedSlot && IsBingBongIn(BingBongSlot.For(player)))
			{
				num++;
			}
			return num + InBackpack(player);
		}

		private static int InBackpack(Player player)
		{
			BackpackSlot backpackSlot = player.backpackSlot;
			if (backpackSlot == null || ((ItemSlot)backpackSlot).IsEmpty() || ((ItemSlot)backpackSlot).data == null)
			{
				return 0;
			}
			BackpackData val = default(BackpackData);
			if (!((ItemSlot)backpackSlot).data.TryGetDataEntry<BackpackData>((DataEntryKey)7, ref val))
			{
				return 0;
			}
			if (val == null || val.itemSlots == null)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < val.itemSlots.Length; i++)
			{
				if (IsBingBongIn(val.itemSlots[i]))
				{
					num++;
				}
			}
			return num;
		}

		private static bool IsBingBongIn(ItemSlot slot)
		{
			if (slot != null && !slot.IsEmpty())
			{
				return BingBongSlot.IsBingBong(slot.prefab);
			}
			return false;
		}

		public bool IsAlive(int handle)
		{
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: 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)
			PhotonView val = PhotonView.Find(handle);
			if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null || !((Component)val).gameObject.activeInHierarchy)
			{
				return false;
			}
			if (_isSpeaking(handle))
			{
				return true;
			}
			Character component = ((Component)val).GetComponent<Character>();
			if ((Object)(object)component != (Object)null)
			{
				if ((Object)(object)component.player != (Object)null)
				{
					return CarriedBy(component.player) > 0;
				}
				return false;
			}
			Item component2 = ((Component)val).GetComponent<Item>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)component2.holderCharacter != (Object)null)
			{
				return true;
			}
			BingBongSettings bingBong = _settings().BingBong;
			bool flag = bingBong.MaxDistanceFromPlayersMeters > 0.0;
			bool flag2 = bingBong.OutOfBoundsDropMeters > 0.0;
			if (!flag && !flag2)
			{
				return true;
			}
			Vector3 position = ((Component)val).transform.position;
			if (!TryFindNearestCharacter(position, out var nearest, out var distance))
			{
				return true;
			}
			if (flag && (double)distance > bingBong.MaxDistanceFromPlayersMeters)
			{
				return false;
			}
			if (flag2 && (double)(nearest.Center.y - position.y) > bingBong.OutOfBoundsDropMeters)
			{
				return false;
			}
			return true;
		}

		private string ResolvePrefabName()
		{
			if (_prefabName != null)
			{
				return _prefabName;
			}
			string prefabNameOverride = _settings().BingBong.PrefabNameOverride;
			if (!string.IsNullOrEmpty(prefabNameOverride))
			{
				_prefabName = prefabNameOverride;
				return _prefabName;
			}
			ItemDatabase instance;
			try
			{
				instance = SingletonAsset<ItemDatabase>.Instance;
			}
			catch (Exception ex)
			{
				_log.Warn("Item database not ready yet (" + ex.Message + "); retrying later.");
				return null;
			}
			if ((Object)(object)instance == (Object)null || instance.itemLookup == null || instance.itemLookup.Count == 0)
			{
				return null;
			}
			foreach (KeyValuePair<ushort, Item> item in instance.itemLookup)
			{
				Item value = item.Value;
				if (!((Object)(object)value == (Object)null) && !((Object)(object)((Component)value).GetComponentInChildren<Action_AskBingBong>(true) == (Object)null))
				{
					_prefabName = ((Object)((Component)value).gameObject).name;
					_log.Info("Resolved Bing Bong prefab as '0_Items/" + _prefabName + "'.");
					return _prefabName;
				}
			}
			if (!_prefabErrorLogged)
			{
				_prefabErrorLogged = true;
				_log.Error("Could not find a Bing Bong item in the item database. If this persists, set BingBong.PrefabNameOverride (normally 'BingBong').");
			}
			return null;
		}

		private bool TryFindSpawnPosition(out Vector3 position)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: 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)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: 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)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			BingBongSettings bingBong = _settings().BingBong;
			position = Vector3.zero;
			if (!TryFindAnchor(out var anchor))
			{
				return false;
			}
			float num = Mathf.Max(1f, (float)bingBong.SpawnRadiusMeters);
			float num2 = Mathf.Max(0.5f, (float)bingBong.SpawnHeightOffsetMeters);
			for (int i = 0; i < 12; i++)
			{
				Vector2 insideUnitCircle = Random.insideUnitCircle;
				Vector2 val = ((Vector2)(ref insideUnitCircle)).normalized * Random.Range(1f, num);
				Vector3 val2 = anchor + new Vector3(val.x, num2, val.y);
				RaycastHit val3 = HelperFunctions.LineCheck(val2, val2 + Vector3.down * (num2 + 8f), (LayerType)5, 0f, (QueryTriggerInteraction)1);
				if (!((Object)(object)((RaycastHit)(ref val3)).transform == (Object)null))
				{
					Vector3 val4 = ((RaycastHit)(ref val3)).point + Vector3.up * 0.5f;
					if (!Physics.CheckSphere(val4, 0.35f, LayerMask.op_Implicit(HelperFunctions.AllPhysicalExceptCharacter), (QueryTriggerInteraction)1))
					{
						position = val4;
						return true;
					}
				}
			}
			position = anchor + Vector3.up * num2;
			return true;
		}

		private static bool TryFindAnchor(out Vector3 anchor)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: 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)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter != (Object)null)
			{
				anchor = localCharacter.Center;
				return true;
			}
			List<Character> allCharacters = Character.AllCharacters;
			if (allCharacters != null)
			{
				for (int i = 0; i < allCharacters.Count; i++)
				{
					if ((Object)(object)allCharacters

BepInEx/plugins/CipherPeak-BingBongTTS/CipherPeak.Core.dll

Decompiled 2 months ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CipherPeak.Core.Commands;
using CipherPeak.Core.Config;
using CipherPeak.Core.Filtering;
using CipherPeak.Core.Logging;
using CipherPeak.Core.Net;
using CipherPeak.Core.Queueing;
using CipherPeak.Core.Tts;
using CipherPeak.Core.Twitch;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Mrzeapple")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+ff720e6ad4c9eb80aee667592390f4cda551bd07")]
[assembly: AssemblyProduct("CipherPeak.Core")]
[assembly: AssemblyTitle("CipherPeak.Core")]
[assembly: AssemblyVersion("1.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 CipherPeak.Core.Twitch
{
	public sealed class ChatMessage
	{
		public string Login = "";

		public string DisplayName = "";

		public string Text = "";

		public bool IsSubscriber;

		public bool IsModerator;

		public bool IsBroadcaster;

		public bool IsVip;

		public DateTimeOffset ReceivedAt;

		public bool HasElevatedRole
		{
			get
			{
				if (!IsModerator)
				{
					return IsBroadcaster;
				}
				return true;
			}
		}

		public override string ToString()
		{
			return Login + ": " + Text;
		}
	}
	public static class IrcMessageParser
	{
		public static bool IsPing(string line)
		{
			return line?.StartsWith("PING", StringComparison.Ordinal) ?? false;
		}

		public static string PongFor(string pingLine)
		{
			int num = pingLine.IndexOf(':');
			string text = ((num >= 0) ? pingLine.Substring(num + 1) : "tmi.twitch.tv");
			return "PONG :" + text;
		}

		public static bool IsAuthFailure(string line)
		{
			if (string.IsNullOrEmpty(line))
			{
				return false;
			}
			if (line.IndexOf("Login authentication failed", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return line.IndexOf("Improperly formatted auth", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		public static bool TryParsePrivmsg(string line, DateTimeOffset receivedAt, out ChatMessage message)
		{
			message = null;
			if (string.IsNullOrEmpty(line))
			{
				return false;
			}
			string text = line;
			Dictionary<string, string> dictionary = null;
			if (text[0] == '@')
			{
				int num = text.IndexOf(' ');
				if (num < 0)
				{
					return false;
				}
				dictionary = ParseTags(text.Substring(1, num - 1));
				text = text.Substring(num + 1);
			}
			if (text.Length == 0 || text[0] != ':')
			{
				return false;
			}
			int num2 = text.IndexOf(' ');
			if (num2 < 0)
			{
				return false;
			}
			string text2 = text.Substring(1, num2 - 1);
			text = text.Substring(num2 + 1);
			if (!text.StartsWith("PRIVMSG ", StringComparison.Ordinal))
			{
				return false;
			}
			int num3 = text.IndexOf(" :", StringComparison.Ordinal);
			if (num3 < 0)
			{
				return false;
			}
			string text3 = text.Substring(num3 + 2);
			int num4 = text2.IndexOf('!');
			string text4 = ((num4 > 0) ? text2.Substring(0, num4) : text2).ToLowerInvariant();
			message = new ChatMessage
			{
				Login = text4,
				DisplayName = text4,
				Text = StripActionWrapper(text3).Trim(),
				ReceivedAt = receivedAt
			};
			if (dictionary != null)
			{
				ApplyTags(message, dictionary);
			}
			if (string.IsNullOrEmpty(message.DisplayName))
			{
				message.DisplayName = text4;
			}
			return true;
		}

		private static string StripActionWrapper(string text)
		{
			if (text.Length > "\u0001ACTION ".Length && text.StartsWith("\u0001ACTION ", StringComparison.Ordinal) && text[text.Length - 1] == '\u0001')
			{
				return text.Substring("\u0001ACTION ".Length, text.Length - "\u0001ACTION ".Length - 1);
			}
			return text;
		}

		private static void ApplyTags(ChatMessage message, Dictionary<string, string> tags)
		{
			if (tags.TryGetValue("display-name", out var value) && !string.IsNullOrWhiteSpace(value))
			{
				message.DisplayName = value;
			}
			if (tags.TryGetValue("subscriber", out value))
			{
				message.IsSubscriber = value == "1";
			}
			if (tags.TryGetValue("mod", out value))
			{
				message.IsModerator = value == "1";
			}
			if (tags.TryGetValue("vip", out value))
			{
				message.IsVip = value == "1";
			}
			if (tags.TryGetValue("badges", out value) && !string.IsNullOrEmpty(value))
			{
				if (value.IndexOf("broadcaster/", StringComparison.Ordinal) >= 0)
				{
					message.IsBroadcaster = true;
				}
				if (value.IndexOf("moderator/", StringComparison.Ordinal) >= 0)
				{
					message.IsModerator = true;
				}
				if (value.IndexOf("vip/", StringComparison.Ordinal) >= 0)
				{
					message.IsVip = true;
				}
				if (value.IndexOf("subscriber/", StringComparison.Ordinal) >= 0 || value.IndexOf("founder/", StringComparison.Ordinal) >= 0)
				{
					message.IsSubscriber = true;
				}
			}
			if (message.IsBroadcaster)
			{
				message.IsModerator = true;
			}
		}

		private static Dictionary<string, string> ParseTags(string raw)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			string[] array = raw.Split(';');
			foreach (string text in array)
			{
				if (text.Length != 0)
				{
					int num = text.IndexOf('=');
					if (num < 0)
					{
						dictionary[text] = "";
					}
					else
					{
						dictionary[text.Substring(0, num)] = Unescape(text.Substring(num + 1));
					}
				}
			}
			return dictionary;
		}

		private static string Unescape(string value)
		{
			if (value.IndexOf('\\') < 0)
			{
				return value;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			for (int i = 0; i < value.Length; i++)
			{
				if (value[i] != '\\' || i == value.Length - 1)
				{
					stringBuilder.Append(value[i]);
					continue;
				}
				i++;
				switch (value[i])
				{
				case ':':
					stringBuilder.Append(';');
					break;
				case 's':
					stringBuilder.Append(' ');
					break;
				case 'r':
					stringBuilder.Append('\r');
					break;
				case 'n':
					stringBuilder.Append('\n');
					break;
				case '\\':
					stringBuilder.Append('\\');
					break;
				default:
					stringBuilder.Append(value[i]);
					break;
				}
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class TwitchIrcClient : IDisposable
	{
		private const int MaxPendingMessages = 200;

		private readonly Func<ModSettings> _settings;

		private readonly ILog _log;

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

		private Thread _thread;

		private volatile bool _running;

		private volatile bool _connected;

		private volatile string _status = "stopped";

		private CancellationTokenSource _cancellation;

		private volatile bool _authFailed;

		public bool IsConnected => _connected;

		public string Status => _status;

		public TwitchIrcClient(Func<ModSettings> settings, ILog log)
		{
			_settings = settings;
			_log = log ?? NullLog.Instance;
		}

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

		public void Start()
		{
			if (!_running)
			{
				if ((_settings().Twitch.Channel ?? "").Trim().TrimStart('#').ToLowerInvariant()
					.Length == 0)
				{
					_status = "no channel configured";
					_log.Warn("Twitch channel is not set; chat ingestion stays off.");
					return;
				}
				_authFailed = false;
				_running = true;
				_cancellation = new CancellationTokenSource();
				_thread = new Thread(RunLoop)
				{
					IsBackground = true,
					Name = "CipherPeak-Twitch"
				};
				_thread.Start();
			}
		}

		public void Stop()
		{
			if (_running)
			{
				_running = false;
				_connected = false;
				_status = "stopped";
				try
				{
					_cancellation?.Cancel();
				}
				catch
				{
				}
				Thread thread = _thread;
				_thread = null;
				if (thread != null && !thread.Join(TimeSpan.FromSeconds(2.0)))
				{
					_log.Warn("Twitch thread did not stop in time; it is a background thread and will exit on its own.");
				}
				ChatMessage result;
				while (_inbox.TryDequeue(out result))
				{
				}
			}
		}

		public void Dispose()
		{
			Stop();
			_cancellation?.Dispose();
			_cancellation = null;
		}

		private void RunLoop()
		{
			double num = _settings().Twitch.ReconnectDelaySeconds;
			CancellationToken token = _cancellation.Token;
			while (_running && !token.IsCancellationRequested)
			{
				try
				{
					ConnectAndRead(token);
					num = _settings().Twitch.ReconnectDelaySeconds;
				}
				catch (Exception ex)
				{
					_status = "disconnected: " + ex.Message;
					_log.Warn("Twitch connection dropped: " + ex.Message);
				}
				_connected = false;
				if (!_running || token.IsCancellationRequested)
				{
					break;
				}
				if (_authFailed)
				{
					_status = "authentication failed; fix the token and re-enable the mod";
					_log.Error(_status);
					break;
				}
				_status = "reconnecting in " + (int)num + "s";
				if (token.WaitHandle.WaitOne(TimeSpan.FromSeconds(num)))
				{
					break;
				}
				num = Math.Min(num * 2.0, Math.Max(1.0, _settings().Twitch.MaxReconnectDelaySeconds));
			}
			_connected = false;
			if (_status.StartsWith("reconnecting", StringComparison.Ordinal))
			{
				_status = "stopped";
			}
		}

		private void ConnectAndRead(CancellationToken token)
		{
			TwitchSettings twitch = _settings().Twitch;
			string text = (twitch.Channel ?? "").Trim().TrimStart('#').ToLowerInvariant();
			using TcpClient tcpClient = new TcpClient();
			tcpClient.Connect(twitch.Host, twitch.Port);
			tcpClient.ReceiveTimeout = 300000;
			using SslStream stream = OpenTls(tcpClient, twitch);
			using StreamReader streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			using StreamWriter writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
			{
				AutoFlush = true,
				NewLine = "\r\n"
			};
			Login(writer, twitch, text);
			_connected = true;
			_status = "connected to #" + text;
			_log.Info("Twitch chat connected to #" + text + ".");
			string line;
			while (_running && !token.IsCancellationRequested && (line = streamReader.ReadLine()) != null)
			{
				HandleLine(line, writer);
			}
		}

		private SslStream OpenTls(TcpClient tcp, TwitchSettings twitch)
		{
			bool warned = false;
			SslStream sslStream = new SslStream(tcp.GetStream(), leaveInnerStreamOpen: false, delegate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors)
			{
				if (errors == SslPolicyErrors.None)
				{
					return true;
				}
				if (!twitch.AllowInsecureTls)
				{
					return false;
				}
				if (!warned)
				{
					warned = true;
					_log.Warn("Twitch TLS certificate could not be validated (" + errors.ToString() + "). Continuing because Twitch.AllowInsecureTls is true - this is normally Unity's Mono runtime missing a root certificate store, not an attack. Set AllowInsecureTls=false to refuse instead.");
				}
				return true;
			});
			try
			{
				sslStream.AuthenticateAsClient(twitch.Host, null, SslProtocols.Tls12, checkCertificateRevocation: false);
				return sslStream;
			}
			catch
			{
				sslStream.Dispose();
				throw;
			}
		}

		private void Login(StreamWriter writer, TwitchSettings twitch, string channel)
		{
			string text = (twitch.OAuthToken ?? "").Trim();
			string text2 = (twitch.Username ?? "").Trim().ToLowerInvariant();
			if (text.Length > 0)
			{
				if (!text.StartsWith("oauth:", StringComparison.OrdinalIgnoreCase))
				{
					text = "oauth:" + text;
				}
				SecretScrubber.Register(text);
				if (text2.Length == 0)
				{
					throw new InvalidOperationException("Twitch.Username must be set when an OAuth token is configured.");
				}
			}
			else
			{
				text = "SCHMOOPIIE";
				text2 = "justinfan" + new Random().Next(10000, 99999);
				_log.Info("No Twitch OAuth token configured; connecting anonymously (read-only).");
			}
			writer.WriteLine("PASS " + text);
			writer.WriteLine("NICK " + text2);
			writer.WriteLine("CAP REQ :twitch.tv/tags twitch.tv/commands");
			writer.WriteLine("JOIN #" + channel);
		}

		private void HandleLine(string line, StreamWriter writer)
		{
			if (IrcMessageParser.IsPing(line))
			{
				writer.WriteLine(IrcMessageParser.PongFor(line));
				return;
			}
			if (IrcMessageParser.IsAuthFailure(line))
			{
				_authFailed = true;
				throw new AuthenticationException("Twitch rejected the login credentials.");
			}
			if (IrcMessageParser.TryParsePrivmsg(line, DateTimeOffset.UtcNow, out var message))
			{
				if (_inbox.Count >= 200)
				{
					_inbox.TryDequeue(out var _);
				}
				_inbox.Enqueue(message);
			}
		}
	}
}
namespace CipherPeak.Core.Tts
{
	public interface IAudioCache
	{
		bool TryGet(string key, out byte[] audio);

		void Put(string key, byte[] audio);

		void Purge();
	}
	public static class AudioCacheKey
	{
		public static string For(string provider, string voiceId, string text)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(provider + "\0" + voiceId + "\0" + text));
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class NullAudioCache : IAudioCache
	{
		public static readonly NullAudioCache Instance = new NullAudioCache();

		public bool TryGet(string key, out byte[] audio)
		{
			audio = null;
			return false;
		}

		public void Put(string key, byte[] audio)
		{
		}

		public void Purge()
		{
		}
	}
	public sealed class FileAudioCache : IAudioCache
	{
		private readonly string _directory;

		private readonly Func<long> _maxBytes;

		private readonly ILog _log;

		public string Directory => _directory;

		public FileAudioCache(string directory, Func<long> maxBytes, ILog log = null)
		{
			_directory = directory;
			_maxBytes = maxBytes;
			_log = log ?? NullLog.Instance;
			TryEnsureDirectory();
		}

		public bool TryGet(string key, out byte[] audio)
		{
			audio = null;
			try
			{
				string path = PathFor(key);
				if (!File.Exists(path))
				{
					return false;
				}
				audio = File.ReadAllBytes(path);
				File.SetLastAccessTimeUtc(path, DateTime.UtcNow);
				return audio.Length != 0;
			}
			catch (Exception ex)
			{
				_log.Warn("Audio cache read failed: " + ex.Message);
				return false;
			}
		}

		public void Put(string key, byte[] audio)
		{
			if (audio == null || audio.Length == 0)
			{
				return;
			}
			try
			{
				TryEnsureDirectory();
				string text = PathFor(key);
				string text2 = text + ".tmp";
				File.WriteAllBytes(text2, audio);
				if (File.Exists(text))
				{
					File.Delete(text);
				}
				File.Move(text2, text);
				Trim();
			}
			catch (Exception ex)
			{
				_log.Warn("Audio cache write failed: " + ex.Message);
			}
		}

		public void Purge()
		{
			try
			{
				if (System.IO.Directory.Exists(_directory))
				{
					System.IO.Directory.Delete(_directory, recursive: true);
				}
			}
			catch (Exception ex)
			{
				_log.Warn("Audio cache purge failed: " + ex.Message);
			}
		}

		private void Trim()
		{
			long num = _maxBytes();
			if (num <= 0)
			{
				return;
			}
			FileInfo[] files = new DirectoryInfo(_directory).GetFiles("*.mp3");
			long num2 = files.Sum((FileInfo f) => f.Length);
			if (num2 <= num)
			{
				return;
			}
			foreach (FileInfo item in files.OrderBy((FileInfo f) => f.LastAccessTimeUtc))
			{
				if (num2 <= num)
				{
					break;
				}
				try
				{
					num2 -= item.Length;
					item.Delete();
				}
				catch (Exception ex)
				{
					_log.Warn("Audio cache trim failed: " + ex.Message);
				}
			}
		}

		private string PathFor(string key)
		{
			return Path.Combine(_directory, key + ".mp3");
		}

		private void TryEnsureDirectory()
		{
			try
			{
				System.IO.Directory.CreateDirectory(_directory);
			}
			catch (Exception ex)
			{
				_log.Warn("Could not create audio cache directory: " + ex.Message);
			}
		}
	}
	public sealed class ElevenLabsTtsProvider : ITtsProvider
	{
		public const string ProviderName = "elevenlabs";

		private const string BaseUrl = "https://api.elevenlabs.io/v1/text-to-speech/";

		private readonly IHttpTransport _http;

		private readonly Func<ModSettings> _settings;

		public string Name => "elevenlabs";

		public ElevenLabsTtsProvider(IHttpTransport http, Func<ModSettings> settings)
		{
			_http = http;
			_settings = settings;
		}

		public bool IsAvailable(out string reason)
		{
			if (string.IsNullOrWhiteSpace(_settings().Tts.ElevenLabsApiKey))
			{
				reason = "no ElevenLabs API key configured";
				return false;
			}
			reason = null;
			return true;
		}

		public async Task<TtsResult> SynthesizeAsync(string text, string voiceId, CancellationToken cancellationToken)
		{
			if (!IsAvailable(out var reason))
			{
				return TtsResult.Fail(reason);
			}
			if (string.IsNullOrWhiteSpace(voiceId))
			{
				return TtsResult.Fail("no ElevenLabs voice id for that alias");
			}
			if (string.IsNullOrWhiteSpace(text))
			{
				return TtsResult.Fail("empty text");
			}
			TtsSettings tts = _settings().Tts;
			string url = "https://api.elevenlabs.io/v1/text-to-speech/" + Uri.EscapeDataString(voiceId) + "?output_format=" + Uri.EscapeDataString(NonEmpty(tts.ElevenLabsOutputFormat, "mp3_44100_64"));
			Dictionary<string, string> headers = new Dictionary<string, string>
			{
				{ "xi-api-key", tts.ElevenLabsApiKey },
				{ "Accept", "audio/mpeg" }
			};
			byte[] bytes = Encoding.UTF8.GetBytes(BuildRequestJson(text, tts));
			HttpResponse httpResponse = await _http.PostAsync(url, headers, "application/json", bytes, tts.RequestTimeoutSeconds, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (httpResponse.TransportError != null)
			{
				return TtsResult.Fail("ElevenLabs transport failure (" + httpResponse.TransportError + ")");
			}
			if (httpResponse.IsRateLimited)
			{
				return TtsResult.Fail("ElevenLabs rate limited (429)", (httpResponse.RetryAfterSeconds > 0.0) ? httpResponse.RetryAfterSeconds : 5.0);
			}
			if (!httpResponse.IsSuccess)
			{
				return TtsResult.Fail("ElevenLabs HTTP " + httpResponse.StatusCode + " " + Describe(httpResponse.StatusCode), httpResponse.RetryAfterSeconds);
			}
			if (httpResponse.Body == null || httpResponse.Body.Length == 0)
			{
				return TtsResult.Fail("ElevenLabs returned an empty body");
			}
			if (httpResponse.Body[0] == 123 || (httpResponse.ContentType ?? "").IndexOf("json", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return TtsResult.Fail("ElevenLabs returned JSON instead of audio");
			}
			if (httpResponse.Body.Length > _settings().Tts.MaxAudioBytes)
			{
				return TtsResult.Fail("ElevenLabs audio exceeded MaxAudioBytes (" + httpResponse.Body.Length + " bytes)");
			}
			return TtsResult.Ok(httpResponse.Body);
		}

		private static string BuildRequestJson(string text, TtsSettings tts)
		{
			StringBuilder stringBuilder = new StringBuilder(text.Length + 160);
			stringBuilder.Append("{\"text\":").Append(JsonString(text));
			stringBuilder.Append(",\"model_id\":").Append(JsonString(NonEmpty(tts.ElevenLabsModelId, "eleven_multilingual_v2")));
			stringBuilder.Append(",\"voice_settings\":{\"stability\":").Append(tts.ElevenLabsStability.ToString("0.###", CultureInfo.InvariantCulture)).Append(",\"similarity_boost\":")
				.Append(tts.ElevenLabsSimilarityBoost.ToString("0.###", CultureInfo.InvariantCulture))
				.Append("}}");
			return stringBuilder.ToString();
		}

		private static string NonEmpty(string value, string fallback)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value;
			}
			return fallback;
		}

		private static string Describe(int status)
		{
			switch (status)
			{
			case 401:
				return "(bad or missing API key)";
			case 403:
				return "(key lacks permission for this voice)";
			case 404:
				return "(voice id not found)";
			case 422:
				return "(request rejected, check model id and text)";
			default:
				if (status < 500)
				{
					return "";
				}
				return "(provider outage)";
			}
		}

		internal static string JsonString(string value)
		{
			StringBuilder stringBuilder = new StringBuilder(value.Length + 2);
			stringBuilder.Append('"');
			foreach (char c in value)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\b':
					stringBuilder.Append("\\b");
					continue;
				case '\f':
					stringBuilder.Append("\\f");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			stringBuilder.Append('"');
			return stringBuilder.ToString();
		}
	}
	public sealed class TtsResult
	{
		public byte[] Audio;

		public string Format = "mp3";

		public string Error;

		public double RetryAfterSeconds;

		public bool Success
		{
			get
			{
				if (Error == null && Audio != null)
				{
					return Audio.Length != 0;
				}
				return false;
			}
		}

		public static TtsResult Ok(byte[] audio, string format = "mp3")
		{
			return new TtsResult
			{
				Audio = audio,
				Format = format
			};
		}

		public static TtsResult Fail(string error, double retryAfterSeconds = 0.0)
		{
			return new TtsResult
			{
				Error = error,
				RetryAfterSeconds = retryAfterSeconds
			};
		}
	}
	public interface ITtsProvider
	{
		string Name { get; }

		bool IsAvailable(out string reason);

		Task<TtsResult> SynthesizeAsync(string text, string voiceId, CancellationToken cancellationToken);
	}
	public sealed class TikTokTtsProvider : ITtsProvider
	{
		public const string ProviderName = "tiktok";

		private readonly IHttpTransport _http;

		private readonly Func<ModSettings> _settings;

		public string Name => "tiktok";

		public TikTokTtsProvider(IHttpTransport http, Func<ModSettings> settings)
		{
			_http = http;
			_settings = settings;
		}

		public bool IsAvailable(out string reason)
		{
			TtsSettings tts = _settings().Tts;
			if (string.IsNullOrWhiteSpace(tts.TikTokEndpoint))
			{
				reason = "TikTok has no public, permitted TTS API; set Tts.TikTokEndpoint to your own proxy to enable it (see README, TikTok limitation)";
				return false;
			}
			if (string.IsNullOrWhiteSpace(tts.TikTokSessionId))
			{
				reason = "TikTok endpoint configured but TikTokSessionId is empty";
				return false;
			}
			reason = null;
			return true;
		}

		public async Task<TtsResult> SynthesizeAsync(string text, string voiceId, CancellationToken cancellationToken)
		{
			if (!IsAvailable(out var reason))
			{
				return TtsResult.Fail(reason);
			}
			if (string.IsNullOrWhiteSpace(voiceId))
			{
				return TtsResult.Fail("no TikTok voice id for that alias");
			}
			if (string.IsNullOrWhiteSpace(text))
			{
				return TtsResult.Fail("empty text");
			}
			TtsSettings tts = _settings().Tts;
			string url = tts.TikTokEndpoint + ((tts.TikTokEndpoint.IndexOf('?') >= 0) ? "&" : "?") + "text_speaker=" + Uri.EscapeDataString(voiceId) + "&req_text=" + Uri.EscapeDataString(text);
			Dictionary<string, string> headers = new Dictionary<string, string> { 
			{
				"Cookie",
				"sessionid=" + tts.TikTokSessionId
			} };
			HttpResponse httpResponse = await _http.PostAsync(url, headers, "application/x-www-form-urlencoded", Array.Empty<byte>(), tts.RequestTimeoutSeconds, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (httpResponse.TransportError != null)
			{
				return TtsResult.Fail("TikTok transport failure (" + httpResponse.TransportError + ")");
			}
			if (httpResponse.IsRateLimited)
			{
				return TtsResult.Fail("TikTok rate limited (429)", (httpResponse.RetryAfterSeconds > 0.0) ? httpResponse.RetryAfterSeconds : 10.0);
			}
			if (!httpResponse.IsSuccess)
			{
				return TtsResult.Fail("TikTok HTTP " + httpResponse.StatusCode);
			}
			string json;
			try
			{
				json = Encoding.UTF8.GetString(httpResponse.Body ?? Array.Empty<byte>());
			}
			catch (Exception ex)
			{
				return TtsResult.Fail("TikTok response was not text (" + ex.Message + ")");
			}
			string text2 = ExtractVStr(json);
			if (text2 == null)
			{
				return TtsResult.Fail("TikTok returned a malformed response (no data.v_str); the private endpoint has most likely changed or revoked access");
			}
			byte[] array;
			try
			{
				array = Convert.FromBase64String(text2);
			}
			catch (FormatException)
			{
				return TtsResult.Fail("TikTok returned malformed base64 audio");
			}
			if (array.Length == 0)
			{
				return TtsResult.Fail("TikTok returned empty audio");
			}
			if (array.Length > tts.MaxAudioBytes)
			{
				return TtsResult.Fail("TikTok audio exceeded MaxAudioBytes (" + array.Length + " bytes)");
			}
			return TtsResult.Ok(array);
		}

		internal static string ExtractVStr(string json)
		{
			if (string.IsNullOrEmpty(json))
			{
				return null;
			}
			int num = json.IndexOf("\"v_str\"", StringComparison.Ordinal);
			if (num < 0)
			{
				return null;
			}
			int num2 = json.IndexOf(':', num + "\"v_str\"".Length);
			if (num2 < 0)
			{
				return null;
			}
			int num3 = json.IndexOf('"', num2 + 1);
			if (num3 < 0)
			{
				return null;
			}
			int num4 = json.IndexOf('"', num3 + 1);
			if (num4 < 0)
			{
				return null;
			}
			string text = json.Substring(num3 + 1, num4 - num3 - 1);
			if (text.Length != 0)
			{
				return text;
			}
			return null;
		}
	}
	public sealed class TtsRouter
	{
		private struct Attempt
		{
			public ITtsProvider Provider;

			public string VoiceId;
		}

		private readonly Func<ModSettings> _settings;

		private readonly VoiceRegistry _voices;

		private readonly IReadOnlyList<ITtsProvider> _providers;

		private readonly IAudioCache _cache;

		private readonly ILog _log;

		private readonly Dictionary<string, DateTimeOffset> _cooldownUntil = new Dictionary<string, DateTimeOffset>(StringComparer.OrdinalIgnoreCase);

		public TtsRouter(Func<ModSettings> settings, VoiceRegistry voices, IReadOnlyList<ITtsProvider> providers, IAudioCache cache = null, ILog log = null)
		{
			_settings = settings;
			_voices = voices;
			_providers = providers;
			_cache = cache ?? NullAudioCache.Instance;
			_log = log ?? NullLog.Instance;
		}

		public async Task<TtsResult> SynthesizeAsync(string text, string alias, CancellationToken cancellationToken)
		{
			TtsSettings tts = _settings().Tts;
			if (!_voices.TryResolve(alias, out var voice) && !_voices.TryResolve(tts.DefaultVoiceAlias, out voice))
			{
				return TtsResult.Fail("no usable voice alias configured");
			}
			string cacheKey = AudioCacheKey.For(voice.Provider, voice.VoiceId, text);
			if (tts.CacheEnabled && _cache.TryGet(cacheKey, out var audio))
			{
				return TtsResult.Ok(audio);
			}
			List<Attempt> list = ProviderOrder(voice);
			TtsResult ttsResult = TtsResult.Fail("no TTS provider available");
			foreach (Attempt attempt in list)
			{
				ttsResult = await CallWithRetries(attempt.Provider, text, attempt.VoiceId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				if (ttsResult.Success)
				{
					if (tts.CacheEnabled)
					{
						_cache.Put(cacheKey, ttsResult.Audio);
					}
					return ttsResult;
				}
				_log.Warn("TTS provider '" + attempt.Provider.Name + "' failed: " + ttsResult.Error);
				if (!tts.FallbackToOtherProvider)
				{
					break;
				}
			}
			return ttsResult;
		}

		private List<Attempt> ProviderOrder(VoiceAlias voice)
		{
			List<Attempt> list = new List<Attempt>();
			DateTimeOffset utcNow = DateTimeOffset.UtcNow;
			foreach (ITtsProvider provider in _providers)
			{
				if (provider == null)
				{
					continue;
				}
				bool flag = string.Equals(provider.Name, voice.Provider, StringComparison.OrdinalIgnoreCase);
				if (!provider.IsAvailable(out var reason))
				{
					if (flag)
					{
						_log.Warn("TTS provider '" + provider.Name + "' unavailable: " + reason);
					}
					continue;
				}
				if (_cooldownUntil.TryGetValue(provider.Name, out var value) && utcNow < value)
				{
					_log.Info("Skipping '" + provider.Name + "', rate limited for another " + (int)(value - utcNow).TotalSeconds + "s");
					continue;
				}
				string text = (flag ? voice.VoiceId : FallbackVoiceIdFor(provider.Name));
				if (!string.IsNullOrEmpty(text))
				{
					Attempt item = new Attempt
					{
						Provider = provider,
						VoiceId = text
					};
					if (flag)
					{
						list.Insert(0, item);
					}
					else
					{
						list.Add(item);
					}
				}
			}
			return list;
		}

		private string FallbackVoiceIdFor(string providerName)
		{
			List<VoiceAlias> voiceAliases = _settings().Tts.VoiceAliases;
			if (voiceAliases == null)
			{
				return null;
			}
			for (int i = 0; i < voiceAliases.Count; i++)
			{
				if (voiceAliases[i] != null && string.Equals(voiceAliases[i].Provider, providerName, StringComparison.OrdinalIgnoreCase))
				{
					return voiceAliases[i].VoiceId;
				}
			}
			return null;
		}

		private async Task<TtsResult> CallWithRetries(ITtsProvider provider, string text, string voiceId, CancellationToken cancellationToken)
		{
			int maxRetries = Math.Max(0, _settings().Tts.MaxRetries);
			TtsResult result = TtsResult.Fail("not attempted");
			for (int attempt = 0; attempt <= maxRetries; attempt++)
			{
				if (cancellationToken.IsCancellationRequested)
				{
					return TtsResult.Fail("cancelled");
				}
				try
				{
					result = await provider.SynthesizeAsync(text, voiceId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
				}
				catch (Exception ex)
				{
					result = TtsResult.Fail(provider.Name + " threw " + ex.GetType().Name + ": " + ex.Message);
				}
				if (result.Success)
				{
					return result;
				}
				if (result.RetryAfterSeconds > 0.0)
				{
					_cooldownUntil[provider.Name] = DateTimeOffset.UtcNow.AddSeconds(result.RetryAfterSeconds);
				}
				if (attempt == maxRetries)
				{
					break;
				}
				double num = Math.Max(result.RetryAfterSeconds, Math.Pow(2.0, attempt) * 0.5);
				if (num > 0.0)
				{
					try
					{
						await Task.Delay(TimeSpan.FromSeconds(Math.Min(num, 10.0)), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
					}
					catch (OperationCanceledException)
					{
						return TtsResult.Fail("cancelled");
					}
				}
			}
			return result;
		}
	}
	public sealed class VoiceRegistry
	{
		private readonly Func<ModSettings> _settings;

		private readonly Dictionary<string, string> _userChoice = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		public VoiceRegistry(Func<ModSettings> settings)
		{
			_settings = settings;
		}

		public bool TryResolve(string alias, out VoiceAlias voice)
		{
			voice = null;
			if (string.IsNullOrWhiteSpace(alias))
			{
				return false;
			}
			List<VoiceAlias> voiceAliases = _settings().Tts.VoiceAliases;
			if (voiceAliases == null)
			{
				return false;
			}
			for (int i = 0; i < voiceAliases.Count; i++)
			{
				if (voiceAliases[i] != null && string.Equals(voiceAliases[i].Alias, alias.Trim(), StringComparison.OrdinalIgnoreCase))
				{
					voice = voiceAliases[i];
					return true;
				}
			}
			return false;
		}

		public IReadOnlyList<string> AllowedAliases()
		{
			List<string> list = new List<string>();
			List<VoiceAlias> voiceAliases = _settings().Tts.VoiceAliases;
			if (voiceAliases == null)
			{
				return list;
			}
			for (int i = 0; i < voiceAliases.Count; i++)
			{
				if (voiceAliases[i] != null)
				{
					list.Add(voiceAliases[i].Alias);
				}
			}
			return list;
		}

		public bool TrySetUserVoice(string login, string alias)
		{
			if (!TryResolve(alias, out var voice))
			{
				return false;
			}
			_userChoice[login ?? ""] = voice.Alias;
			return true;
		}

		public void ClearUserVoices()
		{
			_userChoice.Clear();
		}

		public string AliasFor(string login)
		{
			TtsSettings tts = _settings().Tts;
			if (tts.AllowVoiceCommand && login != null && _userChoice.TryGetValue(login, out var value))
			{
				return value;
			}
			if (TryResolve(tts.DefaultVoiceAlias, out var voice))
			{
				return voice.Alias;
			}
			List<VoiceAlias> voiceAliases = tts.VoiceAliases;
			if (voiceAliases == null || voiceAliases.Count == 0)
			{
				return "";
			}
			for (int i = 0; i < voiceAliases.Count; i++)
			{
				if (voiceAliases[i] != null && string.Equals(voiceAliases[i].Provider, tts.DefaultProvider, StringComparison.OrdinalIgnoreCase))
				{
					return voiceAliases[i].Alias;
				}
			}
			return voiceAliases[0].Alias;
		}
	}
}
namespace CipherPeak.Core.Speech
{
	public sealed class SpeakerRotation
	{
		private int _lastIndex = -1;

		public int Next(IReadOnlyList<bool> available)
		{
			if (available == null || available.Count == 0)
			{
				return -1;
			}
			for (int i = 1; i <= available.Count; i++)
			{
				int num = (_lastIndex + i) % available.Count;
				if (num < 0)
				{
					num += available.Count;
				}
				if (available[num])
				{
					_lastIndex = num;
					return num;
				}
			}
			return -1;
		}

		public void Reset()
		{
			_lastIndex = -1;
		}
	}
}
namespace CipherPeak.Core.Queueing
{
	public enum EnqueueVerdict
	{
		Queued,
		QueueFull,
		UserCooldown,
		GlobalCooldown,
		Disabled
	}
	public sealed class SpeechQueue
	{
		private readonly Func<ModSettings> _settings;

		private readonly Queue<SpeechRequest> _items = new Queue<SpeechRequest>();

		private readonly Dictionary<string, DateTimeOffset> _lastAcceptedPerUser = new Dictionary<string, DateTimeOffset>(StringComparer.OrdinalIgnoreCase);

		private DateTimeOffset _lastAcceptedAny = DateTimeOffset.MinValue;

		private int _nextId = 1;

		public int Count => _items.Count;

		public SpeechQueue(Func<ModSettings> settings)
		{
			_settings = settings ?? throw new ArgumentNullException("settings");
		}

		public SpeechQueue(ModSettings settings)
			: this(() => settings)
		{
		}

		public EnqueueVerdict TryEnqueue(string login, string displayName, string text, string voiceAlias, DateTimeOffset now, out SpeechRequest request)
		{
			request = null;
			ModSettings modSettings = _settings();
			if (!modSettings.Enabled)
			{
				return EnqueueVerdict.Disabled;
			}
			QueueSettings queue = modSettings.Queue;
			if (queue.MaxQueuedMessages > 0 && _items.Count >= queue.MaxQueuedMessages)
			{
				return EnqueueVerdict.QueueFull;
			}
			if (queue.GlobalCooldownSeconds > 0.0 && _lastAcceptedAny != DateTimeOffset.MinValue && (now - _lastAcceptedAny).TotalSeconds < queue.GlobalCooldownSeconds)
			{
				return EnqueueVerdict.GlobalCooldown;
			}
			if (queue.PerUserCooldownSeconds > 0.0 && !string.IsNullOrEmpty(login) && _lastAcceptedPerUser.TryGetValue(login, out var value) && (now - value).TotalSeconds < queue.PerUserCooldownSeconds)
			{
				return EnqueueVerdict.UserCooldown;
			}
			request = new SpeechRequest
			{
				Id = _nextId++,
				Login = (login ?? ""),
				DisplayName = (string.IsNullOrEmpty(displayName) ? (login ?? "") : displayName),
				Text = (text ?? ""),
				VoiceAlias = (voiceAlias ?? ""),
				QueuedAt = now
			};
			_items.Enqueue(request);
			_lastAcceptedAny = now;
			if (!string.IsNullOrEmpty(login))
			{
				_lastAcceptedPerUser[login] = now;
			}
			return EnqueueVerdict.Queued;
		}

		public bool TryDequeue(out SpeechRequest request)
		{
			if (_items.Count == 0)
			{
				request = null;
				return false;
			}
			request = _items.Dequeue();
			return true;
		}

		public int Clear()
		{
			int count = _items.Count;
			_items.Clear();
			return count;
		}

		public void Reset()
		{
			_items.Clear();
			_lastAcceptedPerUser.Clear();
			_lastAcceptedAny = DateTimeOffset.MinValue;
		}
	}
	public sealed class SpeechRequest
	{
		public int Id;

		public string Login = "";

		public string DisplayName = "";

		public string Text = "";

		public string VoiceAlias = "";

		public DateTimeOffset QueuedAt;

		public override string ToString()
		{
			return "#" + Id + " " + Login + ": " + Text;
		}
	}
}
namespace CipherPeak.Core.Net
{
	public static class AudioChunker
	{
		public const int DefaultChunkSize = 12000;

		public static List<byte[]> Split(byte[] audio, int chunkSize = 12000)
		{
			List<byte[]> list = new List<byte[]>();
			if (audio == null || audio.Length == 0)
			{
				return list;
			}
			if (chunkSize <= 0)
			{
				chunkSize = 12000;
			}
			for (int i = 0; i < audio.Length; i += chunkSize)
			{
				int num = Math.Min(chunkSize, audio.Length - i);
				byte[] array = new byte[num];
				Buffer.BlockCopy(audio, i, array, 0, num);
				list.Add(array);
			}
			return list;
		}
	}
	public sealed class AudioReassembler
	{
		private sealed class Pending
		{
			public byte[][] Chunks;

			public int Received;

			public int SpeakerId;

			public long Sequence;
		}

		private readonly int _maxConcurrent;

		private readonly Dictionary<int, Pending> _pending = new Dictionary<int, Pending>();

		private long _sequence;

		public int PendingCount => _pending.Count;

		public AudioReassembler(int maxConcurrent = 4)
		{
			_maxConcurrent = Math.Max(1, maxConcurrent);
		}

		public bool Accept(int messageId, int speakerId, int chunkIndex, int chunkCount, byte[] data, out byte[] audio, out int completedSpeakerId)
		{
			audio = null;
			completedSpeakerId = 0;
			if (chunkCount <= 0 || chunkIndex < 0 || chunkIndex >= chunkCount || data == null)
			{
				return false;
			}
			if (!_pending.TryGetValue(messageId, out var value))
			{
				EvictOldestIfFull();
				value = new Pending
				{
					Chunks = new byte[chunkCount][],
					SpeakerId = speakerId,
					Sequence = ++_sequence
				};
				_pending[messageId] = value;
			}
			if (value.Chunks.Length != chunkCount)
			{
				return false;
			}
			if (value.Chunks[chunkIndex] != null)
			{
				return false;
			}
			value.Chunks[chunkIndex] = data;
			value.Received++;
			if (value.Received < chunkCount)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < chunkCount; i++)
			{
				num += value.Chunks[i].Length;
			}
			audio = new byte[num];
			int num2 = 0;
			for (int j = 0; j < chunkCount; j++)
			{
				Buffer.BlockCopy(value.Chunks[j], 0, audio, num2, value.Chunks[j].Length);
				num2 += value.Chunks[j].Length;
			}
			completedSpeakerId = value.SpeakerId;
			_pending.Remove(messageId);
			return true;
		}

		public void Clear()
		{
			_pending.Clear();
		}

		private void EvictOldestIfFull()
		{
			while (_pending.Count >= _maxConcurrent)
			{
				int key = 0;
				long num = long.MaxValue;
				foreach (KeyValuePair<int, Pending> item in _pending)
				{
					if (item.Value.Sequence < num)
					{
						num = item.Value.Sequence;
						key = item.Key;
					}
				}
				_pending.Remove(key);
			}
		}
	}
	public sealed class HttpClientTransport : IHttpTransport, IDisposable
	{
		private readonly HttpClient _client;

		public HttpClientTransport()
		{
			_client = new HttpClient
			{
				Timeout = Timeout.InfiniteTimeSpan
			};
			_client.DefaultRequestHeaders.Add("User-Agent", "CipherPeak-BingBongTTS/1.0");
		}

		public async Task<HttpResponse> PostAsync(string url, IReadOnlyDictionary<string, string> headers, string contentType, byte[] body, int timeoutSeconds, CancellationToken cancellationToken)
		{
			HttpResponse result = new HttpResponse();
			using (CancellationTokenSource timeout = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { cancellationToken }))
			{
				timeout.CancelAfter(TimeSpan.FromSeconds(Math.Max(1, timeoutSeconds)));
				try
				{
					using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
					request.Content = new ByteArrayContent(body ?? Array.Empty<byte>());
					if (!string.IsNullOrEmpty(contentType))
					{
						request.Content.Headers.TryAddWithoutValidation("Content-Type", contentType);
					}
					if (headers != null)
					{
						foreach (KeyValuePair<string, string> header in headers)
						{
							request.Headers.TryAddWithoutValidation(header.Key, header.Value);
						}
					}
					using HttpResponseMessage response = await _client.SendAsync(request, HttpCompletionOption.ResponseContentRead, timeout.Token).ConfigureAwait(continueOnCapturedContext: false);
					result.StatusCode = (int)response.StatusCode;
					HttpResponse httpResponse = result;
					httpResponse.Body = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext: false);
					result.ContentType = response.Content.Headers.ContentType?.MediaType ?? "";
					RetryConditionHeaderValue retryAfter = response.Headers.RetryAfter;
					if (retryAfter != null)
					{
						if (retryAfter.Delta.HasValue)
						{
							result.RetryAfterSeconds = retryAfter.Delta.Value.TotalSeconds;
						}
						else if (retryAfter.Date.HasValue)
						{
							result.RetryAfterSeconds = Math.Max(0.0, (retryAfter.Date.Value - DateTimeOffset.UtcNow).TotalSeconds);
						}
					}
				}
				catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
				{
					result.TransportError = "timeout after " + timeoutSeconds + "s";
				}
				catch (OperationCanceledException)
				{
					result.TransportError = "cancelled";
				}
				catch (Exception ex3)
				{
					result.TransportError = ex3.GetType().Name + ": " + ex3.Message;
				}
			}
			return result;
		}

		public void Dispose()
		{
			_client.Dispose();
		}
	}
	public sealed class HttpResponse
	{
		public int StatusCode;

		public byte[] Body;

		public string ContentType = "";

		public double RetryAfterSeconds;

		public string TransportError;

		public bool IsSuccess
		{
			get
			{
				if (TransportError == null && StatusCode >= 200)
				{
					return StatusCode < 300;
				}
				return false;
			}
		}

		public bool IsRateLimited => StatusCode == 429;

		public bool IsRetryable
		{
			get
			{
				if (TransportError == null && StatusCode < 500)
				{
					return StatusCode == 429;
				}
				return true;
			}
		}
	}
	public interface IHttpTransport
	{
		Task<HttpResponse> PostAsync(string url, IReadOnlyDictionary<string, string> headers, string contentType, byte[] body, int timeoutSeconds, CancellationToken cancellationToken);
	}
}
namespace CipherPeak.Core.Logging
{
	public interface ILog
	{
		void Info(string message);

		void Warn(string message);

		void Error(string message);
	}
	public sealed class NullLog : ILog
	{
		public static readonly NullLog Instance = new NullLog();

		public void Info(string message)
		{
		}

		public void Warn(string message)
		{
		}

		public void Error(string message)
		{
		}
	}
	public sealed class DelegateLog : ILog
	{
		private readonly Action<string> _info;

		private readonly Action<string> _warn;

		private readonly Action<string> _error;

		public DelegateLog(Action<string> info, Action<string> warn, Action<string> error)
		{
			_info = info;
			_warn = warn;
			_error = error;
		}

		public void Info(string message)
		{
			_info?.Invoke(message);
		}

		public void Warn(string message)
		{
			_warn?.Invoke(message);
		}

		public void Error(string message)
		{
			_error?.Invoke(message);
		}
	}
	public static class SecretScrubber
	{
		private static readonly List<string> Secrets = new List<string>();

		public static void Register(string secret)
		{
			if (string.IsNullOrEmpty(secret) || secret.Length < 6)
			{
				return;
			}
			lock (Secrets)
			{
				if (!Secrets.Contains(secret))
				{
					Secrets.Add(secret);
				}
			}
		}

		public static void Clear()
		{
			lock (Secrets)
			{
				Secrets.Clear();
			}
		}

		public static string Scrub(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text;
			}
			lock (Secrets)
			{
				for (int i = 0; i < Secrets.Count; i++)
				{
					text = text.Replace(Secrets[i], "<redacted>");
				}
				return text;
			}
		}
	}
	public sealed class ScrubbingLog : ILog
	{
		private readonly ILog _inner;

		public ScrubbingLog(ILog inner)
		{
			_inner = inner ?? NullLog.Instance;
		}

		public void Info(string message)
		{
			_inner.Info(SecretScrubber.Scrub(message));
		}

		public void Warn(string message)
		{
			_inner.Warn(SecretScrubber.Scrub(message));
		}

		public void Error(string message)
		{
			_inner.Error(SecretScrubber.Scrub(message));
		}
	}
}
namespace CipherPeak.Core.Ingestion
{
	public enum IngestOutcome
	{
		Queued,
		Command,
		Filtered,
		Rejected,
		Disabled
	}
	public sealed class ChatPipeline
	{
		private readonly Func<ModSettings> _settings;

		private readonly ChatCommandProcessor _commands;

		private readonly MessageFilter _filter;

		private readonly SpeechQueue _queue;

		private readonly VoiceRegistry _voices;

		private readonly ILog _log;

		public ChatPipeline(Func<ModSettings> settings, ChatCommandProcessor commands, MessageFilter filter, SpeechQueue queue, VoiceRegistry voices, ILog log = null)
		{
			_settings = settings;
			_commands = commands;
			_filter = filter;
			_queue = queue;
			_voices = voices;
			_log = log ?? NullLog.Instance;
		}

		public IngestOutcome Handle(ChatMessage message, DateTimeOffset now, out SpeechRequest request)
		{
			request = null;
			if (message == null)
			{
				return IngestOutcome.Rejected;
			}
			if (!_settings().Enabled)
			{
				return IngestOutcome.Disabled;
			}
			if (_commands.Process(message) != CommandOutcome.NotACommand)
			{
				return IngestOutcome.Command;
			}
			FilterResult filterResult = _filter.Evaluate(message);
			if (!filterResult.Accepted)
			{
				_log.Info("Dropped message from " + message.Login + ": " + filterResult.Verdict);
				return IngestOutcome.Filtered;
			}
			string voiceAlias = _voices.AliasFor(message.Login);
			EnqueueVerdict enqueueVerdict = _queue.TryEnqueue(message.Login, message.DisplayName, filterResult.Text, voiceAlias, now, out request);
			if (enqueueVerdict == EnqueueVerdict.Queued)
			{
				_log.Info("Queued a message from " + message.Login + ".");
				return IngestOutcome.Queued;
			}
			_log.Info("Not queued (" + enqueueVerdict.ToString() + ") for " + message.Login + ".");
			if (enqueueVerdict != EnqueueVerdict.Disabled)
			{
				return IngestOutcome.Rejected;
			}
			return IngestOutcome.Disabled;
		}
	}
}
namespace CipherPeak.Core.Filtering
{
	public enum FilterVerdict
	{
		Accepted,
		Empty,
		TooShort,
		TooLong,
		Command,
		IgnoredUser,
		BlockedWord,
		Profanity,
		ContainsLink,
		Duplicate,
		NotSubscriber,
		NotModerator
	}
	public struct FilterResult
	{
		public FilterVerdict Verdict;

		public string Text;

		public bool Accepted => Verdict == FilterVerdict.Accepted;

		public static FilterResult Reject(FilterVerdict verdict)
		{
			return new FilterResult
			{
				Verdict = verdict,
				Text = null
			};
		}

		public static FilterResult Accept(string text)
		{
			return new FilterResult
			{
				Verdict = FilterVerdict.Accepted,
				Text = text
			};
		}
	}
	public sealed class MessageFilter
	{
		private static readonly Regex LinkPattern = new Regex("(https?://|www\\.)\\S+|\\b[a-z0-9\\-]+\\.(com|net|org|io|gg|tv|co|me|ru|xyz|link|be|ly)\\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Regex Whitespace = new Regex("\\s+", RegexOptions.Compiled);

		private readonly Func<ModSettings> _settings;

		private readonly Queue<string> _recent = new Queue<string>();

		private readonly HashSet<string> _recentSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private ProfanityMatcher _profanity;

		private string _profanityKey;

		public MessageFilter(Func<ModSettings> settings)
		{
			_settings = settings ?? throw new ArgumentNullException("settings");
		}

		public MessageFilter(ModSettings settings)
			: this(() => settings)
		{
		}

		public void Reset()
		{
			_recent.Clear();
			_recentSet.Clear();
		}

		public FilterResult Evaluate(ChatMessage message)
		{
			if (message == null)
			{
				return FilterResult.Reject(FilterVerdict.Empty);
			}
			FilterSettings filter = _settings().Filter;
			string text = Whitespace.Replace(message.Text ?? "", " ").Trim();
			if (text.Length == 0)
			{
				return FilterResult.Reject(FilterVerdict.Empty);
			}
			if (Contains(filter.IgnoredUsers, message.Login))
			{
				return FilterResult.Reject(FilterVerdict.IgnoredUser);
			}
			if (StartsWithAny(text, filter.CommandPrefixes))
			{
				return FilterResult.Reject(FilterVerdict.Command);
			}
			if (!filter.ModeratorsBypassLimits || !message.HasElevatedRole)
			{
				if (filter.ModeratorsOnly && !message.HasElevatedRole)
				{
					return FilterResult.Reject(FilterVerdict.NotModerator);
				}
				if (filter.SubscribersOnly && !message.IsSubscriber && !message.HasElevatedRole)
				{
					return FilterResult.Reject(FilterVerdict.NotSubscriber);
				}
				if (text.Length < Math.Max(1, filter.MinMessageLength))
				{
					return FilterResult.Reject(FilterVerdict.TooShort);
				}
				if (filter.MaxMessageLength > 0 && text.Length > filter.MaxMessageLength)
				{
					return FilterResult.Reject(FilterVerdict.TooLong);
				}
			}
			if (filter.BlockLinks && LinkPattern.IsMatch(text))
			{
				return FilterResult.Reject(FilterVerdict.ContainsLink);
			}
			if (ContainsBlockedWord(text, filter.BlockedWords))
			{
				return FilterResult.Reject(FilterVerdict.BlockedWord);
			}
			if (filter.Profanity != ProfanityAction.Off)
			{
				ProfanityMatcher matcher = GetMatcher(filter);
				if (filter.Profanity == ProfanityAction.Block && matcher.IsMatch(text))
				{
					return FilterResult.Reject(FilterVerdict.Profanity);
				}
				if (filter.Profanity == ProfanityAction.Mask)
				{
					text = Whitespace.Replace(matcher.Mask(text, filter.ProfanityMask), " ").Trim();
					if (text.Length == 0)
					{
						return FilterResult.Reject(FilterVerdict.Empty);
					}
				}
			}
			if (filter.DuplicateHistorySize > 0)
			{
				string text2 = message.Login + "\0" + text.ToLowerInvariant();
				if (_recentSet.Contains(text2))
				{
					return FilterResult.Reject(FilterVerdict.Duplicate);
				}
				Remember(text2, filter.DuplicateHistorySize);
			}
			return FilterResult.Accept(text);
		}

		private ProfanityMatcher GetMatcher(FilterSettings f)
		{
			string text = ((f.ProfanityWords == null) ? "" : string.Join("\n", f.ProfanityWords.ToArray()));
			if (_profanity == null || text != _profanityKey)
			{
				_profanity = new ProfanityMatcher(f.ProfanityWords);
				_profanityKey = text;
			}
			return _profanity;
		}

		private void Remember(string key, int capacity)
		{
			_recent.Enqueue(key);
			_recentSet.Add(key);
			while (_recent.Count > capacity)
			{
				_recentSet.Remove(_recent.Dequeue());
			}
		}

		private static bool Contains(List<string> list, string value)
		{
			if (list == null || value == null)
			{
				return false;
			}
			for (int i = 0; i < list.Count; i++)
			{
				if (string.Equals(list[i], value, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static bool StartsWithAny(string text, List<string> prefixes)
		{
			if (prefixes == null)
			{
				return false;
			}
			for (int i = 0; i < prefixes.Count; i++)
			{
				string value = prefixes[i];
				if (!string.IsNullOrEmpty(value) && text.StartsWith(value, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static bool ContainsBlockedWord(string text, List<string> blocked)
		{
			if (blocked == null || blocked.Count == 0)
			{
				return false;
			}
			for (int i = 0; i < blocked.Count; i++)
			{
				string text2 = blocked[i];
				if (!string.IsNullOrWhiteSpace(text2) && text.IndexOf(text2.Trim(), StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}
	}
	public enum ProfanityAction
	{
		Off,
		Mask,
		Block
	}
	public sealed class ProfanityMatcher
	{
		public static readonly string[] DefaultWords = new string[26]
		{
			"fuck", "shit", "bitch", "cunt", "asshole", "arsehole", "bastard", "dick", "cock", "pussy",
			"whore", "slut", "wank", "twat", "prick", "damn", "crap", "nigger", "nigga", "faggot",
			"fag", "retard", "tranny", "spic", "kike", "chink"
		};

		private const string SuffixGroup = "[a-z]?(?:s|es|ed|ing|er|ers|ry|y|ies|head|hole)?";

		private readonly Regex _pattern;

		public bool IsEmpty => _pattern == null;

		public ProfanityMatcher(IEnumerable<string> extraWords, bool includeDefaults = true)
		{
			List<string> list = new List<string>();
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (includeDefaults)
			{
				string[] defaultWords = DefaultWords;
				foreach (string word in defaultWords)
				{
					Add(list, seen, word);
				}
			}
			if (extraWords != null)
			{
				foreach (string extraWord in extraWords)
				{
					Add(list, seen, extraWord);
				}
			}
			if (list.Count == 0)
			{
				_pattern = null;
			}
			else
			{
				_pattern = new Regex("\\b(?:" + string.Join("|", list.ToArray()) + ")[a-z]?(?:s|es|ed|ing|er|ers|ry|y|ies|head|hole)?\\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
			}
		}

		public bool IsMatch(string text)
		{
			if (_pattern != null && !string.IsNullOrEmpty(text))
			{
				return _pattern.IsMatch(text);
			}
			return false;
		}

		public string Mask(string text, string replacement)
		{
			if (_pattern == null || string.IsNullOrEmpty(text))
			{
				return text;
			}
			return _pattern.Replace(text, replacement ?? "");
		}

		private static void Add(List<string> stems, HashSet<string> seen, string word)
		{
			if (!string.IsNullOrWhiteSpace(word))
			{
				word = word.Trim();
				if (seen.Add(word))
				{
					stems.Add(Regex.Escape(word));
				}
			}
		}
	}
}
namespace CipherPeak.Core.Config
{
	public sealed class ModSettings
	{
		public bool Enabled = true;

		public TwitchSettings Twitch = new TwitchSettings();

		public FilterSettings Filter = new FilterSettings();

		public QueueSettings Queue = new QueueSettings();

		public TtsSettings Tts = new TtsSettings();

		public BingBongSettings BingBong = new BingBongSettings();

		public AudioSettings Audio = new AudioSettings();
	}
	public sealed class TwitchSettings
	{
		public string Channel = "";

		public string Username = "";

		public string OAuthToken = "";

		public bool UseMyOwnChat;

		public string Host = "irc.chat.twitch.tv";

		public int Port = 6697;

		public bool AllowInsecureTls = true;

		public double ReconnectDelaySeconds = 3.0;

		public double MaxReconnectDelaySeconds = 120.0;
	}
	public sealed class FilterSettings
	{
		public int MinMessageLength = 2;

		public int MaxMessageLength = 200;

		public List<string> CommandPrefixes = new List<string> { "!", "/", ".", "?" };

		public List<string> IgnoredUsers = new List<string> { "nightbot", "streamelements", "streamlabs", "moobot", "fossabot", "sery_bot" };

		public List<string> BlockedWords = new List<string>();

		public ProfanityAction Profanity;

		public List<string> ProfanityWords = new List<string>();

		public string ProfanityMask = "beep";

		public bool BlockLinks = true;

		public int DuplicateHistorySize = 25;

		public bool SubscribersOnly;

		public bool ModeratorsOnly;

		public bool ModeratorsBypassLimits = true;
	}
	public sealed class QueueSettings
	{
		public int MaxQueuedMessages = 10;

		public double PerUserCooldownSeconds = 20.0;

		public double GlobalCooldownSeconds;

		public double GapBetweenMessagesSeconds = 0.5;
	}
	public sealed class TtsSettings
	{
		public string DefaultProvider = "elevenlabs";

		public string DefaultVoiceAlias = "default";

		public bool AllowVoiceCommand;

		public List<VoiceAlias> VoiceAliases = new List<VoiceAlias>();

		public string ElevenLabsApiKey = "";

		public string ElevenLabsModelId = "eleven_multilingual_v2";

		public string ElevenLabsOutputFormat = "mp3_44100_64";

		public double ElevenLabsStability = 0.5;

		public double ElevenLabsSimilarityBoost = 0.75;

		public string TikTokEndpoint = "";

		public string TikTokSessionId = "";

		public int RequestTimeoutSeconds = 20;

		public int MaxRetries = 2;

		public bool FallbackToOtherProvider = true;

		public bool CacheEnabled = true;

		public int CacheMaxMegabytes = 64;

		public int MaxAudioBytes = 524288;
	}
	public sealed class BingBongSettings
	{
		public string PrefabNameOverride = "";

		public double SpawnRadiusMeters = 3.0;

		public double SpawnHeightOffsetMeters = 1.5;

		public double LifecycleTickSeconds = 3.0;

		public double MaxDistanceFromPlayersMeters;

		public double OutOfBoundsDropMeters;

		public bool Weightless;

		public bool DedicatedSlot = true;

		public double SlotUiOffsetX;
	}
	public sealed class AudioSettings
	{
		public double Volume = 1.0;

		public double MinDistance = 10.0;

		public double MaxDistance = 60.0;

		public bool AnimateMouth = true;
	}
	public sealed class VoiceAlias
	{
		public string Alias;

		public string Provider;

		public string VoiceId;

		public VoiceAlias()
		{
		}

		public VoiceAlias(string alias, string provider, string voiceId)
		{
			Alias = alias;
			Provider = provider;
			VoiceId = voiceId;
		}

		public static VoiceAlias Parse(string line)
		{
			if (string.IsNullOrWhiteSpace(line))
			{
				return null;
			}
			line = line.Trim();
			if (line.StartsWith("#"))
			{
				return null;
			}
			int num = line.IndexOf('=');
			if (num <= 0 || num == line.Length - 1)
			{
				return null;
			}
			string text = line.Substring(0, num).Trim();
			string text2 = line.Substring(num + 1).Trim();
			int num2 = text2.IndexOf(':');
			if (num2 <= 0 || num2 == text2.Length - 1)
			{
				return null;
			}
			string text3 = text2.Substring(0, num2).Trim().ToLowerInvariant();
			string text4 = text2.Substring(num2 + 1).Trim();
			if (text.Length == 0 || text3.Length == 0 || text4.Length == 0)
			{
				return null;
			}
			return new VoiceAlias(text.ToLowerInvariant(), text3, text4);
		}

		public static List<VoiceAlias> ParseAll(IEnumerable<string> lines)
		{
			List<VoiceAlias> list = new List<VoiceAlias>();
			if (lines == null)
			{
				return list;
			}
			foreach (string line in lines)
			{
				VoiceAlias parsed = Parse(line);
				if (parsed != null)
				{
					list.RemoveAll((VoiceAlias v) => string.Equals(v.Alias, parsed.Alias, StringComparison.OrdinalIgnoreCase));
					list.Add(parsed);
				}
			}
			return list;
		}

		public static List<VoiceAlias> ParseCsv(string csv)
		{
			if (string.IsNullOrWhiteSpace(csv))
			{
				return new List<VoiceAlias>();
			}
			return ParseAll(csv.Split(','));
		}
	}
}
namespace CipherPeak.Core.Commands
{
	public enum CommandOutcome
	{
		NotACommand,
		Handled,
		Denied,
		UnknownVoice
	}
	public sealed class ChatCommandProcessor
	{
		private readonly Func<ModSettings> _settings;

		private readonly VoiceRegistry _voices;

		private readonly ILog _log;

		public event Action SkipRequested;

		public event Action ClearRequested;

		public event Action<bool> EnabledChangeRequested;

		public ChatCommandProcessor(Func<ModSettings> settings, VoiceRegistry voices, ILog log = null)
		{
			_settings = settings;
			_voices = voices;
			_log = log ?? NullLog.Instance;
		}

		public CommandOutcome Process(ChatMessage message)
		{
			if (message == null)
			{
				return CommandOutcome.NotACommand;
			}
			string text = (message.Text ?? "").Trim();
			if (text.Length < 2 || text[0] != '!')
			{
				return CommandOutcome.NotACommand;
			}
			string[] array = text.Substring(1).Split(new char[1] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				return CommandOutcome.NotACommand;
			}
			string text2 = array[0].ToLowerInvariant();
			string text3 = ((array.Length > 1) ? array[1] : "");
			if (!(text2 == "voice"))
			{
				if (text2 == "tts")
				{
					return HandleTts(message, text3.ToLowerInvariant());
				}
				return CommandOutcome.NotACommand;
			}
			return HandleVoice(message, text3);
		}

		private CommandOutcome HandleVoice(ChatMessage message, string alias)
		{
			if (!_settings().Tts.AllowVoiceCommand)
			{
				return CommandOutcome.Denied;
			}
			if (alias.Length == 0)
			{
				return CommandOutcome.UnknownVoice;
			}
			if (!_voices.TrySetUserVoice(message.Login, alias))
			{
				_log.Info("'" + message.Login + "' asked for unknown voice alias '" + alias + "'. Allowed: " + string.Join(", ", _voices.AllowedAliases()));
				return CommandOutcome.UnknownVoice;
			}
			_log.Info("'" + message.Login + "' switched to voice alias '" + alias + "'.");
			return CommandOutcome.Handled;
		}

		private CommandOutcome HandleTts(ChatMessage message, string action)
		{
			if (!message.HasElevatedRole)
			{
				return CommandOutcome.Denied;
			}
			switch (action)
			{
			case "skip":
				this.SkipRequested?.Invoke();
				_log.Info("TTS skip requested by " + message.Login + ".");
				return CommandOutcome.Handled;
			case "clear":
				this.ClearRequested?.Invoke();
				_log.Info("TTS queue cleared by " + message.Login + ".");
				return CommandOutcome.Handled;
			case "on":
				this.EnabledChangeRequested?.Invoke(obj: true);
				_log.Info("TTS enabled by " + message.Login + ".");
				return CommandOutcome.Handled;
			case "off":
				this.EnabledChangeRequested?.Invoke(obj: false);
				_log.Info("TTS disabled by " + message.Login + ".");
				return CommandOutcome.Handled;
			default:
				return CommandOutcome.NotACommand;
			}
		}
	}
}
namespace CipherPeak.Core.BingBong
{
	public interface IBingBongWorld
	{
		bool CanManage { get; }

		IReadOnlyList<int> FindManaged();

		int Spawn();

		void Despawn(int handle);

		bool IsAlive(int handle);
	}
	public sealed class BingBongDirector
	{
		public const int TargetCount = 2;

		private readonly IBingBongWorld _world;

		private readonly ILog _log;

		private readonly List<int> _handles = new List<int>(2);

		public IReadOnlyList<int> Handles => _handles;

		public int Count => _handles.Count;

		public BingBongDirector(IBingBongWorld world, ILog log = null)
		{
			_world = world;
			_log = log ?? NullLog.Instance;
		}

		public bool TryGetHandle(int index, out int handle)
		{
			if (index < 0 || index >= _handles.Count)
			{
				handle = 0;
				return false;
			}
			handle = _handles[index];
			return true;
		}

		public void Tick()
		{
			if (_world.CanManage)
			{
				Adopt();
				DropDead();
				TrimExtras();
				TopUp();
			}
		}

		private void Adopt()
		{
			IReadOnlyList<int> readOnlyList = _world.FindManaged();
			if (readOnlyList == null)
			{
				return;
			}
			for (int i = 0; i < readOnlyList.Count; i++)
			{
				int num = readOnlyList[i];
				if (num != 0 && !_handles.Contains(num) && _world.IsAlive(num))
				{
					_handles.Add(num);
					_log.Info("Adopted existing mod Bing Bong " + num);
				}
			}
		}

		private void DropDead()
		{
			for (int num = _handles.Count - 1; num >= 0; num--)
			{
				if (!_world.IsAlive(_handles[num]))
				{
					_log.Info("Bing Bong " + _handles[num] + " is gone or out of bounds; scheduling replacement.");
					_world.Despawn(_handles[num]);
					_handles.RemoveAt(num);
				}
			}
		}

		private void TrimExtras()
		{
			while (_handles.Count > 2)
			{
				int index = _handles.Count - 1;
				int handle = _handles[index];
				_handles.RemoveAt(index);
				_world.Despawn(handle);
				_log.Warn("Removed surplus mod Bing Bong " + handle);
			}
		}

		private void TopUp()
		{
			while (_handles.Count < 2)
			{
				int num = _world.Spawn();
				if (num == 0 || _handles.Contains(num))
				{
					break;
				}
				_handles.Add(num);
				_log.Info("Spawned mod Bing Bong " + num + " (" + _handles.Count + " of " + 2 + ")");
			}
		}

		public void ReleaseAll()
		{
			for (int i = 0; i < _handles.Count; i++)
			{
				_world.Despawn(_handles[i]);
			}
			_handles.Clear();
		}

		public void Forget()
		{
			_handles.Clear();
		}
	}
}