Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SleepGuard v0.5.2
SleepGuard.dll
Decompiled 2 hours agousing 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(); } } }