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 TooManyEmotes; using TooManyEmotes.Patches; using TooManyEmotes.Props; using Unity.Collections; using Unity.Netcode; using UnityEngine; [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.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("EmotePlaylist")] [assembly: AssemblyTitle("EmotePlaylist")] [assembly: AssemblyVersion("1.0.0.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 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 ulong _followingOwner; private int _lastIndex = -1; private bool _running; private string _tip = ""; private void Update() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return; } PlaylistNet.EnsureRegistered(); PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null || val.isPlayerDead) { StopLocal(_running); return; } if (Input.GetKeyDown(Plugin.PlaylistKey.Value) && !val.isTypingChat && !val.inTerminalMenu) { ToggleOwnPlaylist(val); } if (!_running) { return; } if (!PlaylistRegistry.TryGet(_followingOwner, out PlaylistState state) || state.EmoteNames.Length == 0) { StopLocal(broadcast: false); 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"; } 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; Plugin.Log.LogInfo((object)$"Joined playlist owner={ownerPlayerId} index={num} duration={state.SecondsPerEmote}s"); } } private void ToggleOwnPlaylist(PlayerControllerB player) { if (_running && _followingOwner == player.playerClientId) { StopLocal(broadcast: true); return; } List list = BuildPlaylist(); if (list.Count == 0) { Plugin.Log.LogWarning((object)"Playlist empty — assign quick emote slots (wheel remotes) first."); 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; 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 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.0")] [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.0"; 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 SecondsPerEmote { get; private set; } internal static ConfigEntry PlaylistOverride { get; private set; } private void Awake() { //IL_009f: 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)."); 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(); Log.LogInfo((object)"EmotePlaylist v1.0.0 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.0"; } }