Decompiled source of ChatPoll v1.2.0

ChatPoll.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Api;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;

[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("ChatPoll")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A mod for Lethal Company that allows the host to start polls in chat that players can vote on")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+7aa0ff671029034d598715f9ee7b63aee542d096")]
[assembly: AssemblyProduct("ChatPoll")]
[assembly: AssemblyTitle("ChatPoll")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 ChatPoll
{
	internal class ChatPollConfig
	{
		internal readonly ConfigEntry<bool> pollCreatorMenu_CloseOnStart;

		internal readonly ConfigEntry<bool> pollCreatorMenu_CloseOnEnd;

		internal readonly ConfigEntry<string> pollCreatorMenu_OpenPhrase;

		internal readonly ConfigEntry<float> pollCreatorMenu_Scale;

		internal readonly ConfigEntry<bool> pollCreatorMenu_ScaleWithScreenSize;

		internal readonly ConfigEntry<bool> voteMessage_Visible;

		internal readonly ConfigEntry<bool> voteMessage_ShowVotedOption;

		internal readonly ConfigEntry<bool> voteMessage_ShowTotalVotes;

		internal ChatPollConfig(ConfigFile config)
		{
			pollCreatorMenu_CloseOnStart = config.Bind<bool>("PollCreatorMenu", "CloseOnStart", true, "Whether or not the Poll Creator menu should close when the 'Start poll' button is pressed.");
			pollCreatorMenu_CloseOnEnd = config.Bind<bool>("PollCreatorMenu", "CloseOnEnd", true, "Whether or not the Poll Creator menu should close when the 'End poll' button is pressed.");
			pollCreatorMenu_OpenPhrase = config.Bind<string>("PollCreatorMenu", "OpenPhrase", "/poll", "The phrase said in chat to open the poll menu. Leave blank to disable this feature.");
			pollCreatorMenu_Scale = config.Bind<float>("PollCreatorMenu", "Scale", 1f, "The base scale of the Poll Creator menu.");
			pollCreatorMenu_ScaleWithScreenSize = config.Bind<bool>("PollCreatorMenu", "ScaleWithScreenSize", true, "Whether or not the Poll Creator menu should scale with screen size.");
			voteMessage_Visible = config.Bind<bool>("VoteMessage", "Visible", true, "Whether or not to show a local message in chat when someone votes.");
			voteMessage_ShowVotedOption = config.Bind<bool>("VoteMessage", "ShowVotedOption", false, "Whether or not to show what the player voted for in the vote message.");
			voteMessage_ShowTotalVotes = config.Bind<bool>("VoteMessage", "ShowTotalVotes", true, "Whether or not to show how many players have voted in the vote message.");
		}
	}
	internal class InputActions : LcInputActions
	{
		[InputAction(/*Could not decode attribute arguments.*/)]
		internal InputAction OpenPollCreator { get; private set; }

		[InputAction(/*Could not decode attribute arguments.*/)]
		internal InputAction EndPoll { get; private set; }

		internal static bool ShouldInputBeIgnored()
		{
			if ((Object)(object)StartOfRound.Instance == (Object)null)
			{
				return true;
			}
			if (StartOfRound.Instance.localPlayerController.isTypingChat)
			{
				return true;
			}
			if (StartOfRound.Instance.localPlayerController.inTerminalMenu)
			{
				return true;
			}
			if (StartOfRound.Instance.localPlayerController.quickMenuManager.isMenuOpen)
			{
				return true;
			}
			return false;
		}
	}
	[BepInPlugin("BGN.ChatPoll", "Chat Poll", "1.2.0")]
	[BepInProcess("Lethal Company.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "BGN.ChatPoll";

		public const string NAME = "Chat Poll";

		public const string VERSION = "1.2.0";

		internal static ManualLogSource Logger;

		internal static ChatPollConfig config;

		internal static readonly InputActions inputActions = new InputActions();

		private static readonly MethodInfo hudManagerAddChatMessageMethod = typeof(HUDManager).GetMethod("AddChatMessage", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly MethodInfo hudManagerAddTextMessageServerRpcMethod = typeof(HUDManager).GetMethod("AddTextMessageServerRpc", BindingFlags.Instance | BindingFlags.NonPublic);

		private void Awake()
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			config = new ChatPollConfig(((BaseUnityPlugin)this).Config);
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
			Logger.LogInfo((object)"Plugin BGN.ChatPoll is loaded!");
		}

		internal static void PrintToChatLocally(string message)
		{
			hudManagerAddChatMessageMethod.Invoke(HUDManager.Instance, new object[2] { message, "" });
		}

		internal static void PrintToChat(string message)
		{
			hudManagerAddTextMessageServerRpcMethod.Invoke(HUDManager.Instance, new object[1] { message });
		}
	}
	internal class PollCreatorUI : MonoBehaviour
	{
		private const string WindowTitle = "Poll Creator";

		private const int WindowWidth = 600;

		private const int WindowHeight = 350;

		private static readonly GUILayoutOption[] WindowOptions = (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.Width(600f),
			GUILayout.Height(350f)
		};

		private bool isWindowOpen;

		private Vector2 windowPollOptionsScrollPosition;

		private string pollTitle = string.Empty;

		private bool hasDuration;

		private float duration = 40f;

		private const float MinDuration = 10f;

		private const float MaxDuration = 90f;

		private readonly List<string> pollOptions = new List<string>();

		private const int MinimumPollOptions = 2;

		private const int MaximumPollOptions = 10;

		private void Start()
		{
			for (int i = 0; i < 2; i++)
			{
				pollOptions.Add(string.Empty);
			}
			Plugin.inputActions.OpenPollCreator.performed += OpenPollCreator_performed;
		}

		private void OnDestroy()
		{
			Plugin.inputActions.OpenPollCreator.performed -= OpenPollCreator_performed;
		}

		private void OpenPollCreator_performed(CallbackContext context)
		{
			if (!isWindowOpen && !InputActions.ShouldInputBeIgnored())
			{
				SetOpen(isOpen: true);
			}
		}

		private void Update()
		{
			if (isWindowOpen && !StartOfRound.Instance.localPlayerController.quickMenuManager.isMenuOpen)
			{
				SetOpen(isOpen: false);
			}
		}

		internal void SetOpen(bool isOpen)
		{
			isWindowOpen = isOpen;
			StartOfRound.Instance.localPlayerController.quickMenuManager.isMenuOpen = isOpen;
			Cursor.lockState = (CursorLockMode)((!isOpen) ? 1 : 0);
			Cursor.visible = isOpen;
		}

		private void OnGUI()
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: 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_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: 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_00ea: Expected O, but got Unknown
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			if (isWindowOpen)
			{
				float num = Plugin.config.pollCreatorMenu_Scale.Value;
				if (Plugin.config.pollCreatorMenu_ScaleWithScreenSize.Value)
				{
					num *= Math.Min((float)Screen.width / 1920f, (float)Screen.height / 1080f);
				}
				int num2 = (Screen.width - 600) / 2;
				int num3 = (Screen.height - 350) / 2;
				float num4 = ((float)Screen.width * num - (float)Screen.width) / -2f;
				float num5 = ((float)Screen.height * num - (float)Screen.height) / -2f;
				GUI.matrix = Matrix4x4.TRS(Vector2.op_Implicit(new Vector2(num4, num5)), Quaternion.identity, Vector3.one * num);
				GUILayout.Window(GUIUtility.GetControlID((FocusType)2), new Rect((float)num2, (float)num3, 600f, 350f), new WindowFunction(DrawUI), "Poll Creator", WindowOptions);
			}
		}

		private void DrawUI(int id)
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayoutOption val = GUILayout.MaxWidth(300f);
			GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { val });
			DrawSettingsUI();
			GUILayout.EndVertical();
			GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { val });
			DrawOptionsUI();
			GUILayout.EndVertical();
			GUILayout.EndHorizontal();
		}

		private void DrawSettingsUI()
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			GUILayout.Label("Title:", Array.Empty<GUILayoutOption>());
			pollTitle = GUILayout.TextArea(pollTitle, 80, Array.Empty<GUILayoutOption>());
			hasDuration = GUILayout.Toggle(hasDuration, " Duration: (" + (hasDuration ? $"{duration} seconds" : "disabled") + ")", Array.Empty<GUILayoutOption>());
			GUI.enabled = hasDuration;
			duration = Mathf.Round(GUILayout.HorizontalSlider(duration, 10f, 90f, Array.Empty<GUILayoutOption>()));
			GUI.enabled = true;
			GUILayout.FlexibleSpace();
			GUILayout.EndVertical();
			Color color = GUI.color;
			if (PollManager.Instance.IsPollActive)
			{
				GUI.color = Color.red;
				if (GUILayout.Button("End poll", Array.Empty<GUILayoutOption>()))
				{
					PollManager.Instance.EndPoll();
					if (Plugin.config.pollCreatorMenu_CloseOnEnd.Value)
					{
						SetOpen(isOpen: false);
					}
				}
			}
			else
			{
				GUI.color = Color.green;
				if (GUILayout.Button("Start poll", Array.Empty<GUILayoutOption>()))
				{
					PollManager.Instance.StartPoll(pollTitle, hasDuration ? duration : float.PositiveInfinity, pollOptions.ToArray());
					if (Plugin.config.pollCreatorMenu_CloseOnStart.Value)
					{
						SetOpen(isOpen: false);
					}
				}
			}
			GUI.color = color;
		}

		private void DrawOptionsUI()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: 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)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			windowPollOptionsScrollPosition = GUILayout.BeginScrollView(windowPollOptionsScrollPosition, GUI.skin.box);
			int num = -1;
			for (int i = 0; i < pollOptions.Count; i++)
			{
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				pollOptions[i] = GUILayout.TextArea(pollOptions[i], 80, Array.Empty<GUILayoutOption>());
				GUI.enabled = pollOptions.Count > 2;
				Color color = GUI.color;
				GUI.color = Color.red;
				if (GUILayout.Button("X", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }))
				{
					num = i;
				}
				GUI.color = color;
				GUI.enabled = true;
				GUILayout.EndHorizontal();
			}
			if (num >= 0)
			{
				pollOptions.RemoveAt(num);
			}
			GUILayout.EndScrollView();
			GUI.enabled = pollOptions.Count < 10;
			if (GUILayout.Button("Add option", Array.Empty<GUILayoutOption>()))
			{
				pollOptions.Add(string.Empty);
			}
			GUI.enabled = true;
		}
	}
	internal class PollManager : MonoBehaviour
	{
		private const string PrimaryTextColorCode = "green";

		private const string SecondaryTextColorCode = "yellow";

		private const string TertiaryTextColorCode = "white";

		private float timer;

		private string[] options = Array.Empty<string>();

		private readonly Dictionary<ulong, int> votersOptions = new Dictionary<ulong, int>();

		public static PollManager Instance { get; private set; }

		internal PollCreatorUI PollCreatorUI { get; private set; }

		public bool IsPollActive { get; private set; }

		public IReadOnlyList<string> Options => options;

		public int TotalVoteCount => votersOptions.Count;

		private void Awake()
		{
			Instance = this;
			PollCreatorUI = ((Component)this).gameObject.AddComponent<PollCreatorUI>();
		}

		private void Start()
		{
			Plugin.inputActions.EndPoll.performed += EndPoll_performed;
		}

		private void OnDestroy()
		{
			Plugin.inputActions.EndPoll.performed -= EndPoll_performed;
		}

		private void EndPoll_performed(CallbackContext context)
		{
			if (!InputActions.ShouldInputBeIgnored())
			{
				EndPoll();
			}
		}

		private void Update()
		{
			if (IsPollActive)
			{
				timer -= Time.deltaTime;
				if (timer <= 0f)
				{
					EndPoll();
				}
			}
		}

		internal void StartPoll(string title, float duration, string[] options)
		{
			if (IsPollActive)
			{
				return;
			}
			IsPollActive = true;
			timer = duration;
			this.options = options;
			votersOptions.Clear();
			StringBuilder stringBuilder = new StringBuilder("<color=green>");
			stringBuilder.AppendLine("<align=flush>╔ POLL ╗</align>");
			if (!string.IsNullOrEmpty(title))
			{
				stringBuilder.AppendLine("<align=justified><color=yellow>\"" + title + "\"</color></align>");
			}
			stringBuilder.Append("<color=white>");
			for (int i = 0; i < options.Length; i++)
			{
				stringBuilder.Append($"{i + 1}. {options[i]}");
				if (i < options.Length - 1)
				{
					stringBuilder.AppendLine();
				}
			}
			stringBuilder.AppendLine("</color>");
			stringBuilder.AppendLine("<align=justified><color=yellow>^ Vote by sending the number!</color></align>");
			stringBuilder.Append("<align=flush>╚╝</align>");
			stringBuilder.Append("</color>");
			Plugin.PrintToChat(stringBuilder.ToString());
		}

		internal void EndPoll()
		{
			if (!IsPollActive)
			{
				return;
			}
			IsPollActive = false;
			Dictionary<int, int> dictionary = new Dictionary<int, int>(options.Length);
			for (int i = 0; i < options.Length; i++)
			{
				dictionary[i] = 0;
			}
			foreach (int value in votersOptions.Values)
			{
				dictionary[value]++;
			}
			StringBuilder stringBuilder = new StringBuilder("<color=green>");
			stringBuilder.AppendLine("<align=flush>╔ RESULTS ╗</align>");
			if (votersOptions.Count > 0)
			{
				stringBuilder.Append("<color=white>");
				for (int j = 0; j < options.Length; j++)
				{
					float num = (float)dictionary[j] / (float)votersOptions.Count * 100f;
					stringBuilder.Append($"({num:n0}%) {options[j]}");
					if (j < options.Length - 1)
					{
						stringBuilder.AppendLine();
					}
				}
				stringBuilder.AppendLine("</color>");
			}
			else
			{
				stringBuilder.AppendLine("<color=yellow>No one voted...</color>");
			}
			stringBuilder.Append("<align=flush>╚╝</align>");
			stringBuilder.Append("</color>");
			Plugin.PrintToChat(stringBuilder.ToString());
		}

		internal void AddVote(ulong clientId, int optionIndex)
		{
			if (optionIndex >= 0 && optionIndex < options.Length)
			{
				votersOptions[clientId] = optionIndex;
			}
		}

		internal bool TryGetVote(ulong clientId, [NotNullWhen(true)] out int? optionIndex)
		{
			if (votersOptions.TryGetValue(clientId, out var value))
			{
				optionIndex = value;
				return true;
			}
			optionIndex = null;
			return false;
		}

		internal void RemoveVote(ulong clientId)
		{
			votersOptions.Remove(clientId);
		}
	}
}
namespace ChatPoll.Patches
{
	[HarmonyPatch]
	internal static class PollPatch
	{
		[HarmonyPrefix]
		[HarmonyPatch(typeof(StartOfRound), "Awake")]
		private static void InitializePollManagerPatch(StartOfRound __instance)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (GameNetworkManager.Instance.isHostingGame)
			{
				if ((Object)(object)PollManager.Instance != (Object)null)
				{
					Plugin.Logger.LogError((object)"Tried to create a poll manager but one already exists");
				}
				else
				{
					new GameObject("PollManager").AddComponent<PollManager>();
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")]
		private static bool OpenPollPhrasePatch(ref string chatMessage)
		{
			if (chatMessage.ToLower() != Plugin.config.pollCreatorMenu_OpenPhrase.Value)
			{
				return true;
			}
			if (!GameNetworkManager.Instance.isHostingGame)
			{
				Plugin.PrintToChatLocally("<color=red>You cannot open the Poll Creator menu; you are not the host.</color>");
				return false;
			}
			if ((Object)(object)PollManager.Instance == (Object)null)
			{
				Plugin.PrintToChatLocally("<color=red>You cannot open the Poll Creator menu; the Poll Manager was not created. If you're seeing this message, something went terribly wrong.</color>");
				return false;
			}
			PollManager.Instance.PollCreatorUI.SetOpen(isOpen: true);
			return false;
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(HUDManager), "__rpc_handler_2930587515")]
		private static bool ProcessIncomingChatMessagePatch(ref NetworkBehaviour target, ref FastBufferReader reader, ref __RpcParams rpcParams)
		{
			//IL_006b: 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 ((Object)(object)PollManager.Instance == (Object)null)
			{
				return true;
			}
			if (!PollManager.Instance.IsPollActive)
			{
				return true;
			}
			ulong senderClientId = rpcParams.Server.Receive.SenderClientId;
			if (!StartOfRound.Instance.ClientPlayerList.TryGetValue(senderClientId, out var value))
			{
				return true;
			}
			if (value < 0 || value >= StartOfRound.Instance.allPlayerScripts.Length)
			{
				return true;
			}
			PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[value];
			bool flag = default(bool);
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag, default(ForPrimitives));
			if (!flag)
			{
				return true;
			}
			string s = default(string);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref s, false);
			((FastBufferReader)(ref reader)).Seek(0);
			if (!int.TryParse(s, out var result))
			{
				return true;
			}
			int num = result - 1;
			if (num < 0 || num >= PollManager.Instance.Options.Count)
			{
				return true;
			}
			if (PollManager.Instance.TryGetVote(senderClientId, out var optionIndex) && num == optionIndex)
			{
				return false;
			}
			PollManager.Instance.AddVote(rpcParams.Server.Receive.SenderClientId, num);
			VoteMessage(val.playerUsername, optionIndex.HasValue, PollManager.Instance.Options[num]);
			return false;
		}

		private static void VoteMessage(string username, bool hasVotedAlready, string votedOption)
		{
			if (Plugin.config.voteMessage_Visible.Value)
			{
				StringBuilder stringBuilder = new StringBuilder(username + " ");
				string value = (hasVotedAlready ? "changed their vote" : "voted");
				string text = (hasVotedAlready ? "to" : "for");
				stringBuilder.Append(value);
				if (Plugin.config.voteMessage_ShowVotedOption.Value)
				{
					stringBuilder.Append(" " + text + " '" + votedOption + "'");
				}
				if (!hasVotedAlready && Plugin.config.voteMessage_ShowTotalVotes.Value)
				{
					stringBuilder.Append($" ({PollManager.Instance.TotalVoteCount}/{StartOfRound.Instance.ClientPlayerList.Count})");
				}
				stringBuilder.Append('.');
				Plugin.PrintToChatLocally(stringBuilder.ToString());
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(StartOfRound), "OnClientDisconnect")]
		private static void RemoveVoteOnDisconnect(ref ulong clientId)
		{
			if (GameNetworkManager.Instance.isHostingGame)
			{
				PollManager.Instance.RemoveVote(clientId);
			}
		}
	}
}