using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using TooManyEmotes; using TooManyEmotes.Patches; using TooManyEmotes.Props; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("EmotePlaylist")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3")] [assembly: AssemblyProduct("EmotePlaylist")] [assembly: AssemblyTitle("EmotePlaylist")] [assembly: AssemblyVersion("1.0.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EmotePlaylist { internal static class InputUtil { private struct EdgeCache { public int Frame; public bool Result; public bool WasDown; } private static readonly Dictionary KeyMap = new Dictionary { { (KeyCode)97, (Key)15 }, { (KeyCode)98, (Key)16 }, { (KeyCode)99, (Key)17 }, { (KeyCode)100, (Key)18 }, { (KeyCode)101, (Key)19 }, { (KeyCode)102, (Key)20 }, { (KeyCode)103, (Key)21 }, { (KeyCode)104, (Key)22 }, { (KeyCode)105, (Key)23 }, { (KeyCode)106, (Key)24 }, { (KeyCode)107, (Key)25 }, { (KeyCode)108, (Key)26 }, { (KeyCode)109, (Key)27 }, { (KeyCode)110, (Key)28 }, { (KeyCode)111, (Key)29 }, { (KeyCode)112, (Key)30 }, { (KeyCode)113, (Key)31 }, { (KeyCode)114, (Key)32 }, { (KeyCode)115, (Key)33 }, { (KeyCode)116, (Key)34 }, { (KeyCode)117, (Key)35 }, { (KeyCode)118, (Key)36 }, { (KeyCode)119, (Key)37 }, { (KeyCode)120, (Key)38 }, { (KeyCode)121, (Key)39 }, { (KeyCode)122, (Key)40 }, { (KeyCode)48, (Key)50 }, { (KeyCode)49, (Key)41 }, { (KeyCode)50, (Key)42 }, { (KeyCode)51, (Key)43 }, { (KeyCode)52, (Key)44 }, { (KeyCode)53, (Key)45 }, { (KeyCode)54, (Key)46 }, { (KeyCode)55, (Key)47 }, { (KeyCode)56, (Key)48 }, { (KeyCode)57, (Key)49 }, { (KeyCode)32, (Key)1 }, { (KeyCode)304, (Key)51 }, { (KeyCode)303, (Key)52 }, { (KeyCode)306, (Key)55 }, { (KeyCode)305, (Key)56 }, { (KeyCode)308, (Key)53 }, { (KeyCode)307, (Key)54 }, { (KeyCode)9, (Key)3 }, { (KeyCode)301, (Key)72 }, { (KeyCode)8, (Key)65 }, { (KeyCode)13, (Key)2 }, { (KeyCode)27, (Key)60 }, { (KeyCode)273, (Key)63 }, { (KeyCode)274, (Key)64 }, { (KeyCode)276, (Key)61 }, { (KeyCode)275, (Key)62 }, { (KeyCode)277, (Key)70 }, { (KeyCode)127, (Key)71 }, { (KeyCode)278, (Key)68 }, { (KeyCode)279, (Key)69 }, { (KeyCode)280, (Key)67 }, { (KeyCode)281, (Key)66 }, { (KeyCode)282, (Key)94 }, { (KeyCode)283, (Key)95 }, { (KeyCode)284, (Key)96 }, { (KeyCode)285, (Key)97 }, { (KeyCode)286, (Key)98 }, { (KeyCode)287, (Key)99 }, { (KeyCode)288, (Key)100 }, { (KeyCode)289, (Key)101 }, { (KeyCode)290, (Key)102 }, { (KeyCode)291, (Key)103 }, { (KeyCode)292, (Key)104 }, { (KeyCode)293, (Key)105 } }; private static readonly Dictionary Edges = new Dictionary(); public static bool WasPressedThisFrame(KeyCode keyCode) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) int frameCount = Time.frameCount; if (Edges.TryGetValue(keyCode, out var value) && value.Frame == frameCount) { return value.Result; } bool wasDown = value.WasDown; bool flag = false; try { Keyboard current = Keyboard.current; if (current != null && KeyMap.TryGetValue(keyCode, out var value2)) { KeyControl val = current[value2]; if (val != null && ((ButtonControl)val).wasPressedThisFrame) { flag = true; } } } catch { } if (!flag) { try { if (Input.GetKeyDown(keyCode)) { flag = true; } } catch { } } bool flag2 = IsDown(keyCode); if (!flag) { flag = flag2 && !wasDown; } Edges[keyCode] = new EdgeCache { Frame = frameCount, Result = flag, WasDown = flag2 }; return flag; } public static bool IsDown(KeyCode keyCode) { //IL_000e: 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_0035: Unknown result type (might be due to invalid IL or missing references) try { Keyboard current = Keyboard.current; if (current != null && KeyMap.TryGetValue(keyCode, out var value)) { KeyControl val = current[value]; if (val != null && ((ButtonControl)val).isPressed) { return true; } } } catch { } try { if (Input.GetKey(keyCode)) { return true; } } catch { } return false; } public unsafe static string TipLabel(KeyCode keyCode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //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) //IL_0005: Invalid comparison between Unknown and I4 //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected I4, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Invalid comparison between Unknown and I4 //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 //IL_0066: 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_006a: Unknown result type (might be due to invalid IL or missing references) KeyCode val = keyCode; if ((int)val != 13) { if ((int)val != 27) { switch (val - 303) { case 0: case 1: return "Shift"; case 2: case 3: return "Ctrl"; case 4: case 5: return "Alt"; default: if ((int)keyCode >= 48 && (int)keyCode <= 57) { return ((char)(48 + (keyCode - 48))).ToString(); } return ((object)(*(KeyCode*)(&keyCode))/*cast due to .constrained prefix*/).ToString(); } } return "Esc"; } return "Enter"; } } internal sealed class PlaylistHud : MonoBehaviour { private static PlaylistHud? _instance; private Canvas? _canvas; private Image? _backdrop; private TextMeshProUGUI? _label; private bool _fontAssigned; private bool _visible; private float _nextRefresh; private string _cachedText = ""; private static readonly Color PanelBg = new Color(0.05f, 0.05f, 0.08f, 0.78f); private static readonly Color TextColor = new Color(0.92f, 0.92f, 0.95f, 1f); private static readonly Color AccentOrange = new Color(0.855f, 0.4f, 0.185f, 1f); internal static PlaylistHud? Instance => _instance; internal static bool IsVisible { get { if ((Object)(object)_instance != (Object)null) { return _instance._visible; } return false; } } internal static void EnsureExists() { //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) //IL_001e: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("EmotePlaylistHUD"); Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent(); } } internal static void Toggle() { EnsureExists(); if (!((Object)(object)_instance == (Object)null)) { _instance.SetVisible(!_instance._visible); } } internal static void Show() { EnsureExists(); _instance?.SetVisible(visible: true); } internal static void Hide() { _instance?.SetVisible(visible: false); } private void Awake() { _instance = this; BuildUi(); } private void OnDestroy() { if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } private void BuildUi() { //IL_0058: 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_008c: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0102: 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_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0176: 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_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_canvas != (Object)null)) { _canvas = ((Component)this).gameObject.AddComponent(); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 4900; CanvasScaler obj = ((Component)this).gameObject.AddComponent(); obj.uiScaleMode = (ScaleMode)1; obj.referenceResolution = new Vector2(1920f, 1080f); ((Component)this).gameObject.AddComponent(); GameObject val = new GameObject("Panel", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(((Component)this).transform, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0f, 0.5f); component.anchorMax = new Vector2(0f, 0.5f); component.pivot = new Vector2(0f, 0.5f); component.sizeDelta = new Vector2(360f, 280f); component.anchoredPosition = new Vector2(24f, 40f); _backdrop = val.AddComponent(); ((Graphic)_backdrop).color = PanelBg; ((Graphic)_backdrop).raycastTarget = false; GameObject val2 = new GameObject("Label", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component2 = val2.GetComponent(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = new Vector2(14f, 10f); component2.offsetMax = new Vector2(-14f, -10f); _label = val2.AddComponent(); ((TMP_Text)_label).alignment = (TextAlignmentOptions)257; ((TMP_Text)_label).fontStyle = (FontStyles)0; ((Graphic)_label).color = TextColor; ((TMP_Text)_label).enableWordWrapping = true; ((Graphic)_label).raycastTarget = false; ((TMP_Text)_label).overflowMode = (TextOverflowModes)0; ((TMP_Text)_label).fontSize = 20f; ((TMP_Text)_label).lineSpacing = 4f; ((TMP_Text)_label).text = ""; TryAssignFont(); ((Behaviour)_canvas).enabled = false; if ((Object)(object)_backdrop != (Object)null) { ((Component)_backdrop).gameObject.SetActive(false); } Plugin.Log.LogInfo((object)"EmotePlaylist HUD built."); } } private void TryAssignFont() { if ((Object)(object)_label == (Object)null || _fontAssigned) { return; } try { TMP_FontAsset val = null; HUDManager instance = HUDManager.Instance; if ((Object)(object)instance != (Object)null) { if (instance.controlTipLines != null) { TextMeshProUGUI[] controlTipLines = instance.controlTipLines; foreach (TextMeshProUGUI val2 in controlTipLines) { if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { val = ((TMP_Text)val2).font; break; } } } if ((Object)(object)val == (Object)null && (Object)(object)instance.clockNumber != (Object)null && (Object)(object)((TMP_Text)instance.clockNumber).font != (Object)null) { val = ((TMP_Text)instance.clockNumber).font; } if ((Object)(object)val == (Object)null && (Object)(object)instance.weightCounter != (Object)null) { val = ((TMP_Text)instance.weightCounter).font; } } if ((Object)(object)val == (Object)null && (Object)(object)TMP_Settings.defaultFontAsset != (Object)null) { val = TMP_Settings.defaultFontAsset; } if ((Object)(object)val != (Object)null) { ((TMP_Text)_label).font = val; _fontAssigned = true; Plugin.Log.LogInfo((object)("EmotePlaylist HUD font: " + ((Object)val).name)); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("EmotePlaylist HUD font assign failed: " + ex.Message)); } } private void LateUpdate() { if ((Object)(object)_canvas == (Object)null || (Object)(object)_label == (Object)null) { BuildUi(); } if (_visible) { TryAssignFont(); if (!(Time.unscaledTime < _nextRefresh)) { _nextRefresh = Time.unscaledTime + 0.5f; RefreshText(); } } } private void RefreshText() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_label == (Object)null) { return; } StringBuilder stringBuilder = new StringBuilder(256); stringBuilder.Append("Emote Playlist\n"); int index; int remainSeconds; int total; bool flag = PlaylistRunner.TryGetStatus(out index, out remainSeconds, out total); string[] overrideNames = GetOverrideNames(); for (int i = 0; i < 8; i++) { string slotName = GetSlotName(i, overrideNames); string text = $"{i + 1}. {slotName}"; if (flag && i == index) { stringBuilder.Append($"> {text} ({remainSeconds}s)\n"); } else { stringBuilder.Append(text).Append('\n'); } } string text2 = InputUtil.TipLabel(Plugin.PlaylistKey.Value); string text3 = InputUtil.TipLabel(Plugin.ListKey.Value); if (flag) { stringBuilder.Append($"\n{text2} stop · {text3} hide · {index + 1}/{Mathf.Max(total, 1)} ({remainSeconds}s)"); } else { stringBuilder.Append("\n" + text2 + " start/stop · " + text3 + " hide"); } _cachedText = stringBuilder.ToString(); ((TMP_Text)_label).text = _cachedText; ((Graphic)_label).color = TextColor; } private static string[]? GetOverrideNames() { string text = Plugin.PlaylistOverride?.Value; if (string.IsNullOrWhiteSpace(text)) { return null; } string[] array = text.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); string[] array2 = new string[array.Length]; for (int i = 0; i < array.Length; i++) { array2[i] = array[i].Trim(); } return array2; } private static string GetSlotName(int index, string[]? overrideNames) { if (overrideNames != null) { if (index < overrideNames.Length && !string.IsNullOrEmpty(overrideNames[index])) { return overrideNames[index]; } return "(empty)"; } try { UnlockableEmote quickEmote = QuickEmotes.GetQuickEmote(index); if (quickEmote != null && !string.IsNullOrEmpty(quickEmote.emoteName)) { return quickEmote.emoteName; } } catch (Exception ex) { Plugin.Log.LogWarning((object)$"HUD quick slot {index}: {ex.Message}"); } return "(empty)"; } private void SetVisible(bool visible) { _visible = visible; if ((Object)(object)_canvas != (Object)null) { ((Behaviour)_canvas).enabled = visible; } if ((Object)(object)_backdrop != (Object)null && ((Component)_backdrop).gameObject.activeSelf != visible) { ((Component)_backdrop).gameObject.SetActive(visible); } if (visible) { _nextRefresh = 0f; RefreshText(); Plugin.Log.LogInfo((object)"EmotePlaylist HUD shown."); } else { Plugin.Log.LogInfo((object)"EmotePlaylist HUD hidden."); } } } internal static class PlaylistNet { [CompilerGenerated] private static class <>O { public static HandleNamedMessageDelegate <0>__OnMessage; } public const string MessageName = "MrGlim.EmotePlaylist"; private static bool _registered; public static void EnsureRegistered() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && !_registered) { CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager; object obj = <>O.<0>__OnMessage; if (obj == null) { HandleNamedMessageDelegate val = OnMessage; <>O.<0>__OnMessage = val; obj = (object)val; } customMessagingManager.RegisterNamedMessageHandler("MrGlim.EmotePlaylist", (HandleNamedMessageDelegate)obj); _registered = true; } } public unsafe static void BroadcastStart(PlaylistState state) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) EnsureRegistered(); NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { return; } FastBufferWriter val = WriteState(1, state); try { if (singleton.IsServer) { singleton.CustomMessagingManager.SendNamedMessageToAll("MrGlim.EmotePlaylist", val, (NetworkDelivery)2); } else { singleton.CustomMessagingManager.SendNamedMessage("MrGlim.EmotePlaylist", 0uL, val, (NetworkDelivery)2); } } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } public static void BroadcastStop(ulong ownerPlayerId) { //IL_0028: 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_003a: 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) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) EnsureRegistered(); NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null)) { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(16, (Allocator)2, -1); byte b = 0; ((FastBufferWriter)(ref val)).WriteValueSafe(ref b, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref ownerPlayerId, default(ForPrimitives)); if (singleton.IsServer) { singleton.CustomMessagingManager.SendNamedMessageToAll("MrGlim.EmotePlaylist", val, (NetworkDelivery)2); } else { singleton.CustomMessagingManager.SendNamedMessage("MrGlim.EmotePlaylist", 0uL, val, (NetworkDelivery)2); } ((FastBufferWriter)(ref val)).Dispose(); } } private static FastBufferWriter WriteState(byte op, PlaylistState state) { //IL_0030: 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_0046: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0078: 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) string text = string.Join("|", state.EmoteNames); int byteCount = Encoding.UTF8.GetByteCount(text); FastBufferWriter result = default(FastBufferWriter); ((FastBufferWriter)(ref result))..ctor(64 + byteCount, (Allocator)2, -1); ((FastBufferWriter)(ref result)).WriteValueSafe(ref op, default(ForPrimitives)); ((FastBufferWriter)(ref result)).WriteValueSafe(ref state.OwnerPlayerId, default(ForPrimitives)); ((FastBufferWriter)(ref result)).WriteValueSafe(ref state.StartServerTime, default(ForPrimitives)); ((FastBufferWriter)(ref result)).WriteValueSafe(ref state.SecondsPerEmote, default(ForPrimitives)); ((FastBufferWriter)(ref result)).WriteValueSafe(text, false); return result; } private unsafe static void OnMessage(ulong sender, FastBufferReader reader) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_0060: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0134: 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_0146: Unknown result type (might be due to invalid IL or missing references) byte b = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b, default(ForPrimitives)); ulong ownerPlayerId = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe(ref ownerPlayerId, default(ForPrimitives)); NetworkManager singleton = NetworkManager.Singleton; if (b == 0) { PlaylistRegistry.Clear(ownerPlayerId); if ((Object)(object)singleton != (Object)null && singleton.IsServer) { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(16, (Allocator)2, -1); byte b2 = 0; ((FastBufferWriter)(ref val)).WriteValueSafe(ref b2, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref ownerPlayerId, default(ForPrimitives)); singleton.CustomMessagingManager.SendNamedMessageToAll("MrGlim.EmotePlaylist", val, (NetworkDelivery)2); ((FastBufferWriter)(ref val)).Dispose(); } return; } double startServerTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe(ref startServerTime, default(ForPrimitives)); int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref num, default(ForPrimitives)); string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); num = Mathf.Clamp(num, 60, 300); string[] emoteNames = (string.IsNullOrEmpty(text) ? Array.Empty() : text.Split('|')); PlaylistState state = new PlaylistState { OwnerPlayerId = ownerPlayerId, StartServerTime = startServerTime, SecondsPerEmote = num, EmoteNames = emoteNames }; PlaylistRegistry.Set(state); if (!((Object)(object)singleton != (Object)null) || !singleton.IsServer || sender == 0L) { return; } FastBufferWriter val2 = WriteState(1, state); try { singleton.CustomMessagingManager.SendNamedMessageToAll("MrGlim.EmotePlaylist", val2, (NetworkDelivery)2); } finally { ((IDisposable)(*(FastBufferWriter*)(&val2))/*cast due to .constrained prefix*/).Dispose(); } } } internal sealed class PlaylistRunner : MonoBehaviour { private static PlaylistRunner? _instance; private ulong _followingOwner; private int _lastIndex = -1; private bool _running; private string _tip = ""; private float _tipUntil; private bool _didShowListOnStart; private bool _hadLocalPlayer; internal static bool IsRunning { get { if ((Object)(object)_instance != (Object)null) { return _instance._running; } return false; } } internal static bool TryGetStatus(out int index, out int remainSeconds, out int total) { index = 0; remainSeconds = 0; total = 0; if ((Object)(object)_instance == (Object)null || !_instance._running) { return false; } if (!PlaylistRegistry.TryGet(_instance._followingOwner, out PlaylistState state) || state.EmoteNames.Length == 0) { return false; } double num = ServerNow(); index = state.IndexAt(num); total = state.EmoteNames.Length; remainSeconds = state.SecondsPerEmote - (int)((num - state.StartServerTime) % (double)state.SecondsPerEmote); if (remainSeconds <= 0) { remainSeconds = state.SecondsPerEmote; } return true; } private void Awake() { _instance = this; } private void OnDestroy() { if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } private void Update() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return; } PlaylistNet.EnsureRegistered(); PlaylistHud.EnsureExists(); PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null || val.isPlayerDead) { StopLocal(_running); ClearStatusTip(); _hadLocalPlayer = false; return; } if (!_hadLocalPlayer) { _hadLocalPlayer = true; MaybeShowListOnStart(); } if (!val.isTypingChat && !val.inTerminalMenu) { if (InputUtil.WasPressedThisFrame(Plugin.PlaylistKey.Value)) { ToggleOwnPlaylist(val); } if (InputUtil.WasPressedThisFrame(Plugin.ListKey.Value)) { PlaylistHud.Toggle(); } } if (!_running) { if (Time.unscaledTime < _tipUntil && !string.IsNullOrEmpty(_tip)) { ShowControlTip(_tip); } return; } if (!PlaylistRegistry.TryGet(_followingOwner, out PlaylistState state) || state.EmoteNames.Length == 0) { StopLocal(broadcast: false); ClearStatusTip(); return; } double num = ServerNow(); int num2 = state.IndexAt(num); if (num2 != _lastIndex) { PlayIndex(state, num2); _lastIndex = num2; } int num3 = state.SecondsPerEmote - (int)((num - state.StartServerTime) % (double)state.SecondsPerEmote); if (num3 <= 0) { num3 = state.SecondsPerEmote; } _tip = $"Playlist {num2 + 1}/{state.EmoteNames.Length}: {state.EmoteNames[num2]} ({num3}s) [{InputUtil.TipLabel(Plugin.PlaylistKey.Value)} stop]"; ShowControlTip(_tip); } private void MaybeShowListOnStart() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (_didShowListOnStart || Plugin.ShowListOnStart == null || !Plugin.ShowListOnStart.Value) { return; } _didShowListOnStart = true; PlaylistHud.Show(); string text = InputUtil.TipLabel(Plugin.ListKey.Value); string text2 = InputUtil.TipLabel(Plugin.PlaylistKey.Value); try { HUDManager instance = HUDManager.Instance; if (instance != null) { instance.DisplayTip("Emote Playlist", text + " list · " + text2 + " start after setting TME quick slots", false, false, "LC_Tip1"); } } catch { } Plugin.Log.LogInfo((object)"ShowListOnStart: playlist HUD shown + tip."); } internal void Join(ulong ownerPlayerId) { if (PlaylistRegistry.TryGet(ownerPlayerId, out PlaylistState state) && state.EmoteNames.Length != 0) { _followingOwner = ownerPlayerId; _running = true; _lastIndex = -1; double serverTime = ServerNow(); int num = state.IndexAt(serverTime); PlayIndex(state, num); _lastIndex = num; FlashTip($"Joined playlist ({state.EmoteNames.Length} emotes)", 3f); Plugin.Log.LogInfo((object)$"Joined playlist owner={ownerPlayerId} index={num} duration={state.SecondsPerEmote}s"); } } private void ToggleOwnPlaylist(PlayerControllerB player) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) Plugin.Log.LogInfo((object)$"Playlist key pressed ({Plugin.PlaylistKey.Value})."); if (_running && _followingOwner == player.playerClientId) { StopLocal(broadcast: true); FlashTip("Playlist stopped", 2.5f); return; } List list = BuildPlaylist(); if (list.Count == 0) { Plugin.Log.LogWarning((object)"Playlist empty — assign TooManyEmotes quick slots (wheel remotes 1–8) first."); FlashTip("Playlist empty — set TME quick slots 1–8 first", 4f); try { string text = InputUtil.TipLabel(Plugin.PlaylistKey.Value); HUDManager instance = HUDManager.Instance; if (instance != null) { instance.DisplayTip("Emote Playlist", "Assign TooManyEmotes quick slots (wheel remotes), then press " + text + ".", false, false, "LC_Tip1"); } return; } catch { return; } } PlaylistState playlistState = new PlaylistState { OwnerPlayerId = player.playerClientId, StartServerTime = ServerNow(), SecondsPerEmote = Plugin.ClampedSeconds(), EmoteNames = list.ToArray() }; PlaylistRegistry.Set(playlistState); PlaylistNet.BroadcastStart(playlistState); _followingOwner = player.playerClientId; _running = true; _lastIndex = -1; FlashTip($"Playlist started ({list.Count} emotes)", 2.5f); Plugin.Log.LogInfo((object)$"Started playlist ({list.Count} emotes, {playlistState.SecondsPerEmote}s each)."); } private void StopLocal(bool broadcast) { if (_running) { ulong followingOwner = _followingOwner; _running = false; _lastIndex = -1; if (broadcast) { PlaylistRegistry.Clear(followingOwner); PlaylistNet.BroadcastStop(followingOwner); Plugin.Log.LogInfo((object)"Stopped playlist."); } } } private void FlashTip(string text, float seconds) { _tip = text; _tipUntil = Time.unscaledTime + seconds; ShowControlTip(text); } private void ClearStatusTip() { _tip = ""; _tipUntil = 0f; } private static void ShowControlTip(string text) { try { HUDManager instance = HUDManager.Instance; if (!((Object)(object)instance == (Object)null)) { instance.ChangeControlTip(3, text, false); } } catch { } } private static List BuildPlaylist() { List list = new List(); string value = Plugin.PlaylistOverride.Value; if (!string.IsNullOrWhiteSpace(value)) { string[] array = value.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } return list; } for (int j = 0; j < 8; j++) { try { UnlockableEmote quickEmote = QuickEmotes.GetQuickEmote(j); if (quickEmote != null && !string.IsNullOrEmpty(quickEmote.emoteName)) { list.Add(quickEmote.emoteName); } } catch (Exception ex) { Plugin.Log.LogWarning((object)$"Quick slot {j} failed: {ex.Message}"); } } return list; } private static void PlayIndex(PlaylistState state, int index) { EmoteControllerPlayer emoteControllerLocal = EmoteControllerPlayer.emoteControllerLocal; if ((Object)(object)emoteControllerLocal == (Object)null || index < 0 || index >= state.EmoteNames.Length) { return; } string text = state.EmoteNames[index]; if (!EmotesManager.allUnlockableEmotesDict.TryGetValue(text, out var value) || value == null) { Plugin.Log.LogWarning((object)("Playlist emote not found: " + text)); return; } try { emoteControllerLocal.TryPerformingEmoteLocal(value, -1, (GrabbablePropObject)null); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to play " + text + ": " + ex.Message)); } } private static double ServerNow() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { return Time.realtimeSinceStartupAsDouble; } NetworkTime serverTime = singleton.ServerTime; return ((NetworkTime)(ref serverTime)).Time; } } [HarmonyPatch(typeof(SyncWithEmoteControllerManager), "SyncWithEmoteController_performed")] internal static class SyncPlaylistPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(PlayerControllerB __instance) { if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return true; } EmoteController lookingAtSyncableEmoteController = SyncWithEmoteControllerManager.lookingAtSyncableEmoteController; EmoteControllerPlayer val = (EmoteControllerPlayer)(object)((lookingAtSyncableEmoteController is EmoteControllerPlayer) ? lookingAtSyncableEmoteController : null); if (val == null || (Object)(object)val.playerController == (Object)null) { return true; } ulong playerClientId = val.playerController.playerClientId; if (!PlaylistRegistry.TryGet(playerClientId, out PlaylistState _)) { return true; } (((Object)(object)Plugin.Instance != (Object)null) ? ((Component)Plugin.Instance).GetComponent() : null)?.Join(playerClientId); SyncWithEmoteControllerManager.ResetState(); return false; } } internal sealed class PlaylistState { public ulong OwnerPlayerId; public double StartServerTime; public int SecondsPerEmote; public string[] EmoteNames = Array.Empty(); public int IndexAt(double serverTime) { if (EmoteNames.Length == 0 || SecondsPerEmote <= 0) { return 0; } double num = serverTime - StartServerTime; if (num < 0.0) { num = 0.0; } int num2 = (int)(num / (double)SecondsPerEmote) % EmoteNames.Length; if (num2 >= 0) { return num2; } return num2 + EmoteNames.Length; } } internal static class PlaylistRegistry { private static readonly Dictionary ByOwner = new Dictionary(); public static void Set(PlaylistState state) { ByOwner[state.OwnerPlayerId] = state; } public static void Clear(ulong ownerPlayerId) { ByOwner.Remove(ownerPlayerId); } public static bool TryGet(ulong ownerPlayerId, out PlaylistState state) { return ByOwner.TryGetValue(ownerPlayerId, out state); } public static void ClearAll() { ByOwner.Clear(); } } [BepInPlugin("com.benhough.lethal.EmotePlaylist", "EmotePlaylist", "1.0.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.benhough.lethal.EmotePlaylist"; public const string ModName = "EmotePlaylist"; public const string ModVersion = "1.0.3"; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry PlaylistKey { get; private set; } internal static ConfigEntry ListKey { get; private set; } internal static ConfigEntry ShowListOnStart { get; private set; } internal static ConfigEntry SecondsPerEmote { get; private set; } internal static ConfigEntry PlaylistOverride { get; private set; } private void Awake() { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Enable emote playlists."); PlaylistKey = ((BaseUnityPlugin)this).Config.Bind("General", "PlaylistKey", (KeyCode)290, "Start/stop a looping playlist of your 8 TooManyEmotes quick slots (the wheel remotes)."); ListKey = ((BaseUnityPlugin)this).Config.Bind("General", "ListKey", (KeyCode)291, "Toggle the on-screen emote playlist panel."); ShowListOnStart = ((BaseUnityPlugin)this).Config.Bind("General", "ShowListOnStart", true, "Auto-show the playlist panel (and a tip) when your local player first becomes available this session."); SecondsPerEmote = ((BaseUnityPlugin)this).Config.Bind("General", "SecondsPerEmote", 120, "How long each playlist emote plays (clamped 60–300)."); PlaylistOverride = ((BaseUnityPlugin)this).Config.Bind("General", "PlaylistOverride", "", "Optional comma-separated emote names. Empty = use current quick slots 1–8."); try { new Harmony("com.benhough.lethal.EmotePlaylist").PatchAll(typeof(Plugin).Assembly); ((Component)this).gameObject.AddComponent(); PlaylistHud.EnsureExists(); Log.LogInfo((object)"EmotePlaylist v1.0.3 loaded."); } catch (Exception arg) { Log.LogError((object)$"Failed to start: {arg}"); } } internal static int ClampedSeconds() { return Mathf.Clamp(SecondsPerEmote?.Value ?? 120, 60, 300); } } internal static class PluginInfo { public const string PLUGIN_GUID = "com.benhough.lethal.EmotePlaylist"; public const string PLUGIN_NAME = "EmotePlaylist"; public const string PLUGIN_VERSION = "1.0.3"; } }