Decompiled source of EmotePlaylist v1.0.0

EmotePlaylist.dll

Decompiled 6 hours ago
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<byte>(ref b, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(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<byte>(ref op, default(ForPrimitives));
			((FastBufferWriter)(ref result)).WriteValueSafe<ulong>(ref state.OwnerPlayerId, default(ForPrimitives));
			((FastBufferWriter)(ref result)).WriteValueSafe<double>(ref state.StartServerTime, default(ForPrimitives));
			((FastBufferWriter)(ref result)).WriteValueSafe<int>(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<byte>(ref b, default(ForPrimitives));
			ulong ownerPlayerId = default(ulong);
			((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(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<byte>(ref b2, default(ForPrimitives));
					((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(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<double>(ref startServerTime, default(ForPrimitives));
			int num = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(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<string>() : 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<string> 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<string> BuildPlaylist()
		{
			List<string> list = new List<string>();
			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<PlaylistRunner>() : 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<string>();

		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<ulong, PlaylistState> ByOwner = new Dictionary<ulong, PlaylistState>();

		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<bool> Enabled { get; private set; }

		internal static ConfigEntry<KeyCode> PlaylistKey { get; private set; }

		internal static ConfigEntry<int> SecondsPerEmote { get; private set; }

		internal static ConfigEntry<string> 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<bool>("General", "Enabled", true, "Enable emote playlists.");
			PlaylistKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "PlaylistKey", (KeyCode)290, "Start/stop a looping playlist of your 8 TooManyEmotes quick slots (the wheel remotes).");
			SecondsPerEmote = ((BaseUnityPlugin)this).Config.Bind<int>("General", "SecondsPerEmote", 120, "How long each playlist emote plays (clamped 60–300).");
			PlaylistOverride = ((BaseUnityPlugin)this).Config.Bind<string>("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<PlaylistRunner>();
				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";
	}
}