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);
}
}
}
}