Decompiled source of SleepGuard v0.5.2

SleepGuard.dll

Decompiled 2 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JG224.ModCore.API;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[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("SleepGuard")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.2.0")]
[assembly: AssemblyInformationalVersion("0.5.2")]
[assembly: AssemblyProduct("SleepGuard")]
[assembly: AssemblyTitle("SleepGuard")]
[assembly: AssemblyVersion("0.5.2.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 SleepGuard
{
	internal enum BallotDecision
	{
		Open,
		Approved,
		Rejected
	}
	internal static class ApprovalRules
	{
		internal static void ApproveUnanswered(IEnumerable<long> members, ISet<long> sleepers, ISet<long> approvals, ISet<long> rejections, bool deadlineReached, bool explicitResponses)
		{
			if (!deadlineReached || explicitResponses)
			{
				return;
			}
			foreach (long member in members)
			{
				if (!sleepers.Contains(member) && !rejections.Contains(member))
				{
					approvals.Add(member);
				}
			}
		}

		internal static int RequiredApprovals(int electorate, int percentage)
		{
			if (electorate <= 0)
			{
				return 1;
			}
			int num = Math.Max(1, Math.Min(100, percentage));
			return Math.Max(1, (electorate * num + 99) / 100);
		}

		internal static BallotDecision BeforeDeadline(int approvals, int pending, int electorate, int percentage)
		{
			int num = RequiredApprovals(electorate, percentage);
			if (approvals >= num)
			{
				return BallotDecision.Approved;
			}
			if (approvals + pending >= num)
			{
				return BallotDecision.Open;
			}
			return BallotDecision.Rejected;
		}

		internal static BallotDecision AtDeadline(int approvals, int rejections, int percentage)
		{
			int num = approvals + rejections;
			if (num <= 0)
			{
				return BallotDecision.Rejected;
			}
			if (approvals < RequiredApprovals(num, percentage))
			{
				return BallotDecision.Rejected;
			}
			return BallotDecision.Approved;
		}
	}
	internal static class BallotHistory
	{
		private static readonly Queue<string> Entries = new Queue<string>();

		private static string _path;

		internal static void Reset()
		{
			Entries.Clear();
			_path = null;
		}

		internal static void Record(int round, string reason, int electorate, int approvals, int rejections)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			EnsureLoaded();
			string text = DateTime.UtcNow.ToString("u", CultureInfo.InvariantCulture) + $" | round {round} | {reason} | electorate={electorate}, approved={approvals}, rejected={rejections}";
			Entries.Enqueue(text);
			while (Entries.Count > 50)
			{
				Entries.Dequeue();
			}
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("Rest history: " + text));
			}
			try
			{
				string text2 = _path + ".tmp";
				File.WriteAllLines(text2, Entries);
				if (File.Exists(_path))
				{
					File.Replace(text2, _path, _path + ".bak");
				}
				else
				{
					File.Move(text2, _path);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not save rest history: " + ex.Message));
				}
			}
		}

		internal static IEnumerable<string> Read()
		{
			EnsureLoaded();
			return Entries.ToArray();
		}

		private static void EnsureLoaded()
		{
			if (_path != null || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			long worldUID = ZNet.instance.GetWorldUID();
			_path = Path.Combine(Paths.ConfigPath, "jg224.SleepGuard.world-" + worldUID.ToString(CultureInfo.InvariantCulture) + ".history.txt");
			try
			{
				if (!File.Exists(_path))
				{
					return;
				}
				foreach (string item in File.ReadLines(_path))
				{
					if (item.Length <= 512)
					{
						Entries.Enqueue(item);
					}
					while (Entries.Count > 50)
					{
						Entries.Dequeue();
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not read rest history: " + ex.Message));
				}
			}
		}
	}
	internal enum RestSafety
	{
		Safe,
		Combat,
		BossThreat
	}
	internal readonly struct RestSafetyObservation
	{
		internal RestSafety Safety { get; }

		internal string PlayerName { get; }

		internal RestSafetyObservation(RestSafety safety, string playerName)
		{
			Safety = safety;
			PlayerName = playerName;
		}
	}
	internal static class CombatMonitor
	{
		private static readonly FieldInfo AiField = AccessTools.Field(typeof(Character), "m_baseAI");

		private static readonly Dictionary<Player, float> RecentThreats = new Dictionary<Player, float>();

		private static readonly List<Player> ExpiredThreats = new List<Player>();

		internal static RestSafetyObservation Observe()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			ExpiredThreats.Clear();
			foreach (KeyValuePair<Player, float> recentThreat in RecentThreats)
			{
				if ((Object)(object)recentThreat.Key == (Object)null || ((Character)recentThreat.Key).InBed() || realtimeSinceStartup - recentThreat.Value >= (float)Plugin.CombatQuietSeconds.Value)
				{
					ExpiredThreats.Add(recentThreat.Key);
				}
			}
			for (int i = 0; i < ExpiredThreats.Count; i++)
			{
				RecentThreats.Remove(ExpiredThreats[i]);
			}
			ExpiredThreats.Clear();
			bool flag = false;
			bool flag2 = false;
			string text = null;
			string text2 = null;
			List<Character> allCharacters = Character.GetAllCharacters();
			if (allCharacters != null)
			{
				for (int j = 0; j < allCharacters.Count; j++)
				{
					Character val = allCharacters[j];
					if ((Object)(object)val == (Object)null || val.IsDead())
					{
						continue;
					}
					object? obj = AiField?.GetValue(val);
					BaseAI val2 = (BaseAI)((obj is BaseAI) ? obj : null);
					if ((Object)(object)val2 == (Object)null || !val2.IsAlerted())
					{
						continue;
					}
					Character targetCreature = val2.GetTargetCreature();
					Player val3 = (Player)(object)((targetCreature is Player) ? targetCreature : null);
					if ((Object)(object)val3 == (Object)null || ((Character)val3).IsDead() || ((Character)val3).InBed() || !BaseAI.IsEnemy(val, (Character)(object)val3))
					{
						continue;
					}
					flag = true;
					RecentThreats[val3] = realtimeSinceStartup;
					string playerName = val3.GetPlayerName();
					if (string.IsNullOrWhiteSpace(text))
					{
						text = playerName;
					}
					if (val.IsBoss())
					{
						flag2 = true;
						if (string.IsNullOrWhiteSpace(text2))
						{
							text2 = playerName;
						}
					}
				}
			}
			if (flag2 && Plugin.HardBlockBosses.Value)
			{
				return new RestSafetyObservation(RestSafety.BossThreat, text2);
			}
			if (flag)
			{
				return new RestSafetyObservation(RestSafety.Combat, text);
			}
			Player val4 = null;
			float num = float.NegativeInfinity;
			foreach (KeyValuePair<Player, float> recentThreat2 in RecentThreats)
			{
				if (recentThreat2.Value > num)
				{
					val4 = recentThreat2.Key;
					num = recentThreat2.Value;
				}
			}
			if ((Object)(object)val4 != (Object)null)
			{
				return new RestSafetyObservation(RestSafety.Combat, val4.GetPlayerName());
			}
			return new RestSafetyObservation(RestSafety.Safe, null);
		}

		internal static void Reset()
		{
			RecentThreats.Clear();
			ExpiredThreats.Clear();
		}
	}
	internal static class ConfigFileMigration
	{
		internal const string CurrentFileName = "jg224.SleepGuard.cfg";

		internal const string LegacyFileName = "garst.SleepGuard.cfg";

		internal static ConfigFile Open(BaseUnityPlugin plugin, string configDirectory, ManualLogSource log)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			if (MoveLegacy(configDirectory) && log != null)
			{
				log.LogInfo((object)"Renamed legacy config garst.SleepGuard.cfg to jg224.SleepGuard.cfg.");
			}
			return PluginConfigFiles.Attach(plugin, new ConfigFile(Path.Combine(configDirectory, "jg224.SleepGuard.cfg"), true, plugin.Info.Metadata));
		}

		internal static bool MoveLegacy(string configDirectory)
		{
			Directory.CreateDirectory(configDirectory);
			if (ExactPath(configDirectory, "jg224.SleepGuard.cfg") != null)
			{
				return false;
			}
			string text = ExactPath(configDirectory, "garst.SleepGuard.cfg");
			if (text == null)
			{
				return false;
			}
			File.Move(text, Path.Combine(configDirectory, "jg224.SleepGuard.cfg"));
			return true;
		}

		private static string ExactPath(string directory, string fileName)
		{
			return Directory.EnumerateFiles(directory, "*.cfg", SearchOption.TopDirectoryOnly).FirstOrDefault((string path) => string.Equals(Path.GetFileName(path), fileName, StringComparison.Ordinal));
		}
	}
	internal static class VotePolicy
	{
		internal static bool TrustedServer(bool isServer, long sender, long serverUid, long localUid)
		{
			if (!isServer)
			{
				if (serverUid != 0L)
				{
					return sender == serverUid;
				}
				return false;
			}
			if (sender != 0L)
			{
				return sender == localUid;
			}
			return true;
		}

		internal static bool TryAnswer(string payload, out int round, out bool approved)
		{
			round = 0;
			approved = false;
			if (string.IsNullOrEmpty(payload) || payload.Length > 24)
			{
				return false;
			}
			int num = payload.IndexOf(',');
			if (num <= 0 || num != payload.Length - 2 || (payload[num + 1] != '0' && payload[num + 1] != '1') || !int.TryParse(payload.Substring(0, num), NumberStyles.None, CultureInfo.InvariantCulture, out round) || round <= 0)
			{
				return false;
			}
			approved = payload[num + 1] == '1';
			return true;
		}

		internal static bool Eligible(bool ready, bool sleeping, bool includeLoading, float idleSeconds, int excludeAfterSeconds)
		{
			if (!(!ready && includeLoading))
			{
				if (ready)
				{
					if (!sleeping && excludeAfterSeconds > 0)
					{
						return idleSeconds < (float)excludeAfterSeconds;
					}
					return true;
				}
				return false;
			}
			return true;
		}
	}
	internal sealed class VoteRateLimit
	{
		private readonly Dictionary<long, float> _last = new Dictionary<long, float>();

		internal bool Accept(long playerId, float now)
		{
			if (playerId == 0L || float.IsNaN(now) || float.IsInfinity(now))
			{
				return false;
			}
			if (_last.TryGetValue(playerId, out var value) && now - value < 0.5f)
			{
				return false;
			}
			_last[playerId] = now;
			return true;
		}

		internal void Remove(long playerId)
		{
			_last.Remove(playerId);
		}

		internal void Clear()
		{
			_last.Clear();
		}
	}
	internal static class DebugCommands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__RunTest;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
				{
					args.Context.AddString("Run sleepguard_history on the server console or host.");
					return;
				}
				foreach (string item in BallotHistory.Read())
				{
					args.Context.AddString(item);
				}
			}
		}

		private static bool _registered;

		internal static void Register()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_0066: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			object obj = <>O.<0>__RunTest;
			if (obj == null)
			{
				ConsoleEvent val = RunTest;
				<>O.<0>__RunTest = val;
				obj = (object)val;
			}
			new ConsoleCommand("sleepguard_test", "open a SleepGuard vote for single-player menu testing", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj2 = <>c.<>9__1_0;
			if (obj2 == null)
			{
				ConsoleEvent val2 = delegate(ConsoleEventArgs args)
				{
					if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
					{
						args.Context.AddString("Run sleepguard_history on the server console or host.");
						return;
					}
					foreach (string item in BallotHistory.Read())
					{
						args.Context.AddString(item);
					}
				};
				<>c.<>9__1_0 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("sleepguard_history", "show the last 50 world ballot outcomes (server console/host)", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			_registered = true;
		}

		private static void RunTest(ConsoleEventArgs args)
		{
			if (Plugin.IsActive)
			{
				SleepBallot.TryStartSinglePlayerTest(out var message);
				args.Context.AddString("[SleepGuard] " + message);
			}
		}
	}
	[HarmonyPatch(typeof(Game), "EverybodyIsTryingToSleep")]
	internal static class RestDecisionPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(ref bool __result)
		{
			if (!Plugin.Enabled.Value)
			{
				SleepBallot.CancelActiveSession();
				return true;
			}
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return true;
			}
			return SleepBallot.ProcessSleepCheck(ref __result);
		}
	}
	[HarmonyPatch(typeof(Game), "Awake")]
	internal static class WorldResetPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			CombatMonitor.Reset();
			SleepBallot.ResetForWorld();
			RestPrompt.ResetForWorld();
		}
	}
	[HarmonyPatch(typeof(Game), "Start")]
	internal static class RestRpcRegistrationPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			RestTransport.RegisterHandlers();
		}
	}
	public enum AutomaticResponse
	{
		Ask,
		Approve,
		Reject
	}
	[BepInPlugin("garst.SleepGuard", "SleepGuard", "0.5.2")]
	[BepInDependency("com.jg224.modcore", "0.5.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "garst.SleepGuard";

		public const string PluginName = "SleepGuard";

		public const string PluginVersion = "0.5.2";

		public const string ModCoreGuid = "com.jg224.modcore";

		public const int ProtocolVersion = 2;

		public static readonly ModuleId ModuleId = new ModuleId("sleepguard");

		internal static ManualLogSource Log;

		internal static ICoreServices Core;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<int> CombatQuietSeconds;

		internal static ConfigEntry<bool> HardBlockBosses;

		internal static ConfigEntry<bool> AnnounceCombatBlocks;

		internal static ConfigEntry<int> MinimumSleepers;

		internal static ConfigEntry<int> ApprovalPercent;

		internal static ConfigEntry<int> CountdownSeconds;

		internal static ConfigEntry<int> ResponseSeconds;

		internal static ConfigEntry<int> RetryDelaySeconds;

		internal static ConfigEntry<bool> IncludeLoadingPlayers;

		internal static ConfigEntry<int> ExcludeIdleAfterSeconds;

		internal static ConfigEntry<AutomaticResponse> ClientResponse;

		internal static ConfigEntry<KeyboardShortcut> ApproveVoteKey;

		internal static ConfigEntry<KeyboardShortcut> RejectVoteKey;

		internal static ConfigEntry<bool> VerboseLogging;

		internal static ConfigEntry<bool> AlwaysRequireExplicitVote;

		private Harmony _harmony;

		private readonly List<IDisposable> _registrations = new List<IDisposable>();

		private bool _shutDown;

		internal static bool IsActive { get; private set; }

		private void Awake()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Expected O, but got Unknown
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Expected O, but got Unknown
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Expected O, but got Unknown
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_023f: Expected O, but got Unknown
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Expected O, but got Unknown
			//IL_0290: Unknown result type (might be due to invalid IL or missing references)
			//IL_029a: Expected O, but got Unknown
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ca: Expected O, but got Unknown
			//IL_030b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0315: Expected O, but got Unknown
			//IL_0347: Unknown result type (might be due to invalid IL or missing references)
			//IL_036d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cc: Expected O, but got Unknown
			//IL_03e7: Unknown result type (might be due to invalid IL or missing references)
			IsActive = false;
			_shutDown = false;
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigFile val = ConfigFileMigration.Open((BaseUnityPlugin)(object)this, Paths.ConfigPath, ((BaseUnityPlugin)this).Logger);
			if (!ModCoreApi.IsAvailable)
			{
				throw new InvalidOperationException("ModCore did not initialize before SleepGuard.");
			}
			Core = ModCoreApi.Services;
			SemanticVersion val2 = default(SemanticVersion);
			if (!SemanticVersion.TryParse("0.5.2", ref val2))
			{
				throw new InvalidOperationException("SleepGuard has invalid release version metadata.");
			}
			_registrations.Add(Core.Modules.Register(new ModuleDescriptor(ModuleId, "garst.SleepGuard", "SleepGuard", val2, 2, (ModuleSide)3, (ModuleRequirement)4, 0uL, 1, 1)));
			_registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)6, "SG_", 1, Array.Empty<string>()));
			_registrations.Add(RoutedRpcIngress.Register(ModuleId, new string[7] { "SG_ShowRestBallot", "SG_RestBallotState", "SG_AnswerRestBallot", "SG_CloseRestBallot", "SG_RestBallotOutcome", "SG_RestBlocked", "SG_RestCountdown" }));
			_registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)8, "sleepguard.", 1, Array.Empty<string>()));
			_registrations.Add(Core.Ui.Reserve(new UiReservation(ModuleId, (UiSurface)6, "center-screen.rest-ballot", 30, false)));
			Enabled = val.Bind<bool>("General", "Enabled", true, "Enable SleepGuard's combat-aware rest voting.");
			CombatQuietSeconds = val.Bind<int>("General", "CombatQuietSeconds", 3, new ConfigDescription("Seconds without an alerted hostile creature targeting a living, awake player before rest may continue. Players in bed are ignored.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 60), Array.Empty<object>()));
			HardBlockBosses = val.Bind<bool>("General", "HardBlockBosses", true, "Use a distinct hard-block reason while a boss is attacking an awake player. Players in bed are ignored.");
			AnnounceCombatBlocks = val.Bind<bool>("General", "AnnounceCombatBlocks", true, "Show connected players why an active rest request is paused.");
			MinimumSleepers = val.Bind<int>("Voting", "MinimumSleepers", 1, new ConfigDescription("Connected players who must be in bed before a vote can begin.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			ApprovalPercent = val.Bind<int>("Voting", "ApprovalPercent", 60, new ConfigDescription("Percentage required to advance the world to morning.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>()));
			CountdownSeconds = val.Bind<int>("Voting", "CountdownSeconds", 5, new ConfigDescription("Lead-in time before voting opens. Set to 0 to open immediately.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 60), Array.Empty<object>()));
			ResponseSeconds = val.Bind<int>("Voting", "ResponseSeconds", 20, new ConfigDescription("Time allowed for responses. Unanswered eligible players approve at the deadline, except in explicit debug mode. Set to 0 to wait indefinitely.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 120), Array.Empty<object>()));
			RetryDelaySeconds = val.Bind<int>("Voting", "RetryDelaySeconds", 0, new ConfigDescription("Delay after a completed vote before another may begin.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 600), Array.Empty<object>()));
			IncludeLoadingPlayers = val.Bind<bool>("Voting", "IncludeLoadingPlayers", false, "Include peers whose player character has not loaded. Server policy; defaults to excluding loading peers.");
			ExcludeIdleAfterSeconds = val.Bind<int>("Voting", "ExcludeIdleAfterSeconds", 0, new ConfigDescription("Exclude ready players after this many seconds without observed movement or a vote. 0 disables idle exclusion; sleeping players always remain eligible.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3600), Array.Empty<object>()));
			ClientResponse = val.Bind<AutomaticResponse>("Client", "AutomaticResponse", AutomaticResponse.Ask, "Local preference for answering a rest vote.");
			ApproveVoteKey = val.Bind<KeyboardShortcut>("Client", "ApproveVoteKey", new KeyboardShortcut((KeyCode)121, Array.Empty<KeyCode>()), "Key used to approve a visible rest vote without interrupting gameplay.");
			RejectVoteKey = val.Bind<KeyboardShortcut>("Client", "RejectVoteKey", new KeyboardShortcut((KeyCode)110, Array.Empty<KeyCode>()), "Key used to decline a visible rest vote without interrupting gameplay.");
			VerboseLogging = val.Bind<bool>("Debug", "VerboseLogging", false, "Write state transitions and responses to the BepInEx log.");
			AlwaysRequireExplicitVote = val.Bind<bool>("Debug", "AlwaysRequireExplicitVote", false, "Testing only: require every player, including sleepers, to answer explicitly. Also enables the sleepguard_test command, which bypasses the bed requirement in single-player but still enforces combat safety.");
			DebugCommands.Register();
			_harmony = new Harmony("garst.SleepGuard");
			_harmony.PatchAll();
			IsActive = true;
			Core.Modules.SetState(ModuleId, (ModuleRuntimeState)2, "Required combat-aware rest voting protocol ready.");
			Log.LogInfo((object)("SleepGuard v0.5.2 loaded. " + $"MinimumSleepers={MinimumSleepers.Value}, approval={ApprovalPercent.Value}%, " + $"countdown={CountdownSeconds.Value}s, response={ResponseSeconds.Value}s"));
		}

		private void Update()
		{
			if (IsActive)
			{
				RestTransport.ObservePopulation();
				SleepBallot.TickSinglePlayerTest();
				RestPrompt.Tick();
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (_shutDown)
			{
				return;
			}
			_shutDown = true;
			IsActive = false;
			RestPrompt.ResetForWorld();
			SleepBallot.ResetForWorld();
			CombatMonitor.Reset();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			for (int num = _registrations.Count - 1; num >= 0; num--)
			{
				try
				{
					_registrations[num].Dispose();
				}
				catch (Exception ex)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogWarning((object)("Registration cleanup failed: " + ex.Message));
					}
				}
			}
			_registrations.Clear();
			if (Core != null)
			{
				Core.Metrics.RemoveOwner(ModuleId);
			}
			Core = null;
		}

		internal static void Trace(string message)
		{
			if (VerboseLogging != null && VerboseLogging.Value)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogDebug((object)message);
				}
			}
		}
	}
	internal static class RestPrompt
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__Approve;

			public static UnityAction <1>__Reject;
		}

		private const float PanelWidth = 660f;

		private const float PanelHeight = 172f;

		private const float TopOffset = 70f;

		private const float VerticalAnchor = 0.9f;

		private static readonly FieldInfo PopupInstance = AccessTools.Field(typeof(UnifiedPopup), "instance");

		private static readonly FieldInfo PopupParent = AccessTools.Field(typeof(UnifiedPopup), "popupUIParent");

		private static readonly FieldInfo HeaderText = AccessTools.Field(typeof(UnifiedPopup), "headerText");

		private static readonly FieldInfo BodyText = AccessTools.Field(typeof(UnifiedPopup), "bodyText");

		private static readonly FieldInfo LeftButton = AccessTools.Field(typeof(UnifiedPopup), "buttonLeft");

		private static readonly FieldInfo LeftButtonText = AccessTools.Field(typeof(UnifiedPopup), "buttonLeftText");

		private static RestSnapshot _snapshot;

		private static int _activeRound;

		private static int _lastCountdown = -1;

		private static bool _visible;

		private static bool _buildErrorLogged;

		private static GameObject _canvasObject;

		private static GameObject _panelObject;

		private static TMP_Text _statusText;

		private static TMP_Text _detailText;

		private static TMP_Text _hintText;

		private static TMP_Text _approveText;

		private static TMP_Text _rejectText;

		internal static void ReceivePrompt(long senderId, int round)
		{
			if (RestTransport.IsServerMessage(senderId) && round > 0 && round != _activeRound && !((Object)(object)Player.m_localPlayer == (Object)null))
			{
				_activeRound = round;
				switch (Plugin.ClientResponse.Value)
				{
				case AutomaticResponse.Approve:
					RestTransport.SubmitResponse(round, approved: true);
					ShowMessage((MessageType)1, "Rest request approved automatically.");
					break;
				case AutomaticResponse.Reject:
					RestTransport.SubmitResponse(round, approved: false);
					ShowMessage((MessageType)1, "Rest request rejected automatically.");
					break;
				default:
					_visible = true;
					ShowHud();
					break;
				}
			}
		}

		internal static void ReceiveSnapshot(long senderId, string encodedSnapshot)
		{
			if (RestTransport.IsServerMessage(senderId) && RestSnapshot.TryDecode(encodedSnapshot, out var snapshot))
			{
				_snapshot = snapshot;
				UpdateHudText();
			}
		}

		internal static void ReceiveCountdown(long senderId, int seconds)
		{
			if (RestTransport.IsServerMessage(senderId) && seconds >= 0 && seconds <= 60)
			{
				int num = ((_lastCountdown >= 0) ? 1 : 2);
				_lastCountdown = seconds;
				ShowMessage((MessageType)num, string.Format("A rest vote will open in {0} second{1}.", seconds, (seconds == 1) ? string.Empty : "s"));
			}
		}

		internal static void ReceiveClose(long senderId)
		{
			if (RestTransport.IsServerMessage(senderId))
			{
				ClearLocalState();
			}
		}

		internal static void ReceiveOutcome(long senderId, string message)
		{
			if (RestTransport.IsServerMessage(senderId) && message != null && message.Length <= 512)
			{
				ClearLocalState();
				ShowMessage((MessageType)2, message);
			}
		}

		internal static void ReceiveNotice(long senderId, string message)
		{
			if (RestTransport.IsServerMessage(senderId) && message != null && message.Length <= 512)
			{
				ShowMessage((MessageType)2, message);
			}
		}

		internal static void Tick()
		{
			//IL_001a: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (!_visible)
			{
				return;
			}
			ShowHud();
			if (!CanAcceptVoteInput())
			{
				return;
			}
			KeyboardShortcut value = Plugin.ApproveVoteKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				Approve();
				return;
			}
			value = Plugin.RejectVoteKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				Reject();
			}
		}

		internal static void ResetForWorld()
		{
			ClearLocalState();
			DestroyHud();
		}

		private static void ShowHud()
		{
			if (EnsureHud())
			{
				_panelObject.SetActive(true);
				UpdateHudText();
			}
		}

		private static bool EnsureHud()
		{
			if ((Object)(object)_panelObject != (Object)null)
			{
				return true;
			}
			object obj = PopupInstance?.GetValue(null);
			if (obj == null)
			{
				return false;
			}
			object? obj2 = PopupParent?.GetValue(obj);
			GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null);
			object? obj3 = HeaderText?.GetValue(obj);
			TMP_Text val2 = (TMP_Text)((obj3 is TMP_Text) ? obj3 : null);
			object? obj4 = BodyText?.GetValue(obj);
			TMP_Text val3 = (TMP_Text)((obj4 is TMP_Text) ? obj4 : null);
			object? obj5 = LeftButton?.GetValue(obj);
			Button val4 = (Button)((obj5 is Button) ? obj5 : null);
			object? obj6 = LeftButtonText?.GetValue(obj);
			TMP_Text val5 = (TMP_Text)((obj6 is TMP_Text) ? obj6 : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null)
			{
				return false;
			}
			try
			{
				BuildHud(FindPanelImage(val, val3, val4), val2, val3, val4, val5);
				_buildErrorLogged = false;
				return true;
			}
			catch (Exception ex)
			{
				DestroyHud();
				if (!_buildErrorLogged)
				{
					_buildErrorLogged = true;
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("Could not create the native rest voting HUD: " + ex.Message));
					}
				}
				return false;
			}
		}

		private static void BuildHud(Image panelTemplate, TMP_Text headerTemplate, TMP_Text bodyTemplate, Button buttonTemplate, TMP_Text buttonTextTemplate)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_007d: 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_00d4: Expected O, but got Unknown
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0306: Unknown result type (might be due to invalid IL or missing references)
			//IL_033e: Unknown result type (might be due to invalid IL or missing references)
			//IL_034d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Expected O, but got Unknown
			//IL_038b: Unknown result type (might be due to invalid IL or missing references)
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0362: Unknown result type (might be due to invalid IL or missing references)
			//IL_0367: Unknown result type (might be due to invalid IL or missing references)
			//IL_036d: Expected O, but got Unknown
			_canvasObject = new GameObject("SleepGuard_VoteCanvas", new Type[4]
			{
				typeof(RectTransform),
				typeof(Canvas),
				typeof(CanvasScaler),
				typeof(GraphicRaycaster)
			});
			Canvas component = _canvasObject.GetComponent<Canvas>();
			component.renderMode = (RenderMode)0;
			component.sortingOrder = 25;
			CanvasScaler component2 = _canvasObject.GetComponent<CanvasScaler>();
			component2.uiScaleMode = (ScaleMode)1;
			component2.referenceResolution = new Vector2(1920f, 1080f);
			component2.screenMatchMode = (ScreenMatchMode)0;
			component2.matchWidthOrHeight = 0.5f;
			_panelObject = new GameObject("SleepGuard_RestVote", new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			RectTransform component3 = _panelObject.GetComponent<RectTransform>();
			((Transform)component3).SetParent(_canvasObject.transform, false);
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0.5f, 0.9f);
			component3.anchorMax = val;
			component3.anchorMin = val;
			component3.pivot = new Vector2(0.5f, 1f);
			component3.anchoredPosition = new Vector2(0f, -70f);
			component3.sizeDelta = new Vector2(660f, 172f);
			Image component4 = _panelObject.GetComponent<Image>();
			CopyImageAppearance(panelTemplate, component4, new Color(0.18f, 0.12f, 0.07f, 0.96f));
			((Graphic)component4).raycastTarget = false;
			((Graphic)CreateText("Title", _panelObject.transform, headerTemplate, "REST REQUEST", 25f, (FontStyles)1, new Vector2(0f, -23f), new Vector2(610f, 35f))).color = ((Graphic)headerTemplate).color;
			_statusText = CreateText("Status", _panelObject.transform, bodyTemplate, string.Empty, 20f, (FontStyles)1, new Vector2(0f, -57f), new Vector2(610f, 27f));
			_detailText = CreateText("Details", _panelObject.transform, bodyTemplate, string.Empty, 17f, (FontStyles)0, new Vector2(0f, -82f), new Vector2(620f, 24f));
			_hintText = CreateText("Hint", _panelObject.transform, bodyTemplate, string.Empty, 15f, (FontStyles)2, new Vector2(0f, -105f), new Vector2(610f, 22f));
			Transform transform = _panelObject.transform;
			Vector2 position = new Vector2(-158f, -140f);
			Vector2 dimensions = new Vector2(292f, 40f);
			object obj = <>O.<0>__Approve;
			if (obj == null)
			{
				UnityAction val2 = Approve;
				<>O.<0>__Approve = val2;
				obj = (object)val2;
			}
			Button val3 = CreateButton("Approve", transform, buttonTemplate, position, dimensions, (UnityAction)obj);
			_approveText = CreateText("Label", ((Component)val3).transform, buttonTextTemplate, string.Empty, 18f, (FontStyles)1, Vector2.zero, new Vector2(280f, 36f));
			CenterInParent(_approveText.rectTransform);
			Transform transform2 = _panelObject.transform;
			Vector2 position2 = new Vector2(158f, -140f);
			Vector2 dimensions2 = new Vector2(292f, 40f);
			object obj2 = <>O.<1>__Reject;
			if (obj2 == null)
			{
				UnityAction val4 = Reject;
				<>O.<1>__Reject = val4;
				obj2 = (object)val4;
			}
			Button val5 = CreateButton("Decline", transform2, buttonTemplate, position2, dimensions2, (UnityAction)obj2);
			_rejectText = CreateText("Label", ((Component)val5).transform, buttonTextTemplate, string.Empty, 18f, (FontStyles)1, Vector2.zero, new Vector2(280f, 36f));
			CenterInParent(_rejectText.rectTransform);
		}

		private static Image FindPanelImage(GameObject popupParent, TMP_Text body, Button button)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			Transform parent = body.transform.parent;
			while ((Object)(object)parent != (Object)null)
			{
				Image component = ((Component)parent).GetComponent<Image>();
				if ((Object)(object)component != (Object)null && (Object)(object)component.sprite != (Object)null)
				{
					return component;
				}
				if ((Object)(object)parent == (Object)(object)popupParent.transform)
				{
					break;
				}
				parent = parent.parent;
			}
			Image component2 = ((Component)button).GetComponent<Image>();
			Image result = null;
			float num = 0f;
			Image[] componentsInChildren = popupParent.GetComponentsInChildren<Image>(true);
			foreach (Image val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)component2) && !((Object)(object)val.sprite == (Object)null))
				{
					Rect rect = ((Graphic)val).rectTransform.rect;
					float num2 = Mathf.Abs(((Rect)(ref rect)).width * ((Rect)(ref rect)).height);
					if (num2 > num)
					{
						result = val;
						num = num2;
					}
				}
			}
			return result;
		}

		private static TMP_Text CreateText(string name, Transform parent, TMP_Text template, string value, float size, FontStyles style, Vector2 position, Vector2 dimensions)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(TextMeshProUGUI)
			});
			RectTransform component = val.GetComponent<RectTransform>();
			((Transform)component).SetParent(parent, false);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 1f);
			component.anchorMax = val2;
			component.anchorMin = val2;
			component.pivot = new Vector2(0.5f, 0.5f);
			component.anchoredPosition = position;
			component.sizeDelta = dimensions;
			TextMeshProUGUI component2 = val.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component2).text = value;
			((TMP_Text)component2).font = template.font;
			((TMP_Text)component2).fontSharedMaterial = template.fontSharedMaterial;
			((TMP_Text)component2).fontSize = size;
			((TMP_Text)component2).fontStyle = style;
			((Graphic)component2).color = ((Graphic)template).color;
			((TMP_Text)component2).alignment = (TextAlignmentOptions)514;
			((TMP_Text)component2).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)component2).overflowMode = (TextOverflowModes)3;
			((Graphic)component2).raycastTarget = false;
			return (TMP_Text)(object)component2;
		}

		private static void CenterInParent(RectTransform rect)
		{
			//IL_0013: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0.5f, 0.5f);
			rect.anchorMax = val;
			rect.anchorMin = val;
			rect.pivot = new Vector2(0.5f, 0.5f);
			rect.anchoredPosition = Vector2.zero;
		}

		private static Button CreateButton(string name, Transform parent, Button template, Vector2 position, Vector2 dimensions, UnityAction callback)
		{
			//IL_003b: 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_0061: 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)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[4]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image),
				typeof(Button)
			});
			RectTransform component = val.GetComponent<RectTransform>();
			((Transform)component).SetParent(parent, false);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 1f);
			component.anchorMax = val2;
			component.anchorMin = val2;
			component.pivot = new Vector2(0.5f, 0.5f);
			component.anchoredPosition = position;
			component.sizeDelta = dimensions;
			Image component2 = val.GetComponent<Image>();
			CopyImageAppearance(((Component)template).GetComponent<Image>(), component2, Color.white);
			Button component3 = val.GetComponent<Button>();
			((Selectable)component3).targetGraphic = (Graphic)(object)component2;
			((Selectable)component3).transition = ((Selectable)template).transition;
			((Selectable)component3).colors = ((Selectable)template).colors;
			((Selectable)component3).spriteState = ((Selectable)template).spriteState;
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)0;
			((Selectable)component3).navigation = navigation;
			((UnityEvent)component3.onClick).AddListener(callback);
			return component3;
		}

		private static void CopyImageAppearance(Image source, Image destination, Color fallback)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)source == (Object)null || (Object)(object)source.sprite == (Object)null)
			{
				((Graphic)destination).color = fallback;
				return;
			}
			destination.sprite = source.sprite;
			destination.overrideSprite = source.overrideSprite;
			destination.type = source.type;
			destination.preserveAspect = source.preserveAspect;
			destination.fillCenter = source.fillCenter;
			destination.pixelsPerUnitMultiplier = source.pixelsPerUnitMultiplier;
			((Graphic)destination).material = ((Graphic)source).material;
			((Graphic)destination).color = ((Graphic)source).color;
		}

		private static void UpdateHudText()
		{
			if (!((Object)(object)_statusText == (Object)null))
			{
				string arg = ((_snapshot.SecondsLeft < 0) ? "No time limit" : $"{_snapshot.SecondsLeft}s remaining");
				_statusText.text = $"Support: {_snapshot.Approvals}/{_snapshot.Required} required     {arg}";
				_detailText.text = $"Sleeping: {_snapshot.Sleeping}  Opposed: {_snapshot.Rejections}  Awaiting: {_snapshot.Pending}  Eligible: {_snapshot.Connected}/{_snapshot.TotalConnected}";
				string arg2 = (_snapshot.ExplicitResponses ? "Explicit test vote required." : ((_snapshot.SecondsLeft < 0) ? "Waiting for votes; no automatic timeout." : "No response counts as YES when time expires."));
				_hintText.text = $"Excluded: {_snapshot.ExcludedLoading} loading, {_snapshot.ExcludedIdle} idle. {arg2}";
				_approveText.text = "APPROVE  [" + ShortcutName(Plugin.ApproveVoteKey) + "]";
				_rejectText.text = "DECLINE  [" + ShortcutName(Plugin.RejectVoteKey) + "]";
			}
		}

		private static void Approve()
		{
			if (_visible)
			{
				_visible = false;
				HideHud();
				RestTransport.SubmitResponse(_activeRound, approved: true);
				Plugin.Trace($"Local player approved rest vote round {_activeRound}.");
			}
		}

		private static void Reject()
		{
			if (_visible)
			{
				_visible = false;
				HideHud();
				RestTransport.SubmitResponse(_activeRound, approved: false);
				Plugin.Trace($"Local player rejected rest vote round {_activeRound}.");
			}
		}

		private static bool CanAcceptVoteInput()
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null || Console.IsVisible() || Menu.IsVisible() || TextInput.IsVisible())
			{
				return false;
			}
			if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return false;
			}
			if (!((Object)(object)Minimap.instance == (Object)null))
			{
				return !Minimap.InTextInput();
			}
			return true;
		}

		private unsafe static string ShortcutName(ConfigEntry<KeyboardShortcut> entry)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: 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_001a: 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)
			if (entry != null)
			{
				KeyboardShortcut value = entry.Value;
				if ((int)((KeyboardShortcut)(ref value)).MainKey != 0)
				{
					value = entry.Value;
					return ((object)(*(KeyboardShortcut*)(&value))/*cast due to .constrained prefix*/).ToString().ToUpperInvariant();
				}
			}
			return "UNBOUND";
		}

		private static void HideHud()
		{
			if ((Object)(object)_panelObject != (Object)null)
			{
				_panelObject.SetActive(false);
			}
		}

		private static void DestroyHud()
		{
			if ((Object)(object)_canvasObject != (Object)null)
			{
				Object.Destroy((Object)(object)_canvasObject);
			}
			_canvasObject = null;
			_panelObject = null;
			_statusText = null;
			_detailText = null;
			_hintText = null;
			_approveText = null;
			_rejectText = null;
		}

		private static void ShowMessage(MessageType location, string message)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message(location, message, 0, (Sprite)null, false);
			}
		}

		private static void ClearLocalState()
		{
			_visible = false;
			HideHud();
			_snapshot = default(RestSnapshot);
			_activeRound = 0;
			_lastCountdown = -1;
		}
	}
	internal struct RestSnapshot
	{
		internal int Round;

		internal int Sleeping;

		internal int Approvals;

		internal int Rejections;

		internal int Pending;

		internal int Connected;

		internal int Required;

		internal int SecondsLeft;

		internal bool ExplicitResponses;

		internal int TotalConnected;

		internal int ExcludedLoading;

		internal int ExcludedIdle;

		internal string Encode()
		{
			return string.Join(",", Round.ToString(), Sleeping.ToString(), Approvals.ToString(), Rejections.ToString(), Pending.ToString(), Connected.ToString(), Required.ToString(), SecondsLeft.ToString(), ExplicitResponses ? "1" : "0", TotalConnected.ToString(), ExcludedLoading.ToString(), ExcludedIdle.ToString());
		}

		internal static bool TryDecode(string data, out RestSnapshot snapshot)
		{
			snapshot = default(RestSnapshot);
			if (data == null || data.Length > 192)
			{
				return false;
			}
			string[] array = data.Split(new char[1] { ',' });
			if (array.Length != 12)
			{
				return false;
			}
			int result = 0;
			bool num = int.TryParse(array[0], out snapshot.Round) && int.TryParse(array[1], out snapshot.Sleeping) && int.TryParse(array[2], out snapshot.Approvals) && int.TryParse(array[3], out snapshot.Rejections) && int.TryParse(array[4], out snapshot.Pending) && int.TryParse(array[5], out snapshot.Connected) && int.TryParse(array[6], out snapshot.Required) && int.TryParse(array[7], out snapshot.SecondsLeft) && int.TryParse(array[8], out result) && int.TryParse(array[9], out snapshot.TotalConnected) && int.TryParse(array[10], out snapshot.ExcludedLoading) && int.TryParse(array[11], out snapshot.ExcludedIdle);
			snapshot.ExplicitResponses = result == 1;
			if (num && snapshot.Round > 0 && snapshot.Connected >= 0 && snapshot.Connected <= 1000 && snapshot.Sleeping >= 0 && snapshot.Sleeping <= snapshot.Connected && snapshot.Approvals >= 0 && snapshot.Rejections >= 0 && snapshot.Pending >= 0 && snapshot.Approvals + snapshot.Rejections + snapshot.Pending <= snapshot.Connected && snapshot.Required >= 1 && snapshot.Required <= 1000 && snapshot.SecondsLeft >= -1 && snapshot.SecondsLeft <= 120 && (result == 0 || result == 1) && snapshot.ExcludedLoading >= 0 && snapshot.ExcludedIdle >= 0 && snapshot.TotalConnected <= 1000)
			{
				return snapshot.TotalConnected == snapshot.Connected + snapshot.ExcludedLoading + snapshot.ExcludedIdle;
			}
			return false;
		}
	}
	internal static class RestTransport
	{
		private sealed class Activity
		{
			internal Vector3 Position;

			internal float ActiveAt;

			internal ZDOID CharacterId;
		}

		internal const string ShowPromptRpc = "SG_ShowRestBallot";

		internal const string SnapshotRpc = "SG_RestBallotState";

		internal const string AnswerRpc = "SG_AnswerRestBallot";

		internal const string CloseRpc = "SG_CloseRestBallot";

		internal const string OutcomeRpc = "SG_RestBallotOutcome";

		internal const string NoticeRpc = "SG_RestBlocked";

		internal const string CountdownRpc = "SG_RestCountdown";

		private const float NoticeRepeatSeconds = 5f;

		private static readonly FieldInfo GameSleepingField = AccessTools.Field(typeof(Game), "m_sleeping");

		private static ZRoutedRpc _registeredInstance;

		private static string _lastNotice;

		private static float _lastNoticeAt = -999f;

		private static readonly Dictionary<long, Activity> ActivityByPlayer = new Dictionary<long, Activity>();

		private static readonly HashSet<long> EligiblePlayers = new HashSet<long>();

		private static readonly VoteRateLimit AnswerLimits = new VoteRateLimit();

		private static float _nextObservation;

		internal static int TotalConnected { get; private set; }

		internal static int ExcludedLoading { get; private set; }

		internal static int ExcludedIdle { get; private set; }

		internal static bool IsServerMessage(long sender)
		{
			if (!Plugin.IsActive)
			{
				return false;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return VotePolicy.TrustedServer(instance.IsServer(), sender, instance.GetServerPeer()?.m_uid ?? 0, ZNet.GetUID());
			}
			return false;
		}

		internal static void Reset()
		{
			ActivityByPlayer.Clear();
			EligiblePlayers.Clear();
			AnswerLimits.Clear();
			_nextObservation = 0f;
			TotalConnected = (ExcludedLoading = (ExcludedIdle = 0));
		}

		internal static void ObservePopulation()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || Time.realtimeSinceStartup < _nextObservation)
			{
				return;
			}
			_nextObservation = Time.realtimeSinceStartup + 1f;
			EligiblePlayers.Clear();
			TotalConnected = (ExcludedLoading = (ExcludedIdle = 0));
			HashSet<long> hashSet = new HashSet<long>();
			foreach (ZNetPeer peer in instance.GetPeers())
			{
				if (peer != null)
				{
					ZDOMan instance2 = ZDOMan.instance;
					ZDO val = ((instance2 != null) ? instance2.GetZDO(peer.m_characterID) : null);
					bool flag = val != null && val.IsValid() && val.GetOwner() == peer.m_uid;
					ObservePlayer(PlayerId(peer), flag ? val : null, hashSet);
				}
			}
			if (!instance.IsDedicated())
			{
				Player localPlayer = Player.m_localPlayer;
				ZNetView val2 = ((localPlayer != null) ? ((Component)localPlayer).GetComponent<ZNetView>() : null);
				ObservePlayer(ZNet.GetUID(), ((Object)(object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO() : null, hashSet);
			}
			List<long> list = new List<long>();
			foreach (long key in ActivityByPlayer.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (long item in list)
			{
				ActivityByPlayer.Remove(item);
				AnswerLimits.Remove(item);
			}
		}

		private static void ObservePlayer(long id, ZDO character, HashSet<long> present)
		{
			//IL_0042: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: 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_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if (id != 0L && present.Add(id))
			{
				TotalConnected++;
				bool flag = character != null;
				bool flag2 = flag && character.GetBool(ZDOVars.s_inBed, false);
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				Vector3 val = (flag ? character.GetPosition() : Vector3.zero);
				ZDOID val2 = (flag ? character.m_uid : ZDOID.None);
				if (!ActivityByPlayer.TryGetValue(id, out var value) || value.CharacterId != val2)
				{
					Dictionary<long, Activity> activityByPlayer = ActivityByPlayer;
					Activity obj = new Activity
					{
						Position = val,
						ActiveAt = realtimeSinceStartup,
						CharacterId = val2
					};
					value = obj;
					activityByPlayer[id] = obj;
				}
				Vector3 val3 = val - value.Position;
				if (((Vector3)(ref val3)).sqrMagnitude >= 0.25f || flag2)
				{
					value.Position = val;
					value.ActiveAt = realtimeSinceStartup;
				}
				if (VotePolicy.Eligible(flag, flag2, Plugin.IncludeLoadingPlayers.Value, realtimeSinceStartup - value.ActiveAt, Plugin.ExcludeIdleAfterSeconds.Value))
				{
					EligiblePlayers.Add(id);
				}
				else if (!flag)
				{
					ExcludedLoading++;
				}
				else
				{
					ExcludedIdle++;
				}
			}
		}

		internal static bool AcceptAnswer(long playerId)
		{
			if (!AnswerLimits.Accept(playerId, Time.realtimeSinceStartup))
			{
				return false;
			}
			if (ActivityByPlayer.TryGetValue(playerId, out var value))
			{
				value.ActiveAt = Time.realtimeSinceStartup;
			}
			return true;
		}

		internal static void RegisterHandlers()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != _registeredInstance)
			{
				_registeredInstance = instance;
				instance.Register<int>("SG_ShowRestBallot", (Action<long, int>)RestPrompt.ReceivePrompt);
				instance.Register<string>("SG_RestBallotState", (Action<long, string>)RestPrompt.ReceiveSnapshot);
				instance.Register<string>("SG_AnswerRestBallot", (Action<long, string>)ReceiveAnswer);
				instance.Register("SG_CloseRestBallot", (Action<long>)RestPrompt.ReceiveClose);
				instance.Register<string>("SG_RestBallotOutcome", (Action<long, string>)RestPrompt.ReceiveOutcome);
				instance.Register<string>("SG_RestBlocked", (Action<long, string>)RestPrompt.ReceiveNotice);
				instance.Register<int>("SG_RestCountdown", (Action<long, int>)RestPrompt.ReceiveCountdown);
				Plugin.Trace("Rest voting RPC handlers registered.");
			}
		}

		internal static HashSet<long> GetConnectedPlayerIds()
		{
			ObservePopulation();
			return new HashSet<long>(EligiblePlayers);
		}

		internal static void SendPrompt(long playerId, int round)
		{
			if (IsLocalPlayer(playerId))
			{
				RestPrompt.ReceivePrompt(0L, round);
				return;
			}
			ZNetPeer val = FindPeerByPlayerId(playerId);
			if (val != null)
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(val.m_uid, "SG_ShowRestBallot", new object[1] { round });
				}
			}
		}

		internal static void ClosePrompt(long playerId)
		{
			if (IsLocalPlayer(playerId))
			{
				RestPrompt.ReceiveClose(0L);
				return;
			}
			ZNetPeer val = FindPeerByPlayerId(playerId);
			if (val != null)
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(val.m_uid, "SG_CloseRestBallot", Array.Empty<object>());
				}
			}
		}

		internal static void SubmitResponse(int round, bool approved)
		{
			if (!Plugin.IsActive)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null)
			{
				return;
			}
			if (instance.IsServer())
			{
				SleepBallot.RecordResponse(ZNet.GetUID(), round, approved);
				return;
			}
			ZNetPeer serverPeer = instance.GetServerPeer();
			if (serverPeer != null)
			{
				string text = $"{round},{(approved ? 1 : 0)}";
				instance2.InvokeRoutedRPC(serverPeer.m_uid, "SG_AnswerRestBallot", new object[1] { text });
			}
		}

		internal static void BroadcastSnapshot(string encodedSnapshot)
		{
			SendStringToRemotePlayers("SG_RestBallotState", encodedSnapshot);
			if (HasLocalPlayer())
			{
				RestPrompt.ReceiveSnapshot(0L, encodedSnapshot);
			}
		}

		internal static void BroadcastCountdown(int seconds)
		{
			SendIntToRemotePlayers("SG_RestCountdown", seconds);
			if (HasLocalPlayer())
			{
				RestPrompt.ReceiveCountdown(0L, seconds);
			}
		}

		internal static void BroadcastClose()
		{
			SendEmptyToRemotePlayers("SG_CloseRestBallot");
			if (HasLocalPlayer())
			{
				RestPrompt.ReceiveClose(0L);
			}
		}

		internal static void BroadcastOutcome(string message)
		{
			SendStringToRemotePlayers("SG_RestBallotOutcome", message);
			if (HasLocalPlayer())
			{
				RestPrompt.ReceiveOutcome(0L, message);
			}
		}

		internal static void BroadcastCombatNotice(string message)
		{
			if (!Plugin.AnnounceCombatBlocks.Value)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!string.Equals(message, _lastNotice, StringComparison.Ordinal) || !(realtimeSinceStartup - _lastNoticeAt < 5f))
			{
				_lastNotice = message;
				_lastNoticeAt = realtimeSinceStartup;
				SendStringToRemotePlayers("SG_RestBlocked", message);
				if (HasLocalPlayer())
				{
					RestPrompt.ReceiveNotice(0L, message);
				}
			}
		}

		internal static void ClearNoticeThrottle()
		{
			_lastNotice = null;
			_lastNoticeAt = -999f;
		}

		internal static void WakeSleepingPlayers()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null)
			{
				instance.InvokeRoutedRPC(0L, "SleepStop", Array.Empty<object>());
			}
		}

		internal static bool BeginSinglePlayerTestSleep()
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			Game instance3 = Game.instance;
			EnvMan instance4 = EnvMan.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || instance.IsDedicated() || instance2 == null || (Object)(object)instance3 == (Object)null || (Object)(object)instance4 == (Object)null || GameSleepingField == null)
			{
				return false;
			}
			try
			{
				instance4.SkipToMorning();
				GameSleepingField.SetValue(instance3, true);
				instance2.InvokeRoutedRPC(0L, "SleepStart", Array.Empty<object>());
				return true;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("Could not start the approved single-player test sleep: " + ex));
				}
				return false;
			}
		}

		private static void ReceiveAnswer(long senderId, string encodedAnswer)
		{
			if (Plugin.IsActive && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				ZNetPeer peer = ZNet.instance.GetPeer(senderId);
				if (peer != null && VotePolicy.TryAnswer(encodedAnswer, out var round, out var approved))
				{
					SleepBallot.RecordResponse(PlayerId(peer), round, approved);
				}
			}
		}

		private static void SendEmptyToRemotePlayers(string rpcName)
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!((Object)(object)instance == (Object)null) && instance2 != null)
			{
				List<ZNetPeer> peers = instance.GetPeers();
				for (int i = 0; i < peers.Count; i++)
				{
					instance2.InvokeRoutedRPC(peers[i].m_uid, rpcName, Array.Empty<object>());
				}
			}
		}

		private static void SendIntToRemotePlayers(string rpcName, int value)
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!((Object)(object)instance == (Object)null) && instance2 != null)
			{
				List<ZNetPeer> peers = instance.GetPeers();
				for (int i = 0; i < peers.Count; i++)
				{
					instance2.InvokeRoutedRPC(peers[i].m_uid, rpcName, new object[1] { value });
				}
			}
		}

		private static void SendStringToRemotePlayers(string rpcName, string value)
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!((Object)(object)instance == (Object)null) && instance2 != null)
			{
				List<ZNetPeer> peers = instance.GetPeers();
				for (int i = 0; i < peers.Count; i++)
				{
					instance2.InvokeRoutedRPC(peers[i].m_uid, rpcName, new object[1] { value });
				}
			}
		}

		private static ZNetPeer FindPeerByPlayerId(long playerId)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			List<ZNetPeer> peers = instance.GetPeers();
			for (int i = 0; i < peers.Count; i++)
			{
				if (PlayerId(peers[i]) == playerId)
				{
					return peers[i];
				}
			}
			return null;
		}

		private static long ResolvePlayerId(long routedId)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return routedId;
			}
			ZNetPeer peer = instance.GetPeer(routedId);
			if (peer != null)
			{
				return PlayerId(peer);
			}
			return routedId;
		}

		private static long PlayerId(ZNetPeer peer)
		{
			if (peer == null)
			{
				return 0L;
			}
			long userID = ((ZDOID)(ref peer.m_characterID)).UserID;
			if (userID != 0L)
			{
				return userID;
			}
			return peer.m_uid;
		}

		private static bool HasLocalPlayer()
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return !ZNet.instance.IsDedicated();
			}
			return false;
		}

		private static bool IsLocalPlayer(long playerId)
		{
			if (HasLocalPlayer())
			{
				return playerId == ZNet.GetUID();
			}
			return false;
		}
	}
	internal enum RestPhase
	{
		Idle,
		Countdown,
		Voting
	}
	internal static class SleepBallot
	{
		private static readonly HashSet<long> Members = new HashSet<long>();

		private static readonly HashSet<long> Sleepers = new HashSet<long>();

		private static readonly HashSet<long> Approvals = new HashSet<long>();

		private static readonly HashSet<long> Rejections = new HashSet<long>();

		private static readonly HashSet<long> Prompted = new HashSet<long>();

		private static RestPhase _phase;

		private static float _phaseBeganAt;

		private static float _nextRequestAt;

		private static int _round;

		private static int _lastCountdown = -1;

		private static string _lastPublishedSnapshot;

		private static bool _explicitResponses;

		private static bool _singlePlayerTestActive;

		private static string _closeReason = "cancelled";

		internal static bool ProcessSleepCheck(ref bool result)
		{
			result = false;
			if (_singlePlayerTestActive)
			{
				TickSinglePlayerTest();
				return false;
			}
			RefreshPopulation();
			RestSafetyObservation restSafetyObservation = CombatMonitor.Observe();
			if (Members.Count == 0 || Sleepers.Count == 0)
			{
				CancelSession(notifyClients: true);
				RestTransport.ClearNoticeThrottle();
				return false;
			}
			if (restSafetyObservation.Safety != RestSafety.Safe)
			{
				if (restSafetyObservation.Safety == RestSafety.BossThreat)
				{
					FailForBossCombat(restSafetyObservation.PlayerName, singlePlayerTest: false);
				}
				else
				{
					PauseForCombat(restSafetyObservation.PlayerName, preserveSinglePlayerTest: false);
				}
				return false;
			}
			RestTransport.ClearNoticeThrottle();
			bool value = Plugin.AlwaysRequireExplicitVote.Value;
			if (_phase != RestPhase.Idle && value != _explicitResponses)
			{
				CancelSession(notifyClients: true);
				return false;
			}
			if (!value && Sleepers.Count == Members.Count)
			{
				_closeReason = "all eligible players sleeping";
				CancelSession(notifyClients: true);
				result = true;
				return false;
			}
			if (Sleepers.Count < Plugin.MinimumSleepers.Value)
			{
				CancelSession(notifyClients: true);
				return false;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextRequestAt)
			{
				return false;
			}
			if (_phase == RestPhase.Idle)
			{
				BeginRequest(realtimeSinceStartup, value);
				if (_phase == RestPhase.Countdown)
				{
					return false;
				}
			}
			if (_phase == RestPhase.Countdown)
			{
				int num = (int)Math.Ceiling((float)Plugin.CountdownSeconds.Value - (realtimeSinceStartup - _phaseBeganAt));
				if (num > 0)
				{
					if (num != _lastCountdown)
					{
						_lastCountdown = num;
						RestTransport.BroadcastCountdown(num);
					}
					return false;
				}
				OpenBallot(realtimeSinceStartup);
			}
			return EvaluateOpenBallot(realtimeSinceStartup, ref result);
		}

		internal static bool TryStartSinglePlayerTest(out string message)
		{
			if (Plugin.Enabled == null || !Plugin.Enabled.Value)
			{
				message = "SleepGuard is disabled in the configuration.";
				return false;
			}
			if (Plugin.AlwaysRequireExplicitVote == null || !Plugin.AlwaysRequireExplicitVote.Value)
			{
				message = "Enable [Debug] AlwaysRequireExplicitVote first, then run sleepguard_test again.";
				return false;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || instance.IsDedicated() || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				message = "This command can only be used after loading a single-player world.";
				return false;
			}
			RefreshPopulation();
			if (Members.Count != 1)
			{
				message = "This debug command is restricted to single-player worlds.";
				return false;
			}
			if (_phase != RestPhase.Idle)
			{
				message = "A rest vote is already active.";
				return false;
			}
			_singlePlayerTestActive = true;
			TickSinglePlayerTest();
			if (!_singlePlayerTestActive)
			{
				message = "Test vote auto-failed because boss combat is active.";
			}
			else if (_phase == RestPhase.Idle)
			{
				message = ((Plugin.CombatQuietSeconds.Value > 0) ? $"Test vote queued. It will remain hidden until combat has been clear for {Plugin.CombatQuietSeconds.Value} second(s)." : "Test vote queued. It will remain hidden until combat ends.");
			}
			else
			{
				message = ((Plugin.CountdownSeconds.Value > 0) ? $"Test vote started. It will open in {Plugin.CountdownSeconds.Value} second(s)." : "Test vote opened.");
			}
			return true;
		}

		internal static void TickSinglePlayerTest()
		{
			if (!_singlePlayerTestActive)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			if (Plugin.Enabled == null || !Plugin.Enabled.Value || Plugin.AlwaysRequireExplicitVote == null || !Plugin.AlwaysRequireExplicitVote.Value || (Object)(object)instance == (Object)null || !instance.IsServer() || instance.IsDedicated() || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				CloseState(notifyClients: true);
				return;
			}
			RefreshPopulation();
			if (Members.Count != 1)
			{
				CloseState(notifyClients: true);
				return;
			}
			RestSafetyObservation restSafetyObservation = CombatMonitor.Observe();
			if (restSafetyObservation.Safety != RestSafety.Safe)
			{
				if (restSafetyObservation.Safety == RestSafety.BossThreat)
				{
					FailForBossCombat(restSafetyObservation.PlayerName, singlePlayerTest: true);
				}
				else
				{
					PauseForCombat(restSafetyObservation.PlayerName, preserveSinglePlayerTest: true);
				}
				return;
			}
			RestTransport.ClearNoticeThrottle();
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (_phase == RestPhase.Idle)
			{
				BeginRequest(realtimeSinceStartup, explicitResponses: true);
			}
			if (_phase == RestPhase.Countdown)
			{
				int num = (int)Math.Ceiling((float)Plugin.CountdownSeconds.Value - (realtimeSinceStartup - _phaseBeganAt));
				if (num > 0)
				{
					if (num != _lastCountdown)
					{
						_lastCountdown = num;
						RestTransport.BroadcastCountdown(num);
					}
					return;
				}
				OpenBallot(realtimeSinceStartup);
			}
			bool result = false;
			EvaluateOpenBallot(realtimeSinceStartup, ref result);
		}

		internal static void RecordResponse(long playerId, int round, bool approved)
		{
			if (_phase == RestPhase.Voting && round == _round && playerId != 0L && Members.Contains(playerId) && (_explicitResponses || !Sleepers.Contains(playerId)) && !(approved ? Approvals.Contains(playerId) : Rejections.Contains(playerId)) && RestTransport.AcceptAnswer(playerId))
			{
				if (approved)
				{
					Approvals.Add(playerId);
					Rejections.Remove(playerId);
				}
				else
				{
					Rejections.Add(playerId);
					Approvals.Remove(playerId);
				}
				Plugin.Trace(string.Format("Round {0}: player {1} responded {2}.", _round, playerId, approved ? "Approve" : "Reject"));
				PublishSnapshot(Time.realtimeSinceStartup, force: true);
			}
		}

		internal static void CancelActiveSession()
		{
			CancelSession(notifyClients: true);
			RestTransport.ClearNoticeThrottle();
		}

		internal static void ResetForWorld()
		{
			CloseState(notifyClients: false);
			_nextRequestAt = 0f;
			_round = 0;
			RestTransport.ClearNoticeThrottle();
			RestTransport.Reset();
			BallotHistory.Reset();
		}

		private static void BeginRequest(float now, bool explicitResponses)
		{
			_explicitResponses = explicitResponses;
			int value = Plugin.CountdownSeconds.Value;
			if (value <= 0)
			{
				OpenBallot(now);
				return;
			}
			_phase = RestPhase.Countdown;
			_phaseBeganAt = now;
			_lastCountdown = value;
			RestTransport.BroadcastCountdown(value);
			Plugin.Trace($"Rest request countdown started for {value} second(s).");
		}

		private static void OpenBallot(float now)
		{
			_phase = RestPhase.Voting;
			_phaseBeganAt = now;
			_round = ((_round == int.MaxValue) ? 1 : (_round + 1));
			_lastPublishedSnapshot = null;
			Approvals.Clear();
			Rejections.Clear();
			Prompted.Clear();
			Plugin.Trace($"Rest vote round {_round} opened.");
		}

		private static void PauseForCombat(string playerName, bool preserveSinglePlayerTest)
		{
			_closeReason = "ordinary combat";
			if (_phase != RestPhase.Idle)
			{
				Plugin.Trace($"Rest request canceled during {_phase} because a player entered combat.");
				CloseState(notifyClients: true, preserveSinglePlayerTest);
			}
			string text = PlayerLabel(playerName);
			string message = ((Plugin.CombatQuietSeconds.Value > 0) ? $"Rest vote failed: {text} is in combat.\n\nWaiting for {Plugin.CombatQuietSeconds.Value} seconds without combat before retrying." : ("Rest vote failed: " + text + " is in combat.\n\nWaiting for combat to end before retrying."));
			RestTransport.BroadcastCombatNotice(message);
			Plugin.Trace(message);
		}

		private static void FailForBossCombat(string playerName, bool singlePlayerTest)
		{
			_closeReason = "boss combat";
			string text = PlayerLabel(playerName);
			string message = "Rest vote failed: " + text + " is fighting a boss.";
			if (_phase != RestPhase.Idle || singlePlayerTest)
			{
				CloseState(notifyClients: true);
			}
			if (!singlePlayerTest)
			{
				RestTransport.WakeSleepingPlayers();
			}
			RestTransport.BroadcastCombatNotice(message);
			Plugin.Trace(message);
		}

		private static string PlayerLabel(string playerName)
		{
			if (!string.IsNullOrWhiteSpace(playerName))
			{
				return playerName;
			}
			return "A player";
		}

		private static bool EvaluateOpenBallot(float now, ref bool result)
		{
			ReconcileResponses();
			int value = Plugin.ResponseSeconds.Value;
			bool flag = value > 0 && now - _phaseBeganAt >= (float)value;
			ApprovalRules.ApproveUnanswered(Members, Sleepers, Approvals, Rejections, flag, _explicitResponses);
			int num = ((!_explicitResponses) ? Sleepers.Count : 0);
			int approvals = num + Approvals.Count;
			int count = Rejections.Count;
			int pending = Math.Max(0, Members.Count - num - Approvals.Count - Rejections.Count);
			PublishSnapshot(now, force: false);
			BallotDecision ballotDecision = (flag ? ApprovalRules.AtDeadline(approvals, count, Plugin.ApprovalPercent.Value) : ApprovalRules.BeforeDeadline(approvals, pending, Members.Count, Plugin.ApprovalPercent.Value));
			if (ballotDecision == BallotDecision.Open)
			{
				PromptOutstandingPlayers();
				return false;
			}
			_nextRequestAt = now + (float)Plugin.RetryDelaySeconds.Value;
			_closeReason = ((ballotDecision == BallotDecision.Approved) ? "approved" : "declined");
			if (ballotDecision == BallotDecision.Approved)
			{
				if (_singlePlayerTestActive)
				{
					bool flag2 = RestTransport.BeginSinglePlayerTestSleep();
					RestTransport.BroadcastOutcome(flag2 ? "Test rest vote approved. Advancing to morning." : "Test rest vote approved, but Valheim could not start sleeping.");
					Plugin.Trace($"Test rest vote round {_round} approved; sleepStarted={flag2}.");
					CloseState(notifyClients: false);
					return false;
				}
				RestTransport.BroadcastOutcome("Rest approved. Advancing to morning.");
				Plugin.Trace($"Rest vote round {_round} approved.");
				CloseState(notifyClients: false);
				result = true;
				return false;
			}
			if (_singlePlayerTestActive)
			{
				RestTransport.BroadcastOutcome("Test rest vote declined.");
				Plugin.Trace($"Test rest vote round {_round} rejected.");
			}
			else
			{
				RestTransport.WakeSleepingPlayers();
				RestTransport.BroadcastOutcome("Rest request declined.");
				Plugin.Trace($"Rest vote round {_round} rejected.");
			}
			CloseState(notifyClients: false);
			return false;
		}

		private static void RefreshPopulation()
		{
			Members.Clear();
			Members.UnionWith(RestTransport.GetConnectedPlayerIds());
			List<ZDO> allCharacterZDOS = ZNet.instance.GetAllCharacterZDOS();
			Sleepers.Clear();
			if (allCharacterZDOS == null)
			{
				return;
			}
			for (int i = 0; i < allCharacterZDOS.Count; i++)
			{
				ZDO val = allCharacterZDOS[i];
				long userID = ((ZDOID)(ref val.m_uid)).UserID;
				if (Members.Contains(userID) && val.GetBool(ZDOVars.s_inBed, false))
				{
					Sleepers.Add(userID);
				}
			}
		}

		private static void ReconcileResponses()
		{
			Approvals.IntersectWith(Members);
			Rejections.IntersectWith(Members);
			Prompted.IntersectWith(Members);
			if (_explicitResponses)
			{
				return;
			}
			foreach (long sleeper in Sleepers)
			{
				Approvals.Remove(sleeper);
				Rejections.Remove(sleeper);
				if (Prompted.Remove(sleeper))
				{
					RestTransport.ClosePrompt(sleeper);
				}
			}
		}

		private static void PromptOutstandingPlayers()
		{
			foreach (long member in Members)
			{
				if ((_explicitResponses || !Sleepers.Contains(member)) && !Approvals.Contains(member) && !Rejections.Contains(member) && !Prompted.Contains(member))
				{
					RestTransport.SendPrompt(member, _round);
					Prompted.Add(member);
				}
			}
		}

		private static void PublishSnapshot(float now, bool force)
		{
			if (_phase == RestPhase.Voting)
			{
				int num = ((!_explicitResponses) ? Sleepers.Count : 0);
				int num2 = num + Approvals.Count;
				int count = Rejections.Count;
				int num3 = Math.Max(0, Members.Count - num - Approvals.Count - Rejections.Count);
				int value = Plugin.ResponseSeconds.Value;
				bool flag = value > 0 && now - _phaseBeganAt >= (float)value;
				int num4 = num2 + count;
				string text = new RestSnapshot
				{
					Round = _round,
					Sleeping = Sleepers.Count,
					Approvals = num2,
					Rejections = count,
					Pending = ((!flag || !_explicitResponses) ? num3 : 0),
					Connected = Members.Count,
					Required = ApprovalRules.RequiredApprovals((flag && _explicitResponses) ? num4 : Members.Count, Plugin.ApprovalPercent.Value),
					SecondsLeft = ((value <= 0) ? (-1) : Math.Max(0, (int)Math.Ceiling((float)value - (now - _phaseBeganAt)))),
					ExplicitResponses = _explicitResponses,
					TotalConnected = RestTransport.TotalConnected,
					ExcludedLoading = RestTransport.ExcludedLoading,
					ExcludedIdle = RestTransport.ExcludedIdle
				}.Encode();
				if (force || !string.Equals(text, _lastPublishedSnapshot, StringComparison.Ordinal))
				{
					_lastPublishedSnapshot = text;
					RestTransport.BroadcastSnapshot(text);
				}
			}
		}

		private static void CancelSession(bool notifyClients)
		{
			if (_phase != RestPhase.Idle)
			{
				Plugin.Trace($"Rest request canceled during {_phase}.");
				CloseState(notifyClients);
			}
		}

		private static void CloseState(bool notifyClients, bool preserveSinglePlayerTest = false)
		{
			if (_phase != RestPhase.Idle)
			{
				BallotHistory.Record(_round, _closeReason, Members.Count, Approvals.Count + ((!_explicitResponses) ? Sleepers.Count : 0), Rejections.Count);
			}
			_closeReason = "cancelled";
			if (notifyClients)
			{
				RestTransport.BroadcastClose();
			}
			_phase = RestPhase.Idle;
			_phaseBeganAt = 0f;
			_lastCountdown = -1;
			_lastPublishedSnapshot = null;
			_explicitResponses = false;
			_singlePlayerTestActive = preserveSinglePlayerTest;
			Approvals.Clear();
			Rejections.Clear();
			Prompted.Clear();
			Members.Clear();
			Sleepers.Clear();
		}
	}
}