Decompiled source of LessSleep v1.0.0

BepInEx/plugins/LessSleep.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("LessSleep")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+87cc3a25a482ab4adefb1d55ae2bcabf371743b7")]
[assembly: AssemblyProduct("LessSleep")]
[assembly: AssemblyTitle("LessSleep")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace LessSleep
{
	internal static class ClientPatches
	{
		[HarmonyPatch(typeof(Chat), "SendText")]
		internal static class ChatSendTextPatch
		{
			private static bool Prefix(string text)
			{
				return !ClientState.TryChatVote(text);
			}
		}

		[HarmonyPatch(typeof(Terminal), "TryRunCommand")]
		internal static class TerminalTryRunCommandPatch
		{
			private static bool Prefix(string text)
			{
				if (string.IsNullOrEmpty(text))
				{
					return true;
				}
				string text2 = text.Trim();
				if (text2.StartsWith("/"))
				{
					text2 = text2.Substring(1);
				}
				if (text2.IndexOf(' ') >= 0)
				{
					return true;
				}
				return !ClientState.TryChatVote(text2);
			}
		}
	}
	internal static class ClientState
	{
		private static bool voteOpen;

		private static float helloNext;

		public static void Tick()
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			if (Time.time >= helloNext)
			{
				helloNext = Time.time + 30f;
				Rpc.ToServer("LessSleep_Hello", "1.0.0");
			}
			if (voteOpen && LessSleepPlugin.EnableVotePrompt.Value && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()))
			{
				if (Input.GetKeyDown((KeyCode)121))
				{
					CastVote(accept: true);
				}
				else if (Input.GetKeyDown((KeyCode)110))
				{
					CastVote(accept: false);
				}
			}
		}

		public static bool TryChatVote(string text)
		{
			if (!voteOpen || !LessSleepPlugin.AllowChatYesNo.Value)
			{
				return false;
			}
			switch ((text ?? string.Empty).Trim().ToLowerInvariant())
			{
			case "yes":
			case "y":
				CastVote(accept: true);
				return true;
			case "no":
			case "n":
				CastVote(accept: false);
				return true;
			default:
				return false;
			}
		}

		public static void OnVoteStart(string requester, float timeout, int yesNeeded, int awakeCount)
		{
			voteOpen = true;
		}

		public static void OnVoteUpdate(int yesVotes, int yesNeeded)
		{
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)1, $"Sleep vote: {yesVotes}/{yesNeeded}", 0, (Sprite)null, false, false);
			}
		}

		public static void OnVoteResult(bool passed, string reason)
		{
			voteOpen = false;
		}

		private static void CastVote(bool accept)
		{
			Rpc.ToServer("LessSleep_Vote", accept);
			if ((Object)(object)MessageHud.instance != (Object)null)
			{
				MessageHud.instance.ShowMessage((MessageType)1, accept ? "Sleep vote: accepted" : "Sleep vote: declined", 0, (Sprite)null, false, false);
			}
		}
	}
	internal static class Net
	{
		public const string Hello = "LessSleep_Hello";

		public const string VoteStart = "LessSleep_VoteStart";

		public const string VoteUpdate = "LessSleep_VoteUpdate";

		public const string VoteResult = "LessSleep_VoteResult";

		public const string Vote = "LessSleep_Vote";

		public const string VanillaShowMessage = "ShowMessage";
	}
	[BepInPlugin("LessCx.LessSleep", "LessSleep", "1.0.0")]
	public class LessSleepPlugin : BaseUnityPlugin
	{
		public const string Guid = "LessCx.LessSleep";

		public const string Version = "1.0.0";

		internal static ConfigEntry<int> PercentInBedToStartVote;

		internal static ConfigEntry<int> MinimumPlayersInBed;

		internal static ConfigEntry<int> VotePercentOfAwakeToPass;

		internal static ConfigEntry<float> VoteTimeoutSeconds;

		internal static ConfigEntry<float> VoteCooldownSeconds;

		internal static ConfigEntry<bool> EnableVotePrompt;

		internal static ConfigEntry<bool> AllowChatYesNo;

		private Harmony harmony;

		private static ManualLogSource log;

		internal static void Log(string message)
		{
			ManualLogSource obj = log;
			if (obj != null)
			{
				obj.LogInfo((object)message);
			}
		}

		private void Awake()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected O, but got Unknown
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Expected O, but got Unknown
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Expected O, but got Unknown
			log = ((BaseUnityPlugin)this).Logger;
			PercentInBedToStartVote = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "PercentInBedToStartVote", 50, new ConfigDescription("Percentage of online players that must be in bed before a sleep vote starts.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			MinimumPlayersInBed = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "MinimumPlayersInBed", 1, new ConfigDescription("Minimum number of players in bed before a sleep vote can start.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			VotePercentOfAwakeToPass = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "VotePercentOfAwakeToPass", 50, new ConfigDescription("Percentage of awake LessSleep clients that must accept for the night to be skipped.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			VoteTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Server", "VoteTimeoutSeconds", 30f, new ConfigDescription("How long a sleep vote stays open before it fails.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 120f), Array.Empty<object>()));
			VoteCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Server", "VoteCooldownSeconds", 60f, new ConfigDescription("Wait time after a failed or cancelled vote before a new one can start.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f), Array.Empty<object>()));
			EnableVotePrompt = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "EnableVotePrompt", true, "Show sleep vote prompts and allow voting with the Y and N keys.");
			AllowChatYesNo = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "AllowChatYesNo", true, "While a sleep vote is open, typing yes or no in chat casts your vote.");
			harmony = new Harmony("LessCx.LessSleep");
			harmony.PatchAll(typeof(ServerPatches));
			harmony.PatchAll(typeof(ClientPatches));
			Rpc.Register();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"LessSleep 1.0.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}

		private void Update()
		{
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				Rpc.Register();
				if (instance.IsServer())
				{
					VoteManager.Tick();
				}
				ClientState.Tick();
			}
		}
	}
	internal static class Rpc
	{
		private static bool registered;

		public static void Register()
		{
			if (!registered)
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					registered = true;
					instance.Register<string>("LessSleep_Hello", (Action<long, string>)OnHello);
					instance.Register<bool>("LessSleep_Vote", (Action<long, bool>)OnVote);
					instance.Register<string, float, int, int>("LessSleep_VoteStart", (Method<string, float, int, int>)OnVoteStart);
					instance.Register<int, int>("LessSleep_VoteUpdate", (Action<long, int, int>)OnVoteUpdate);
					instance.Register<bool, string>("LessSleep_VoteResult", (Action<long, bool, string>)OnVoteResult);
				}
			}
		}

		public static void SendToAll(string method, params object[] args)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, method, args);
		}

		public static void ToServer(string method, params object[] args)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(method, args);
		}

		public static void MessageAll(string text)
		{
			SendToAll("ShowMessage", 1, text);
		}

		private static void OnHello(long sender, string version)
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				VoteManager.RegisterModdedPeer(sender, version);
			}
		}

		private static void OnVote(long sender, bool accept)
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				VoteManager.CastVote(sender, accept);
			}
		}

		private static void OnVoteStart(long sender, string requester, float timeout, int yesNeeded, int awakeCount)
		{
			ClientState.OnVoteStart(requester, timeout, yesNeeded, awakeCount);
		}

		private static void OnVoteUpdate(long sender, int yesVotes, int yesNeeded)
		{
			ClientState.OnVoteUpdate(yesVotes, yesNeeded);
		}

		private static void OnVoteResult(long sender, bool passed, string reason)
		{
			ClientState.OnVoteResult(passed, reason);
		}
	}
	internal static class ServerPatches
	{
		[HarmonyPatch(typeof(Game), "EverybodyIsTryingToSleep")]
		internal static class EverybodyIsTryingToSleepPatch
		{
			private static bool Prefix(ref bool __result)
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null || !instance.IsServer())
				{
					return true;
				}
				List<ZDO> allCharacterZDOS = instance.GetAllCharacterZDOS();
				if (allCharacterZDOS == null || allCharacterZDOS.Count == 0)
				{
					return true;
				}
				int num = 0;
				HashSet<long> hashSet = new HashSet<long>();
				foreach (ZDO item in allCharacterZDOS)
				{
					if (item.GetBool(ZDOVars.s_inBed, false))
					{
						num++;
					}
					else
					{
						hashSet.Add(((ZDOID)(ref item.m_uid)).UserID);
					}
				}
				int count = allCharacterZDOS.Count;
				if (num >= count)
				{
					VoteManager.Cancel("everyone is already in bed");
					VoteManager.ConsumePass();
					return true;
				}
				int num2 = Math.Max(LessSleepPlugin.MinimumPlayersInBed.Value, (int)Math.Ceiling((double)count * ((double)LessSleepPlugin.PercentInBedToStartVote.Value / 100.0)));
				if (num >= num2)
				{
					if (VoteManager.IsVoting)
					{
						__result = false;
						return false;
					}
					if (VoteManager.PassedNow)
					{
						VoteManager.ConsumePass();
						__result = true;
						return false;
					}
					if (Time.time < VoteManager.CooldownUntil)
					{
						__result = false;
						return false;
					}
					VoteManager.StartVote(FindRequesterName(allCharacterZDOS), VoteManager.FilterModdedAwake(hashSet));
					__result = false;
					return false;
				}
				if (VoteManager.IsVoting)
				{
					VoteManager.Cancel("players left their beds");
				}
				return true;
			}

			private static string FindRequesterName(List<ZDO> zdos)
			{
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0043: Unknown result type (might be due to invalid IL or missing references)
				//IL_0045: Unknown result type (might be due to invalid IL or missing references)
				//IL_0047: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: 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)
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				List<PlayerInfo> playerList = ZNet.instance.GetPlayerList();
				if (playerList == null)
				{
					return "A player";
				}
				foreach (ZDO zdo in zdos)
				{
					if (!zdo.GetBool(ZDOVars.s_inBed, false))
					{
						continue;
					}
					foreach (PlayerInfo item in playerList)
					{
						if (item.m_characterID == zdo.m_uid && !string.IsNullOrEmpty(item.m_name))
						{
							return item.m_name;
						}
					}
					break;
				}
				return "A player";
			}
		}
	}
	internal static class VoteManager
	{
		private static readonly HashSet<long> moddedPeers = new HashSet<long>();

		private static readonly Dictionary<long, bool> votes = new Dictionary<long, bool>();

		private static bool voting;

		private static bool passed;

		private static string requester = "A player";

		private static int yesNeeded;

		private static int awakeCount;

		private static float deadline;

		private static float cooldownUntil;

		public static bool IsVoting => voting;

		public static bool PassedNow => passed;

		public static float CooldownUntil => cooldownUntil;

		public static void RegisterModdedPeer(long peer, string version)
		{
			moddedPeers.Add(peer);
		}

		public static HashSet<long> FilterModdedAwake(HashSet<long> awakePeerIds)
		{
			PruneConnected();
			HashSet<long> hashSet = new HashSet<long>();
			foreach (long awakePeerId in awakePeerIds)
			{
				if (moddedPeers.Contains(awakePeerId))
				{
					hashSet.Add(awakePeerId);
				}
			}
			return hashSet;
		}

		private static void PruneConnected()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: 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)
			//IL_0031: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			List<PlayerInfo> playerList = ZNet.instance.GetPlayerList();
			if (playerList == null)
			{
				moddedPeers.Clear();
				return;
			}
			HashSet<long> hashSet = new HashSet<long>();
			foreach (PlayerInfo item in playerList)
			{
				if (item.m_characterID != ZDOID.None)
				{
					ZDOID characterID = item.m_characterID;
					hashSet.Add(((ZDOID)(ref characterID)).UserID);
				}
			}
			moddedPeers.IntersectWith(hashSet);
		}

		public static void StartVote(string requesterName, HashSet<long> moddedAwake)
		{
			requester = (string.IsNullOrEmpty(requesterName) ? "A player" : requesterName);
			awakeCount = moddedAwake.Count;
			if (awakeCount == 0)
			{
				Pass("nobody to ask");
				return;
			}
			votes.Clear();
			voting = true;
			passed = false;
			yesNeeded = Mathf.Max(1, Mathf.CeilToInt((float)awakeCount * ((float)LessSleepPlugin.VotePercentOfAwakeToPass.Value / 100f)));
			deadline = Time.time + LessSleepPlugin.VoteTimeoutSeconds.Value;
			Rpc.SendToAll("LessSleep_VoteStart", requester, LessSleepPlugin.VoteTimeoutSeconds.Value, yesNeeded, awakeCount);
			Rpc.MessageAll(requester + " wants to sleep. Press Y to accept or N to decline " + $"({yesNeeded} of {awakeCount} votes needed, {LessSleepPlugin.VoteTimeoutSeconds.Value:0}s). " + "You can also type yes or no in chat.");
			LessSleepPlugin.Log($"[LessSleep] Vote started by {requester}: {yesNeeded}/{awakeCount} needed.");
		}

		public static void CastVote(long peer, bool accept)
		{
			if (voting && moddedPeers.Contains(peer) && (!votes.TryGetValue(peer, out var value) || value != accept))
			{
				votes[peer] = accept;
				BroadcastUpdate();
				CheckOutcome();
			}
		}

		public static void Tick()
		{
			if (voting)
			{
				if (Time.time >= deadline)
				{
					Fail("timed out");
				}
				else
				{
					CheckOutcome();
				}
			}
		}

		public static void Cancel(string reason)
		{
			if (voting)
			{
				voting = false;
				passed = false;
				cooldownUntil = Time.time + LessSleepPlugin.VoteCooldownSeconds.Value;
				Rpc.SendToAll("LessSleep_VoteResult", false, reason);
				Rpc.MessageAll("Sleep vote cancelled: " + reason + ".");
				LessSleepPlugin.Log("[LessSleep] Vote cancelled: " + reason + ".");
			}
		}

		public static void ConsumePass()
		{
			passed = false;
			cooldownUntil = Time.time + 10f;
		}

		private static void CheckOutcome()
		{
			int num = 0;
			foreach (bool value in votes.Values)
			{
				if (value)
				{
					num++;
				}
			}
			if (num >= yesNeeded)
			{
				Pass("vote passed");
			}
			else if (votes.Count >= awakeCount)
			{
				Fail("not enough votes");
			}
		}

		private static void BroadcastUpdate()
		{
			int num = 0;
			foreach (bool value in votes.Values)
			{
				if (value)
				{
					num++;
				}
			}
			Rpc.SendToAll("LessSleep_VoteUpdate", num, yesNeeded);
		}

		private static void Pass(string reason)
		{
			voting = false;
			passed = true;
			cooldownUntil = Time.time + 10f;
			Rpc.SendToAll("LessSleep_VoteResult", true, reason);
			Rpc.MessageAll("Sleep vote passed - skipping to morning.");
			LessSleepPlugin.Log("[LessSleep] Vote passed.");
		}

		private static void Fail(string reason)
		{
			voting = false;
			passed = false;
			cooldownUntil = Time.time + LessSleepPlugin.VoteCooldownSeconds.Value;
			Rpc.SendToAll("LessSleep_VoteResult", false, reason);
			Rpc.MessageAll("Sleep vote failed: " + reason + ".");
			LessSleepPlugin.Log("[LessSleep] Vote failed: " + reason + ".");
		}
	}
}