Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ChatPoll v1.2.0
ChatPoll.dll
Decompiled a year agousing 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); } } } }