Decompiled source of TooManyEmotes v2.2.14

plugins/TooManyEmotes.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Versioning;
using AdvancedCompany.Cosmetics;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance.Integrations.Unity_NFGO;
using GameNetcodeStuff;
using HarmonyLib;
using LCVR.Player;
using LethalCompanyInputUtils.Api;
using MoreCompany.Cosmetics;
using TMPro;
using TooManyEmotes.Audio;
using TooManyEmotes.Compatibility;
using TooManyEmotes.Config;
using TooManyEmotes.Input;
using TooManyEmotes.Networking;
using TooManyEmotes.Patches;
using TooManyEmotes.Props;
using TooManyEmotes.UI;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Animations.Rigging;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Experimental.Rendering;
using UnityEngine.InputSystem;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("TooManyEmotes")]
[assembly: AssemblyDescription("Mod made by flipf17")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TooManyEmotes")]
[assembly: AssemblyCopyright("Copyright ©  2023")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("d6950625-e3a1-4896-a183-87110491bf18")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("TooManyEmotesScrap")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace TooManyEmotes
{
	[HarmonyPatch]
	public static class AdditionalEmoteData
	{
		public static void SetAdditionalEmoteData()
		{
		}

		public static void SetAdditionalPropData()
		{
			if (EmotesManager.allUnlockableEmotesDict != null)
			{
				AssignPropToEmote("jug_prop", "jug_band.jug");
				AssignPropToEmote("guitar_prop", "jug_band.guitar");
				AssignPropToEmote("banjo_prop", "jug_band.banjo");
				AssignPropToEmote("fiddle_prop", "jug_band.fiddle");
				AssignPropToEmote("sexy_saxophone.sexy_sax.prop", "sexy_saxophone.sexy_sax");
				AssignPropToEmote("sexy_saxophone.epic_sax.prop", "sexy_saxophone.epic_sax");
				AssignPropToEmote("trombone.prop", "sad_trombone");
				AssignPropToEmote("old_chair.prop", "ma-ya-hi");
				AssignPropToEmote("baseball_bat.prop", "miracle_trickshot");
				AssignPropToEmote("dumbbell.prop", "pumping_iron");
				AssignPropToEmote("paddle.prop", "paddle_royale");
				AssignPropToEmote("gamepad.prop", "controller_crew");
				AssignPropToEmote("jar_of_dirt.prop", "jar_of_dirt");
				AssignPropToEmote("travelers.banjo.prop", "travelers.banjo");
				AssignPropToEmote("travelers.harmonica.prop", "travelers.harmonica");
				AssignPropToEmote("travelers.drums.prop", "travelers.drums");
				AssignPropToEmote("travelers.flute.prop", "travelers.flute");
				AssignPropToEmote("travelers.whistle.prop", "travelers.whistle");
				AssignPropToEmote("travelers.piano.prop", "travelers.piano");
				AssignPropToEmote("travelers.bow.prop", "travelers.bow");
			}
		}

		public static void SetAdditionalMusicData()
		{
			if (EmotesManager.allUnlockableEmotesDict == null)
			{
				return;
			}
			SetEmoteDoesNotUseBoombox("jug_band.jug");
			SetEmoteDoesNotUseBoombox("jug_band.guitar");
			SetEmoteDoesNotUseBoombox("jug_band.banjo");
			SetEmoteDoesNotUseBoombox("jug_band.fiddle");
			SetEmoteDoesNotUseBoombox("hand_signals");
			SetEmoteDoesNotUseBoombox("red_card");
			SetEmoteDoesNotUseBoombox("sexy_saxophone.sexy_sax");
			SetEmoteDoesNotUseBoombox("sexy_saxophone.epic_sax");
			SetEmoteDoesNotUseBoombox("sad_trombone");
			SetEmoteDoesNotUseBoombox("snake_summoner");
			SetEmoteDoesNotUseBoombox("miracle_trickshot");
			SetEmoteDoesNotUseBoombox("junk_food");
			SetEmoteDoesNotUseBoombox("pumping_iron");
			SetEmoteDoesNotUseBoombox("paddle_royale");
			SetEmoteDoesNotUseBoombox("wolf_howl");
			SetEmoteDoesNotUseBoombox("jar_of_dirt");
			SetEmoteDoesNotUseBoombox("travelers.banjo");
			SetEmoteDoesNotUseBoombox("travelers.harmonica");
			SetEmoteDoesNotUseBoombox("travelers.drums");
			SetEmoteDoesNotUseBoombox("travelers.flute");
			SetEmoteDoesNotUseBoombox("travelers.whistle");
			SetEmoteDoesNotUseBoombox("travelers.piano");
			SetEmoteDoesNotUseBoombox("travelers.bow");
			AssignMusicToEmote("jug_band.jug", "jug_band.jug");
			AssignMusicToEmote("jug_band.guitar", "jug_band.guitar");
			AssignMusicToEmote("jug_band.banjo", "jug_band.banjo");
			AssignMusicToEmote("jug_band.fiddle", "jug_band.fiddle");
			AssignMusicToEmote("travelers.banjo", "travelers.banjo");
			AssignMusicToEmote("travelers.harmonica", "travelers.harmonica");
			AssignMusicToEmote("travelers.drums", "travelers.drums");
			AssignMusicToEmote("travelers.flute", "travelers.flute");
			AssignMusicToEmote("travelers.whistle", "travelers.whistle");
			AssignMusicToEmote("travelers.piano", "travelers.piano");
			AssignMusicToEmote("travelers.bow", "travelers.bow");
			if (EmotesManager.allUnlockableEmotesDict.TryGetValue("jug_band.banjo", out var value) && value.inEmoteSyncGroup)
			{
				foreach (UnlockableEmote item in value.emoteSyncGroup)
				{
					item.recordSongLoopValue = 1f;
				}
			}
			if (!EmotesManager.allUnlockableEmotesDict.TryGetValue("travelers.banjo", out value) || !value.inEmoteSyncGroup)
			{
				return;
			}
			foreach (UnlockableEmote item2 in value.emoteSyncGroup)
			{
				item2.recordSongLoopValue = 0.5f;
			}
		}

		public static void AssignPropToEmote(string propName, string emoteName)
		{
			if (!EmotePropManager.emotePropsDataDict.TryGetValue(propName, out var _))
			{
				CustomLogging.LogWarning("Failed to assign prop: " + propName + " to emote. Prop does not exist!");
				return;
			}
			if (!EmotesManager.allUnlockableEmotesDict.TryGetValue(emoteName, out var value2))
			{
				CustomLogging.LogWarning("Failed to assign prop: " + propName + " to emote: " + emoteName + ". Emote does not exist!");
				return;
			}
			if (!EmotePropManager.emotePropsDataDict.TryGetValue(propName, out var value3))
			{
				CustomLogging.LogWarning("Failed to assign prop: " + propName + " to emote: " + emoteName + ". Prop data does not exist for: " + propName);
				return;
			}
			if (value2.propNamesInEmote == null)
			{
				value2.propNamesInEmote = new List<string>();
			}
			value2.propNamesInEmote.Add(propName);
			if (value3.parentEmotes == null)
			{
				value3.parentEmotes = new List<UnlockableEmote>();
			}
			if (!value3.parentEmotes.Contains(value2))
			{
				value3.parentEmotes.Add(value2);
			}
		}

		public static void SetEmoteDoesNotUseBoombox(string emoteName)
		{
			if (EmotesManager.allUnlockableEmotesDict.TryGetValue(emoteName, out var value))
			{
				value.isBoomboxAudio = false;
			}
		}

		public static void SetCanMoveWhileEmoting(string emoteName)
		{
			if (EmotesManager.allUnlockableEmotesDict.TryGetValue(emoteName, out var value))
			{
				value.canMoveWhileEmoting = true;
			}
		}

		public static void AssignMusicToEmote(string emoteName, string audioName, string audioLoopName = "")
		{
			if (EmotesManager.allUnlockableEmotesDict.TryGetValue(emoteName, out var value) && AudioManager.AudioExists(audioName))
			{
				value.overrideAudioClipName = audioName;
				value.overrideAudioLoopClipName = audioLoopName;
			}
		}
	}
	public static class QuickEmotes
	{
		public static UnlockableEmote GetQuickEmote(int index)
		{
			if (index < 0 || index >= 8)
			{
				CustomLogging.LogError("Failed to get quick emote name at index: " + index + ". Index must be within range: 0 and 8");
				return null;
			}
			string key = ES3.Load<string>("QuickEmote" + index, SaveManager.TooManyEmotesSaveFileName, string.Empty);
			UnlockableEmote value = null;
			EmotesManager.allUnlockableEmotesDict.TryGetValue(key, out value);
			return value;
		}
	}
	public static class BoneMapper
	{
		public static Dictionary<Transform, Transform> CreateBoneMap(Transform sourceSkeleton, Transform targetSkeleton, List<string> sourceBoneNames, List<string> targetBoneNames = null)
		{
			if ((Object)(object)sourceSkeleton == (Object)null || (Object)(object)targetSkeleton == (Object)null || sourceBoneNames == null)
			{
				return null;
			}
			if (targetBoneNames == null)
			{
				targetBoneNames = sourceBoneNames;
			}
			if (sourceBoneNames.Count != targetBoneNames.Count)
			{
				CustomLogging.LogError("Attempted to map humanoid skeleton, but passed two sets of bone names with differing sizes.");
				return null;
			}
			int count = sourceBoneNames.Count;
			Transform[] array = (Transform[])(object)new Transform[count];
			Transform[] array2 = (Transform[])(object)new Transform[count];
			FindBones(sourceSkeleton, sourceBoneNames, array);
			FindBones(targetSkeleton, targetBoneNames, array2);
			Dictionary<Transform, Transform> dictionary = new Dictionary<Transform, Transform>();
			for (int i = 0; i < count; i++)
			{
				if ((Object)(object)array[i] != (Object)null && !dictionary.ContainsKey(array[i]))
				{
					dictionary.Add(array[i], array2[i]);
				}
			}
			return dictionary;
		}

		private static void FindBones(Transform bone, List<string> boneNames, Transform[] boneArray)
		{
			if ((Object)(object)((Component)bone).GetComponent<Rig>() != (Object)null || ((Object)bone).name == "ScavengerModelArmsOnly")
			{
				return;
			}
			if (boneNames.Contains(((Object)bone).name))
			{
				int num = boneNames.IndexOf(((Object)bone).name);
				if (!((Object)(object)boneArray[num] != (Object)null))
				{
					boneArray[num] = bone;
				}
			}
			for (int i = 0; i < bone.childCount; i++)
			{
				FindBones(bone.GetChild(i), boneNames, boneArray);
			}
		}
	}
	internal static class CustomLogging
	{
		private static ManualLogSource logger;

		public static void InitLogger()
		{
			try
			{
				logger = Logger.CreateLogSource($"{((BaseUnityPlugin)Plugin.instance).Info.Metadata.Name}-{((BaseUnityPlugin)Plugin.instance).Info.Metadata.Version}");
			}
			catch
			{
				logger = Plugin.defaultLogger;
			}
		}

		public static void Log(string message)
		{
			logger.LogInfo((object)message);
		}

		public static void LogError(string message)
		{
			logger.LogError((object)message);
		}

		public static void LogWarning(string message)
		{
			logger.LogWarning((object)message);
		}

		public static void LogVerbose(string message)
		{
			if (ConfigSettings.verboseLogs.Value)
			{
				logger.LogInfo((object)("[VERBOSE] " + message));
			}
		}

		public static void LogErrorVerbose(string message)
		{
			if (ConfigSettings.verboseLogs.Value)
			{
				logger.LogError((object)("[VERBOSE] " + message));
			}
		}

		public static void LogWarningVerbose(string message)
		{
			if (ConfigSettings.verboseLogs.Value)
			{
				logger.LogWarning((object)("[VERBOSE] " + message));
			}
		}

		public static bool Assert(bool condition, string failMessage)
		{
			if (!condition)
			{
				LogWarning(failMessage);
			}
			return condition;
		}
	}
	public class OnAnimatorIKHandler : MonoBehaviour
	{
		private EmoteController emoteController;

		private Animator animator;

		public float handIKWeight = 0.8f;

		private void Awake()
		{
			animator = ((Component)this).GetComponent<Animator>();
			if ((Object)(object)animator == (Object)null)
			{
				CustomLogging.LogWarning("OnIKHandler must be attached to a gameobject with an animator component.");
			}
		}

		public void SetParentEmoteController(EmoteController emoteController)
		{
			this.emoteController = emoteController;
		}

		protected void OnAnimatorIK(int layerIndex)
		{
			//IL_004e: 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_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)emoteController) && emoteController.initialized && emoteController.IsPerformingCustomEmote())
			{
				if (Object.op_Implicit((Object)(object)emoteController.ikLeftHand) && emoteController.ikLeftHand.localPosition != Vector3.zero)
				{
					animator.SetIKPositionWeight((AvatarIKGoal)2, handIKWeight);
					animator.SetIKRotationWeight((AvatarIKGoal)2, handIKWeight);
					animator.SetIKPosition((AvatarIKGoal)2, emoteController.ikLeftHand.position);
					animator.SetIKRotation((AvatarIKGoal)2, emoteController.ikLeftHand.rotation);
				}
				if (Object.op_Implicit((Object)(object)emoteController.ikRightHand) && emoteController.ikRightHand.localPosition != Vector3.zero)
				{
					animator.SetIKPositionWeight((AvatarIKGoal)3, handIKWeight);
					animator.SetIKRotationWeight((AvatarIKGoal)3, handIKWeight);
					animator.SetIKPosition((AvatarIKGoal)3, emoteController.ikRightHand.position);
					animator.SetIKRotation((AvatarIKGoal)3, emoteController.ikRightHand.rotation);
				}
				if (Object.op_Implicit((Object)(object)emoteController.ikHead) && emoteController.ikHead.localPosition != Vector3.zero)
				{
					animator.SetLookAtWeight(1f, 0.25f, 0.5f);
					animator.SetLookAtPosition(emoteController.ikHead.position);
				}
			}
		}
	}
	[HarmonyPatch]
	public class EmoteSyncGroup
	{
		public static int currentEmoteSyncId = 0;

		public static Dictionary<int, EmoteSyncGroup> allEmoteSyncGroups = new Dictionary<int, EmoteSyncGroup>();

		public int syncId = -1;

		public List<EmoteController> syncGroup;

		public Dictionary<UnlockableEmote, EmoteController> leadEmoteControllerByEmote = new Dictionary<UnlockableEmote, EmoteController>();

		public Dictionary<UnlockableEmote, EmoteAudioSource> currentEmoteAudioSources;

		public float timeStartedEmote;

		public UnlockableEmote performingEmote;

		public bool useAudio = true;

		public EmoteAudioPlayer currentAudioPlayer;

		public EmoteController leadEmoteController => (syncGroup != null && syncGroup.Count > 0) ? syncGroup[0] : null;

		public EmoteAudioSource leadEmoteAudioSource
		{
			get
			{
				if (currentEmoteAudioSources != null && currentEmoteAudioSources.Count > 0)
				{
					foreach (EmoteController item in syncGroup)
					{
						if ((Object)(object)item?.personalEmoteAudioSource != (Object)null && currentEmoteAudioSources.ContainsValue(item.personalEmoteAudioSource))
						{
							return item.personalEmoteAudioSource;
						}
					}
				}
				return null;
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "Awake")]
		[HarmonyPrefix]
		public static void Init()
		{
			currentEmoteSyncId = 0;
			allEmoteSyncGroups?.Clear();
		}

		public static EmoteSyncGroup CreateEmoteSyncGroup(EmoteController emoteController, bool useAudio = true)
		{
			return new EmoteSyncGroup(emoteController, useAudio);
		}

		public EmoteSyncGroup(EmoteController emoteController, bool useAudio = true)
		{
			syncGroup = new List<EmoteController> { emoteController };
			syncId = currentEmoteSyncId++;
			performingEmote = emoteController.performingEmote;
			leadEmoteControllerByEmote.Add(performingEmote, emoteController);
			this.useAudio = useAudio;
			if (useAudio && performingEmote.hasAudio)
			{
				if (!performingEmote.isBoomboxAudio)
				{
					currentEmoteAudioSources = new Dictionary<UnlockableEmote, EmoteAudioSource>();
					if ((Object)(object)emoteController.personalEmoteAudioSource == (Object)null)
					{
						CustomLogging.LogError("Attempted to perform emote with personal audio source, which is null.");
						this.useAudio = false;
					}
					else
					{
						currentEmoteAudioSources[performingEmote] = emoteController.personalEmoteAudioSource;
						emoteController.personalEmoteAudioSource.SyncWithEmoteControllerAudio(emoteController);
						emoteController.personalEmoteAudioSource.AddToEmoteSyncGroup(this);
					}
				}
				else
				{
					EmoteAudioPlayer nearestEmoteAudioPlayer = EmoteAudioPlayerManager.GetNearestEmoteAudioPlayer(((Component)emoteController).transform);
					if ((Object)(object)nearestEmoteAudioPlayer != (Object)null && nearestEmoteAudioPlayer.CanPlayMusic())
					{
						AssignExternalAudioPlayer(nearestEmoteAudioPlayer);
					}
					else
					{
						CustomLogging.LogWarning("Performing emote with no music. No available boomboxes found nearby. Don't worry. Everything will be okay.");
					}
				}
				UpdateAudioVolume();
			}
			allEmoteSyncGroups[syncId] = this;
			timeStartedEmote = Time.time;
		}

		public void AddToEmoteSyncGroup(EmoteController emoteController)
		{
			if (syncGroup == null || syncGroup.Contains(emoteController))
			{
				return;
			}
			syncGroup.Add(emoteController);
			if (!leadEmoteControllerByEmote.ContainsKey(emoteController.performingEmote) || (Object)(object)leadEmoteControllerByEmote[emoteController.performingEmote] == (Object)null)
			{
				leadEmoteControllerByEmote[emoteController.performingEmote] = emoteController;
			}
			if (!useAudio || !performingEmote.hasAudio)
			{
				return;
			}
			if (!performingEmote.isBoomboxAudio)
			{
				if (currentEmoteAudioSources != null && (!currentEmoteAudioSources.ContainsKey(emoteController.performingEmote) || (Object)(object)currentEmoteAudioSources[emoteController.performingEmote] == (Object)null))
				{
					currentEmoteAudioSources[emoteController.performingEmote] = emoteController.personalEmoteAudioSource;
					emoteController.personalEmoteAudioSource.SyncWithEmoteSyncGroup(this, emoteController);
					emoteController.personalEmoteAudioSource.AddToEmoteSyncGroup(this);
				}
			}
			else if ((Object)(object)currentAudioPlayer == (Object)null)
			{
				EmoteAudioPlayer nearestEmoteAudioPlayer = EmoteAudioPlayerManager.GetNearestEmoteAudioPlayer(((Component)emoteController).transform);
				if ((Object)(object)nearestEmoteAudioPlayer != (Object)null && nearestEmoteAudioPlayer.CanPlayMusic())
				{
					AssignExternalAudioPlayer(nearestEmoteAudioPlayer);
				}
			}
			UpdateAudioVolume();
		}

		public void RemoveFromEmoteSyncGroup(EmoteController emoteController)
		{
			if (syncGroup == null)
			{
				return;
			}
			syncGroup.Remove(emoteController);
			if (syncGroup.Count <= 0)
			{
				DestroyEmoteSyncGroup();
				return;
			}
			if (leadEmoteControllerByEmote.ContainsValue(emoteController))
			{
				UnlockableEmote key = leadEmoteControllerByEmote.FirstOrDefault((KeyValuePair<UnlockableEmote, EmoteController> x) => (Object)(object)x.Value == (Object)(object)emoteController).Key;
				leadEmoteControllerByEmote.Remove(key);
				foreach (EmoteController item in syncGroup)
				{
					if ((Object)(object)item == (Object)null || item.performingEmote == null || item.performingEmote != key)
					{
						continue;
					}
					leadEmoteControllerByEmote[key] = item;
					break;
				}
			}
			if (currentEmoteAudioSources == null)
			{
				return;
			}
			if (currentEmoteAudioSources.ContainsValue(emoteController.personalEmoteAudioSource))
			{
				emoteController.personalEmoteAudioSource.StopAudio();
				emoteController.personalEmoteAudioSource.RemoveFromEmoteSyncGroup();
				UnlockableEmote key2 = currentEmoteAudioSources.FirstOrDefault((KeyValuePair<UnlockableEmote, EmoteAudioSource> x) => (Object)(object)x.Value == (Object)(object)emoteController.personalEmoteAudioSource).Key;
				currentEmoteAudioSources.Remove(key2);
				if (useAudio && key2.hasAudio && !key2.isBoomboxAudio)
				{
					foreach (EmoteController item2 in syncGroup)
					{
						if (!((Object)(object)item2 == (Object)null) && item2.performingEmote != null && !((Object)(object)item2 == (Object)(object)emoteController))
						{
							EmoteAudioSource personalEmoteAudioSource = item2.personalEmoteAudioSource;
							if (item2.performingEmote == key2 && (Object)(object)personalEmoteAudioSource != (Object)null && personalEmoteAudioSource.CanPlayMusic())
							{
								currentEmoteAudioSources[key2] = personalEmoteAudioSource;
								personalEmoteAudioSource.SyncWithEmoteControllerAudio(item2);
								break;
							}
						}
					}
				}
			}
			UpdateAudioVolume();
		}

		public void DestroyEmoteSyncGroup()
		{
			CustomLogging.LogVerbose("Cleaning up emote sync group with id: " + syncId);
			if (currentEmoteAudioSources != null)
			{
				foreach (EmoteAudioSource value in currentEmoteAudioSources.Values)
				{
					if ((Object)(object)value != (Object)null)
					{
						value.StopAudio();
						value.RemoveFromEmoteSyncGroup();
					}
				}
				currentEmoteAudioSources = null;
			}
			if ((Object)(object)currentAudioPlayer != (Object)null)
			{
				currentAudioPlayer.StopAudio();
				currentAudioPlayer.RemoveFromEmoteSyncGroup();
			}
			allEmoteSyncGroups.Remove(syncId);
			syncGroup = null;
			syncId = -1;
		}

		public void UpdateAudioVolume()
		{
			if (currentEmoteAudioSources != null)
			{
				foreach (EmoteAudioSource value in currentEmoteAudioSources.Values)
				{
					if ((Object)(object)value != (Object)null)
					{
						value.UpdateVolume();
					}
				}
			}
			if ((Object)(object)currentAudioPlayer != (Object)null)
			{
				currentAudioPlayer.UpdateVolume();
			}
		}

		public void AssignExternalAudioPlayer(EmoteAudioPlayer audioPlayer)
		{
			if ((Object)(object)currentAudioPlayer != (Object)null)
			{
				currentAudioPlayer.StopAudio();
				currentAudioPlayer.RemoveFromEmoteSyncGroup();
				currentAudioPlayer = null;
			}
			if (useAudio && performingEmote.hasAudio && performingEmote.isBoomboxAudio)
			{
				currentAudioPlayer = audioPlayer;
				if ((Object)(object)currentAudioPlayer != (Object)null)
				{
					currentAudioPlayer.SyncWithEmoteControllerAudio(leadEmoteController);
					currentAudioPlayer.AddToEmoteSyncGroup(this);
					currentAudioPlayer.UpdateVolume();
				}
			}
		}
	}
	internal static class HelperTools
	{
		public static NetworkManager networkManager => NetworkManager.Singleton;

		public static bool isClient => networkManager.IsClient;

		public static bool isServer => networkManager.IsServer;

		public static bool isHost => networkManager.IsHost;

		public static PlayerControllerB localPlayerController => StartOfRound.Instance?.localPlayerController;

		public static QuickMenuManager quickMenuManager => localPlayerController?.quickMenuManager;

		public static TextMeshProUGUI[] controlTipLines => HUDManager.Instance?.controlTipLines;

		public static List<Item> allItems => StartOfRound.Instance?.allItemsList?.itemsList;

		public static SelectableLevel[] selectableLevels => StartOfRound.Instance?.levels;

		public static string currentSaveFileName => GameNetworkManager.Instance?.currentSaveFileName;

		public static int groupCredits => ((Object)(object)TerminalPatcher.terminalInstance != (Object)null) ? groupCredits : (-1);

		public static int currentEmoteCredits => TerminalPatcher.currentEmoteCredits;

		public static EmoteControllerPlayer emoteControllerLocal => EmoteControllerPlayer.emoteControllerLocal;

		public static bool TryGetPlayerByClientId(ulong clientId, out PlayerControllerB playerController)
		{
			playerController = null;
			PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if (val.actualClientId == clientId)
				{
					playerController = val;
					break;
				}
			}
			return (Object)(object)playerController != (Object)null;
		}

		public static bool TryGetPlayerByUsername(string username, out PlayerControllerB playerController)
		{
			playerController = null;
			PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if (val.playerUsername == username)
				{
					playerController = val;
					break;
				}
			}
			return (Object)(object)playerController != (Object)null;
		}

		public static EmoteController GetEmoteControllerById(ulong id)
		{
			foreach (EmoteController value in EmoteController.allEmoteControllers.Values)
			{
				if (value.emoteControllerId == id)
				{
					return value;
				}
			}
			return null;
		}
	}
	[HarmonyPatch]
	public class EmoteControllerMaskedEnemy : EmoteController
	{
		public static Dictionary<MaskedPlayerEnemy, EmoteControllerMaskedEnemy> allMaskedEnemyEmoteControllers = new Dictionary<MaskedPlayerEnemy, EmoteControllerMaskedEnemy>();

		public MaskedPlayerEnemy maskedEnemy;

		public int emoteCount = 0;

		public bool stoppedAndStaring = false;

		public bool behaviour1 = false;

		public Vector3 emotedAtPosition;

		public int id => (int)((NetworkBehaviour)maskedEnemy).NetworkObjectId;

		public float stopAndStareTimer
		{
			get
			{
				return (float)Traverse.Create((object)maskedEnemy).Field("stopAndStareTimer").GetValue();
			}
			set
			{
				Traverse.Create((object)maskedEnemy).Field("stopAndStareTimer").SetValue((object)value);
			}
		}

		public NavMeshAgent agent => ((EnemyAI)maskedEnemy).agent;

		public PlayerControllerB lookingAtPlayer
		{
			get
			{
				Transform stareAtTransform = maskedEnemy.stareAtTransform;
				return (stareAtTransform != null) ? ((Component)stareAtTransform).GetComponentInParent<PlayerControllerB>() : null;
			}
		}

		public bool inKillAnimation => (bool)Traverse.Create((object)maskedEnemy).Field("inKillAnimation").GetValue();

		public bool handsOut => (bool)Traverse.Create((object)maskedEnemy).Field("handsOut").GetValue();

		public float localSpeed
		{
			get
			{
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = (Vector3)Traverse.Create((object)maskedEnemy).Field("agentLocalVelocity").GetValue();
				return ((Vector3)(ref val)).magnitude;
			}
		}

		public bool isMoving => animator.GetBool("IsMoving");

		public override void Initialize(string sourceRootBoneName = "metarig")
		{
			base.Initialize();
			if (initialized)
			{
				maskedEnemy = ((Component)this).GetComponentInParent<MaskedPlayerEnemy>();
				if ((Object)(object)maskedEnemy == (Object)null)
				{
					CustomLogging.LogError("Failed to find MaskedPlayerEnemy component in parent of EmoteControllerMaskedEnemy.");
				}
				else
				{
					allMaskedEnemyEmoteControllers.Add(maskedEnemy, this);
				}
			}
		}

		protected override void OnDestroy()
		{
			base.OnDestroy();
			allMaskedEnemyEmoteControllers?.Remove(maskedEnemy);
		}

		protected override bool CheckIfShouldStopEmoting()
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			if (!isPerformingEmote)
			{
				return false;
			}
			if (base.CheckIfShouldStopEmoting())
			{
				return true;
			}
			return ((EnemyAI)maskedEnemy).isEnemyDead || (NetworkManager.Singleton.IsServer && (agent.speed > 0f || stopAndStareTimer <= 0f)) || (!NetworkManager.Singleton.IsServer && Vector3.Distance(emotedAtPosition, ((Component)maskedEnemy).transform.position) > 0.01f) || inKillAnimation;
		}

		public override bool IsPerformingCustomEmote()
		{
			return base.IsPerformingCustomEmote();
		}

		public override bool CanPerformEmote()
		{
			return base.CanPerformEmote() && (Object)(object)lookingAtPlayer != (Object)null && (!NetworkManager.Singleton.IsServer || stopAndStareTimer >= 2f) && !inKillAnimation && ((NetworkManager.Singleton.IsServer && agent.speed == 0f) || (!NetworkManager.Singleton.IsServer && !isMoving)) && !((EnemyAI)maskedEnemy).isEnemyDead;
		}

		public override bool PerformEmote(UnlockableEmote emote, int overrideEmoteId = -1, bool doNotTriggerAudio = false)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			bool result = base.PerformEmote(emote, overrideEmoteId, doNotTriggerAudio);
			if (isPerformingEmote)
			{
				emoteCount++;
				emotedAtPosition = ((Component)maskedEnemy).transform.position;
			}
			return result;
		}

		public override void StopPerformingEmote()
		{
			base.StopPerformingEmote();
			stoppedAndStaring = false;
		}

		protected override void CreateBoneMap()
		{
			boneMap = BoneMapper.CreateBoneMap(humanoidSkeleton, metarig, EmoteControllerPlayer.sourceBoneNames);
		}

		protected override ulong GetEmoteControllerId()
		{
			return ((Object)(object)maskedEnemy != (Object)null) ? ((NetworkBehaviour)maskedEnemy).NetworkObjectId : 0;
		}

		protected override string GetEmoteControllerName()
		{
			return ((Object)(object)maskedEnemy != (Object)null) ? ((Object)maskedEnemy).name : base.GetEmoteControllerName();
		}
	}
	[DefaultExecutionOrder(-2)]
	public class EmoteControllerPlayer : EmoteController
	{
		public static Dictionary<PlayerControllerB, EmoteControllerPlayer> allPlayerEmoteControllers = new Dictionary<PlayerControllerB, EmoteControllerPlayer>();

		public PlayerControllerB playerController;

		public Animator originalAnimator;

		private Dictionary<Transform, Transform> boneMapLocalPlayerArms;

		internal Transform humanoidHead;

		private Transform cameraContainerTarget;

		private Transform cameraContainerLerp;

		public static List<string> sourceBoneNames = new List<string>
		{
			"spine", "spine.001", "spine.002", "spine.003", "spine.004", "shoulder.L", "arm.L_upper", "arm.L_lower", "hand.L", "finger1.L",
			"finger1.L.001", "finger2.L", "finger2.L.001", "finger3.L", "finger3.L.001", "finger4.L", "finger4.L.001", "finger5.L", "finger5.L.001", "shoulder.R",
			"arm.R_upper", "arm.R_lower", "hand.R", "finger1.R", "finger1.R.001", "finger2.R", "finger2.R.001", "finger3.R", "finger3.R.001", "finger4.R",
			"finger4.R.001", "finger5.R", "finger5.R.001", "thigh.L", "shin.L", "foot.L", "heel.02.L", "toe.L", "thigh.R", "shin.R",
			"foot.R", "heel.02.R", "toe.R"
		};

		public GrabbablePropObject sourceGrabbableEmoteProp;

		public static EmoteControllerPlayer emoteControllerLocal => ((Object)(object)HelperTools.localPlayerController != (Object)null && allPlayerEmoteControllers.ContainsKey(HelperTools.localPlayerController)) ? allPlayerEmoteControllers[HelperTools.localPlayerController] : null;

		public bool isLocalPlayer => (Object)(object)playerController == (Object)(object)StartOfRound.Instance?.localPlayerController;

		public ulong clientId => playerController.actualClientId;

		public ulong playerId => playerController.playerClientId;

		public ulong steamId => playerController.playerSteamId;

		public string username => playerController.playerUsername;

		public float timeSinceStartingEmote
		{
			get
			{
				return (float)Traverse.Create((object)playerController).Field("timeSinceStartingEmote").GetValue();
			}
			set
			{
				Traverse.Create((object)playerController).Field("timeSinceStartingEmote").SetValue((object)value);
			}
		}

		public override void Initialize(string sourceRootBoneName = "metarig")
		{
			base.Initialize();
			if (initialized)
			{
				originalAnimator = ((Component)metarig).GetComponentInChildren<Animator>();
				playerController = ((Component)this).GetComponentInParent<PlayerControllerB>();
				if ((Object)(object)playerController == (Object)null)
				{
					CustomLogging.LogError("Failed to find PlayerControllerB component in parent of EmoteControllerPlayer.");
				}
				else
				{
					allPlayerEmoteControllers.Add(playerController, this);
				}
			}
		}

		protected override void Start()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			base.Start();
			if (initialized)
			{
				Transform val = FindChildRecursive("spine.004", metarig);
				if ((Object)(object)val != (Object)null)
				{
					cameraContainerTarget = new GameObject("CameraContainer_Target").transform;
					cameraContainerTarget.SetParent(val);
					cameraContainerTarget.localPosition = new Vector3(0f, 0.22f, 0f);
					cameraContainerTarget.localEulerAngles = new Vector3(-3f, 0f, 0f);
					cameraContainerLerp = new GameObject("CameraContainer_Lerp").transform;
					cameraContainerLerp.SetParent(humanoidSkeleton);
					cameraContainerLerp.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
				}
				humanoidHead = FindChildRecursive("head", humanoidSkeleton);
				if (!Object.op_Implicit((Object)(object)humanoidHead))
				{
					CustomLogging.LogError("Failed to find Head on: " + base.emoteControllerName);
				}
			}
		}

		protected override void OnDestroy()
		{
			base.OnDestroy();
			allPlayerEmoteControllers?.Remove(playerController);
		}

		protected override void Update()
		{
			if (initialized && !((Object)(object)playerController == (Object)null) && (!((Object)(object)playerController == (Object)(object)HelperTools.localPlayerController) || (!ConfigSettings.disableEmotesForSelf.Value && !LCVR_Compat.LoadedAndEnabled)))
			{
				base.Update();
			}
		}

		protected override void LateUpdate()
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			if (!initialized || (Object)(object)playerController == (Object)null || ((Object)(object)playerController == (Object)(object)HelperTools.localPlayerController && (ConfigSettings.disableEmotesForSelf.Value || LCVR_Compat.LoadedAndEnabled)))
			{
				return;
			}
			bool flag = isPerformingEmote;
			base.LateUpdate();
			if (flag && !isPerformingEmote && playerController.performingEmote)
			{
				playerController.performingEmote = false;
				originalAnimator.SetInteger("emoteNumber", 0);
				AnimatorStateInfo currentAnimatorStateInfo = originalAnimator.GetCurrentAnimatorStateInfo(0);
				animator.Play(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash, 0, 0f);
				if (isLocalPlayer)
				{
					timeSinceStartingEmote = 0f;
					playerController.StopPerformingEmoteServerRpc();
				}
			}
		}

		protected override void TranslateAnimation()
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: 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_00f7: 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_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			if (!initialized || !isPerformingEmote || (Object)(object)playerController == (Object)null)
			{
				return;
			}
			base.TranslateAnimation();
			if (Object.op_Implicit((Object)(object)humanoidHead) && Object.op_Implicit((Object)(object)cameraContainerLerp) && Object.op_Implicit((Object)(object)cameraContainerTarget))
			{
				cameraContainerLerp.position = Vector3.Lerp(cameraContainerLerp.position, cameraContainerTarget.position, 25f * Time.deltaTime);
				cameraContainerLerp.rotation = Quaternion.Lerp(cameraContainerLerp.rotation, cameraContainerTarget.rotation, 25f * Time.deltaTime);
				if (!isLocalPlayer || !ThirdPersonEmoteController.firstPersonEmotesEnabled || !ThirdPersonEmoteController.isMovingWhileEmoting)
				{
					playerController.cameraContainerTransform.position = cameraContainerLerp.position;
					playerController.cameraContainerTransform.rotation = cameraContainerLerp.rotation;
				}
				if (isLocalPlayer)
				{
					playerController.localVisor.position = playerController.localVisorTargetPoint.position;
					playerController.localVisor.rotation = playerController.localVisorTargetPoint.rotation;
				}
			}
			if (!isLocalPlayer)
			{
				return;
			}
			playerController.playerModelArmsMetarig.rotation = playerController.localArmsRotationTarget.rotation;
			if (boneMapLocalPlayerArms == null)
			{
				return;
			}
			foreach (KeyValuePair<Transform, Transform> boneMapLocalPlayerArm in boneMapLocalPlayerArms)
			{
				Transform key = boneMapLocalPlayerArm.Key;
				Transform value = boneMapLocalPlayerArm.Value;
				if (!((Object)(object)key == (Object)null) && !((Object)(object)value == (Object)null))
				{
					((Component)value).transform.position = ((Component)key).transform.position;
					((Component)value).transform.rotation = ((Component)key).transform.rotation;
				}
			}
		}

		protected override bool CheckIfShouldStopEmoting()
		{
			if ((Object)(object)playerController == (Object)null || !isPerformingEmote)
			{
				return false;
			}
			if (base.CheckIfShouldStopEmoting() || !playerController.performingEmote || performingEmote == null)
			{
				return true;
			}
			GrabbableObject val = playerController.ItemSlots[playerController.currentItemSlot];
			if ((Object)(object)sourceGrabbableEmoteProp != (Object)null && (Object)(object)sourceGrabbableEmoteProp != (Object)(object)val)
			{
				return true;
			}
			return false;
		}

		public override bool IsPerformingCustomEmote()
		{
			return base.IsPerformingCustomEmote();
		}

		public bool TryPerformingEmoteLocal(UnlockableEmote emote, int overrideEmoteId = -1, GrabbablePropObject sourcePropObject = null)
		{
			if (!initialized || ConfigSettings.disableEmotesForSelf.Value || LCVR_Compat.LoadedAndEnabled)
			{
				return false;
			}
			if (!isLocalPlayer)
			{
				CustomLogging.LogWarning("Cannot run TryPerformEmoteLocal on a character who does not belong to the local player. This is not allowed.");
				return false;
			}
			CustomLogging.Log("Attempting to perform emote on local player.");
			if (!CanPerformEmote())
			{
				return false;
			}
			if (overrideEmoteId >= 0 && (emote.emoteSyncGroup == null || emote.emoteSyncGroup.Count <= 1 || overrideEmoteId < 0 || overrideEmoteId >= emote.emoteSyncGroup.Count))
			{
				overrideEmoteId = -1;
			}
			if (emote.emoteSyncGroup != null && emote.emoteSyncGroup.Count > 1)
			{
				if (emote.randomEmote)
				{
					if (overrideEmoteId < 0)
					{
						overrideEmoteId = Random.Range(0, emote.emoteSyncGroup.Count);
					}
				}
				else
				{
					emote = emote.emoteSyncGroup[0];
				}
			}
			if (overrideEmoteId >= 0 && emote.emoteSyncGroup != null && overrideEmoteId < emote.emoteSyncGroup.Count)
			{
				emote = emote.emoteSyncGroup[overrideEmoteId];
			}
			else
			{
				overrideEmoteId = -1;
			}
			EmoteController emoteController = null;
			if (isPerformingEmote && performingEmote.IsEmoteInEmoteGroup(emote) && (!performingEmote.randomEmote || performingEmote.loopable))
			{
				if (performingEmote.emoteSyncGroup != null && performingEmote.emoteSyncGroup.Count > 1)
				{
					overrideEmoteId = (performingEmote.emoteSyncGroup.IndexOf(performingEmote) + 1) % performingEmote.emoteSyncGroup.Count;
					if (performingEmote.emoteSyncGroup[overrideEmoteId] != null)
					{
						emote = performingEmote.emoteSyncGroup[overrideEmoteId];
					}
				}
				if (emoteSyncGroup?.syncGroup != null && emoteSyncGroup.syncGroup.Count > 1)
				{
					if (emoteSyncGroup.syncGroup.Count > 1 && (performingEmote?.emoteSyncGroup == null || performingEmote.emoteSyncGroup.Count <= 1))
					{
						return true;
					}
					foreach (EmoteController item in emoteSyncGroup.syncGroup)
					{
						if ((Object)(object)item != (Object)(object)this)
						{
							emoteController = item;
							break;
						}
					}
				}
			}
			if ((Object)(object)emoteController != (Object)null)
			{
				return TrySyncingEmoteWithEmoteController(emoteController, overrideEmoteId);
			}
			CustomLogging.LogWarningVerbose("[DEBUG] Trying to perform emote on local player. Emote: " + emote.emoteName + " | Emote id: " + emote.emoteId);
			bool result = ((!((Object)(object)sourcePropObject != (Object)null) || !((Object)(object)sourcePropObject == (Object)(object)HelperTools.localPlayerController.ItemSlots[HelperTools.localPlayerController.currentItemSlot])) ? PerformEmote(emote, overrideEmoteId, AudioManager.emoteOnlyMode) : PerformEmote(emote, sourcePropObject, overrideEmoteId, AudioManager.emoteOnlyMode));
			playerController.StartPerformingEmoteServerRpc();
			SyncPerformingEmoteManager.SendPerformingEmoteUpdateToServer(emote, AudioManager.emoteOnlyMode);
			timeSinceStartingEmote = 0f;
			playerController.performingEmote = true;
			return result;
		}

		public bool TrySyncingEmoteWithEmoteController(EmoteController emoteController, int overrideEmoteId = -1)
		{
			if (!initialized || (Object)(object)emoteController == (Object)null || ConfigSettings.disableEmotesForSelf.Value || LCVR_Compat.LoadedAndEnabled)
			{
				return false;
			}
			if (!isLocalPlayer)
			{
				CustomLogging.LogWarning("Cannot run TrySyncingEmoteWithEmoteController on a character who does not belong to the local player. This is not allowed.");
				return false;
			}
			CustomLogging.Log("Attempting to sync emote for player: " + ((Object)playerController).name + " with emote controller with id: " + emoteController.emoteControllerId);
			if (!CanPerformEmote() || !emoteController.IsPerformingCustomEmote())
			{
				return false;
			}
			if (overrideEmoteId >= 0 && (emoteController.performingEmote?.emoteSyncGroup == null || overrideEmoteId >= emoteController.performingEmote.emoteSyncGroup.Count || emoteController.performingEmote.emoteSyncGroup[overrideEmoteId] == null))
			{
				overrideEmoteId = -1;
			}
			SyncWithEmoteController(emoteController, overrideEmoteId);
			if (performingEmote != null)
			{
				if (performingEmote.inEmoteSyncGroup)
				{
					overrideEmoteId = performingEmote.emoteSyncGroup.IndexOf(performingEmote);
				}
				playerController.StartPerformingEmoteServerRpc();
				SyncPerformingEmoteManager.SendSyncEmoteUpdateToServer(emoteController, overrideEmoteId);
				timeSinceStartingEmote = 0f;
				playerController.performingEmote = true;
				return true;
			}
			return false;
		}

		public override bool CanPerformEmote()
		{
			if (!isLocalPlayer)
			{
				return true;
			}
			if (!initialized || ConfigSettings.disableEmotesForSelf.Value || LCVR_Compat.LoadedAndEnabled)
			{
				return false;
			}
			bool flag = base.CanPerformEmote();
			MethodInfo method = ((object)playerController).GetType().GetMethod("CheckConditionsForEmote", BindingFlags.Instance | BindingFlags.NonPublic);
			flag &= (bool)method.Invoke(playerController, new object[0]);
			bool flag2 = (Object)(object)playerController.inAnimationWithEnemy == (Object)null && (!isLocalPlayer || !CentipedePatcher.IsCentipedeLatchedOntoLocalPlayer());
			return flag && flag2;
		}

		[HarmonyPatch(typeof(PlayerControllerB), "SwitchToItemSlot")]
		[HarmonyPostfix]
		private static void OnSwapItem(int slot, PlayerControllerB __instance)
		{
			if (allPlayerEmoteControllers.TryGetValue(__instance, out var value) && value.IsPerformingCustomEmote())
			{
				GrabbableObject val = __instance.ItemSlots[slot];
				if ((Object)(object)value.sourceGrabbableEmoteProp != (Object)null && (Object)(object)value.sourceGrabbableEmoteProp != (Object)(object)val)
				{
					value.StopPerformingEmote();
				}
				else if (Object.op_Implicit((Object)(object)val) && value.emotingProps.Count > 0)
				{
					val.EnableItemMeshes(false);
				}
			}
		}

		public bool PerformEmote(UnlockableEmote emote, GrabbablePropObject sourcePropObject, int overrideEmoteId = -1, bool doNotTriggerAudio = false)
		{
			if ((Object)(object)sourcePropObject != (Object)null && (Object)(object)sourcePropObject == (Object)(object)playerController.ItemSlots[playerController.currentItemSlot])
			{
				sourceGrabbableEmoteProp = sourcePropObject;
			}
			bool result = PerformEmote(emote, overrideEmoteId, doNotTriggerAudio);
			if (isPerformingEmote)
			{
				if (!isLocalPlayer && SyncManager.isSynced && ConfigSync.instance.syncPersistentUnlocksGlobal && !SessionManager.unlockedEmotesByPlayer.TryGetValue(playerController.playerUsername, out var value) && !value.Contains(performingEmote))
				{
					SessionManager.UnlockEmoteLocal(emote.emoteId, purchased: false, playerController.playerUsername);
				}
			}
			else
			{
				StopPerformingEmote();
			}
			return result;
		}

		public override bool PerformEmote(UnlockableEmote emote, int overrideEmoteId = -1, bool doNotTriggerAudio = false)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)playerController == (Object)null || (isLocalPlayer && (ConfigSettings.disableEmotesForSelf.Value || LCVR_Compat.LoadedAndEnabled)))
			{
				return false;
			}
			bool result = base.PerformEmote(emote, overrideEmoteId, doNotTriggerAudio);
			if (isPerformingEmote)
			{
				cameraContainerLerp.SetPositionAndRotation(cameraContainerTarget.position, cameraContainerTarget.rotation);
				playerController.performingEmote = true;
				if (!isLocalPlayer)
				{
					originalAnimator.SetInteger("emoteNumber", 0);
				}
				GrabbableObject val = playerController.ItemSlots[playerController.currentItemSlot];
				if (Object.op_Implicit((Object)(object)val) && emotingProps.Count > 0)
				{
					val.EnableItemMeshes(false);
				}
				if (isLocalPlayer)
				{
					ThirdPersonEmoteController.OnStartCustomEmoteLocal();
				}
			}
			return result;
		}

		public override bool SyncWithEmoteController(EmoteController emoteController, int overrideEmoteId = -1)
		{
			//IL_0059: 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)
			if ((Object)(object)playerController == (Object)null || (isLocalPlayer && (ConfigSettings.disableEmotesForSelf.Value || LCVR_Compat.LoadedAndEnabled)))
			{
				return false;
			}
			bool result = base.SyncWithEmoteController(emoteController, overrideEmoteId);
			if (isPerformingEmote)
			{
				cameraContainerLerp.SetPositionAndRotation(cameraContainerTarget.position, cameraContainerTarget.rotation);
				playerController.performingEmote = true;
				if (!isLocalPlayer)
				{
					originalAnimator.SetInteger("emoteNumber", 0);
				}
				else
				{
					ThirdPersonEmoteController.OnStartCustomEmoteLocal();
					playerController.StartPerformingEmoteServerRpc();
				}
			}
			return result;
		}

		public override void StopPerformingEmote()
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)playerController == (Object)null || (isLocalPlayer && ConfigSettings.disableEmotesForSelf.Value))
			{
				return;
			}
			base.StopPerformingEmote();
			cameraContainerLerp.SetPositionAndRotation(cameraContainerTarget.position, cameraContainerTarget.rotation);
			GrabbableObject val = playerController.ItemSlots[playerController.currentItemSlot];
			if (Object.op_Implicit((Object)(object)val))
			{
				val.EnableItemMeshes(true);
			}
			if ((Object)(object)sourceGrabbableEmoteProp != (Object)null)
			{
				if (sourceGrabbableEmoteProp.isPerformingEmote)
				{
					sourceGrabbableEmoteProp.StopEmote();
				}
				sourceGrabbableEmoteProp = null;
			}
			playerController.playerBodyAnimator.SetInteger("emote_number", 0);
			playerController.performingEmote = false;
			playerController.playerBodyAnimator.Update(0f);
			if (isLocalPlayer)
			{
				ThirdPersonEmoteController.OnStopCustomEmoteLocal();
				((Component)playerController.gameplayCamera).transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
				playerController.StopPerformingEmoteServerRpc();
			}
		}

		public override void ResetPerformingEmote()
		{
			if ((Object)(object)playerController == (Object)null || (isLocalPlayer && ConfigSettings.disableEmotesForSelf.Value))
			{
				return;
			}
			base.ResetPerformingEmote();
			if ((Object)(object)sourceGrabbableEmoteProp != (Object)null)
			{
				if (sourceGrabbableEmoteProp.isPerformingEmote)
				{
					sourceGrabbableEmoteProp.StopEmote();
				}
				sourceGrabbableEmoteProp = null;
			}
			GrabbableObject val = playerController.ItemSlots[playerController.currentItemSlot];
			if (Object.op_Implicit((Object)(object)val))
			{
				val.EnableItemMeshes(true);
			}
		}

		protected override void CreateBoneMap()
		{
			boneMap = BoneMapper.CreateBoneMap(humanoidSkeleton, metarig, sourceBoneNames);
			List<string> list = new List<string>
			{
				"arm.L_upper", "arm.L_lower", "hand.L", "finger1.L", "finger1.L.001", "finger2.L", "finger2.L.001", "finger3.L", "finger3.L.001", "finger4.L",
				"finger4.L.001", "finger5.L", "finger5.L.001", "arm.R_upper", "arm.R_lower", "hand.R", "finger1.R", "finger1.R.001", "finger2.R", "finger2.R.001",
				"finger3.R", "finger3.R.001", "finger4.R", "finger4.R.001", "finger5.R", "finger5.R.001"
			};
			boneMapLocalPlayerArms = BoneMapper.CreateBoneMap(humanoidSkeleton, playerController.localArmsTransform, list);
		}

		protected override ulong GetEmoteControllerId()
		{
			return ((Object)(object)playerController != (Object)null) ? ((NetworkBehaviour)playerController).NetworkObjectId : 0;
		}

		protected override string GetEmoteControllerName()
		{
			return ((Object)(object)playerController != (Object)null) ? playerController.playerUsername : base.GetEmoteControllerName();
		}
	}
	[HarmonyPatch]
	[DefaultExecutionOrder(-2)]
	public class EmoteController : MonoBehaviour
	{
		public static Dictionary<GameObject, EmoteController> allEmoteControllers = new Dictionary<GameObject, EmoteController>();

		public bool initialized = false;

		public Transform metarig;

		public Transform humanoidSkeleton;

		public Animator animator;

		public AnimatorOverrideController animatorController;

		public bool isPerformingEmote = false;

		public UnlockableEmote performingEmote;

		protected Dictionary<Transform, Transform> boneMap;

		public List<Transform> groundContactPoints = new List<Transform>();

		public Transform propsParent;

		public List<PropObject> emotingProps = new List<PropObject>();

		public Transform ikLeftHand;

		public Transform ikRightHand;

		public Transform ikLeftFoot;

		public Transform ikRightFoot;

		public Transform ikHead;

		public EmoteSyncGroup emoteSyncGroup;

		public EmoteAudioSource personalEmoteAudioSource;

		private float timePerformedEmote = 0f;

		public bool smoothTransitionToEmote = false;

		public ulong emoteControllerId => GetEmoteControllerId();

		public string emoteControllerName => GetEmoteControllerName();

		public float currentAnimationTimeNormalized
		{
			get
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
				return ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime;
			}
		}

		public float currentAnimationTime
		{
			get
			{
				AnimationClip currentAnimationClip = GetCurrentAnimationClip();
				return ((Object)(object)currentAnimationClip != (Object)null) ? (currentAnimationClip.length * (currentAnimationTimeNormalized % 1f)) : 0f;
			}
		}

		public int currentStateHash
		{
			get
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
				return ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash;
			}
		}

		public bool isLooping
		{
			get
			{
				return animator.GetBool("loop");
			}
			set
			{
				animator.SetBool("loop", value);
			}
		}

		public bool isAnimatorInLoopingState
		{
			get
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
				return ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsName("emote_loop");
			}
		}

		public bool isSimpleEmoteController => ((object)this).GetType() == typeof(EmoteController);

		public int emoteSyncId => (emoteSyncGroup != null) ? emoteSyncGroup.syncId : (-1);

		protected virtual void Awake()
		{
			if (!initialized)
			{
				Initialize();
			}
		}

		public virtual void Initialize(string sourceRootBoneName = "metarig")
		{
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Expected O, but got Unknown
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Expected O, but got Unknown
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: Unknown result type (might be due to invalid IL or missing references)
			if (initialized || (Object)(object)Plugin.humanoidSkeletonPrefab == (Object)null || (Object)(object)Plugin.humanoidAnimatorController == (Object)null || (Object)(object)Plugin.humanoidAvatar == (Object)null)
			{
				return;
			}
			try
			{
				metarig = FindChildRecursive(sourceRootBoneName, ((Component)this).transform);
				humanoidSkeleton = Object.Instantiate<GameObject>(Plugin.humanoidSkeletonPrefab, metarig.parent).transform;
				((Object)humanoidSkeleton).name = "HumanoidSkeleton";
				humanoidSkeleton.SetSiblingIndex(metarig.GetSiblingIndex() + 1);
				animator = ((Component)humanoidSkeleton).GetComponent<Animator>();
				OnAnimatorIKHandler onAnimatorIKHandler = ((Component)animator).gameObject.AddComponent<OnAnimatorIKHandler>();
				onAnimatorIKHandler.SetParentEmoteController(this);
				animatorController = new AnimatorOverrideController(Plugin.humanoidAnimatorController);
				animator.runtimeAnimatorController = (RuntimeAnimatorController)(object)animatorController;
				humanoidSkeleton.SetLocalPositionAndRotation(metarig.localPosition + Vector3.down * 0.025f, Quaternion.identity);
				humanoidSkeleton.localScale = metarig.localScale;
				if (!isSimpleEmoteController)
				{
					allEmoteControllers.Add(((Component)this).gameObject, this);
					GameObject val = new GameObject("PersonalEmoteAudioSource");
					val.transform.SetParent(humanoidSkeleton);
					val.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
					val.transform.localScale = Vector3.one;
					personalEmoteAudioSource = val.AddComponent<EmoteAudioSource>();
				}
				if ((Object)(object)propsParent == (Object)null)
				{
					propsParent = ((Component)this).transform.Find("EmoteProps");
					if ((Object)(object)propsParent == (Object)null)
					{
						propsParent = new GameObject("EmoteProps").transform;
						propsParent.SetParent(humanoidSkeleton);
						propsParent.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
						propsParent.localScale = Vector3.one;
					}
				}
				initialized = true;
			}
			catch (Exception ex)
			{
				Debug.LogError((object)("Failed to initialize EmoteController. Error: " + ex));
			}
		}

		protected virtual void Start()
		{
			if (!initialized)
			{
				return;
			}
			if (boneMap == null)
			{
				if (!isSimpleEmoteController)
				{
					CreateBoneMap();
				}
				else
				{
					Debug.LogWarning((object)"Using the base emote controller. Remember that when doing this, the bonemap will need to be built manually.");
				}
			}
			FindIkBones();
		}

		protected virtual void OnEnable()
		{
		}

		protected virtual void OnDisable()
		{
			if (isPerformingEmote)
			{
				StopPerformingEmote();
			}
		}

		protected virtual void OnDestroy()
		{
			if (isPerformingEmote)
			{
				StopPerformingEmote();
			}
			allEmoteControllers?.Remove(((Component)this).gameObject);
			if (SyncPerformingEmoteManager.doNotTriggerAudioDict.ContainsKey(this))
			{
				SyncPerformingEmoteManager.doNotTriggerAudioDict.Remove(this);
			}
		}

		protected virtual void Update()
		{
			if (initialized)
			{
			}
		}

		protected virtual void LateUpdate()
		{
			if (initialized)
			{
				if (isPerformingEmote && CheckIfShouldStopEmoting())
				{
					StopPerformingEmote();
				}
				if (!((Object)(object)animator == (Object)null) && !((Object)(object)animatorController == (Object)null) && boneMap != null && isPerformingEmote)
				{
					TranslateAnimation();
				}
			}
		}

		protected virtual void TranslateAnimation()
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			if (performingEmote == null || boneMap == null || boneMap.Count <= 0)
			{
				return;
			}
			foreach (KeyValuePair<Transform, Transform> item in boneMap)
			{
				Transform key = item.Key;
				Transform value = item.Value;
				if (!((Object)(object)key == (Object)null) && !((Object)(object)value == (Object)null))
				{
					float num = Time.time - timePerformedEmote;
					float num2 = (smoothTransitionToEmote ? Mathf.Clamp01(num / 0.2f) : 1f);
					((Component)value).transform.position = Vector3.Lerp(((Component)value).transform.position, ((Component)key).transform.position, num2);
					((Component)value).transform.rotation = Quaternion.Slerp(((Component)value).transform.rotation, ((Component)key).transform.rotation, num2);
				}
			}
		}

		protected virtual bool CheckIfShouldStopEmoting()
		{
			if (isPerformingEmote)
			{
				return performingEmote == null || (!performingEmote.loopable && !performingEmote.isPose && currentAnimationTimeNormalized >= 1f);
			}
			return false;
		}

		protected virtual void CorrectVerticalPosition()
		{
			//IL_0037: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			if (groundContactPoints == null)
			{
				return;
			}
			float num = 0f;
			foreach (Transform groundContactPoint in groundContactPoints)
			{
				num = Mathf.Min(num, ((Component)groundContactPoint).transform.position.y - metarig.position.y);
			}
			if (num < 0f)
			{
				metarig.position = new Vector3(metarig.position.x, metarig.position.y - num, metarig.position.z);
			}
		}

		public virtual bool IsPerformingCustomEmote()
		{
			return isPerformingEmote && performingEmote != null;
		}

		public virtual bool CanPerformEmote()
		{
			return initialized && (Object)(object)animator != (Object)null && ((Behaviour)animator).enabled;
		}

		public virtual bool PerformEmote(UnlockableEmote emote, int overrideEmoteId = -1, bool doNotTriggerAudio = false)
		{
			if (!initialized || !CanPerformEmote())
			{
				return false;
			}
			if (!isSimpleEmoteController)
			{
				CustomLogging.Log("[" + emoteControllerName + "] Performing emote: " + emote.emoteName);
			}
			if (isPerformingEmote)
			{
				ResetPerformingEmote();
			}
			if (emote.emoteSyncGroup != null)
			{
				if (overrideEmoteId >= 0 && overrideEmoteId < emote.emoteSyncGroup.Count && emote.emoteSyncGroup[overrideEmoteId] != null)
				{
					emote = emote.emoteSyncGroup[overrideEmoteId];
				}
				else if (emote.randomEmote)
				{
					int index = Random.Range(0, emote.emoteSyncGroup.Count);
					UnlockableEmote unlockableEmote = emote.emoteSyncGroup[index];
					if (unlockableEmote != null)
					{
						emote = unlockableEmote;
					}
				}
			}
			animatorController["emote"] = emote.animationClip;
			if ((Object)(object)emote.transitionsToClip != (Object)null)
			{
				animatorController["emote_loop"] = emote.transitionsToClip;
			}
			animator.SetBool("loop", (Object)(object)emote.transitionsToClip != (Object)null);
			animator.Play("emote", 0, 0f);
			animator.Update(0f);
			performingEmote = emote;
			isPerformingEmote = true;
			timePerformedEmote = Time.time;
			RecordStartingBonePositions();
			PerformEmoteProps();
			if (!isSimpleEmoteController)
			{
				CreateEmoteSyncGroup(doNotTriggerAudio);
			}
			DiscoBallPatcher.OnPerformEmote(this);
			return true;
		}

		public virtual bool SyncWithEmoteController(EmoteController emoteController, int overrideEmoteId = -1)
		{
			if (!initialized || !CanPerformEmote() || (Object)(object)emoteController == (Object)null || !emoteController.IsPerformingCustomEmote())
			{
				return false;
			}
			if (!isSimpleEmoteController)
			{
				CustomLogging.Log("[" + emoteControllerName + "] Attempting to sync with emote controller: " + ((Object)emoteController).name + " Emote: " + emoteController.performingEmote.emoteName + " PlayEmoteAtTimeNormalized: " + emoteController.currentAnimationTimeNormalized % 1f);
			}
			if (isPerformingEmote)
			{
				ResetPerformingEmote();
			}
			EmoteSyncGroup emoteSyncGroup = emoteController.emoteSyncGroup;
			if (emoteSyncGroup == null)
			{
				CustomLogging.LogWarning("[" + emoteControllerName + "] Attempted to sync with emote controller who is not a part of an emote sync group. Continuing anyways.");
			}
			UnlockableEmote unlockableEmote = emoteController.performingEmote;
			if (unlockableEmote.emoteSyncGroup != null)
			{
				if (overrideEmoteId >= 0 && overrideEmoteId < unlockableEmote.emoteSyncGroup.Count && unlockableEmote.emoteSyncGroup[overrideEmoteId] != null)
				{
					unlockableEmote = unlockableEmote.emoteSyncGroup[overrideEmoteId];
				}
				else if (unlockableEmote.randomEmote)
				{
					if (unlockableEmote.hasAudio && !unlockableEmote.isBoomboxAudio)
					{
						unlockableEmote = emoteController.performingEmote;
					}
					else
					{
						int index = Random.Range(0, unlockableEmote.emoteSyncGroup.Count);
						UnlockableEmote unlockableEmote2 = unlockableEmote.emoteSyncGroup[index];
						if (unlockableEmote2 != null)
						{
							unlockableEmote = unlockableEmote2;
						}
					}
				}
				else
				{
					bool flag = false;
					foreach (UnlockableEmote item in unlockableEmote.emoteSyncGroup)
					{
						if (!emoteSyncGroup.leadEmoteControllerByEmote.ContainsKey(item) || (Object)(object)emoteSyncGroup.leadEmoteControllerByEmote[unlockableEmote] == (Object)null)
						{
							unlockableEmote = item;
							flag = true;
							break;
						}
					}
					if (!flag)
					{
						int num = unlockableEmote.emoteSyncGroup.IndexOf(unlockableEmote);
						if (num >= 0)
						{
							num = (num + 1) % unlockableEmote.emoteSyncGroup.Count;
							unlockableEmote = unlockableEmote.emoteSyncGroup[num];
						}
					}
				}
			}
			AnimationClip currentAnimationClip = emoteController.GetCurrentAnimationClip();
			if (!unlockableEmote.ClipIsInEmote(currentAnimationClip))
			{
				CustomLogging.LogError("[" + emoteControllerName + "] Attempted to sync with emote controller whose animation clip is not a part of their performing emote? Emote: " + emoteController.performingEmote?.ToString() + " AnimationClip: " + ((Object)currentAnimationClip).name);
				return false;
			}
			animatorController["emote"] = unlockableEmote.animationClip;
			if ((Object)(object)unlockableEmote.transitionsToClip != (Object)null)
			{
				animatorController["emote_loop"] = unlockableEmote.transitionsToClip;
			}
			float num2 = emoteController.currentAnimationTimeNormalized % 1f;
			animator.SetBool("loop", (Object)(object)unlockableEmote.transitionsToClip != (Object)null);
			animator.Play(((Object)(object)currentAnimationClip == (Object)(object)unlockableEmote.transitionsToClip) ? "emote_loop" : "emote", 0, num2);
			animator.Update(0f);
			performingEmote = unlockableEmote;
			isPerformingEmote = true;
			PerformEmoteProps();
			if (!isSimpleEmoteController && emoteController.emoteSyncGroup != null)
			{
				AddToEmoteSyncGroup(emoteController.emoteSyncGroup);
			}
			DiscoBallPatcher.OnPerformEmote(this);
			return true;
		}

		protected void PerformEmoteProps()
		{
			if ((Object)(object)propsParent != (Object)null && performingEmote.propNamesInEmote != null)
			{
				LoadEmoteProps();
			}
			if (emotingProps == null)
			{
				return;
			}
			foreach (PropObject emotingProp in emotingProps)
			{
				emotingProp.SyncWithEmoteController(this);
			}
		}

		protected void LoadEmoteProps()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			UnloadEmoteProps();
			if (performingEmote.propNamesInEmote == null)
			{
				return;
			}
			foreach (string item in performingEmote.propNamesInEmote)
			{
				PropObject propObject = EmotePropManager.LoadEmoteProp(item);
				propObject.SetPropLayer(6);
				emotingProps.Add(propObject);
				((Component)propObject).transform.SetParent(propsParent);
				((Component)propObject).transform.localPosition = Vector3.zero;
				((Component)propObject).transform.localRotation = Quaternion.identity;
			}
		}

		protected void UnloadEmoteProps()
		{
			if (emotingProps == null)
			{
				return;
			}
			foreach (PropObject emotingProp in emotingProps)
			{
				((Component)emotingProp).transform.SetParent(EmotePropManager.propPoolParent);
				emotingProp.active = false;
			}
			emotingProps.Clear();
		}

		public virtual void StopPerformingEmote()
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			if (initialized)
			{
				isPerformingEmote = false;
				if (!isSimpleEmoteController)
				{
					CustomLogging.Log(string.Format("[" + emoteControllerName + "] Stopping emote."));
				}
				animatorController["emote"] = null;
				animatorController["emote_loop"] = null;
				RemoveFromEmoteSyncGroup();
				UnloadEmoteProps();
				if ((Object)(object)ikLeftHand != (Object)null)
				{
					ikLeftHand.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikRightHand != (Object)null)
				{
					ikRightHand.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikLeftFoot != (Object)null)
				{
					ikLeftFoot.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikRightFoot != (Object)null)
				{
					ikRightFoot.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikHead != (Object)null)
				{
					ikHead.localPosition = Vector3.zero;
				}
				DiscoBallPatcher.OnStopPerformingEmote(this);
			}
		}

		public virtual void ResetPerformingEmote()
		{
			//IL_0062: 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_00a4: 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_00ea: Unknown result type (might be due to invalid IL or missing references)
			if (initialized)
			{
				isPerformingEmote = false;
				animatorController["emote"] = null;
				animatorController["emote_loop"] = null;
				RemoveFromEmoteSyncGroup();
				UnloadEmoteProps();
				if ((Object)(object)ikLeftHand != (Object)null)
				{
					ikLeftHand.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikRightHand != (Object)null)
				{
					ikRightHand.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikLeftFoot != (Object)null)
				{
					ikLeftFoot.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikRightFoot != (Object)null)
				{
					ikRightFoot.localPosition = Vector3.zero;
				}
				if ((Object)(object)ikHead != (Object)null)
				{
					ikHead.localPosition = Vector3.zero;
				}
			}
		}

		public AnimationClip GetCurrentAnimationClip()
		{
			//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)
			if (!IsPerformingCustomEmote())
			{
				return null;
			}
			if (!animator.GetBool("loop"))
			{
				return animatorController["emote"];
			}
			AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
			return animatorController[((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsName("emote_loop") ? "emote_loop" : "emote"];
		}

		protected virtual void CreateBoneMap()
		{
		}

		public void CreateBoneMap(List<string> sourceBoneNames, List<string> targetBoneNames = null)
		{
			boneMap = BoneMapper.CreateBoneMap(humanoidSkeleton, metarig, sourceBoneNames, targetBoneNames);
		}

		protected virtual void FindIkBones()
		{
			Transform val = FindChildRecursive("root_ik");
			if ((Object)(object)val == (Object)null)
			{
				CustomLogging.LogError("Failed to find root ik bone called \"root_ik\" in humanoid skeleton: " + emoteControllerName);
				return;
			}
			ikLeftHand = val.Find("hand_ik_l");
			ikRightHand = val.Find("hand_ik_r");
			ikLeftFoot = val.Find("foot_ik_l");
			ikRightFoot = val.Find("foot_ik_r");
			ikHead = val.Find("head_ik");
		}

		protected virtual Transform FindChildRecursive(string objectName, Transform root = null)
		{
			if ((Object)(object)root == (Object)null)
			{
				root = humanoidSkeleton;
			}
			if (((Object)root).name == objectName)
			{
				return root;
			}
			for (int i = 0; i < root.childCount; i++)
			{
				Transform child = root.GetChild(i);
				Transform val = FindChildRecursive(objectName, child);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}

		protected virtual ulong GetEmoteControllerId()
		{
			return 0uL;
		}

		protected virtual string GetEmoteControllerName()
		{
			return ((Object)this).name;
		}

		protected void CreateEmoteSyncGroup(bool doNotTriggerAudio = false)
		{
			emoteSyncGroup = EmoteSyncGroup.CreateEmoteSyncGroup(this, !doNotTriggerAudio);
		}

		protected void AddToEmoteSyncGroup(EmoteSyncGroup emoteSyncGroup)
		{
			CustomLogging.Log("Adding to emote sync group with id: " + emoteSyncGroup.syncId);
			emoteSyncGroup.AddToEmoteSyncGroup(this);
			this.emoteSyncGroup = emoteSyncGroup;
		}

		protected void RemoveFromEmoteSyncGroup()
		{
			if (emoteSyncGroup != null)
			{
				emoteSyncGroup.RemoveFromEmoteSyncGroup(this);
			}
			emoteSyncGroup = null;
		}

		protected void RecordStartingBonePositions()
		{
		}
	}
	[HarmonyPatch]
	public static class EmotesManager
	{
		public static List<UnlockableEmote> allUnlockableEmotes;

		public static Dictionary<string, UnlockableEmote> allUnlockableEmotesDict;

		public static List<UnlockableEmote> complementaryEmotes;

		internal static List<UnlockableEmote> complementaryEmotesDefault;

		public static List<string> allFavoriteEmotes;

		public static List<string> allQuickEmotes;

		public static List<UnlockableEmote> allEmotesTier0;

		public static List<UnlockableEmote> allEmotesTier1;

		public static List<UnlockableEmote> allEmotesTier2;

		public static List<UnlockableEmote> allEmotesTier3;

		internal static CultureInfo defaultSortCulture = CultureInfo.CreateSpecificCulture("en-US");

		public static void BuildEmotesList()
		{
			allUnlockableEmotes = new List<UnlockableEmote>();
			allUnlockableEmotesDict = new Dictionary<string, UnlockableEmote>();
			complementaryEmotesDefault = new List<UnlockableEmote>();
			allFavoriteEmotes = new List<string>();
			allQuickEmotes = new List<string>(8);
			while (allQuickEmotes.Count < allQuickEmotes.Capacity)
			{
				allQuickEmotes.Add("");
			}
			allEmotesTier0 = new List<UnlockableEmote>();
			allEmotesTier1 = new List<UnlockableEmote>();
			allEmotesTier2 = new List<UnlockableEmote>();
			allEmotesTier3 = new List<UnlockableEmote>();
			Dictionary<string, List<UnlockableEmote>> dictionary = new Dictionary<string, List<UnlockableEmote>>();
			for (int i = 0; i < Plugin.customAnimationClips.Count; i++)
			{
				AnimationClip val = Plugin.customAnimationClips[i];
				UnlockableEmote unlockableEmote = new UnlockableEmote
				{
					emoteId = i,
					emoteName = ((Object)val).name,
					displayName = "",
					animationClip = val,
					rarity = 0
				};
				unlockableEmote.rarity = (Plugin.animationClipsTier1.Contains(val) ? 1 : (Plugin.animationClipsTier2.Contains(val) ? 2 : (Plugin.animationClipsTier3.Contains(val) ? 3 : 0)));
				if (Plugin.complementaryAnimationClips.Contains(val))
				{
					unlockableEmote.complementary = true;
				}
				if (unlockableEmote.emoteName.Contains("_start") && !unlockableEmote.emoteName.Contains("_start_"))
				{
					string key = unlockableEmote.emoteName.Replace("_start", "_loop");
					AnimationClip val2 = Plugin.customAnimationClipsLoopDict[key];
					if ((Object)(object)val2 != (Object)null)
					{
						unlockableEmote.transitionsToClip = val2;
						unlockableEmote.emoteName = unlockableEmote.emoteName.Replace("_start", "");
						((Object)unlockableEmote.animationClip).name = unlockableEmote.emoteName + "_start";
						((Object)unlockableEmote.transitionsToClip).name = unlockableEmote.emoteName + "_loop";
					}
				}
				else if (unlockableEmote.emoteName.Contains("_pose"))
				{
					unlockableEmote.isPose = true;
					unlockableEmote.emoteName = unlockableEmote.emoteName.Replace("_pose", "");
					((Object)unlockableEmote.animationClip).name = unlockableEmote.emoteName;
				}
				if (unlockableEmote.emoteName.Contains("."))
				{
					string[] array = unlockableEmote.emoteName.Split(new char[1] { '.' });
					if (array.Length != 0 && array[0].Length > 0)
					{
						if (array.Length > 3)
						{
							CustomLogging.LogError("Error parsing emote name: " + unlockableEmote.emoteName + ". Correct format: \"emote_group.optional_arg.emote_name\"");
							continue;
						}
						unlockableEmote.emoteSyncGroupName = array[0];
						unlockableEmote.emoteName = unlockableEmote.emoteSyncGroupName + "." + array[^1];
						unlockableEmote.displayName = unlockableEmote.emoteSyncGroupName;
						if ((Object)(object)unlockableEmote.transitionsToClip == (Object)null)
						{
							((Object)unlockableEmote.animationClip).name = unlockableEmote.emoteName;
						}
						else
						{
							((Object)unlockableEmote.animationClip).name = unlockableEmote.emoteName + "_start";
							((Object)unlockableEmote.transitionsToClip).name = unlockableEmote.emoteName + "_loop";
						}
						if (!dictionary.TryGetValue(unlockableEmote.emoteSyncGroupName, out unlockableEmote.emoteSyncGroup))
						{
							unlockableEmote.emoteSyncGroup = new List<UnlockableEmote>();
							dictionary.Add(unlockableEmote.emoteSyncGroupName, unlockableEmote.emoteSyncGroup);
						}
						if (array.Length == 3 && array[1].ToLower().Contains("layer_"))
						{
							((Object)val).name = ((Object)val).name.Replace("." + array[1], "");
							if ((Object)(object)unlockableEmote.transitionsToClip != (Object)null)
							{
								((Object)unlockableEmote.transitionsToClip).name = ((Object)unlockableEmote.transitionsToClip).name.Replace("." + array[1], "");
							}
							if (!int.TryParse(array[1].Substring(6), out var result))
							{
								CustomLogging.LogError("Failed to parse emote layer number in arg: " + array[1] + ". Emote will not be added.");
								continue;
							}
							unlockableEmote.purchasable = result == 0;
							while (unlockableEmote.emoteSyncGroup.Count <= result)
							{
								unlockableEmote.emoteSyncGroup.Add(null);
							}
							unlockableEmote.emoteSyncGroup[result] = unlockableEmote;
						}
						else
						{
							unlockableEmote.emoteSyncGroup.Add(unlockableEmote);
							unlockableEmote.purchasable = unlockableEmote.emoteSyncGroup.Count == 1;
							if (array.Length == 3 && array[1].ToLower() == "random")
							{
								unlockableEmote.randomEmote = true;
								((Object)val).name = ((Object)val).name.Replace("." + array[1], "");
								if ((Object)(object)unlockableEmote.transitionsToClip != (Object)null)
								{
									((Object)unlockableEmote.transitionsToClip).name = ((Object)unlockableEmote.transitionsToClip).name.Replace("." + array[1], "");
								}
							}
						}
					}
				}
				if (unlockableEmote.emoteName.Contains("_start") && !unlockableEmote.emoteName.Contains("_start_"))
				{
					string key2 = unlockableEmote.emoteName.Replace("_start", "_loop");
					AnimationClip val3 = Plugin.customAnimationClipsLoopDict[key2];
					if ((Object)(object)val3 != (Object)null)
					{
						unlockableEmote.transitionsToClip = val3;
						unlockableEmote.emoteName = unlockableEmote.emoteName.Replace("_start", "");
						((Object)unlockableEmote.animationClip).name = unlockableEmote.emoteName + "_start";
						((Object)unlockableEmote.transitionsToClip).name = unlockableEmote.emoteName + "_loop";
					}
				}
				else if (unlockableEmote.emoteName.Contains("_pose"))
				{
					unlockableEmote.isPose = true;
					unlockableEmote.emoteName = unlockableEmote.emoteName.Replace("_pose", "");
					((Object)unlockableEmote.animationClip).name = unlockableEmote.emoteName;
				}
				if ((!((Object)(object)unlockableEmote.transitionsToClip != (Object)null) && !((Motion)unlockableEmote.animationClip).isLooping && !unlockableEmote.isPose && unlockableEmote.emoteSyncGroup == null) || true)
				{
					unlockableEmote.canSyncEmote = true;
				}
				if (unlockableEmote.displayName == "")
				{
					unlockableEmote.displayName = unlockableEmote.emoteName;
				}
				unlockableEmote.displayName = unlockableEmote.displayName.Replace('_', ' ').Trim(new char[1] { ' ' });
				unlockableEmote.displayName = char.ToUpper(unlockableEmote.displayName[0]) + unlockableEmote.displayName.Substring(1).ToLower();
				if (!allUnlockableEmotes.Contains(unlockableEmote))
				{
					allUnlockableEmotes.Add(unlockableEmote);
					allUnlockableEmotesDict.Add(unlockableEmote.emoteName, unlockableEmote);
				}
				if (Plugin.complementaryAnimationClips.Contains(val) && unlockableEmote.purchasable)
				{
					unlockableEmote.complementary = true;
					complementaryEmotesDefault.Add(unlockableEmote);
				}
			}
			try
			{
				allUnlockableEmotes.Sort((UnlockableEmote item1, UnlockableEmote item2) => string.Compare(item1.displayName, item2.displayName, ignoreCase: true, defaultSortCulture));
			}
			catch (Exception)
			{
				if (ConfigSettings.verboseLogs.Value)
				{
					CustomLogging.LogWarningVerbose("Failed to apply default emote sort. Reverting to original sort method.");
				}
				allUnlockableEmotes = allUnlockableEmotes.OrderBy((UnlockableEmote item) => item.emoteName).ToList();
			}
			int num = 0;
			foreach (UnlockableEmote allUnlockableEmote in allUnlockableEmotes)
			{
				allUnlockableEmote.emoteId = num++;
				if (!allUnlockableEmote.complementary)
				{
					if (allUnlockableEmote.rarity == 0)
					{
						allEmotesTier0.Add(allUnlockableEmote);
					}
					else if (allUnlockableEmote.rarity == 1)
					{
						allEmotesTier1.Add(allUnlockableEmote);
					}
					else if (allUnlockableEmote.rarity == 2)
					{
						allEmotesTier2.Add(allUnlockableEmote);
					}
					else if (allUnlockableEmote.rarity == 3)
					{
						allEmotesTier3.Add(allUnlockableEmote);
					}
				}
			}
			complementaryEmotes = new List<UnlockableEmote>(complementaryEmotesDefault);
			SaveManager.LoadFavoritedEmotes();
			SaveManager.LoadQuickEmotes();
		}
	}
	[HarmonyPatch]
	public static class SessionManager
	{
		public static List<UnlockableEmote> unlockedEmotes = new List<UnlockableEmote>();

		public static List<UnlockableEmote> unlockedEmotesTier0 = new List<UnlockableEmote>();

		public static List<UnlockableEmote> unlockedEmotesTier1 = new List<UnlockableEmote>();

		public static List<UnlockableEmote> unlockedEmotesTier2 = new List<UnlockableEmote>();

		public static List<UnlockableEmote> unlockedEmotesTier3 = new List<UnlockableEmote>();

		internal static List<UnlockableEmote> emotesUnlockedThisSession = new List<UnlockableEmote>();

		public static Dictionary<string, List<UnlockableEmote>> unlockedEmotesByPlayer = new Dictionary<string, List<UnlockableEmote>>();

		public static List<UnlockableEmote> unlockedFavoriteEmotes = new List<UnlockableEmote>();

		public static string localPlayerUsername => GameNetworkManager.Instance?.username;

		[HarmonyPatch(typeof(StartOfRound), "Awake")]
		[HarmonyPrefix]
		private static void ResetGameValues()
		{
			EmoteController.allEmoteControllers?.Clear();
			EmoteControllerPlayer.allPlayerEmoteControllers?.Clear();
			EmoteControllerMaskedEnemy.allMaskedEnemyEmoteControllers?.Clear();
			EmoteAudioSource.allEmoteAudioSources?.Clear();
			EmotesManager.complementaryEmotes = new List<UnlockableEmote>(EmotesManager.complementaryEmotesDefault);
		}

		[HarmonyPatch(typeof(PlayerControllerB), "ConnectClientToPlayerObject")]
		[HarmonyPostfix]
		private static void OnHostConnected(PlayerControllerB __instance)
		{
			if (!HelperTools.isServer)
			{
				return;
			}
			if (!unlockedEmotesByPlayer.ContainsKey(HelperTools.localPlayerController.playerUsername))
			{
				unlockedEmotesByPlayer.Add(HelperTools.localPlayerController.playerUsername, unlockedEmotes);
				TerminalPatcher.currentEmoteCreditsByPlayer.Add(HelperTools.localPlayerController.playerUsername, TerminalPatcher.currentEmoteCredits);
				return;
			}
			foreach (UnlockableEmote item in unlockedEmotesByPlayer[HelperTools.localPlayerController.playerUsername])
			{
				if (!IsEmoteUnlocked(item))
				{
					unlockedEmotes.Add(item);
				}
			}
			unlockedEmotesByPlayer[HelperTools.localPlayerController.playerUsername] = unlockedEmotes;
			TerminalPatcher.currentEmoteCreditsByPlayer[HelperTools.localPlayerController.playerUsername] = TerminalPatcher.currentEmoteCredits;
		}

		[HarmonyPatch(typeof(StartOfRound), "Start")]
		[HarmonyPostfix]
		private static void OnServerStart(StartOfRound __instance)
		{
			if (HelperTools.isServer)
			{
			}
			SyncManager.RotateEmoteSelectionServer(TerminalPatcher.emoteStoreSeed);
		}

		[HarmonyPatch(typeof(StartOfRound), "StartGame")]
		[HarmonyPostfix]
		private static void ResetOverrideSeedFlag(StartOfRound __instance)
		{
			__instance.overrideRandomSeed = false;
		}

		[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
		[HarmonyPostfix]
		private static void ResetEmotesOnShipReset(StartOfRound __instance)
		{
			if (!ConfigSync.instance.syncUnlockEverything)
			{
				ResetProgressLocal();
			}
			if (HelperTools.isServer)
			{
				SyncManager.RotateEmoteSelectionServer();
			}
		}

		public static void ResetProgressLocal(bool forceResetAll = false)
		{
			CustomLogging.Log("Resetting progress.");
			if (!ConfigSync.instance.syncPersistentUnlocks || forceResetAll)
			{
				ResetEmotesLocal();
			}
			if (!ConfigSync.instance.syncPersistentEmoteCredits || forceResetAll)
			{
				TerminalPatcher.currentEmoteCredits = ConfigSync.instance.syncStartingEmoteCredits;
				List<string> list = new List<string>(TerminalPatcher.currentEmoteCreditsByPlayer.Keys);
				foreach (string item in list)
				{
					TerminalPatcher.currentEmoteCreditsByPlayer[item] = ConfigSync.instance.syncStartingEmoteCredits;
				}
			}
			TerminalPatcher.emoteStoreSeed = 0;
		}

		[HarmonyPatch(typeof(StartOfRound), "SyncShipUnlockablesServerRpc")]
		[HarmonyPostfix]
		private static void SyncUnlockedEmotesWithClients(StartOfRound __instance)
		{
			if (!HelperTools.isServer || ConfigSync.instance.syncUnlockEverything || ConfigSync.instance.syncPersistentUnlocksGlobal)
			{
				return;
			}
			if (ConfigSync.instance.syncShareEverything)
			{
				CustomLogging.Log("Syncing unlocked emotes with clients.");
				SyncManager.SendOnUnlockEmoteUpdateMulti(TerminalPatcher.currentEmoteCredits);
				return;
			}
			HashSet<ulong> hashSet = new HashSet<ulong>();
			PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if (val.actualClientId != 0L && val.playerSteamId != 0)
				{
					hashSet.Add(val.actualClientId);
				}
			}
			foreach (ulong item in hashSet)
			{
				SyncManager.ServerSendSyncToClient(item);
			}
		}

		public static bool IsEmoteUnlocked(string emoteName, string playerUsername = "")
		{
			if (EmotesManager.allUnlockableEmotesDict.TryGetValue(emoteName, out var value))
			{
				return IsEmoteUnlocked(value, playerUsername);
			}
			return false;
		}

		public static bool IsEmoteUnlocked(UnlockableEmote emote, string playerUsername = "")
		{
			if (emote == null)
			{
				return false;
			}
			List<UnlockableEmote> value = unlockedEmotes;
			if (playerUsername != "" && !unlockedEmotesByPlayer.TryGetValue(playerUsername, out value))
			{
				return false;
			}
			if (emote.emoteSyncGroup != null && emote.emoteSyncGroup.Count > 0)
			{
				foreach (UnlockableEmote item in emote.emoteSyncGroup)
				{
					if (value.Contains(item))
					{
						return true;
					}
				}
			}
			return value.Contains(emote);
		}

		public static List<UnlockableEmote> GetUnlockedEmotes(PlayerControllerB playerController)
		{
			return GetUnlockedEmotes(((Object)(object)playerController != (Object)null) ? playerController.playerUsername : "");
		}

		public static List<UnlockableEmote> GetUnlockedEmotes(string playerUsername)
		{
			if (unlockedEmotesByPlayer.TryGetValue(playerUsername, out var value))
			{
				return value;
			}
			return null;
		}

		public static void UnlockEmotesLocal(IEnumerable<UnlockableEmote> emotes, bool purchased = false, string playerUsername = "")
		{
			foreach (UnlockableEmote emote in emotes)
			{
				UnlockEmoteLocal(emote, purchased, playerUsername);
			}
		}

		public static void UnlockEmoteLocal(int emoteId, bool purchased = false, string playerUsername = "")
		{
			UnlockEmoteLocal((emoteId >= 0 && emoteId < EmotesManager.allUnlockableEmotes.Count) ? EmotesManager.allUnlockableEmotes[emoteId] : null, purchased, playerUsername);
		}

		public static void UnlockEmoteLocal(UnlockableEmote emote, bool purchased = false, string playerUsername = "")
		{
			if (emote == null || (emote.requiresHeldProp && ConfigSync.instance.syncRemoveGrabbableEmotesPartyPooperMode))
			{
				return;
			}
			List<UnlockableEmote> list = unlockedEmotes;
			if (playerUsername != "" && playerUsername != localPlayerUsername)
			{
				if (!unlockedEmotesByPlayer.TryGetValue(playerUsername, out var value) && !ConfigSync.instance.syncShareEverything)
				{
					return;
				}
				if (value != null)
				{
					list = value;
				}
			}
			if (IsEmoteUnlocked(emote, playerUsername))
			{
				return;
			}
			if (emote.emoteSyncGroup != null)
			{
				foreach (UnlockableEmote item in emote.emoteSyncGroup)
				{
					if (list.Contains(item))
					{
						return;
					}
				}
			}
			if (!list.Contains(emote))
			{
				list.Add(emote);
			}
			if (list != unlockedEmotes)
			{
				return;
			}
			if (!emote.complementary)
			{
				if (emote.rarity == 3 && !unlockedEmotesTier3.Contains(emote))
				{
					unlockedEmotesTier3.Add(emote);
				}
				else if (emote.rarity == 2 && !unlockedEmotesTier2.Contains(emote))
				{
					unlockedEmotesTier2.Add(emote);
				}
				else if (emote.rarity == 1 && !unlockedEmotesTier1.Contains(emote))
				{
					unlockedEmotesTier1.Add(emote);
				}
				else if (emote.rarity == 0 && !unlockedEmotesTier0.Contains(emote))
				{
					unlockedEmotesTier0.Add(emote);
				}
			}
			if (EmotesManager.allFavoriteEmotes.Contains(emote.emoteName) && !unlockedFavoriteEmotes.Contains(emote))
			{
				unlockedFavoriteEmotes.Add(emote);
			}
			if (ConfigSync.instance.syncPersistentUnlocksGlobal && purchased && !emotesUnlockedThisSession.Contains(emote))
			{
				emotesUnlockedThisSession.Add(emote);
			}
		}

		public static void RemoveEmoteLocal(UnlockableEmote emote)
		{
			unlockedEmotes.Remove(emote);
			unlockedEmotesTier0.Remove(emote);
			unlockedEmotesTier1.Remove(emote);
			unlockedEmotesTier2.Remove(emote);
			unlockedEmotesTier3.Remove(emote);
			unlockedFavoriteEmotes.Remove(emote);
			emotesUnlockedThisSession.Remove(emote);
			foreach (List<UnlockableEmote> value in unlockedEmotesByPlayer.Values)
			{
				value.Remove(emote);
			}
		}

		public static void ResetEmotesLocal()
		{
			CustomLogging.Log("Resetting unlocked emotes.");
			unlockedEmotes.Clear();
			unlockedEmotesTier0.Clear();
			unlockedEmotesTier1.Clear();
			unlockedEmotesTier2.Clear();
			unlockedEmotesTier3.Clear();
			emotesUnlockedThisSession.Clear();
			unlockedEmotesByPlayer.Clear();
			PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if (val.playerSteamId != 0)
				{
					unlockedEmotesByPlayer.Add(val.playerUsername, ((Object)(object)val == (Object)(object)HelperTools.localPlayerController || ConfigSync.instance.syncShareEverything) ? unlockedEmotes : new List<UnlockableEmote>());
				}
			}
			UnlockEmotesLocal(ConfigSync.instance.syncUnlockEverything ? EmotesManager.allUnlockableEmotes : EmotesManager.complementaryEmotes);
			UpdateUnlockedFavoriteEmotes();
		}

		public static void UpdateUnlockedFavoriteEmotes()
		{
			unlockedFavoriteEmotes?.Clear();
			if (EmotesManager.allFavoriteEmotes == null || EmotesManager.allUnlockableEmotesDict == null || unlockedFavoriteEmotes == null)
			{
				return;
			}
			foreach (string allFavoriteEmote in EmotesManager.allFavoriteEmotes)
			{
				if (EmotesManager.allUnlockableEmotesDict.TryGetValue(allFavoriteEmote, out var value))
				{
					if (value.emoteSyncGroup != null && value.emoteSyncGroup.Count > 0)
					{
						value = value.emoteSyncGroup[0];
					}
					if (IsEmoteUnlocked(value))
					{
						unlockedFavoriteEmotes.Add(value);
					}
				}
			}
		}
	}
	public class UnlockableEmote
	{
		public int emoteId;

		public string emoteName;

		public string displayName = "";

		public AnimationClip animationClip;

		public AnimationClip transitionsToClip = null;

		public bool purchasable = true;

		public bool requiresHeldProp = false;

		public GameObject requiredHeldPropPrefab = null;

		public bool complementary = false;

		public bool isPose = false;

		public bool canMoveWhileEmoting = false;

		private bool _isBoomboxAudio = true;

		public string overrideAudioClipName = "";

		public string overrideAudioLoopClipName = "";

		public string emoteSyncGroupName = "";

		public List<UnlockableEmote> emoteSyncGroup;

		public float recordSongLoopValue = 0f;

		public bool randomEmote = false;

		public List<string> propNamesInEmote;

		public bool canSyncEmote = false;

		public int rarity = 0;

		public static string[] rarityColorCodes = new string[4]
		{
			ConfigSettings.emoteNameColorTier0.Value,
			ConfigSettings.emoteNameColorTier1.Value,
			ConfigSettings.emoteNameColorTier2.Value,
			ConfigSettings.emoteNameColorTier3.Value
		};

		public string displayNameColorCoded => $"<color={nameColor}>{displayName}</color>";

		public bool humanoidAnimation => ((Motion)animationClip).isHumanMotion;

		public bool loopable => ((Motion)animationClip).isLooping || ((Object)(object)transitionsToClip != (Object)null && ((Motion)transitionsToClip).isLooping);

		public bool hasAudio => audioClipName != "" || audioLoopClipName != "";

		public bool isBoomboxAudio
		{
			get
			{
				return _isBoomboxAudio && !ConfigSettings.disableBoomboxRequirement.Value;
			}
			set
			{
				_isBoomboxAudio = value;
			}
		}

		public string audioClipName => ((Object)(object)animationClip != (Object)null && AudioManager.AudioExists(((Object)animationClip).name)) ? ((Object)animationClip).name : ((overrideAudioClipName != "" && AudioManager.AudioExists(overrideAudioClipName)) ? overrideAudioClipName : "");

		public string audioLoopClipName => ((Object)(object)transitionsToClip != (Object)null && AudioManager.AudioExists(((Object)transitionsToClip).name)) ? ((Object)transitionsToClip).name : ((overrideAudioLoopClipName != "" && AudioManager.AudioExists(overrideAudioLoopClipName)) ? overrideAudioLoopClipName : "");

		public bool inEmoteSyncGroup => emoteSyncGroup != null && !randomEmote;

		public bool favorite => EmotesManager.allFavoriteEmotes.Contains(emoteName);

		public string rarityText
		{
			get
			{
				if (rarity == 0)
				{
					return "Common";
				}
				if (rarity == 1)
				{
					return "Rare";
				}
				if (rarity == 2)
				{
					return "Epic";
				}
				if (rarity == 3)
				{
					return "Legendary";
				}
				return "Invalid";
			}
		}

		public int price
		{
			get
			{
				int num = -1;
				if (complementary)
				{
					num = 0;
				}
				else if (rarity == 0)
				{
					num = ConfigSync.instance.syncBasePriceEmoteTier0;
				}
				else if (rarity == 1)
				{
					num = ConfigSync.instance.syncBasePriceEmoteTier1;
				}
				else if (rarity == 2)
				{
					num = ConfigSync.instance.syncBasePriceEmoteTier2;
				}
				else if (rarity == 3)
				{
					num = ConfigSync.instance.syncBasePriceEmoteTier3;
				}
				return (int)Mathf.Max((float)num * ConfigSync.instance.syncPriceMultiplierEmotesStore, 0f);
			}
		}

		public string nameColor => rarityColorCodes[rarity];

		public bool IsEmoteInEmoteGroup(UnlockableEmote emote)
		{
			return this == emote || (emoteSyncGroup != null && emoteSyncGroup.Contains(emote));
		}

		public bool ClipIsInEmote(AnimationClip clip)
		{
			if ((Object)(object)clip == (Object)null)
			{
				return false;
			}
			if ((Object)(object)clip == (Object)(object)animationClip || (Object)(object)clip == (Object)(object)transitionsToClip)
			{
				return true;
			}
			if (emoteSyncGroup != null)
			{
				foreach (UnlockableEmote item in emoteSyncGroup)
				{
					if ((Object)(object)clip == (Object)(object)item.animationClip || (Object)(object)clip == (Object)(object)item.transitionsToClip)
					{
						return true;
					}
				}
			}
			return false;
		}

		public AudioClip LoadAudioClip()
		{
			if (hasAudio && audioClipName.Length > 0)
			{
				return AudioManager.LoadAudioClip(audioClipName);
			}
			return null;
		}

		public AudioClip LoadAudioLoopClip()
		{
			if (hasAudio && (Object)(object)transitionsToClip != (Object)null && audioLoopClipName.Length > 0)
			{
				return AudioManager.LoadAudioClip(audioLoopClipName);
			}
			return null;
		}
	}
	[HarmonyPatch]
	public static class SaveManager
	{
		public static string TooManyEmotesSaveFileName = "TooManyEmotes_LocalSaveData";

		private static List<string> globallyUnlockedEmoteNames = new List<string>();

		[HarmonyPatch(typeof(PlayerControllerB), "ConnectClientToPlayerObject")]
		[HarmonyPrefix]
		private static void CheckIfShouldResetLocalSettings()
		{
			if (ConfigSettings.resetGloballyUnlockedEmotes)
			{
				ResetGloballyUnlockedEmotes();
			}
			if (ConfigSettings.resetFavoriteEmotes)
			{
				ResetFavoritedEmotes();
			}
			ConfigSettings.resetGloballyUnlockedEmotes = false;
			ConfigSettings.resetFavoriteEmotes = false;
		}

		[HarmonyPatch(typeof(GameNetworkManager), "SaveGameValues")]
		[HarmonyPostfix]
		private static void SaveUnlockedEmotes(GameNetworkManager __instance)
		{
			if (!__instance.isHostingGame || !StartOfRound.Instance.inShipPhase || ConfigSync.instance.syncUnlockEverything)
			{
				return;
			}
			CustomLogging.Log("Saving game values.");
			try
			{
				HashSet<string> hashSet;
				try
				{
					hashSet = new HashSet<string>(ES3.Load<string[]>("TooManyEmotes.UnlockedEmotes.PlayersList", HelperTools.currentSaveFileName, new string[0]));
				}
				catch (Exception ex)
				{
					CustomLogging.LogErrorVerbose("Error loading previous users list. Deleting key: TooManyEmotes.UnlockedEmotes.PlayersList from file: " + HelperTools.currentSaveFileName);
					CustomLogging.LogErrorVerbose(ex.ToString());
					ES3.DeleteKey("TooManyEmotes.UnlockedEmotes.PlayersList", HelperTools.currentSaveFileName);
					hashSet = new HashSet<string>();
				}
				foreach (string key in SessionManager.unlockedEmotesByPlayer.Keys)
				{
					hashSet.Add(key);
				}
				ES3.Save<string[]>("TooManyEmotes.UnlockedEmotes.PlayersList", hashSet.ToArray(), HelperTools.currentSaveFileName);
				foreach (string item in hashSet)
				{
					if (!ConfigSync.instance.syncPersistentUnlocksGlobal)
					{
						if (!SessionManager.unlockedEmotesByPlayer.ContainsKey(item))
						{
							continue;
						}
						if (SessionManager.unlockedEmotesByPlayer.TryGetValue(item, out var value))
						{
							CustomLogging.Log("Saving " + value.Count + " unlocked emotes for player: " + item);
							string[] array = new string[value.Count];
							for (int i = 0; i < value.Count; i++)
							{
								array[i] = value[i].emoteName;
							}
							if (value == SessionManager.unlockedEmotes)
							{
								ES3.Save<string[]>("TooManyEmotes.UnlockedEmotes", array, HelperTools.currentSaveFileName);
							}
							else
							{
								ES3.Save<string[]>("TooManyEmotes.UnlockedEmotes.Player_" + item, array, HelperTools.currentSaveFileName);
							}
						}
					}
					if (TerminalPatcher.currentEmoteCreditsByPlayer.ContainsKey(item))
					{
						CustomLogging.Log("Saving " + TerminalPatcher.currentEmoteCreditsByPlayer[item] + " emote credits for player: " + item);
						string text = "TooManyEmotes.CurrentEmoteCredits" + (ConfigSync.instance.syncPersistentUnlocks ? ".Persistent" : "");
						if ((Object)(object)HelperTools.localPlayerController != (Object)null && HelperTools.localPlayerController.playerSteamId != 0L && item == HelperTools.localPlayerController.playerUsername)
						{
							ES3.Save<int>(text, TerminalPatcher.currentEmoteCredits, __instance.currentSaveFileName);
						}
						else
						{
							ES3.Save<int>(text + ".Player_" + item, TerminalPatcher.currentEmoteCreditsByPlayer[item], __instance.currentSaveFileName);
						}
					}
				}
				ES3.Save<int>("TooManyEmotes.EmoteStoreSeed", TerminalPatcher.emoteStoreSeed, __instance.currentSaveFileName);
				CustomLogging.Log("Saved Seed: " + TerminalPatcher.emoteStoreSeed);
			}
			catch (Exception ex2)
			{
				CustomLogging.LogError("Error while trying to save TooManyEmotes values when disconnecting as host.");
				CustomLogging.LogError(ex2.ToString());
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "LoadUnlockables")]
		[HarmonyPostfix]
		private static void LoadUnlockedEmotes(StartOfRound __instance)
		{
			if (!GameNetworkManager.Instance.isHostingGame || ConfigSync.instance.syncUnlockEverything)
			{
				return;
			}
			CustomLogging.Log("Loading game values.");
			try
			{
				if (!ConfigSync.instance.syncPersistentUnlocksGlobal)
				{
					SessionManager.ResetEmotesLocal();
					string[] array = ES3.Load<string[]>("TooManyEmotes.UnlockedEmotes", HelperTools.currentSaveFileName, new string[0]);
					string[] array2 = array;
					foreach (string key in array2)
					{
						if (EmotesManager.allUnlockableEmotesDict.TryGetValue(key, out var value))
						{
							SessionManager.UnlockEmoteLocal(value);
						}
					}
				}
				string text = "TooManyEmotes.CurrentEmoteCredits"