Decompiled source of Too Many Emotes v1.0.0

plugins/TooManyEmotes.dll

Decompiled a day 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.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Photon;
using SideLoader;
using SideLoader.Model;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace TooManyEmotes;

public class Emote
{
	public string Name;

	public string DisplayName;

	public AnimationClip Clip;

	public AnimationClip LoopClip;

	public bool IsPose;

	public string SyncGroupName = "";

	public int SyncLayer = -1;

	public bool RandomInGroup;

	public List<Emote> SyncGroup;

	public bool Loops
	{
		get
		{
			if (!((Motion)Clip).isLooping)
			{
				return (Object)(object)LoopClip != (Object)null;
			}
			return true;
		}
	}

	public bool ShownInMenu
	{
		get
		{
			if (SyncGroup != null)
			{
				return SyncGroup.IndexOf(this) == 0;
			}
			return true;
		}
	}

	public override string ToString()
	{
		return Name;
	}
}
public static class EmoteCamera
{
	private class State
	{
		public float Target;

		public float Current;

		public bool Applied;

		public Vector3 SavedLocalPosition;
	}

	[HarmonyPatch(typeof(CharacterCamera), "LateUpdate")]
	private static class Apply
	{
		[HarmonyPriority(800)]
		private static void Prefix(CharacterCamera __instance)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (states.TryGetValue(__instance, out var value) && value.Applied && (Object)(object)__instance.CameraScript != (Object)null)
			{
				((Component)__instance.CameraScript).transform.localPosition = value.SavedLocalPosition;
				value.Applied = false;
			}
		}

		[HarmonyPriority(0)]
		private static void Postfix(CharacterCamera __instance)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: 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_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			//IL_011f: 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_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: 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)
			if (!states.TryGetValue(__instance, out var value) || (Object)(object)__instance.CameraScript == (Object)null || (Object)(object)__instance.TargetCharacter == (Object)null)
			{
				return;
			}
			value.Current = Mathf.MoveTowards(value.Current, value.Target, 8f * Time.deltaTime * Mathf.Max(1f, Mathf.Abs(value.Target - value.Current)));
			if (Mathf.Abs(value.Current) < 0.001f && value.Target == 0f)
			{
				states.Remove(__instance);
				return;
			}
			Transform transform = ((Component)__instance.CameraScript).transform;
			value.SavedLocalPosition = transform.localPosition;
			value.Applied = true;
			Vector3 val = ((Component)__instance.TargetCharacter).transform.position + Vector3.up * 1.5f;
			float num = Vector3.Distance(transform.position, val);
			float num2 = Mathf.Max(value.Current, 0f - Mathf.Max(0f, num - 0.4f));
			Vector3 val2 = transform.position - transform.forward * num2;
			Vector3 val3 = val2 - val;
			RaycastHit val4 = default(RaycastHit);
			if (((Vector3)(ref val3)).sqrMagnitude > 0.0001f && Physics.SphereCast(new Ray(val, val3), 0.2f, ref val4, ((Vector3)(ref val3)).magnitude, Global.LargeEnvironmentMask))
			{
				val2 = ((RaycastHit)(ref val4)).point + ((RaycastHit)(ref val4)).normal * 0.2f;
			}
			transform.position = val2;
		}
	}

	private const float Step = 0.5f;

	private const float HeldSpeed = 4f;

	private const float MinZoom = -3f;

	private const float MaxZoom = 6f;

	private const float SmoothSpeed = 8f;

	private const float MinPivotDistance = 0.4f;

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

	public static float ZoomOf(Character character)
	{
		if (!((Object)(object)character.CharacterCamera != (Object)null) || !states.TryGetValue(character.CharacterCamera, out var value))
		{
			return 0f;
		}
		return value.Target;
	}

	public static void UpdateFor(Character character, bool emoteActive, bool menuOpen, int zoomStep, bool wheelNotch)
	{
		CharacterCamera characterCamera = character.CharacterCamera;
		if ((Object)(object)characterCamera == (Object)null)
		{
			return;
		}
		states.TryGetValue(characterCamera, out var value);
		if (!emoteActive && !menuOpen)
		{
			if (value != null)
			{
				value.Target = 0f;
			}
		}
		else if (!menuOpen && emoteActive && zoomStep != 0)
		{
			if (value == null)
			{
				value = (states[characterCamera] = new State());
			}
			float num = (wheelNotch ? 0.5f : (4f * Time.deltaTime));
			value.Target = Mathf.Clamp(value.Target - (float)zoomStep * num, -3f, 6f);
		}
	}
}
public class EmoteController : MonoBehaviour
{
	private const float FadeInTime = 0.25f;

	private const float FadeOutTime = 0.3f;

	private const float MoveInputThreshold = 0.2f;

	private const float MaxSheatheWait = 2f;

	private Character character;

	private Animator animator;

	private PlayableGraph graph;

	private AnimationPlayableOutput output;

	private AnimationMixerPlayable mixer;

	private AnimationClipPlayable introPlayable;

	private AnimationClipPlayable loopPlayable;

	private bool inLoopPhase;

	private float weight;

	private float targetWeight;

	private bool savedApplyRootMotion;

	private Coroutine pendingStart;

	private Coroutine pendingEntry;

	private bool inSession;

	private bool restoreWeapon;

	private Equipment restoreLeftHand;

	private bool restoreLit;

	private float inactiveSince = -1f;

	private Coroutine restoring;

	private SpellCastType watchedPose = (SpellCastType)(-1);

	private float watchStart;

	private float skillCastStart = -100f;

	public Emote Current { get; private set; }

	public bool IsEmoting
	{
		get
		{
			if (Current != null)
			{
				return targetWeight > 0f;
			}
			return false;
		}
	}

	public float Weight
	{
		get
		{
			if (!((PlayableGraph)(ref graph)).IsValid())
			{
				return 0f;
			}
			return weight;
		}
	}

	private bool WeaponDrawn
	{
		get
		{
			if ((Object)(object)character.CurrentWeapon != (Object)null)
			{
				return !character.Sheathed;
			}
			return false;
		}
	}

	public float CurrentTime
	{
		get
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (((PlayableGraph)(ref graph)).IsValid() && Current != null)
			{
				if (!inLoopPhase)
				{
					return (float)PlayableExtensions.GetTime<AnimationClipPlayable>(introPlayable);
				}
				return Current.Clip.length + (float)PlayableExtensions.GetTime<AnimationClipPlayable>(loopPlayable);
			}
			return 0f;
		}
	}

	private bool SkillEmoteActive
	{
		get
		{
			if (!(Time.time - skillCastStart < 0.5f))
			{
				if (character.IsCasting)
				{
					return Time.time - skillCastStart < 30f;
				}
				return false;
			}
			return true;
		}
	}

	public bool EmoteActive
	{
		get
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			if (!IsEmoting && (int)watchedPose == -1)
			{
				return SkillEmoteActive;
			}
			return true;
		}
	}

	public static EmoteController For(Character character)
	{
		EmoteController emoteController = ((Component)character).GetComponent<EmoteController>();
		if ((Object)(object)emoteController == (Object)null)
		{
			emoteController = ((Component)character).gameObject.AddComponent<EmoteController>();
		}
		return emoteController;
	}

	private void Awake()
	{
		character = ((Component)this).GetComponent<Character>();
		animator = character.Animator;
	}

	public bool CanEmote()
	{
		if ((Object)(object)character != (Object)null && (Object)(object)animator != (Object)null && animator.isHuman && !character.IsDead && character.InLocomotion && !character.IsCasting && !character.Dodging && !character.Blocking)
		{
			return !character.Stunned;
		}
		return false;
	}

	public string DescribeState()
	{
		Animator obj = animator;
		string text = $"human={((obj != null) ? new bool?(obj.isHuman) : ((bool?)null))} dead={character.IsDead} locomotion={character.InLocomotion} ";
		string text2 = $"casting={character.IsCasting} dodging={character.Dodging} blocking={character.Blocking} ";
		object arg = character.Stunned;
		object arg2 = character.Sheathed;
		Weapon currentWeapon = character.CurrentWeapon;
		return text + text2 + string.Format("stunned={0} sheathed={1} weapon={2}", arg, arg2, ((currentWeapon != null) ? ((Item)currentWeapon).Name : null) ?? "none");
	}

	public string DescribeGraph()
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		if (!((PlayableGraph)(ref graph)).IsValid())
		{
			return "graph=none";
		}
		string text = $"graph playing={((PlayableGraph)(ref graph)).IsPlaying()} outputWeight={PlayableOutputExtensions.GetWeight<AnimationPlayableOutput>(output):0.00} weight={weight:0.00} target={targetWeight:0.00} ";
		object[] obj = new object[4]
		{
			PlayableExtensions.GetTime<AnimationClipPlayable>(introPlayable),
			null,
			null,
			null
		};
		Emote current = Current;
		obj[1] = ((current != null) ? new float?(current.Clip.length) : ((float?)null));
		obj[2] = inLoopPhase;
		obj[3] = animator.applyRootMotion;
		return text + string.Format("intro t={0:0.00}/{1:0.00} loopPhase={2} rootMotion={3}", obj);
	}

	private static void Trace(string message)
	{
		if (Plugin.Verbose)
		{
			Plugin.Log.LogMessage((object)("[EmoteController] " + message));
		}
	}

	public void Play(Emote emote)
	{
		if (emote == null)
		{
			return;
		}
		if (!CanEmote())
		{
			Trace("cannot emote: " + DescribeState());
			return;
		}
		if (pendingStart != null)
		{
			((MonoBehaviour)this).StopCoroutine(pendingStart);
		}
		pendingStart = ((MonoBehaviour)this).StartCoroutine(SheatheThenPlay(emote));
	}

	private IEnumerator SheatheThenPlay(Emote emote)
	{
		if (WeaponDrawn)
		{
			Trace("sheathing first: " + DescribeState());
			character.SheatheInput();
			float deadline = Time.time + 2f;
			while (WeaponDrawn || !character.InLocomotion)
			{
				if (Time.time > deadline)
				{
					Trace("gave up waiting for sheathe: " + DescribeState());
					pendingStart = null;
					yield break;
				}
				yield return null;
			}
		}
		pendingStart = null;
		Begin(emote);
	}

	public void PlayRemote(Emote emote, float time)
	{
		if (pendingStart != null)
		{
			((MonoBehaviour)this).StopCoroutine(pendingStart);
			pendingStart = null;
		}
		Begin(emote, time);
	}

	private void Begin(Emote emote, float startTime = 0f)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: 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_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: 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_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0160: Unknown result type (might be due to invalid IL or missing references)
		//IL_0211: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_019d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
		bool num = ((PlayableGraph)(ref graph)).IsValid();
		if (num)
		{
			((PlayableGraph)(ref graph)).Destroy();
		}
		else
		{
			savedApplyRootMotion = animator.applyRootMotion;
		}
		graph = PlayableGraph.Create("TooManyEmotes_" + UID.op_Implicit(character.UID));
		((PlayableGraph)(ref graph)).SetTimeUpdateMode((DirectorUpdateMode)1);
		output = AnimationPlayableOutput.Create(graph, "Emote", animator);
		mixer = AnimationMixerPlayable.Create(graph, 2, false);
		introPlayable = AnimationClipPlayable.Create(graph, emote.Clip);
		((PlayableGraph)(ref graph)).Connect<AnimationClipPlayable, AnimationMixerPlayable>(introPlayable, 0, mixer, 0);
		PlayableExtensions.SetInputWeight<AnimationMixerPlayable>(mixer, 0, 1f);
		if ((Object)(object)emote.LoopClip != (Object)null)
		{
			loopPlayable = AnimationClipPlayable.Create(graph, emote.LoopClip);
			((PlayableGraph)(ref graph)).Connect<AnimationClipPlayable, AnimationMixerPlayable>(loopPlayable, 0, mixer, 1);
			PlayableExtensions.SetInputWeight<AnimationMixerPlayable>(mixer, 1, 0f);
			PlayableExtensions.Pause<AnimationClipPlayable>(loopPlayable);
		}
		PlayableOutputExtensions.SetSourcePlayable<AnimationPlayableOutput, AnimationMixerPlayable>(output, mixer);
		animator.applyRootMotion = false;
		Current = emote;
		inLoopPhase = false;
		if (startTime > 0f)
		{
			if (PlayableExtensions.IsValid<AnimationClipPlayable>(loopPlayable) && startTime >= emote.Clip.length)
			{
				PlayableExtensions.SetTime<AnimationClipPlayable>(introPlayable, (double)emote.Clip.length);
				PlayableExtensions.Pause<AnimationClipPlayable>(introPlayable);
				PlayableExtensions.SetTime<AnimationClipPlayable>(loopPlayable, (double)(startTime - emote.Clip.length));
				PlayableExtensions.Play<AnimationClipPlayable>(loopPlayable);
				PlayableExtensions.SetInputWeight<AnimationMixerPlayable>(mixer, 0, 0f);
				PlayableExtensions.SetInputWeight<AnimationMixerPlayable>(mixer, 1, 1f);
				inLoopPhase = true;
			}
			else
			{
				PlayableExtensions.SetTime<AnimationClipPlayable>(introPlayable, (double)startTime);
			}
		}
		if (!num)
		{
			weight = 0f;
		}
		targetWeight = 1f;
		PlayableOutputExtensions.SetWeight<AnimationPlayableOutput>(output, weight);
		((PlayableGraph)(ref graph)).Play();
		Trace($"started {emote.Name} at {startTime:0.00} s: {DescribeGraph()}");
		if (character.IsLocalPlayer)
		{
			EmoteNetwork.SendPlay(character, emote, startTime);
		}
	}

	public void Stop()
	{
		if (pendingStart != null)
		{
			((MonoBehaviour)this).StopCoroutine(pendingStart);
			pendingStart = null;
		}
		FadeOut();
	}

	private void FadeOut()
	{
		if (targetWeight > 0f && character.IsLocalPlayer)
		{
			EmoteNetwork.SendStop(character);
		}
		targetWeight = 0f;
	}

	public void StopAll()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Invalid comparison between Unknown and I4
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		Stop();
		if ((int)watchedPose != -1 || (SkillEmoteActive && character.IsCasting))
		{
			Trace("stopping pose/cast: " + DescribeState());
			watchedPose = (SpellCastType)(-1);
			skillCastStart = -100f;
			CancelCast();
		}
	}

	public void PlayEntry(MenuEntry entry)
	{
		if (pendingEntry != null)
		{
			((MonoBehaviour)this).StopCoroutine(pendingEntry);
		}
		pendingEntry = ((MonoBehaviour)this).StartCoroutine(PlayEntryAfterCurrent(entry));
	}

	private IEnumerator PlayEntryAfterCurrent(MenuEntry entry)
	{
		if ((int)watchedPose != -1 || (SkillEmoteActive && character.IsCasting))
		{
			Trace("leaving pose/cast for " + entry.Key + ": " + DescribeState());
			watchedPose = (SpellCastType)(-1);
			skillCastStart = -100f;
			CancelCast();
		}
		BeginSession();
		if (Plugin.UnequipLeftHand.Value)
		{
			UnequipLeftHand();
		}
		float deadline = Time.time + 1.5f;
		while ((!character.InLocomotion || !character.NextIsLocomotion || character.IsCasting) && Time.time < deadline)
		{
			yield return null;
		}
		pendingEntry = null;
		entry.Play(character);
	}

	private void UnequipLeftHand()
	{
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Invalid comparison between Unknown and I4
		Equipment leftHandEquipment = character.LeftHandEquipment;
		if (!((Object)(object)leftHandEquipment == (Object)null) && !((Object)(object)leftHandEquipment == (Object)(object)character.CurrentWeapon) && !leftHandEquipment.TwoHanded)
		{
			Trace("unequipping left hand item " + ((Item)leftHandEquipment).Name);
			if ((Object)(object)restoreLeftHand == (Object)null)
			{
				restoreLeftHand = leftHandEquipment;
				restoreLit = (int)((Item)leftHandEquipment).LitStatus == 1;
			}
			character.Inventory.UnequipItem(leftHandEquipment, false);
		}
	}

	private void BeginSession()
	{
		if (restoring != null)
		{
			((MonoBehaviour)this).StopCoroutine(restoring);
			restoring = null;
		}
		if (!inSession)
		{
			inSession = true;
			restoreWeapon = WeaponDrawn;
			restoreLeftHand = null;
			inactiveSince = -1f;
		}
	}

	private void UpdateSession()
	{
		if (!inSession)
		{
			return;
		}
		if (EmoteActive || pendingEntry != null || pendingStart != null)
		{
			inactiveSince = -1f;
			return;
		}
		if (inactiveSince < 0f)
		{
			inactiveSince = Time.time;
		}
		if (!(Time.time - inactiveSince < 0.3f))
		{
			inSession = false;
			if (!character.IsDead && ((Object)(object)restoreLeftHand != (Object)null || restoreWeapon))
			{
				restoring = ((MonoBehaviour)this).StartCoroutine(RestoreHands(restoreLeftHand, restoreLit, restoreWeapon));
			}
			restoreLeftHand = null;
		}
	}

	private IEnumerator RestoreHands(Equipment leftHand, bool lit, bool drawWeapon)
	{
		if ((Object)(object)leftHand != (Object)null && (Object)(object)character.LeftHandEquipment == (Object)null && character.Inventory.OwnsItem(((Item)leftHand).UID))
		{
			Trace("re-equipping " + ((Item)leftHand).Name);
			character.Inventory.EquipItem(leftHand, false);
			float deadline = Time.time + 2f;
			while ((Object)(object)character.LeftHandEquipment != (Object)(object)leftHand && Time.time < deadline)
			{
				yield return null;
			}
			if (lit && (Object)(object)character.LeftHandEquipment == (Object)(object)leftHand && (int)((Item)leftHand).LitStatus == 2)
			{
				((Item)leftHand).SetLight(true);
			}
		}
		if (drawWeapon && Plugin.DrawWeaponAfterEmote.Value)
		{
			float deadline = Time.time + 2f;
			while ((!character.InLocomotion || !character.NextIsLocomotion) && Time.time < deadline)
			{
				yield return null;
			}
			if ((Object)(object)character.CurrentWeapon != (Object)null && character.Sheathed && !character.IsDead)
			{
				Trace("drawing the weapon again");
				character.SheatheInput();
			}
		}
		restoring = null;
	}

	private void CancelCast()
	{
		character.ForceCancel(true, true);
		EmoteNetwork.SendCancel(character);
	}

	public void WatchPose(SpellCastType pose)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		watchedPose = pose;
		watchStart = Time.time;
	}

	public void NoteSkillCast()
	{
		skillCastStart = Time.time;
	}

	private void UpdatePose()
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Invalid comparison between Unknown and I4
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		if ((int)watchedPose == -1 || Time.time - watchStart < 0.5f)
		{
			return;
		}
		if (!character.IsCasting && character.InLocomotion)
		{
			if (Time.time - watchStart > 1.5f)
			{
				watchedPose = (SpellCastType)(-1);
			}
		}
		else if (character.IsLocalPlayer && PlayerWantsToAct())
		{
			Trace($"leaving pose {watchedPose}: {DescribeState()}");
			watchedPose = (SpellCastType)(-1);
			CancelCast();
		}
	}

	private bool PlayerWantsToAct()
	{
		if ((Object)(object)character.OwnerPlayerSys == (Object)null)
		{
			return false;
		}
		int playerID = character.OwnerPlayerSys.PlayerID;
		float num = ControlsInput.MoveHorizontal(playerID);
		float num2 = ControlsInput.MoveVertical(playerID);
		if (!(num * num + num2 * num2 > 0.040000003f) && !ControlsInput.DodgeButtonDown(playerID))
		{
			return ControlsInput.Attack1Press(playerID);
		}
		return true;
	}

	private void Update()
	{
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		UpdatePose();
		UpdateSession();
		if (((PlayableGraph)(ref graph)).IsValid())
		{
			if (targetWeight > 0f && ShouldInterrupt())
			{
				Trace("interrupted: " + DescribeState());
				FadeOut();
			}
			if (targetWeight > 0f)
			{
				AdvancePhase();
			}
			float num = ((targetWeight > weight) ? 0.25f : 0.3f);
			weight = Mathf.MoveTowards(weight, targetWeight, Time.deltaTime / num);
			PlayableOutputExtensions.SetWeight<AnimationPlayableOutput>(output, weight);
			if (weight <= 0f && targetWeight <= 0f)
			{
				Teardown();
			}
		}
	}

	private void AdvancePhase()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		if (!inLoopPhase && !(PlayableExtensions.GetTime<AnimationClipPlayable>(introPlayable) < (double)Current.Clip.length))
		{
			if (PlayableExtensions.IsValid<AnimationClipPlayable>(loopPlayable))
			{
				PlayableExtensions.SetTime<AnimationClipPlayable>(loopPlayable, 0.0);
				PlayableExtensions.Play<AnimationClipPlayable>(loopPlayable);
				PlayableExtensions.Pause<AnimationClipPlayable>(introPlayable);
				PlayableExtensions.SetInputWeight<AnimationMixerPlayable>(mixer, 0, 0f);
				PlayableExtensions.SetInputWeight<AnimationMixerPlayable>(mixer, 1, 1f);
				inLoopPhase = true;
			}
			else if (!((Motion)Current.Clip).isLooping && !Current.IsPose)
			{
				FadeOut();
			}
		}
	}

	private bool ShouldInterrupt()
	{
		if (!character.IsLocalPlayer)
		{
			return character.IsDead;
		}
		if (character.IsDead || !character.InLocomotion || character.IsCasting || character.Dodging || character.Blocking || character.Stunned || WeaponDrawn)
		{
			return true;
		}
		if (character.IsLocalPlayer && (Object)(object)character.OwnerPlayerSys != (Object)null)
		{
			int playerID = character.OwnerPlayerSys.PlayerID;
			float num = ControlsInput.MoveHorizontal(playerID);
			float num2 = ControlsInput.MoveVertical(playerID);
			if (num * num + num2 * num2 > 0.040000003f)
			{
				return true;
			}
		}
		return false;
	}

	private void Teardown()
	{
		if (((PlayableGraph)(ref graph)).IsValid())
		{
			Trace("teardown " + Current?.Name);
			((PlayableGraph)(ref graph)).Destroy();
			if ((Object)(object)animator != (Object)null)
			{
				animator.applyRootMotion = savedApplyRootMotion;
			}
		}
		Current = null;
		inLoopPhase = false;
	}

	private void OnDisable()
	{
		Teardown();
	}

	private void OnDestroy()
	{
		Teardown();
	}
}
public static class EmoteLibrary
{
	private static readonly string[] BundleNames = new string[6] { "emotes_complementary", "emotes_0", "emotes_1", "emotes_2", "emotes_3", "emotes_special" };

	public static readonly List<Emote> All = new List<Emote>();

	public static readonly Dictionary<string, Emote> ByName = new Dictionary<string, Emote>();

	public static List<Emote> Menu = new List<Emote>();

	public static void Load(string assetsDir)
	{
		Dictionary<string, AnimationClip> dictionary = new Dictionary<string, AnimationClip>();
		string[] bundleNames = BundleNames;
		foreach (string path in bundleNames)
		{
			string text = Path.Combine(assetsDir, path);
			AssetBundle val = AssetBundle.LoadFromFile(text);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogError((object)("Failed to load emote bundle " + text));
				continue;
			}
			AnimationClip[] array = val.LoadAllAssets<AnimationClip>();
			foreach (AnimationClip val2 in array)
			{
				if (!dictionary.ContainsKey(((Object)val2).name))
				{
					dictionary.Add(((Object)val2).name, val2);
				}
			}
		}
		Dictionary<string, List<Emote>> dictionary2 = new Dictionary<string, List<Emote>>();
		foreach (AnimationClip value2 in dictionary.Values)
		{
			if (((Object)value2).name.EndsWith("_loop"))
			{
				continue;
			}
			Emote emote = Parse(value2, dictionary);
			if (emote == null || ByName.ContainsKey(emote.Name))
			{
				continue;
			}
			if (emote.SyncGroupName.Length > 0)
			{
				if (!dictionary2.TryGetValue(emote.SyncGroupName, out var value))
				{
					dictionary2.Add(emote.SyncGroupName, value = new List<Emote>());
				}
				if (emote.SyncLayer >= 0)
				{
					while (value.Count <= emote.SyncLayer)
					{
						value.Add(null);
					}
					value[emote.SyncLayer] = emote;
				}
				else
				{
					value.Add(emote);
				}
				emote.SyncGroup = value;
			}
			All.Add(emote);
			ByName.Add(emote.Name, emote);
		}
		foreach (List<Emote> value3 in dictionary2.Values)
		{
			value3.RemoveAll((Emote e) => e == null);
		}
		Menu = All.Where((Emote e) => e.ShownInMenu).OrderBy<Emote, string>((Emote e) => e.DisplayName, StringComparer.InvariantCultureIgnoreCase).ToList();
		Plugin.Log.LogMessage((object)$"Loaded {dictionary.Count} clips: {All.Count} emotes, {Menu.Count} in menu, {dictionary2.Count} sync groups.");
	}

	private static Emote Parse(AnimationClip clip, Dictionary<string, AnimationClip> clips)
	{
		Emote emote = new Emote
		{
			Clip = clip
		};
		string text = ((Object)clip).name;
		if (text.Contains("_start") && !text.Contains("_start_") && clips.TryGetValue(text.Replace("_start", "_loop"), out var value))
		{
			emote.LoopClip = value;
			text = text.Replace("_start", "");
		}
		else if (text.Contains("_pose"))
		{
			emote.IsPose = true;
			text = text.Replace("_pose", "");
		}
		string name = text;
		if (text.Contains("."))
		{
			string[] array = text.Split(new char[1] { '.' });
			if (array.Length > 3 || array[0].Length == 0)
			{
				Plugin.Log.LogWarning((object)("Unexpected emote name format: " + ((Object)clip).name));
				return null;
			}
			emote.SyncGroupName = array[0];
			if (array.Length == 3)
			{
				string text2 = array[1].ToLowerInvariant();
				if (text2.StartsWith("layer_") && int.TryParse(text2.Substring(6), out var result))
				{
					emote.SyncLayer = result;
				}
				else if (text2 == "random")
				{
					emote.RandomInGroup = true;
				}
			}
			text = array[0] + "." + array[^1];
			name = array[0];
		}
		emote.Name = text;
		emote.DisplayName = Prettify(name);
		return emote;
	}

	private static string Prettify(string name)
	{
		string str = name.Replace('_', ' ').Trim();
		return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(str);
	}
}
public static class EmoteNetwork
{
	private const string Magic = "TME1";

	public const string Play = "play";

	public const string Stop = "stop";

	public const string Cancel = "cancel";

	public const string Hello = "hello";

	private static ConfigEntry<int> eventCode;

	internal static Action<object[]> TestSink;

	private static bool Online
	{
		get
		{
			if (PhotonNetwork.inRoom)
			{
				return !PhotonNetwork.offlineMode;
			}
			return false;
		}
	}

	public static void Init(ConfigFile config)
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Expected O, but got Unknown
		eventCode = config.Bind<int>("Network", "Photon event code", 113, new ConfigDescription("Custom Photon event used to sync emotes in co-op. Change it only if another mod uses the same code (every player must use the same value).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 199), Array.Empty<object>()));
		PhotonNetwork.OnEventCall += new EventCallback(OnEvent);
	}

	public static void SendPlay(Character character, Emote emote, float time)
	{
		Send("play", character, emote.Name, time);
	}

	public static void SendStop(Character character)
	{
		Send("stop", character, "", 0f);
	}

	public static void SendCancel(Character character)
	{
		Send("cancel", character, "", 0f);
	}

	public static void SendHello()
	{
		Send("hello", null, "", 0f);
	}

	private static void Send(string op, Character character, string emote, float time)
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Expected O, but got Unknown
		if (!((Object)(object)character != (Object)null) || character.IsLocalPlayer)
		{
			object[] array = new object[5]
			{
				"TME1",
				op,
				((Object)(object)character != (Object)null) ? ((object)character.UID/*cast due to .constrained prefix*/).ToString() : "",
				emote,
				time
			};
			if (Online)
			{
				PhotonNetwork.RaiseEvent((byte)eventCode.Value, (object)array, true, new RaiseEventOptions
				{
					Receivers = (ReceiverGroup)0
				});
				Trace($"sent {op} {array[2]} {emote} {time:0.00}");
			}
			else
			{
				TestSink?.Invoke(array);
			}
		}
	}

	private static void OnEvent(byte code, object content, int senderId)
	{
		if (code != eventCode.Value || !(content is object[] array) || array.Length < 5 || !(array[0] is string text) || text != "TME1")
		{
			return;
		}
		try
		{
			Receive((string)array[1], (string)array[2], (string)array[3], Convert.ToSingle(array[4]), senderId);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Bad emote network message: " + ex.Message));
		}
	}

	internal static void Receive(string op, string characterUID, string emoteName, float time, int senderId)
	{
		Trace($"received {op} {characterUID} {emoteName} {time:0.00} from {senderId}");
		if (op == "hello")
		{
			SendStateOfLocalPlayers();
			return;
		}
		CharacterManager instance = CharacterManager.Instance;
		Character val = ((instance != null) ? instance.GetCharacter(characterUID) : null);
		if ((Object)(object)val == (Object)null || (val.IsLocalPlayer && senderId >= 0))
		{
			return;
		}
		if (senderId >= 0 && (Object)(object)((MonoBehaviour)val).photonView != (Object)null && ((MonoBehaviour)val).photonView.ownerId != senderId)
		{
			Plugin.Log.LogWarning((object)$"Ignored emote message for {val.Name} from player {senderId} (owner {((MonoBehaviour)val).photonView.ownerId})");
			return;
		}
		EmoteController emoteController = EmoteController.For(val);
		switch (op)
		{
		case "play":
		{
			if (EmoteLibrary.ByName.TryGetValue(emoteName, out var value))
			{
				emoteController.PlayRemote(value, time);
				break;
			}
			Plugin.Log.LogWarning((object)("Unknown emote '" + emoteName + "' from " + val.Name + " (different mod version?)"));
			break;
		}
		case "stop":
			emoteController.Stop();
			break;
		case "cancel":
			if (val.IsCasting || !val.InLocomotion)
			{
				val.ForceCancel(true, true);
			}
			break;
		}
	}

	private static void SendStateOfLocalPlayers()
	{
		foreach (Character item in LocalPlayers())
		{
			EmoteController component = ((Component)item).GetComponent<EmoteController>();
			if ((Object)(object)component != (Object)null && component.IsEmoting && component.Current != null)
			{
				SendPlay(item, component.Current, component.CurrentTime);
			}
		}
	}

	private static IEnumerable<Character> LocalPlayers()
	{
		if ((Object)(object)CharacterManager.Instance == (Object)null)
		{
			yield break;
		}
		foreach (Character value in CharacterManager.Instance.Characters.Values)
		{
			if ((Object)(object)value != (Object)null && value.IsLocalPlayer)
			{
				yield return value;
			}
		}
	}

	private static void Trace(string message)
	{
		if (Plugin.Verbose)
		{
			Plugin.Log.LogMessage((object)("[EmoteNetwork] " + message));
		}
	}
}
public static class Favorites
{
	private static ConfigEntry<string> entry;

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

	public static IReadOnlyList<string> Keys => keys;

	public static void Bind(ConfigFile config)
	{
		entry = config.Bind<string>("Menu", "Favorites", "", "Favorite emotes, in menu order (managed from the emote menu).");
		keys = entry.Value.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries).Distinct().ToList();
	}

	public static bool Contains(string key)
	{
		return keys.Contains(key);
	}

	public static void Toggle(string key)
	{
		if (!keys.Remove(key))
		{
			keys.Add(key);
		}
		entry.Value = string.Join("|", keys);
	}
}
[HarmonyPatch(typeof(IKControl), "LeftHandIK")]
internal static class HandIKPatch
{
	internal static bool Enabled = true;

	private static void Postfix(IKControl __instance, Animator ___m_animator, float ___m_leftHandWeight, float ___m_leftHandRotWeight)
	{
		if (Enabled)
		{
			EmoteController component = ((Component)__instance).GetComponent<EmoteController>();
			if (!((Object)(object)component == (Object)null) && !(component.Weight <= 0f))
			{
				float num = 1f - component.Weight;
				___m_animator.SetIKPositionWeight((AvatarIKGoal)2, ___m_leftHandWeight * num);
				___m_animator.SetIKRotationWeight((AvatarIKGoal)2, ___m_leftHandRotWeight * num);
			}
		}
	}
}
public abstract class MenuEntry
{
	public abstract string Key { get; }

	public abstract string Label { get; }

	public abstract void Play(Character character);
}
public class EmoteEntry : MenuEntry
{
	public readonly Emote Emote;

	public override string Key => "tme:" + Emote.Name;

	public override string Label => Emote.DisplayName;

	public EmoteEntry(Emote emote)
	{
		Emote = emote;
	}

	public override void Play(Character character)
	{
		EmoteController.For(character).Play(Emote);
	}
}
public class SkillEntry : MenuEntry
{
	private readonly int itemID;

	private readonly string name;

	public override string Key
	{
		get
		{
			int num = itemID;
			return "skill:" + num;
		}
	}

	public override string Label => name;

	public SkillEntry(Skill skill)
	{
		itemID = ((Item)skill).ItemID;
		name = ((Item)skill).Name;
	}

	public override void Play(Character character)
	{
		Skill val = FindLearned(character, itemID);
		if (!((Object)(object)val == (Object)null))
		{
			EmoteController.For(character).Stop();
			if (Plugin.Verbose)
			{
				Plugin.Log.LogMessage((object)$"[SkillEntry] {itemID} '{name}': {DescribeRequirements(val, character)}");
			}
			((Item)val).TryQuickSlotUse();
			EmoteController.For(character).NoteSkillCast();
		}
	}

	private static string DescribeRequirements(Skill skill, Character character)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		string text = $"requirements={skill.HasAllRequirements(false)} child={((Item)skill).IsChildToCharacter} ";
		string text2 = $"additional={skill.HasAllAdditionalConditions(false)} dlc={((Item)skill).m_DLCID} owned={((Item)skill).GetIsDLCOwned()} ";
		Character ownerCharacter = ((EffectSynchronizer)skill).OwnerCharacter;
		return text + text2 + $"owner={((ownerCharacter != null) ? ownerCharacter.Name : null)} locomotion={character.InLocomotion}/{character.NextIsLocomotion} " + $"cooldown={skill.InCooldown()} bag={character.IsHandlingBag} weapon={character.IsHandlingWeapon}";
	}

	internal static Skill FindLearned(Character character, int itemID)
	{
		CharacterInventory inventory = character.Inventory;
		CharacterSkillKnowledge obj = ((inventory != null) ? inventory.SkillKnowledge : null);
		if (obj == null)
		{
			return null;
		}
		return ((CharacterKnowledge)obj).GetLearnedItems().OfType<Skill>().FirstOrDefault((Func<Skill, bool>)((Skill s) => ((Item)s).ItemID == itemID));
	}
}
public class CastEntry : MenuEntry
{
	private readonly SpellCastType castType;

	private readonly string label;

	public override string Key => "cast:" + ((object)castType/*cast due to .constrained prefix*/).ToString();

	public override string Label => label;

	public CastEntry(SpellCastType castType, string label)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		this.castType = castType;
		this.label = label;
	}

	public override void Play(Character character)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		if (character.InLocomotion && !character.IsCasting)
		{
			EmoteController emoteController = EmoteController.For(character);
			emoteController.Stop();
			if (character.CastSpell(castType, ((Component)character).gameObject, (SpellCastModifier)0, 1, -1f))
			{
				emoteController.WatchPose(castType);
			}
			else if (Plugin.Verbose)
			{
				Plugin.Log.LogMessage((object)$"[CastEntry] {castType} refused: {emoteController.DescribeState()} next={character.NextIsLocomotion}");
			}
		}
	}
}
public static class MenuCatalog
{
	private static List<MenuEntry> emoteEntries;

	public const string FavoritesTab = "Favorites";

	public const string AllTab = "All";

	public const string OutwardTab = "Outward";

	public static readonly string TooManyEmotesTab = ModSources.DisplayName("TooManyEmotes");

	public const string OtherModsTab = "Other Mods";

	private static List<MenuEntry> EmoteEntries => emoteEntries ?? (emoteEntries = ((IEnumerable<Emote>)EmoteLibrary.Menu).Select((Func<Emote, MenuEntry>)((Emote e) => new EmoteEntry(e))).ToList());

	private static Dictionary<string, List<MenuEntry>> CosmeticSkillsByTab(Character character)
	{
		Dictionary<string, List<MenuEntry>> dictionary = new Dictionary<string, List<MenuEntry>>();
		CharacterInventory inventory = character.Inventory;
		CharacterSkillKnowledge val = ((inventory != null) ? inventory.SkillKnowledge : null);
		if ((Object)(object)val == (Object)null)
		{
			return dictionary;
		}
		foreach (Skill item in from s in ((CharacterKnowledge)val).GetLearnedItems().OfType<Skill>()
			where s.IsCosmetic
			orderby ((Item)s).Name
			select s)
		{
			string text = ModSources.ModOf(((Item)item).ItemID) ?? ((((Item)item).ItemID >= 0) ? "Outward" : "Other Mods");
			if (text == "Favorites" || text == "All")
			{
				text = "Other Mods";
			}
			if (!dictionary.TryGetValue(text, out var value))
			{
				dictionary.Add(text, value = new List<MenuEntry>());
			}
			value.Add(new SkillEntry(item));
		}
		return dictionary;
	}

	private static List<MenuEntry> OutwardEntries(Dictionary<string, List<MenuEntry>> skills)
	{
		List<MenuEntry> list = new List<MenuEntry>
		{
			new CastEntry((SpellCastType)41, "Sit"),
			new CastEntry((SpellCastType)42, "Idle Stretch")
		};
		if (skills.TryGetValue("Outward", out var value))
		{
			list.AddRange(value);
		}
		return list;
	}

	public static List<string> Tabs(Character character)
	{
		List<string> list = CosmeticSkillsByTab(character).Keys.Where((string k) => k != "Outward").ToList();
		if (!list.Contains(TooManyEmotesTab))
		{
			list.Add(TooManyEmotesTab);
		}
		list.Sort(StringComparer.OrdinalIgnoreCase);
		return new string[3] { "Favorites", "All", "Outward" }.Concat(list).ToList();
	}

	public static List<MenuEntry> Entries(string tab, Character character)
	{
		Dictionary<string, List<MenuEntry>> dictionary = CosmeticSkillsByTab(character);
		if (tab == "Outward")
		{
			return OutwardEntries(dictionary);
		}
		if (tab == TooManyEmotesTab)
		{
			return EmoteEntries;
		}
		if (dictionary.TryGetValue(tab, out var value))
		{
			return value;
		}
		List<MenuEntry> source = (from e in OutwardEntries(dictionary).Concat(dictionary.Where((KeyValuePair<string, List<MenuEntry>> p) => p.Key != "Outward").SelectMany((KeyValuePair<string, List<MenuEntry>> p) => p.Value)).Concat(EmoteEntries)
			group e by e.Key into g
			select g.First()).ToList();
		if (tab == "All")
		{
			return source.OrderBy<MenuEntry, string>((MenuEntry e) => e.Label, StringComparer.InvariantCultureIgnoreCase).ToList();
		}
		if (tab == "Favorites")
		{
			Dictionary<string, MenuEntry> byKey = source.ToDictionary((MenuEntry e) => e.Key);
			return (from k in Favorites.Keys.Where(byKey.ContainsKey)
				select byKey[k]).ToList();
		}
		return new List<MenuEntry>();
	}
}
public static class MenuInput
{
	[HarmonyPatch(typeof(ControlsInput), "RotateCameraHorizontal")]
	private static class BlockCameraHorizontal
	{
		private static bool Prefix(int _playerID, ref float __result)
		{
			return Pass(_playerID, ref __result);
		}
	}

	[HarmonyPatch(typeof(ControlsInput), "RotateCameraVertical")]
	private static class BlockCameraVertical
	{
		private static bool Prefix(int _playerID, ref float __result)
		{
			return Pass(_playerID, ref __result);
		}
	}

	[HarmonyPatch(typeof(LocalCharacterControl), "UpdateMenuInputs")]
	private static class BlockGameMenus
	{
		private static bool Prefix(LocalCharacterControl __instance)
		{
			return !MenuOpenFor(__instance);
		}
	}

	[HarmonyPatch(typeof(LocalCharacterControl), "UpdateTargeting")]
	private static class BlockTargeting
	{
		private static bool Prefix(LocalCharacterControl __instance)
		{
			return !MenuOpenFor(__instance);
		}
	}

	[HarmonyPatch(typeof(LocalCharacterControl), "UpdateQuickSlots")]
	private static class BlockQuickSlots
	{
		private static bool Prefix(LocalCharacterControl __instance)
		{
			return !MenuOpenFor(__instance);
		}
	}

	public const string OpenAction = "Emote Menu";

	public const string NextPageAction = "Emote Menu - Next Page";

	public const string PreviousPageAction = "Emote Menu - Previous Page";

	public const string NextCategoryAction = "Emote Menu - Next Category";

	public const string FavoriteAction = "Emote Menu - Toggle Favorite";

	public const string ZoomInAction = "Emote Camera - Zoom In";

	public const string ZoomOutAction = "Emote Camera - Zoom Out";

	private static ConfigEntry<KeyboardShortcut> openKey;

	private static ConfigEntry<KeyboardShortcut> nextCategoryKey;

	private static ConfigEntry<KeyboardShortcut> favoriteKey;

	private static ConfigEntry<string> zoomInInput;

	private static ConfigEntry<string> zoomOutInput;

	public static ConfigEntry<float> MouseSensitivity;

	internal static bool ReadingAim;

	private static readonly HashSet<int> openFor = new HashSet<int>();

	internal static float? TestScroll;

	public static string NextCategoryKeyName => ((object)nextCategoryKey.Value/*cast due to .constrained prefix*/).ToString();

	public static string FavoriteKeyName => ((object)favoriteKey.Value/*cast due to .constrained prefix*/).ToString();

	public static void Init(ConfigFile config)
	{
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Invalid comparison between Unknown and I4
		//IL_00f8: 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)
		//IL_015b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Expected O, but got Unknown
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Invalid comparison between Unknown and I4
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		string[] array = new string[7] { "Emote Menu", "Emote Menu - Next Page", "Emote Menu - Previous Page", "Emote Menu - Next Category", "Emote Menu - Toggle Favorite", "Emote Camera - Zoom In", "Emote Camera - Zoom Out" };
		for (int i = 0; i < array.Length; i++)
		{
			CustomKeybindings.AddAction(array[i], (KeybindingsCategory)0, (ControlType)2, (InputType)1);
		}
		KeyCode val = KeyUnderEscape();
		openKey = config.Bind<KeyboardShortcut>("Menu", "Open key (keyboard)", new KeyboardShortcut(val, Array.Empty<KeyCode>()), "Keyboard fallback for the 'Emote Menu' binding. Hold, aim, release to play; or tap to keep the menu open and click.");
		KeyboardShortcut value = openKey.Value;
		if ((int)((KeyboardShortcut)(ref value)).MainKey == 96)
		{
			value = openKey.Value;
			if (!((KeyboardShortcut)(ref value)).Modifiers.Any() && (int)val != 96)
			{
				Plugin.Log.LogMessage((object)$"Keyboard layout: the key under Escape is {val}, open key changed from BackQuote");
				openKey.Value = new KeyboardShortcut(val, Array.Empty<KeyCode>());
			}
		}
		nextCategoryKey = config.Bind<KeyboardShortcut>("Menu", "Next category key (keyboard)", new KeyboardShortcut((KeyCode)9, Array.Empty<KeyCode>()), "Switches between Favorites, All Emotes and Outward (vanilla and other mods' emotes) while the menu is open. The mouse wheel changes page.");
		favoriteKey = config.Bind<KeyboardShortcut>("Menu", "Toggle favorite key (keyboard)", new KeyboardShortcut((KeyCode)102, Array.Empty<KeyCode>()), "Adds or removes the highlighted emote from the favorites while the menu is open.");
		MouseSensitivity = config.Bind<float>("Menu", "Mouse sensitivity", 0.04f, new ConfigDescription("How fast the mouse moves the selection.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.005f, 0.3f), Array.Empty<object>()));
		zoomInInput = config.Bind<string>("Camera", "Zoom in input (keyboard/mouse)", "WheelUp", "Zooms the camera in while an emote plays and the menu is closed: WheelUp, WheelDown, None, or a Unity KeyCode name (e.g. PageUp).");
		zoomOutInput = config.Bind<string>("Camera", "Zoom out input (keyboard/mouse)", "WheelDown", "Zooms the camera out while an emote plays and the menu is closed: WheelUp, WheelDown, None, or a Unity KeyCode name (e.g. PageDown).");
	}

	[DllImport("user32.dll")]
	private static extern IntPtr GetKeyboardLayout(uint threadId);

	[DllImport("user32.dll")]
	private static extern uint MapVirtualKeyEx(uint code, uint mapType, IntPtr layout);

	internal static KeyCode KeyUnderEscape()
	{
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			switch (MapVirtualKeyEx(41u, 1u, GetKeyboardLayout(0u)))
			{
			case 192u:
				return (KeyCode)96;
			case 222u:
				return (KeyCode)39;
			case 220u:
				return (KeyCode)92;
			case 186u:
				return (KeyCode)59;
			case 191u:
				return (KeyCode)47;
			case 219u:
				return (KeyCode)91;
			case 221u:
				return (KeyCode)93;
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Keyboard layout detection failed: " + ex.Message));
		}
		return (KeyCode)96;
	}

	private static bool OwnsKeyboard(int playerID)
	{
		return ControlsInput.GetMouseOwner() == playerID;
	}

	private static RewiredInputs Inputs(int playerID)
	{
		if (ControlsInput.m_playerInputManager == null || !ControlsInput.m_playerInputManager.TryGetValue(playerID, out var value))
		{
			return null;
		}
		return value;
	}

	private static bool Down(int playerID, string action)
	{
		RewiredInputs obj = Inputs(playerID);
		if (obj == null)
		{
			return false;
		}
		return obj.GetButtonDown(action);
	}

	private static bool Held(int playerID, string action)
	{
		RewiredInputs obj = Inputs(playerID);
		if (obj == null)
		{
			return false;
		}
		return obj.GetButton(action);
	}

	private static bool KeyHeld(KeyboardShortcut shortcut)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKey(((KeyboardShortcut)(ref shortcut)).MainKey))
		{
			return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey);
		}
		return false;
	}

	private static bool KeyDown(KeyboardShortcut shortcut)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey))
		{
			return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey);
		}
		return false;
	}

	public static bool OpenDown(int playerID)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		if (!Down(playerID, "Emote Menu"))
		{
			if (OwnsKeyboard(playerID))
			{
				return KeyDown(openKey.Value);
			}
			return false;
		}
		return true;
	}

	public static bool OpenHeld(int playerID)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		if (!Held(playerID, "Emote Menu"))
		{
			if (OwnsKeyboard(playerID))
			{
				return KeyHeld(openKey.Value);
			}
			return false;
		}
		return true;
	}

	private static float Scroll()
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		return TestScroll ?? Input.mouseScrollDelta.y;
	}

	public static int PageStep(int playerID)
	{
		int num = 0;
		if (Down(playerID, "Emote Menu - Next Page"))
		{
			num++;
		}
		if (Down(playerID, "Emote Menu - Previous Page"))
		{
			num--;
		}
		if (OwnsKeyboard(playerID))
		{
			float num2 = Scroll();
			if (num2 < 0f)
			{
				num++;
			}
			else if (num2 > 0f)
			{
				num--;
			}
		}
		return num;
	}

	public static bool NextCategory(int playerID)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		if (!Down(playerID, "Emote Menu - Next Category"))
		{
			if (OwnsKeyboard(playerID))
			{
				return KeyDown(nextCategoryKey.Value);
			}
			return false;
		}
		return true;
	}

	public static bool ToggleFavorite(int playerID)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		if (!Down(playerID, "Emote Menu - Toggle Favorite"))
		{
			if (OwnsKeyboard(playerID))
			{
				return KeyDown(favoriteKey.Value);
			}
			return false;
		}
		return true;
	}

	public static bool Confirm(int playerID)
	{
		if (!ControlsInput.Attack1Press(playerID))
		{
			if (IsGamepad(playerID))
			{
				return ControlsInput.Interact(playerID);
			}
			return false;
		}
		return true;
	}

	public static bool Cancel(int playerID)
	{
		if (!ControlsInput.MenuCancel(playerID))
		{
			return ControlsInput.Attack2(playerID);
		}
		return true;
	}

	public static bool IsGamepad(int playerID)
	{
		return ControlsInput.IsLastActionGamepad(playerID);
	}

	public static int ZoomStep(int playerID, out bool fromWheel)
	{
		fromWheel = false;
		int num = 0;
		if (Held(playerID, "Emote Camera - Zoom In"))
		{
			num++;
		}
		if (Held(playerID, "Emote Camera - Zoom Out"))
		{
			num--;
		}
		if (OwnsKeyboard(playerID))
		{
			if (ConfiguredInput(zoomInInput.Value))
			{
				num++;
			}
			if (ConfiguredInput(zoomOutInput.Value))
			{
				num--;
			}
			fromWheel = num != 0 && Scroll() != 0f;
		}
		return Math.Sign(num);
	}

	private static bool ConfiguredInput(string input)
	{
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		if (!string.IsNullOrEmpty(input))
		{
			switch (input)
			{
			case "None":
				break;
			case "WheelUp":
				return Scroll() > 0f;
			case "WheelDown":
				return Scroll() < 0f;
			default:
			{
				if (Enum.TryParse<KeyCode>(input, ignoreCase: true, out KeyCode result))
				{
					return Input.GetKey(result);
				}
				return false;
			}
			}
		}
		return false;
	}

	public static Vector2 Aim(int playerID)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		ReadingAim = true;
		try
		{
			return new Vector2(ControlsInput.RotateCameraHorizontal(playerID), ControlsInput.RotateCameraVertical(playerID));
		}
		finally
		{
			ReadingAim = false;
		}
	}

	internal static void SetOpen(int playerID, bool open)
	{
		if (open)
		{
			openFor.Add(playerID);
		}
		else
		{
			openFor.Remove(playerID);
		}
	}

	public static bool IsOpenFor(int playerID)
	{
		return openFor.Contains(playerID);
	}

	private static bool Pass(int playerID, ref float result)
	{
		if (ReadingAim || !openFor.Contains(playerID))
		{
			return true;
		}
		result = 0f;
		return false;
	}

	private static bool MenuOpenFor(LocalCharacterControl control)
	{
		Character character = ((CharacterControl)control).Character;
		if ((Object)(object)character != (Object)null && (Object)(object)character.OwnerPlayerSys != (Object)null)
		{
			return openFor.Contains(character.OwnerPlayerSys.PlayerID);
		}
		return false;
	}
}
public static class ModSources
{
	private static Dictionary<int, string> modByItemID;

	public static string ModOf(int itemID)
	{
		if (modByItemID == null && SL.PacksLoaded)
		{
			Build();
		}
		if (modByItemID == null || !modByItemID.TryGetValue(itemID, out var value))
		{
			return null;
		}
		return value;
	}

	private static void Build()
	{
		modByItemID = new Dictionary<int, string>();
		string[] array = new string[3] { "folderPacks", "bundlePacks", "embeddedArchivePacks" };
		foreach (string name in array)
		{
			if (!(typeof(SL).GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null) is IDictionary dictionary))
			{
				continue;
			}
			foreach (object value2 in dictionary.Values)
			{
				SLPack val = (SLPack)((value2 is SLPack) ? value2 : null);
				if (val == null)
				{
					continue;
				}
				string value = DisplayName(FolderName(val));
				foreach (SL_Item value3 in val.GetContentOfType<SL_Item>().Values)
				{
					if (value3.New_ItemID != -1 && value3.New_ItemID != value3.Target_ItemID)
					{
						modByItemID[value3.New_ItemID] = value;
					}
				}
			}
		}
		Plugin.Log.LogMessage((object)$"Found {modByItemID.Count} items created by SideLoader packs");
	}

	private static string FolderName(SLPack pack)
	{
		string text = (pack.IsInLegacyFolder ? pack.Name : pack.ActualFolderName);
		if (string.IsNullOrEmpty(text))
		{
			text = pack.Name;
		}
		int num = text.IndexOf('-');
		if (num <= 0 || num >= text.Length - 1)
		{
			return text;
		}
		return text.Substring(num + 1);
	}

	public static string DisplayName(string name)
	{
		StringBuilder stringBuilder = new StringBuilder();
		string text = name.Replace('_', ' ');
		foreach (char c in text)
		{
			if (char.IsUpper(c) && stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ')
			{
				stringBuilder.Append(' ');
			}
			if (c != ' ' || (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' '))
			{
				stringBuilder.Append(c);
			}
		}
		return stringBuilder.ToString().Trim();
	}
}
[BepInPlugin("Tchernobill.TooManyEmotes", "TooManyEmotes", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string GUID = "Tchernobill.TooManyEmotes";

	public const string NAME = "TooManyEmotes";

	public const string VERSION = "1.0.0";

	internal static ManualLogSource Log;

	internal static bool Verbose;

	internal static ConfigEntry<int> SlotsPerPage;

	internal static ConfigEntry<bool> UnequipLeftHand;

	internal static ConfigEntry<bool> DrawWeaponAfterEmote;

	private bool wasLoaded;

	private float helloAt = -1f;

	internal void Awake()
	{
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Expected O, but got Unknown
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Verbose logging", false, "Log why emotes start, stop or are refused.").Value;
		SlotsPerPage = ((BaseUnityPlugin)this).Config.Bind<int>("Menu", "Emotes per page", 10, new ConfigDescription("Number of slots on the radial menu.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(6, 16), Array.Empty<object>()));
		UnequipLeftHand = ((BaseUnityPlugin)this).Config.Bind<bool>("Emotes", "Unequip left hand", true, "Unequip the item held in the left hand (torch, lantern, lexicon, shield, off-hand weapon) when an emote is started from the menu. It goes back to the inventory like a manual unequip, and is equipped again (relit) when the emote ends. Two-handed weapons are only sheathed.");
		DrawWeaponAfterEmote = ((BaseUnityPlugin)this).Config.Bind<bool>("Emotes", "Draw weapon after emote", true, "Draw the weapon again when an emote ends, if it was drawn when the emote started (emotes sheathe it).");
		Favorites.Bind(((BaseUnityPlugin)this).Config);
		MenuInput.Init(((BaseUnityPlugin)this).Config);
		EmoteNetwork.Init(((BaseUnityPlugin)this).Config);
		Stopwatch stopwatch = Stopwatch.StartNew();
		EmoteLibrary.Load(Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "Assets"));
		Log.LogMessage((object)$"Emote library loaded in {stopwatch.ElapsedMilliseconds} ms");
		new Harmony("Tchernobill.TooManyEmotes").PatchAll();
		TestHarness.StartIfRequested(((Component)this).gameObject);
	}

	internal void Update()
	{
		bool flag = (Object)(object)CharacterManager.Instance != (Object)null && (Object)(object)NetworkLevelLoader.Instance != (Object)null && NetworkLevelLoader.Instance.IsOverallLoadingDone;
		if (flag && !wasLoaded)
		{
			helloAt = Time.time + 2f;
		}
		wasLoaded = flag;
		if (!flag)
		{
			return;
		}
		if (helloAt > 0f && Time.time >= helloAt)
		{
			helloAt = -1f;
			EmoteNetwork.SendHello();
		}
		foreach (Character value in CharacterManager.Instance.Characters.Values)
		{
			if ((Object)(object)value == (Object)null || !value.IsLocalPlayer || (Object)(object)value.OwnerPlayerSys == (Object)null || (Object)(object)value.CharacterUI == (Object)null)
			{
				continue;
			}
			int playerID = value.OwnerPlayerSys.PlayerID;
			UpdateZoom(value, playerID);
			if (MenuInput.OpenDown(playerID) && CanOpenMenu(value))
			{
				RadialMenu radialMenu = RadialMenu.For(value);
				if ((Object)(object)radialMenu != (Object)null && !radialMenu.IsOpen)
				{
					radialMenu.Open();
				}
			}
		}
	}

	private static void UpdateZoom(Character character, int playerID)
	{
		EmoteController component = ((Component)character).GetComponent<EmoteController>();
		bool flag = (Object)(object)component != (Object)null && component.EmoteActive;
		bool flag2 = MenuInput.IsOpenFor(playerID);
		int zoomStep = 0;
		bool fromWheel = false;
		if (flag && !flag2)
		{
			zoomStep = MenuInput.ZoomStep(playerID, out fromWheel);
		}
		EmoteCamera.UpdateFor(character, flag, flag2, zoomStep, fromWheel);
	}

	private static bool CanOpenMenu(Character character)
	{
		CharacterUI characterUI = character.CharacterUI;
		if (!character.IsDead && !characterUI.IsMenuFocused && !characterUI.IsDialogueInProgress)
		{
			if ((Object)(object)characterUI.ChatPanel != (Object)null)
			{
				return !characterUI.ChatPanel.IsChatFocused;
			}
			return true;
		}
		return false;
	}
}
public class RadialMenu : MonoBehaviour
{
	private const float InnerRatio = 0.42f;

	private const float TapTime = 0.25f;

	private const float MouseDeadZone = 0.35f;

	private const float StickDeadZone = 0.5f;

	private static Sprite ringSprite;

	private static Sprite diskSprite;

	private static Sprite dotSprite;

	private static string lastCategory = "All";

	private Character character;

	private int playerID;

	private RectTransform root;

	private Image highlight;

	private Image cursorDot;

	private readonly List<Text> labels = new List<Text>();

	private readonly List<Image> separators = new List<Image>();

	private Text tabsText;

	private Text titleText;

	private Text nameText;

	private Text hintText;

	private float radius;

	private int slots;

	private bool sticky;

	private float openedAt;

	private Vector2 mouseCursor;

	private bool savedInputLocked;

	public bool IsOpen { get; private set; }

	public string Category { get; private set; } = "All";

	public List<string> Tabs { get; private set; } = new List<string>();

	public int Page { get; private set; }

	public int Hovered { get; private set; } = -1;

	public List<MenuEntry> Entries { get; private set; } = new List<MenuEntry>();

	public int PageCount => Math.Max(1, (Entries.Count + slots - 1) / slots);

	public MenuEntry HoveredEntry
	{
		get
		{
			if (Hovered < 0 || Page * slots + Hovered >= Entries.Count)
			{
				return null;
			}
			return Entries[Page * slots + Hovered];
		}
	}

	public static RadialMenu For(Character character)
	{
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		CharacterUI characterUI = character.CharacterUI;
		if ((Object)(object)characterUI == (Object)null)
		{
			return null;
		}
		RadialMenu radialMenu = ((Component)characterUI).GetComponentInChildren<RadialMenu>(true);
		if ((Object)(object)radialMenu == (Object)null)
		{
			GameObject val = new GameObject("TooManyEmotes_RadialMenu", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(((Component)characterUI).transform, false);
			radialMenu = val.AddComponent<RadialMenu>();
			radialMenu.character = character;
		}
		return radialMenu;
	}

	private void Awake()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		root = (RectTransform)((Component)this).transform;
		RectTransform obj = root;
		RectTransform obj2 = root;
		Vector2 val = default(Vector2);
		((Vector2)(ref val))..ctor(0.5f, 0.5f);
		obj2.anchorMax = val;
		obj.anchorMin = val;
		root.pivot = new Vector2(0.5f, 0.5f);
		root.anchoredPosition = Vector2.zero;
		((Component)this).gameObject.SetActive(false);
	}

	private void Update()
	{
		if (!IsOpen)
		{
			return;
		}
		if ((Object)(object)character == (Object)null || character.IsDead || character.CharacterUI.IsMenuFocused)
		{
			Close();
			return;
		}
		UpdateAim();
		int num = MenuInput.PageStep(playerID);
		if (num != 0)
		{
			SetPage(Page + num);
		}
		if (MenuInput.NextCategory(playerID))
		{
			SetCategory(Tabs[(Tabs.IndexOf(Category) + 1) % Tabs.Count]);
		}
		if (MenuInput.ToggleFavorite(playerID) && HoveredEntry != null)
		{
			Favorites.Toggle(HoveredEntry.Key);
			if (Category == "Favorites")
			{
				SetCategory(Category, keepPage: true);
			}
			Refresh();
		}
		if (!sticky)
		{
			if (!MenuInput.OpenHeld(playerID))
			{
				if (Time.unscaledTime - openedAt < 0.25f && HoveredEntry == null)
				{
					sticky = true;
				}
				else
				{
					ConfirmAndClose();
				}
			}
		}
		else if (MenuInput.Confirm(playerID) || MenuInput.OpenDown(playerID))
		{
			ConfirmAndClose();
		}
		else if (MenuInput.Cancel(playerID))
		{
			Close();
		}
	}

	public void Open(bool sticky = false)
	{
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		if (!IsOpen && !((Object)(object)character == (Object)null))
		{
			playerID = character.OwnerPlayerSys.PlayerID;
			slots = Mathf.Clamp(Plugin.SlotsPerPage.Value, 6, 16);
			Build();
			IsOpen = true;
			this.sticky = sticky;
			openedAt = Time.unscaledTime;
			mouseCursor = Vector2.zero;
			Hovered = -1;
			Tabs = MenuCatalog.Tabs(character);
			string category = ((!Tabs.Contains(lastCategory) || (lastCategory == "Favorites" && Favorites.Keys.Count == 0)) ? "All" : lastCategory);
			SetCategory(category, keepPage: true);
			savedInputLocked = character.CharacterControl.InputLocked;
			character.CharacterControl.InputLocked = true;
			MenuInput.SetOpen(playerID, open: true);
			((Component)this).gameObject.SetActive(true);
			((Component)this).transform.SetAsLastSibling();
			Refresh();
		}
	}

	public void Close()
	{
		if (IsOpen)
		{
			IsOpen = false;
			MenuInput.SetOpen(playerID, open: false);
			if ((Object)(object)character != (Object)null)
			{
				character.CharacterControl.InputLocked = savedInputLocked;
			}
			((Component)this).gameObject.SetActive(false);
		}
	}

	public void ConfirmAndClose()
	{
		if (!IsOpen)
		{
			return;
		}
		MenuEntry hoveredEntry = HoveredEntry;
		Close();
		if (hoveredEntry == null)
		{
			if (Plugin.Verbose)
			{
				Plugin.Log.LogMessage((object)$"[RadialMenu] player {playerID} stops the current emote");
			}
			EmoteController.For(character).StopAll();
		}
		else
		{
			if (Plugin.Verbose)
			{
				Plugin.Log.LogMessage((object)$"[RadialMenu] player {playerID} plays {hoveredEntry.Key}");
			}
			EmoteController.For(character).PlayEntry(hoveredEntry);
		}
	}

	public void SetCategory(string category, bool keepPage = false)
	{
		int page = ((keepPage && category == Category) ? Page : 0);
		Category = category;
		lastCategory = category;
		Entries = MenuCatalog.Entries(category, character);
		SetPage(page);
	}

	public void SetPage(int page)
	{
		int pageCount = PageCount;
		Page = (page % pageCount + pageCount) % pageCount;
		Refresh();
	}

	public void SetAim(Vector2 aim)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		Hovered = ((((Vector2)(ref aim)).magnitude < 0.35f) ? (-1) : SlotAt(aim));
		if (Hovered >= 0 && HoveredEntry == null)
		{
			Hovered = -1;
		}
		((Graphic)cursorDot).rectTransform.anchoredPosition = Vector2.ClampMagnitude(aim, 1f) * radius * 0.42f * 0.8f;
		Refresh();
	}

	private void UpdateAim()
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		Vector2 val = MenuInput.Aim(playerID);
		if (MenuInput.IsGamepad(playerID))
		{
			if (((Vector2)(ref val)).magnitude >= 0.5f)
			{
				SetAim(((Vector2)(ref val)).normalized);
			}
		}
		else if (!(val == Vector2.zero))
		{
			mouseCursor = Vector2.ClampMagnitude(mouseCursor + val * MenuInput.MouseSensitivity.Value, 1f);
			SetAim(mouseCursor);
		}
	}

	private int SlotAt(Vector2 aim)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		float num = Mathf.Atan2(aim.x, aim.y) * 57.29578f;
		float num2 = 360f / (float)slots;
		return Mathf.FloorToInt(Mathf.Repeat(num + num2 / 2f, 360f) / num2) % slots;
	}

	private static float SlotAngle(int slot, int slots)
	{
		return (float)slot * 360f / (float)slots;
	}

	private void Refresh()
	{
		//IL_0134: 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_00bf: 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)
		if (labels.Count != 0)
		{
			for (int i = 0; i < slots; i++)
			{
				int num = Page * slots + i;
				MenuEntry menuEntry = ((num < Entries.Count) ? Entries[num] : null);
				labels[i].text = menuEntry?.Label ?? "";
				bool flag = menuEntry != null && Favorites.Contains(menuEntry.Key);
				((Graphic)labels[i]).color = (Color)((i == Hovered) ? Color.white : (flag ? new Color(1f, 0.82f, 0.4f) : new Color(0.85f, 0.85f, 0.85f)));
			}
			((Behaviour)highlight).enabled = Hovered >= 0;
			if (Hovered >= 0)
			{
				((Transform)((Graphic)highlight).rectTransform).localRotation = Quaternion.Euler(0f, 0f, 180f / (float)slots - SlotAngle(Hovered, slots));
			}
			tabsText.text = CategoryTabs();
			titleText.text = $"page {Page + 1}/{PageCount}";
			MenuEntry hoveredEntry = HoveredEntry;
			bool flag2 = (Object)(object)character != (Object)null && (((Component)character).GetComponent<EmoteController>()?.EmoteActive ?? false);
			nameText.text = ((hoveredEntry != null) ? (hoveredEntry.Label + (Favorites.Contains(hoveredEntry.Key) ? " *" : "")) : (flag2 ? "<color=#c0c0c0>Stop</color>" : ((Entries.Count != 0) ? "" : ((Category == "Favorites") ? "No favorites yet" : "Nothing here"))));
			hintText.text = (MenuInput.IsGamepad(playerID) ? "Stick: choose   center: stop\nPage / Category / Favorite: see Controls" : ("Wheel: page   " + MenuInput.NextCategoryKeyName + ": category\n" + MenuInput.FavoriteKeyName + ": favorite   center: stop"));
		}
	}

	private string CategoryTabs()
	{
		return string.Join("  ·  ", Tabs.Select((string t) => (!(t == Category)) ? ("<color=#9a9a9a>" + t + "</color>") : ("<b><color=#ffd27a>" + t + "</color></b>")));
	}

	private void Build()
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: 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)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		//IL_018b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0262: Unknown result type (might be due to invalid IL or missing references)
		//IL_0281: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0356: Unknown result type (might be due to invalid IL or missing references)
		//IL_0375: Unknown result type (might be due to invalid IL or missing references)
		//IL_037c: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_040c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0444: Unknown result type (might be due to invalid IL or missing references)
		//IL_0466: Unknown result type (might be due to invalid IL or missing references)
		//IL_0485: Unknown result type (might be due to invalid IL or missing references)
		//IL_04c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0503: Unknown result type (might be due to invalid IL or missing references)
		//IL_0526: Unknown result type (might be due to invalid IL or missing references)
		//IL_0545: Unknown result type (might be due to invalid IL or missing references)
		//IL_056f: Unknown result type (might be due to invalid IL or missing references)
		RectTransform val = (RectTransform)((Transform)root).parent;
		Rect rect = val.rect;
		float width = ((Rect)(ref rect)).width;
		rect = val.rect;
		float num = Mathf.Min(width, ((Rect)(ref rect)).height);
		float num2 = Mathf.Max(150f, num * 0.36f);
		if (labels.Count == slots && Mathf.Approximately(num2, radius))
		{
			return;
		}
		radius = num2;
		foreach (Transform item in (Transform)root)
		{
			Object.Destroy((Object)(object)((Component)item).gameObject);
		}
		labels.Clear();
		separators.Clear();
		EnsureSprites();
		root.sizeDelta = new Vector2(radius * 2f, radius * 2f);
		Font font = FindFont();
		NewImage("Ring", diskSprite, new Color(0f, 0f, 0f, 0.35f), radius * 2f);
		NewImage("Band", ringSprite, new Color(0.06f, 0.06f, 0.07f, 0.8f), radius * 2f);
		highlight = NewImage("Highlight", ringSprite, new Color(0.85f, 0.62f, 0.25f, 0.6f), radius * 2f);
		highlight.type = (Type)3;
		highlight.fillMethod = (FillMethod)4;
		highlight.fillOrigin = 2;
		highlight.fillClockwise = true;
		highlight.fillAmount = 1f / (float)slots;
		float num3 = radius * 0.58000004f;
		float num4 = radius * 1.42f / 2f;
		for (int i = 0; i < slots; i++)
		{
			float num5 = (SlotAngle(i, slots) + 180f / (float)slots) * ((float)Math.PI / 180f);
			Image val2 = NewImage("Separator" + i, null, new Color(1f, 1f, 1f, 0.15f), 0f);
			((Graphic)val2).rectTransform.sizeDelta = new Vector2(2f, num3);
			((Graphic)val2).rectTransform.anchoredPosition = new Vector2(Mathf.Sin(num5), Mathf.Cos(num5)) * num4;
			((Transform)((Graphic)val2).rectTransform).localRotation = Quaternion.Euler(0f, 0f, (0f - num5) * 57.29578f);
			separators.Add(val2);
			float num6 = SlotAngle(i, slots) * ((float)Math.PI / 180f);
			Text val3 = NewText("Label" + i, font, 18, 11);
			float num7 = 2f * num4 * Mathf.Sin((float)Math.PI / (float)slots);
			((Graphic)val3).rectTransform.sizeDelta = new Vector2(Mathf.Min(num3 * 1.05f, num7 * 0.92f), num3 * 0.62f);
			((Graphic)val3).rectTransform.anchoredPosition = new Vector2(Mathf.Sin(num6), Mathf.Cos(num6)) * num4;
			labels.Add(val3);
		}
		float num8 = radius * 0.42f;
		tabsText = NewText("Tabs", font, 18, 10);
		((Graphic)tabsText).rectTransform.sizeDelta = new Vector2(radius * 2.8f, 34f);
		((Graphic)tabsText).rectTransform.anchoredPosition = new Vector2(0f, radius + 24f);
		titleText = NewText("Title", font, 14, 10);
		((Graphic)titleText).rectTransform.sizeDelta = new Vector2(num8 * 1.6f, 24f);
		((Graphic)titleText).rectTransform.anchoredPosition = new Vector2(0f, num8 * 0.55f);
		((Graphic)titleText).color = new Color(0.75f, 0.75f, 0.75f);
		nameText = NewText("Name", font, 22, 12);
		((Graphic)nameText).rectTransform.sizeDelta = new Vector2(num8 * 1.7f, num8 * 0.7f);
		nameText.fontStyle = (FontStyle)1;
		hintText = NewText("Hint", font, 12, 8);
		((Graphic)hintText).rectTransform.sizeDelta = new Vector2(num8 * 1.6f, 36f);
		((Graphic)hintText).rectTransform.anchoredPosition = new Vector2(0f, (0f - num8) * 0.55f);
		((Graphic)hintText).color = new Color(0.65f, 0.65f, 0.65f);
		cursorDot = NewImage("Cursor", dotSprite, new Color(1f, 0.85f, 0.5f, 0.9f), 12f);
	}

	private Image NewImage(string name, Sprite sprite, Color color, float size)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
		val.transform.SetParent((Transform)(object)root, false);
		Image obj = val.AddComponent<Image>();
		obj.sprite = sprite;
		((Graphic)obj).color = color;
		((Graphic)obj).raycastTarget = false;
		((Graphic)obj).rectTransform.sizeDelta = new Vector2(size, size);
		return obj;
	}

	private Text NewText(string name, Font font, int maxSize, int minSize)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Expected O, but got Unknown
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
		val.transform.SetParent((Transform)(object)root, false);
		Text obj = val.AddComponent<Text>();
		obj.font = font;
		obj.alignment = (TextAnchor)4;
		obj.horizontalOverflow = (HorizontalWrapMode)0;
		obj.verticalOverflow = (VerticalWrapMode)0;
		obj.resizeTextForBestFit = true;
		obj.resizeTextMaxSize = maxSize;
		obj.resizeTextMinSize = minSize;
		((Graphic)obj).raycastTarget = false;
		Shadow obj2 = val.AddComponent<Shadow>();
		obj2.effectColor = new Color(0f, 0f, 0f, 0.8f);
		obj2.effectDistance = new Vector2(1f, -1f);
		return obj;
	}

	private Font FindFont()
	{
		Text val = ((IEnumerable<Text>)((Component)character.CharacterUI).GetComponentsInChildren<Text>(true)).FirstOrDefault((Func<Text, bool>)((Text t) => (Object)(object)t.font != (Object)null));
		if (!((Object)(object)val != (Object)null))
		{
			return Resources.GetBuiltinResource<Font>("Arial.ttf");
		}
		return val.font;
	}

	private static void EnsureSprites()
	{
		if (!((Object)(object)ringSprite != (Object)null))
		{
			ringSprite = Circle(256, 0.42f);
			diskSprite = Circle(256, 0f);
			dotSprite = Circle(32, 0f);
		}
	}

	private static Sprite Circle(int size, float inner)
	{
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
		{
			wrapMode = (TextureWrapMode)1
		};
		Color32[] array = (Color32[])(object)new Color32[size * size];
		float num = (float)size / 2f;
		float num2 = 1.5f / num;
		for (int i = 0; i < size; i++)
		{
			for (int j = 0; j < size; j++)
			{
				Vector2 val2 = new Vector2((float)j + 0.5f - num, (float)i + 0.5f - num);
				float num3 = ((Vector2)(ref val2)).magnitude / num;
				float num4 = Mathf.Clamp01((1f - num3) / num2);
				if (inner > 0f)
				{
					num4 = Mathf.Min(num4, Mathf.Clamp01((num3 - inner) / num2));
				}
				array[i * size + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num4 * 255f));
			}
		}
		val.SetPixels32(array);
		val.Apply();
		return Sprite.Create(val, new Rect(0f, 0f, (float)size, (float)size), new Vector2(0.5f, 0.5f));
	}

	private void OnDisable()
	{
		if (IsOpen)
		{
			Close();
		}
	}

	private void OnDestroy()
	{
		if (IsOpen)
		{
			MenuInput.SetOpen(playerID, open: false);
			if ((Object)(object)character != (Object)null)
			{
				character.CharacterControl.InputLocked = savedInputLocked;
			}
		}
	}
}
public class TestHarness : MonoBehaviour
{
	[HarmonyPatch(typeof(ControlsInput), "ToggleInventory")]
	private static class FakeInventoryPatch
	{
		private static void Postfix(ref bool __result)
		{
			if (FakeInventory)
			{
				__result = true;
			}
		}
	}

	[HarmonyPatch(typeof(ControlsInput), "LockToggle")]
	private static class FakeLockTogglePatch
	{
		private static void Postfix(ref bool __result)
		{
			if (FakeLockToggle)
			{
				__result = true;
			}
		}
	}

	[HarmonyPatch(typeof(ControlsInput), "MoveVertical")]
	private static class FakeMovePatch
	{
		private static void Postfix(ref float __result)
		{
			if (FakeMoveVertical != 0f)
			{
				__result = FakeMoveVertical;
			}
		}
	}

	private class HandSamples
	{
		private readonly Transform chest;

		private readonly Transform left;

		private readonly Transform right;

		public readonly Jerk Left = new Jerk();

		public readonly Jerk Right = new Jerk();

		public bool Recording;

		public int Frames;

		public HandSamples(Transform chest, Transform left, Transform right)
		{
			this.chest = chest;
			this.left = left;
			this.right = right;
		}

		public void Sample()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (Recording)
			{
				Frames++;
				Quaternion val = Quaternion.Inverse(chest.rotation);
				Left.Add(val * left.rotation);
				Right.Add(val * right.rotation);
			}
		}
	}

	private class Jerk
	{
		private Quaternion? previous;

		private float previousStep = -1f;

		private float stepSum;

		private float jerkSum;

		private float maxStep;

		private int steps;

		private int jerks;

		public void Add(Quaternion rotation)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			if (previous.HasValue)
			{
				float num = Quaternion.Angle(previous.Value, rotation);
				stepSum += num;
				steps++;
				maxStep = Mathf.Max(maxStep, num);
				if (previousStep >= 0f)
				{
					jerkSum += Mathf.Abs(num - previousStep);
					jerks++;
				}
				previousStep = num;
			}
			previous = rotation;
		}

		public string Describe()
		{
			float num = ((steps > 0) ? (stepSum / (float)steps) : 0f);
			float num2 = ((jerks > 0) ? (jerkSum / (float)jerks) : 0f);
			return $"step {num:0.00} deg/frame, max {maxStep:0.0}, jerk {num2:0.00} (x{((num > 0f) ? (num2 / num) : 0f):0.00})";
		}
	}

	private static readonly HumanBodyBones[] SampledBones;

	private string emoteName;

	private string outputDir;

	private HashSet<string> suites;

	private Animator animator;

	private readonly Dictionary<HumanBodyBones, Transform> bones = new Dictionary<HumanBodyBones, Transform>();

	private readonly Dictionary<HumanBodyBones, Quaternion> idlePose = new Dictionary<HumanBodyBones, Quaternion>();

	private readonly Dictionary<HumanBodyBones, float> maxDelta = new Dictionary<HumanBodyBones, float>();

	private bool sampling;

	internal static bool FakeInventory;

	internal static bool FakeLockToggle;

	internal static float FakeMoveVertical;

	private HandSamples handSamples;

	public static void StartIfRequested(GameObject host)
	{
		string[] commandLineArgs = Environment.GetCommandLineArgs();
		if (UnityEngineExtensions.Contains<string>(commandLineArgs, "--tme-test"))
		{
			TestHarness testHarness = host.AddComponent<TestHarness>();
			int num = Array.IndexOf(commandLineArgs, "--tme-test-emote");
			testHarness.emoteName = ((num >= 0 && num + 1 < commandLineArgs.Length) ? commandLineArgs[num + 1] : null);
			int num2 = Array.IndexOf(commandLineArgs, "--tme-test-suites");
			string text = ((num2 >= 0 && num2 + 1 < commandLineArgs.Length) ? commandLineArgs[num2 + 1] : "basic,offhand,weapons,menu,controls,network,poses,unequip");
			testHarness.suites = new HashSet<string>(text.Split(new char[1] { ',' }));
			Plugin.Verbose = true;
		}
	}

	private void Start()
	{
		outputDir = Path.Combine(Paths.BepInExRootPath, "TooManyEmotesTest");
		Directory.CreateDirectory(outputDir);
		((MonoBehaviour)this).StartCoroutine(Run());
	}

	private static void Log(string message)
	{
		Plugin.Log.LogMessage((object)("tme-test: " + message));
	}

	private IEnumerator Run()
	{
		Character character = null;
		float deadline = Time.realtimeSinceStartup + 600f;
		while ((Object)(object)character == (Object)null)
		{
			if (Time.realtimeSinceStartup > deadline)
			{
				Plugin.Log.LogError((object)"tme-test: FAILED no local character after 600 s");
				yield break;
			}
			yield return null;
			if (!((Object)(object)NetworkLevelLoader.Instance == (Object)null) && NetworkLevelLoader.Instance.IsOverallLoadingDone)
			{
				try
				{
					CharacterManager instance = CharacterManager.Instance;
					character = ((instance != null) ? instance.GetFirstLocalCharacter() : null);
				}
				catch (NullReferenceException)
				{
				}
			}
		}
		yield return WaitRealtime(5f);
		string name = character.Name;
		Scene activeScene = SceneManager.GetActiveScene();
		Log("local character " + name + " in " + ((Scene)(ref activeScene)).name);
		animator = character.Animator;
		DumpRig(character);
		DumpCosmeticSkills(character);
		DumpActionNames(18, 24, 69, 76, 89, 92, 126);
		Emote emote = PickEmote();
		if (emote == null)
		{
			Plugin.Log.LogError((object)"tme-test: FAILED no emote to play");
			yield break;
		}
		AnimatorCullingMode culling = animator.cullingMode;
		animator.cullingMode = (AnimatorCullingMode)0;
		Log($"cullingMode {culling} -> AlwaysAnimate for the test");
		HumanBodyBones[] sampledBones = SampledBones;
		foreach (HumanBodyBones val in sampledBones)
		{
			Transform boneTransform = animator.GetBoneTransform(val);
			if ((Object)(object)boneTransform != (Object)null)
			{
				bones[val] = boneTransform;
			}
		}
		EmoteController controller = EmoteController.For(character);
		if (suites.Contains("basic"))
		{
			yield return null;
			yield return null;
			foreach (KeyValuePair<HumanBodyBones, Transform> bone in bones)
			{
				idlePose[bone.Key] = bone.Value.localRotation;
			}
			Capture(character, "1_idle");
			Log("state before play: " + controller.DescribeState());
			object[] obj = new object[4]
			{
				emote.Name,
				((Object)emote.Clip).name,
				emote.Clip.length,
				null
			};
			AnimationClip loopClip = emote.LoopClip;
			obj[3] = ((loopClip != null) ? ((Object)loopClip).name : null) ?? "none";
			Log(string.Format("playing {0} (clip {1}, {2:0.00} s, loop clip {3})", obj));
			controller.Play(emote);
			sampling = true;
			float start = Time.realtimeSinceStartup;
			float nextReport = 0f;
			bool captured = false;
			while (Time.realtimeSinceStartup - start < 4f)
			{
				float num = Time.realtimeSinceStartup - start;
				if (num >= nextReport)
				{
					Log($"t={num:0.0} {controller.DescribeGraph()} | {DescribeDeltas()}");
					nextReport += 0.5f;
				}
				if (!captured && num >= 1.5f)
				{
					Capture(character, "2_emote");
					captured = true;
				}
				yield return null;
			}
			sampling = false;
			float num2 = ((maxDelta.Count > 0) ? maxDelta.Values.Max() : 0f);
			Log($"max bone deviation from idle: {num2:0.0} deg ({DescribeDeltas()})");
			Log((num2 > 15f) ? "RESULT bones follow the emote" : "RESULT bones do NOT follow the emote");
			controller.Stop();
			yield return WaitRealtime(1f);
			Log("after stop: " + controller.DescribeGraph());
		}
		if (suites.Contains("offhand"))
		{
			yield return OffHandSuite(character, controller, emote);
		}
		if (suites.Contains("weapons"))
		{
			yield return WeaponSuite(character, controller, emote);
		}
		if (suites.Contains("menu"))
		{
			yield return MenuSuite(character, controller);
		}
		if (suites.Contains("controls"))
		{
			yield return ControlsSuite(character, controller);
		}
		if (suites.Contains("network"))
		{
			yield return NetworkSuite(character, controller);
		}
		if (suites.Contains("poses"))
		{
			yield return PosesSuite(character, controller);
		}
		if (suites.Contains("unequip"))
		{
			yield return UnequipSuite(character, controller);
		}
		if (suites.Contains("image"))
		{
			yield return ImageSuite(character);
		}
		animator.cullingMode = culling;
		Plugin.Log.LogMessage((object)"tme-test: done");
	}

	private IEnumerator OffHandSuite(Character character, EmoteController controller, Emote emote)
	{
		(IKMode mode, bool lit)[] cases = new(IKMode, bool)[4]
		{
			((IKMode)2, false),
			((IKMode)2, true),
			((IKMode)1, true),
			((IKMode)3, false)
		};
		yield return MeasureHands(character, controller, emote, "no item", patch: true);
		(IKMode mode, bool lit)[] array = cases;
		for (int i = 0; i < array.Length; i++)
		{
			(IKMode, bool) tuple = array[i];
			IKMode mode = tuple.Item1;
			bool lit = tuple.Item2;
			Equipment prefab = ResourcesPrefabManager.ITEM_PREFABS.Values.OfType<Equipment>().FirstOrDefault((Func<Equipment, bool>)((Equipment e) => e.IKType == mode));
			if ((Object)(object)prefab == (Object)null)
			{
				Log("no item prefab with IK mode " + ((object)Unsafe.As<IKMode, IKMode>(ref mode)/*cast due to .constrained prefix*/).ToString());
				continue;
			}
			Item obj = ItemManager.Instance.GenerateItemNetwork(((Item)prefab).ItemID);
			Equipment item = (Equipment)(object)((obj is Equipment) ? obj : null);
			character.Inventory.TakeItem((Item)(object)item, true);
			yield return null;
			if ((Object)(object)character.LeftHandEquipment != (Object)(object)item)
			{
				character.Inventory.EquipItem(item, false);
			}
			float deadline = Time.realtimeSinceStartup + 5f;
			while ((Object)(object)character.LeftHandEquipment != (Object)(object)item && Time.realtimeSinceStartup < deadline)
			{
				yield return null;
			}
			if ((Object)(object)character.LeftHandEquipment != (Object)(object)item)
			{
				Log($"could not equip {((Item)prefab).Name} ({((Item)prefab).ItemID}) in the left hand");
				continue;
			}
			if (lit)
			{
				((Item)item).SetLight(true);
			}
			yield return WaitRealtime(2f);
			string label = $"{((Item)prefab).Name} ({((Item)prefab).ItemID}, {mode}, {((Item)item).LitStatus})";
			yield return MeasureHands(character, controller, emote, label + " vanilla IK", patch: false);
			yield return MeasureHands(character, controller, emote, label + " IK faded", patch: true);
			character.Inventory.UnequipItem(item, false);
			yield return WaitRealtime(1.5f);
		}
	}

	private IEnumerator MenuSuite(Character character, EmoteController controller)
	{
		RadialMenu menu = RadialMenu.For(character);
		Canvas canvas = ((Component)menu).GetComponentInParent<Canvas>();
		Rect rect = ((RectTransform)((Component)menu).transform.parent).rect;
		Log($"menu parent '{((Object)((Component)menu).transform.parent).name}' rect={((Rect)(ref rect)).width:0}x{((Rect)(ref rect)).height:0} " + $"canvas={((canvas != null) ? ((Object)canvas.rootCanvas).name : null)} mode={((canvas != null) ? new RenderMode?(canvas.rootCanvas.renderMode) : ((RenderMode?)null))} scale={((canvas != null) ? new float?(canvas.rootCanvas.scaleFactor) : ((float?)null)):0.00} " + $"screen={Screen.width}x{Screen.height}");
		bool inputLocked = character.CharacterControl.InputLocked;
		menu.Open(sticky: true);
		Log($"menu open={menu.IsOpen} category={menu.Category} entries={menu.Entries.Count} pages={menu.PageCount} " + $"inputLocked {inputLocked} -> {character.CharacterControl.InputLocked}");
		Log("menu tabs: " + string.Join(" | ", menu.Tabs));
		menu.SetCategory(MenuCatalog.TooManyEmotesTab);
		menu.SetPage(3);
		menu.SetAim(new Vector2(0.7f, 0.7f));
		yield return null;
		Log($"menu page={menu.Page + 1}/{menu.PageCount} hovered slot {menu.Hovered} = {menu.HoveredEntry?.Key}");
		Capture(character, "menu_all", canvas);
		EmoteEntry chosen = menu.HoveredEntry as EmoteEntry;
		menu.ConfirmAndClose();
		Log($"menu closed open={menu.IsOpen} inputLocked={character.CharacterControl.InputLocked}");
		yield return WaitRealtime(1.5f);
		Log($"menu emote playing={controller.Current?.Name} expected={chosen?.Emote.Name} weight={controller.Weight:0.00}");
		Capture(character, "menu_all_played");
		controller.Stop();
		yield return WaitRealtime(1f);
		menu.Open(sticky: true);
		menu.SetAim(new Vector2(-1f, 0f));
		string text = menu.HoveredEntry?.Key;
		if (text != null && !Favorites.Contains(text))
		{
			Favorites.Toggle(text);
		}
		menu.SetCategory("Favorites");
		menu.SetAim(new Vector2(0f, 1f));
		Log($"favorites: added {text}, category entries={menu.Entries.Count} first={UnityEngineExtensions.FirstOrDefault<MenuEntry>((IList<MenuEntry>)menu.Entries)?.Key} hovered={menu.HoveredEntry?.Key}");
		Capture(character, "menu_favorites", canvas);
		menu.Close();
		if (text != null)
		{
			Favorites.Toggle(text);
		}
		menu.Open(sticky: true);
		foreach (string tab in menu.Tabs)
		{
			menu.SetCategory(tab);
			Log(string.Format("tab '{0}': {1} entries, first: {2}", tab, menu.Entries.Count, string.Join(", ", from e in menu.Entries.Take(6)
				select e.Label)));
		}
		menu.SetCategory("Emotes Additions");
		menu.SetAim(new Vector2(0f, 1f));
		Capture(character, "menu_mod_tab", canvas);
		menu.SetCategory("Outward");
		List<MenuEntry> source = (from e in MenuCatalog.Entries("All", character)
			where !(e is EmoteEntry)
			select e).ToList();
		Log("outward entries: " + string.Join(", ", source.Select((MenuEntry e) => e.Key + "='" + e.Label + "'")));
		menu.SetAim(new Vector2(0f, 1f));
		Capture(character, "menu_outward", canvas);
		menu.Close();
		IEnumerable<MenuEntry> enumerable = (from e in source.OfType<SkillEntry>()
			where IsEmoteLikeSkill(character, e)
			where e.Label == "Fireworks" || e.Label == "Clapping" || e.Label == "Waving"
			select e).Cast<MenuEntry>().Concat(source.OfType<CastEntry>());
		foreach (MenuEntry entry in enumerable)
		{
			yield return WaitRealtime(1f);
			entry.Play(character);
			yield return WaitRealtime(0.8f);
			Skill val = ((entry is SkillEntry) ? SkillEntry.FindLearned(character, int.Parse(entry.Key.Substring(6))) : null);
			Log($"outward play {entry.Key} '{entry.Label}': casting={character.IsCasting} cast={character.CurrentSpellCast} " + "cooldown=" + (((Object)(object)val != (Object)null) ? val.InCooldown().ToString() : "-"));
			Capture(character, string.Concat("outward_", string.Concat(entry.Label.Where(char.IsLetterOrDigit))));
			float end = Time.realtimeSinceStartup + 6f;
			while ((character.IsCasting || !character.InLocomotion) && Time.realtimeSinceStartup < end)
			{
				yield return null;
			}
			Log($"outward {entry.Key} after 6 s: casting={character.IsCasting} locomotion={character.InLocomotion}");
			if (!character.InLocomotion || character.IsCasting)
			{
				yield return FakeMove(character, 0.6f);
				Log($"outward {entry.Key} after moving: casting={character.IsCasting} locomotion={character.InLocomotion}");
			}
		}
		controller.Play(EmoteLibrary.Menu.First((Emote e) => e.Loops));
		yield return WaitRealtime(1f);
		Log($"move test: emoting before={controller.IsEmoting}");
		yield return FakeMove(character, 0.3f);
		Log($"move test: emoting after move={controller.IsEmoting} weight={controller.Weight:0.00}");
	}

	private IEnumerator ControlsSuite(Character character, EmoteController controller)
	{
		Log($"keyboard: key under Escape = {MenuInput.KeyUnderEscape()}, mouse owner = {ControlsInput.GetMouseOwner()}");
		_ = character.OwnerPlayerSys.PlayerID;
		RadialMenu menu = RadialMenu.For(character);
		menu.Open(sticky: true);
		FakeLockToggle = true;
		FakeInventory = true;
		yield return null;
		yield return null;
		FakeLockToggle = false;
		FakeInventory = false;
		yield return null;
		Log($"blocked inputs: menu open={menu.IsOpen} game menu focused={character.CharacterUI.IsMenuFocused} " + $"target locked={character.TargetingSystem.Locked}");
		menu.Close();
		yield return Wai