Decompiled source of Too Many Emotes v1.0.0
plugins/TooManyEmotes.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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