Sono.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Sono;

internal sealed class ServerRules
{
	internal bool Enabled;

	internal bool NightRequired;

	internal bool DelayEnabled;

	internal double DelaySeconds;

	internal bool BedPlayersOnly;

	internal bool PermissionAllowed;

	internal double AfkMinutes;

	internal bool CapSpeed;

	internal double MaxSpeed;

	internal bool TimedEffects;

	internal static ServerRules Defaults()
	{
		ServerRules serverRules = new ServerRules();
		serverRules.Enabled = true;
		serverRules.NightRequired = true;
		serverRules.DelayEnabled = false;
		serverRules.DelaySeconds = 10.0;
		serverRules.BedPlayersOnly = true;
		serverRules.PermissionAllowed = true;
		serverRules.AfkMinutes = 1.0;
		serverRules.CapSpeed = true;
		serverRules.MaxSpeed = 60.0;
		serverRules.TimedEffects = true;
		return serverRules;
	}

	internal string Serialize()
	{
		string[] value = new string[11]
		{
			"1",
			B(Enabled),
			B(NightRequired),
			B(DelayEnabled),
			D(DelaySeconds),
			B(BedPlayersOnly),
			B(PermissionAllowed),
			D(AfkMinutes),
			B(CapSpeed),
			D(MaxSpeed),
			B(TimedEffects)
		};
		return string.Join("|", value);
	}

	internal static bool TryDeserialize(string payload, out ServerRules rules)
	{
		rules = null;
		if (string.IsNullOrEmpty(payload))
		{
			return false;
		}
		string[] array = payload.Split('|');
		if (array.Length != 11 || array[0] != "1")
		{
			return false;
		}
		if (!bool.TryParse(array[1], out var result) || !bool.TryParse(array[2], out var result2) || !bool.TryParse(array[3], out var result3) || !TryDouble(array[4], out var value) || !bool.TryParse(array[5], out var result4) || !bool.TryParse(array[6], out var result5) || !TryDouble(array[7], out var value2) || !bool.TryParse(array[8], out var result6) || !TryDouble(array[9], out var value3) || !bool.TryParse(array[10], out var result7))
		{
			return false;
		}
		rules = new ServerRules
		{
			Enabled = result,
			NightRequired = result2,
			DelayEnabled = result3,
			DelaySeconds = Math.Max(0.0, Math.Min(120.0, value)),
			BedPlayersOnly = result4,
			PermissionAllowed = result5,
			AfkMinutes = Math.Max(0.0, Math.Min(60.0, value2)),
			CapSpeed = result6,
			MaxSpeed = Math.Max(1.0, Math.Min(240.0, value3)),
			TimedEffects = result7
		};
		return true;
	}

	internal static string B(bool value)
	{
		if (!value)
		{
			return "false";
		}
		return "true";
	}

	internal static string D(double value)
	{
		return value.ToString("R", CultureInfo.InvariantCulture);
	}

	internal static bool TryDouble(string text, out double value)
	{
		return double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
	}
}
internal struct SleeperState
{
	internal long Owner;

	internal bool InBed;

	internal string Name;

	internal bool IgnoresSleep;

	internal SleeperState(long owner, bool inBed, string name, bool ignoresSleep)
	{
		Owner = owner;
		InBed = inBed;
		Name = name;
		IgnoresSleep = ignoresSleep;
	}
}
internal static class SleepCore
{
	internal const double VanillaSleepDelay = 10.0;

	internal const int MaxSubsteps = 240;

	internal static bool EveryoneReady(IList<SleeperState> players, bool permissionAllowed, IDictionary<long, bool> permits, List<long> blockers, List<string> sleepers)
	{
		blockers.Clear();
		sleepers.Clear();
		for (int i = 0; i < players.Count; i++)
		{
			SleeperState sleeperState = players[i];
			bool value;
			if (sleeperState.InBed)
			{
				sleepers.Add(sleeperState.Name);
			}
			else if (!sleeperState.IgnoresSleep && (!permissionAllowed || !permits.TryGetValue(sleeperState.Owner, out value) || !value))
			{
				blockers.Add(sleeperState.Owner);
			}
		}
		if (sleepers.Count > 0)
		{
			return blockers.Count == 0;
		}
		return false;
	}

	internal static double CountdownStartValue(double now, ServerRules rules)
	{
		double num = (rules.DelayEnabled ? rules.DelaySeconds : 0.0);
		return now - (10.0 - num);
	}

	internal static bool IsAfk(double now, double lastActivity, double afkMinutes)
	{
		if (afkMinutes > 0.0)
		{
			return now - lastActivity >= afkMinutes * 60.0;
		}
		return false;
	}

	internal static double CappedSkipSpeed(double vanillaSpeed, ServerRules rules)
	{
		if (!rules.Enabled || !rules.CapSpeed)
		{
			return vanillaSpeed;
		}
		return Math.Max(1.0, Math.Min(vanillaSpeed, rules.MaxSpeed));
	}

	internal static float EffectMultiplier(ServerRules rules, bool skipActive, double skipSpeed, double skipEnd, double now)
	{
		if (!rules.Enabled || !rules.TimedEffects || !skipActive || now >= skipEnd)
		{
			return 1f;
		}
		double val = (rules.CapSpeed ? rules.MaxSpeed : skipSpeed);
		return (float)(1.0 + Math.Max(0.0, Math.Min(skipSpeed, val)));
	}

	internal static void RunSubsteps(float dt, float multiplier, Action<float> step)
	{
		float num = dt * multiplier;
		int num2 = Math.Min(240, Math.Max(1, (int)Math.Ceiling(multiplier)));
		float obj = num / (float)num2;
		for (int i = 0; i < num2; i++)
		{
			step(obj);
		}
	}
}
internal sealed class SleepGate
{
	private bool _wasEligible;

	internal void Reset()
	{
		_wasEligible = false;
	}

	internal int Step(bool eligible)
	{
		int result = 0;
		if (eligible && !_wasEligible)
		{
			result = 1;
		}
		else if (!eligible && _wasEligible)
		{
			result = -1;
		}
		_wasEligible = eligible;
		return result;
	}
}
internal sealed class PermissionState
{
	internal bool Manual;

	private double _lastActivity;

	private bool _sent;

	private bool _sentValue;

	internal PermissionState(double now)
	{
		_lastActivity = now;
	}

	internal void Activity(double now)
	{
		_lastActivity = now;
	}

	internal void Forget()
	{
		_sent = false;
	}

	internal bool Effective(ServerRules rules, double now)
	{
		if (rules.Enabled && rules.PermissionAllowed)
		{
			if (!Manual)
			{
				return SleepCore.IsAfk(now, _lastActivity, rules.AfkMinutes);
			}
			return true;
		}
		return false;
	}

	internal bool NeedsSend(ServerRules rules, double now, out bool value)
	{
		value = Effective(rules, now);
		if (_sent && value == _sentValue)
		{
			return false;
		}
		_sent = true;
		_sentValue = value;
		return true;
	}
}
[BepInPlugin("razaotium.sono", "Sono", "1.0.0")]
public sealed class SonoPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(Game), "UpdateSleeping")]
	private static class UpdateSleepingPatch
	{
		[HarmonyPriority(800)]
		private static bool Prefix(Game __instance)
		{
			ServerRules rules = Rules;
			if (!rules.Enabled || SleepingField == null || LastSleepTimeField == null)
			{
				return true;
			}
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)EnvMan.instance == (Object)null)
			{
				return false;
			}
			double timeSeconds = ZNet.instance.GetTimeSeconds();
			if ((bool)SleepingField.GetValue(__instance))
			{
				if (EnvMan.instance.IsTimeSkipping() || CinematicsManager.IsPlaying())
				{
					return false;
				}
				LastSleepTimeField.SetValue(__instance, timeSeconds);
				SleepingField.SetValue(__instance, false);
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "SleepStop", new object[0]);
				return false;
			}
			if (EnvMan.instance.IsTimeSkipping())
			{
				return false;
			}
			string text = WhyNotSleeping(rules);
			bool flag = text.Length == 0;
			if (text != _lastReason)
			{
				if (text.Length > 0 && SleeperNames.Count > 0)
				{
					Log.LogInfo((object)("Not sleeping yet: " + text + "."));
				}
				_lastReason = ((SleeperNames.Count > 0) ? text : "");
			}
			int num = Gate.Step(flag);
			if (num > 0)
			{
				LastSleepTimeField.SetValue(__instance, SleepCore.CountdownStartValue(timeSeconds, rules));
				Log.LogInfo((object)("Everyone is ready: " + SleeperNames.Count + "/" + Players.Count + " in bed."));
			}
			else if (num < 0)
			{
				Log.LogInfo((object)"Sleep cancelled: conditions no longer met.");
			}
			if (!flag)
			{
				return false;
			}
			if (timeSeconds - (double)LastSleepTimeField.GetValue(__instance) < 10.0)
			{
				return false;
			}
			EnvMan.instance.SkipToMorning();
			SleepingField.SetValue(__instance, true);
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "SleepStart", new object[0]);
			return false;
		}
	}

	[HarmonyPatch(typeof(Game), "SleepStart")]
	private static class SleepStartPatch
	{
		[HarmonyPriority(800)]
		private static bool Prefix()
		{
			Gate.Reset();
			ServerRules rules = Rules;
			if (!rules.Enabled || !rules.BedPlayersOnly)
			{
				return true;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).InBed())
			{
				localPlayer.SetSleeping(true);
			}
			return false;
		}
	}

	[HarmonyPatch(typeof(Game), "SleepStop")]
	private static class SleepStopPatch
	{
		[HarmonyPriority(800)]
		private static void Prefix()
		{
			_skipActive = false;
		}
	}

	[HarmonyPatch(typeof(EnvMan), "SkipToMorning")]
	private static class SkipToMorningPatch
	{
		private static void Postfix(EnvMan __instance)
		{
			if (!(TimeSkipSpeedField == null) && !(SkipToTimeField == null))
			{
				double num = (double)TimeSkipSpeedField.GetValue(__instance);
				double num2 = SleepCore.CappedSkipSpeed(num, Rules);
				if (num2 != num)
				{
					TimeSkipSpeedField.SetValue(__instance, num2);
				}
				Log.LogInfo((object)("Skipping to morning at " + num2.ToString("0.##") + "x."));
				_skipActive = true;
				_skipSpeed = num2;
				_skipEnd = (double)SkipToTimeField.GetValue(__instance);
				BroadcastSkipState(0L);
			}
		}
	}

	[HarmonyPatch(typeof(SEMan), "Update")]
	private static class StatusEffectTimePatch
	{
		private static bool _inSubstep;

		private static bool Prefix(SEMan __instance, ZDO zdo, float dt)
		{
			if (_inSubstep)
			{
				return true;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !object.ReferenceEquals(__instance, ((Character)localPlayer).GetSEMan()) || (Object)(object)ZNet.instance == (Object)null)
			{
				return true;
			}
			float num = SleepCore.EffectMultiplier(Rules, _skipActive, _skipSpeed, _skipEnd, ZNet.instance.GetTimeSeconds());
			if (num <= 1f)
			{
				return true;
			}
			_inSubstep = true;
			try
			{
				SleepCore.RunSubsteps(dt, num, delegate(float step)
				{
					__instance.Update(zdo, step);
				});
			}
			finally
			{
				_inSubstep = false;
			}
			return false;
		}
	}

	public const string PluginGuid = "razaotium.sono";

	public const string PluginName = "Sono";

	public const string PluginVersion = "1.0.0";

	private const string PermitRpc = "Sono_SetPermit";

	private const string RulesRequestRpc = "Sono_RequestRules";

	private const string RulesSyncRpc = "Sono_SyncRules";

	private const string SkipStateRpc = "Sono_SkipState";

	private const float RulesRequestInterval = 3f;

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

	private static readonly List<SleeperState> Players = new List<SleeperState>();

	private static readonly List<long> Blockers = new List<long>();

	private static readonly List<string> SleeperNames = new List<string>();

	private static readonly SleepGate Gate = new SleepGate();

	private static readonly int GhostIgnoreSleepHash = "DEV_GhostIgnoreSleep".GetHashCode();

	private static string _lastReason = "";

	private static readonly FieldInfo LastSleepTimeField = AccessTools.Field(typeof(Game), "m_lastSleepTime");

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

	private static readonly FieldInfo TimeSkipSpeedField = AccessTools.Field(typeof(EnvMan), "m_timeSkipSpeed");

	private static readonly FieldInfo SkipToTimeField = AccessTools.Field(typeof(EnvMan), "m_skipToTime");

	private static readonly MethodInfo GetServerPeerIdMethod = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null);

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> ModEnabled;

	internal static ConfigEntry<bool> RequireNight;

	internal static ConfigEntry<bool> UseSleepDelay;

	internal static ConfigEntry<double> SleepDelaySeconds;

	internal static ConfigEntry<bool> OnlyBedPlayersSleep;

	internal static ConfigEntry<bool> AllowPermission;

	internal static ConfigEntry<double> AfkMinutes;

	internal static ConfigEntry<KeyCode> ToggleKey;

	internal static ConfigEntry<bool> CapSkipSpeed;

	internal static ConfigEntry<double> MaximumSkipSpeed;

	internal static ConfigEntry<bool> AccelerateStatusEffects;

	private static readonly ServerRules DefaultRules = ServerRules.Defaults();

	private static ServerRules _rules = ServerRules.Defaults();

	private static bool _receivedServerRules;

	private static bool _skipActive;

	private static double _skipSpeed = 1.0;

	private static double _skipEnd;

	private Harmony _harmony;

	private float _nextRulesRequest;

	private ZRoutedRpc _registeredRpc;

	private FileSystemWatcher _configWatcher;

	private volatile bool _configReloadRequested;

	private PermissionState _permission;

	private Vector3 _lastMousePosition;

	private static ServerRules Rules
	{
		get
		{
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				return _rules;
			}
			if (!_receivedServerRules)
			{
				return DefaultRules;
			}
			return _rules;
		}
	}

	private void Awake()
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfiguration();
		ReadRulesFromConfiguration();
		SubscribeToRuleChanges();
		StartConfigWatcher();
		_permission = new PermissionState(Time.unscaledTime);
		_harmony = new Harmony("razaotium.sono");
		_harmony.PatchAll(typeof(SonoPlugin).Assembly);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Sono 1.0.0 loaded; waiting for the server rules.");
	}

	private void BindConfiguration()
	{
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Expected O, but got Unknown
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0116: Expected O, but got Unknown
		//IL_0195: Unknown result type (might be due to invalid IL or missing references)
		//IL_019f: Expected O, but got Unknown
		ModEnabled = BindServer("1 - General", "Enabled", value: true, "Turns every Sono change on or off. False restores vanilla sleep. Editing the server CFG while it runs applies to everyone immediately.");
		RequireNight = BindServer("2 - Starting Sleep", "RequireNight", value: true, "Only allows sleep after nightfall, when the evening music starts. Vanilla also allows the afternoon.");
		UseSleepDelay = BindServer("2 - Starting Sleep", "UseSleepDelay", value: false, "Waits SleepDelaySeconds before skipping to morning. Off by default: like vanilla, the night skips as soon as everyone is ready.");
		SleepDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<double>("2 - Starting Sleep", "SleepDelaySeconds", 10.0, new ConfigDescription("Seconds between the conditions being met and the skip to morning. Vanilla has no such wait: sleep starts almost as soon as everyone lies down. 0 matches vanilla. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 120.0), new object[0]));
		OnlyBedPlayersSleep = BindServer("3 - Experience", "OnlyPlayersInBedSleep", value: true, "Only players in bed get the sleep screen, text, dream and Rested bonus. False uses vanilla SleepStart for everyone.");
		AllowPermission = BindServer("4 - Permission", "AllowPermission", value: true, "Like vanilla, the night only skips when every player is in bed, but a player can press the key to let the others sleep without them. False requires everyone in bed, exactly like vanilla.");
		AfkMinutes = ((BaseUnityPlugin)this).Config.Bind<double>("4 - Permission", "AfkMinutes", 1.0, new ConfigDescription("A player with no mouse or keyboard input for this many minutes counts as letting the others sleep, until they move again. 0 turns it off. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 60.0), new object[0]));
		ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("4 - Permission", "TogglePermissionKey", (KeyCode)288, "Local client setting, not enforced by the server. Toggles whether the others may sleep without you.");
		CapSkipSpeed = BindServer("5 - Time", "CapSkipSpeed", value: true, "Caps the skip-to-morning speed at MaximumSkipSpeed. Vanilla skips the rest of the night in 12 real seconds at a constant speed and stops at dawn.");
		MaximumSkipSpeed = ((BaseUnityPlugin)this).Config.Bind<double>("5 - Time", "MaximumSkipSpeed", 60.0, new ConfigDescription("Maximum world seconds per real second when CapSkipSpeed=true. Night is 30% of the day, so from nightfall vanilla skips at about 45x with a 30-minute day; only a lower cap makes the skip longer. [SERVER RULE]", (AcceptableValueBase)(object)new AcceptableValueRange<double>(1.0, 240.0), new object[0]));
		AccelerateStatusEffects = BindServer("6 - Consequences", "AccelerateStatusEffects", value: true, "Every player's buffs, debuffs and their ticks run down with the skipped night, asleep or awake. Rested is granted on waking, so it is unaffected. Food is not touched.");
	}

	private ConfigEntry<bool> BindServer(string section, string key, bool value, string description)
	{
		return ((BaseUnityPlugin)this).Config.Bind<bool>(section, key, value, description + " [SERVER RULE]");
	}

	private void SubscribeToRuleChanges()
	{
		((BaseUnityPlugin)this).Config.SettingChanged += OnServerRuleChanged;
		((BaseUnityPlugin)this).Config.ConfigReloaded += OnServerConfigReloaded;
	}

	private void StartConfigWatcher()
	{
		string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
		string directoryName = Path.GetDirectoryName(configFilePath);
		string fileName = Path.GetFileName(configFilePath);
		if (!string.IsNullOrEmpty(directoryName) && !string.IsNullOrEmpty(fileName))
		{
			_configWatcher = new FileSystemWatcher(directoryName, fileName);
			_configWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
			_configWatcher.Changed += OnConfigFileChanged;
			_configWatcher.Created += OnConfigFileChanged;
			_configWatcher.Renamed += OnConfigFileRenamed;
			_configWatcher.EnableRaisingEvents = true;
		}
	}

	private void OnConfigFileChanged(object sender, FileSystemEventArgs args)
	{
		_configReloadRequested = true;
	}

	private void OnConfigFileRenamed(object sender, RenamedEventArgs args)
	{
		_configReloadRequested = true;
	}

	private void OnServerRuleChanged(object sender, SettingChangedEventArgs args)
	{
		ApplyAndBroadcastServerRules();
	}

	private void OnServerConfigReloaded(object sender, EventArgs args)
	{
		ApplyAndBroadcastServerRules();
	}

	private static void ApplyAndBroadcastServerRules()
	{
		if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
		{
			ReadRulesFromConfiguration();
			BroadcastRules();
		}
	}

	private static void ReadRulesFromConfiguration()
	{
		ServerRules serverRules = new ServerRules();
		serverRules.Enabled = ModEnabled.Value;
		serverRules.NightRequired = RequireNight.Value;
		serverRules.DelayEnabled = UseSleepDelay.Value;
		serverRules.DelaySeconds = SleepDelaySeconds.Value;
		serverRules.BedPlayersOnly = OnlyBedPlayersSleep.Value;
		serverRules.PermissionAllowed = AllowPermission.Value;
		serverRules.AfkMinutes = AfkMinutes.Value;
		serverRules.CapSpeed = CapSkipSpeed.Value;
		serverRules.MaxSpeed = MaximumSkipSpeed.Value;
		serverRules.TimedEffects = AccelerateStatusEffects.Value;
		_rules = serverRules;
		_receivedServerRules = true;
	}

	private void OnDestroy()
	{
		if (_configWatcher != null)
		{
			_configWatcher.EnableRaisingEvents = false;
			_configWatcher.Dispose();
			_configWatcher = null;
		}
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
	}

	private void Update()
	{
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		RegisterRpcIfNeeded();
		if (_configReloadRequested)
		{
			_configReloadRequested = false;
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				try
				{
					((BaseUnityPlugin)this).Config.Reload();
					Log.LogInfo((object)"CFG reloaded live; rules sent to clients.");
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("Failed to reload CFG live: " + ex.Message));
				}
			}
		}
		UpdateServerSkipState();
		RequestRulesWhenConnected();
		if (!((Object)(object)Player.m_localPlayer == (Object)null))
		{
			TrackActivity();
			ServerRules rules = Rules;
			if (ToggleKey != null && Input.GetKeyDown(ToggleKey.Value))
			{
				TogglePermission(rules);
			}
			SyncPermission(rules);
		}
	}

	private void TrackActivity()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		Vector3 mousePosition = Input.mousePosition;
		if (!Input.anyKey && !(mousePosition != _lastMousePosition))
		{
			Vector2 mouseScrollDelta = Input.mouseScrollDelta;
			if (!(((Vector2)(ref mouseScrollDelta)).sqrMagnitude > 0f))
			{
				goto IL_0040;
			}
		}
		_permission.Activity(Time.unscaledTime);
		goto IL_0040;
		IL_0040:
		_lastMousePosition = mousePosition;
	}

	private void TogglePermission(ServerRules rules)
	{
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		if (!rules.Enabled || !rules.PermissionAllowed)
		{
			((Character)Player.m_localPlayer).Message((MessageType)2, "The server requires everyone in bed; there is nothing to toggle.", 0, (Sprite)null, false);
			return;
		}
		_permission.Manual = !_permission.Manual;
		string text = (_permission.Manual ? "the others MAY sleep without you" : "the others must WAIT for you");
		((Character)Player.m_localPlayer).Message((MessageType)2, string.Concat("Sleep: ", text, ". Press ", ToggleKey.Value, " to change."), 0, (Sprite)null, false);
	}

	private void SyncPermission(ServerRules rules)
	{
		if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && (ZNet.instance.IsServer() || _receivedServerRules) && _permission.NeedsSend(rules, Time.unscaledTime, out var value))
		{
			if (ZNet.instance.IsServer())
			{
				SetPermitted(ZNet.GetUID(), value);
				return;
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Sono_SetPermit", new object[1] { value });
		}
	}

	private void RegisterRpcIfNeeded()
	{
		ZRoutedRpc instance = ZRoutedRpc.instance;
		if (instance != null && !object.ReferenceEquals(instance, _registeredRpc))
		{
			instance.Register<bool>("Sono_SetPermit", (Action<long, bool>)RpcSetPermit);
			instance.Register("Sono_RequestRules", (Action<long>)RpcRequestRules);
			instance.Register<string>("Sono_SyncRules", (Action<long, string>)RpcReceiveRules);
			instance.Register<string>("Sono_SkipState", (Action<long, string>)RpcReceiveSkipState);
			_registeredRpc = instance;
			_skipActive = false;
			_permission.Forget();
			PermittedByPeer.Clear();
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				ReadRulesFromConfiguration();
				BroadcastRules();
			}
			else
			{
				_receivedServerRules = false;
				_nextRulesRequest = 0f;
			}
		}
	}

	private void RequestRulesWhenConnected()
	{
		if (!_receivedServerRules && ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer())
		{
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer != null && serverPeer.IsReady() && !(Time.unscaledTime < _nextRulesRequest))
			{
				_nextRulesRequest = Time.unscaledTime + 3f;
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Sono_RequestRules", new object[0]);
			}
		}
	}

	private static void UpdateServerSkipState()
	{
		if (_skipActive && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && (!((Object)(object)EnvMan.instance != (Object)null) || !EnvMan.instance.IsTimeSkipping()))
		{
			_skipActive = false;
			BroadcastSkipState(0L);
		}
	}

	private static void RpcSetPermit(long sender, bool permit)
	{
		if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
		{
			SetPermitted(sender, permit);
		}
	}

	private static void SetPermitted(long peerId, bool permit)
	{
		PermittedByPeer[peerId] = permit;
		Log.LogInfo((object)("Sleep permission from " + peerId + ": " + (permit ? "may sleep without them" : "wait for them")));
	}

	private static void RpcRequestRules(long sender)
	{
		if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZRoutedRpc.instance != null)
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "Sono_SyncRules", new object[1] { _rules.Serialize() });
			if (_skipActive)
			{
				BroadcastSkipState(sender);
			}
		}
	}

	private static void BroadcastSkipState(long target)
	{
		if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
		{
			string text = (_skipActive ? "1" : "0") + "|" + ServerRules.D(_skipSpeed) + "|" + ServerRules.D(_skipEnd);
			ZRoutedRpc.instance.InvokeRoutedRPC(target, "Sono_SkipState", new object[1] { text });
		}
	}

	private static bool IsFromServer(long sender)
	{
		long serverPeerId = GetServerPeerId();
		if (serverPeerId != 0)
		{
			return sender == serverPeerId;
		}
		return true;
	}

	private static void RpcReceiveSkipState(long sender, string payload)
	{
		if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && IsFromServer(sender))
		{
			string[] array = (payload ?? "").Split('|');
			if (array.Length != 3 || !ServerRules.TryDouble(array[1], out var value) || !ServerRules.TryDouble(array[2], out var value2))
			{
				Log.LogWarning((object)"Invalid time-skip packet; ignored.");
				return;
			}
			_skipActive = array[0] == "1";
			_skipSpeed = Math.Max(1.0, Math.Min(240.0, value));
			_skipEnd = value2;
		}
	}

	private static void BroadcastRules()
	{
		if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
		{
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Sono_SyncRules", new object[1] { _rules.Serialize() });
		}
	}

	private static void RpcReceiveRules(long sender, string payload)
	{
		if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
		{
			return;
		}
		if (!IsFromServer(sender))
		{
			Log.LogWarning((object)"Rules packet ignored: sender is not the server.");
			return;
		}
		if (!ServerRules.TryDeserialize(payload, out var rules))
		{
			Log.LogWarning((object)"Invalid rules packet from the server; keeping the previous rules.");
			return;
		}
		bool flag = _receivedServerRules && _rules.Enabled != rules.Enabled;
		_rules = rules;
		_receivedServerRules = true;
		if (flag && (Object)(object)Player.m_localPlayer != (Object)null)
		{
			((Character)Player.m_localPlayer).Message((MessageType)2, "Sono " + (rules.Enabled ? "ENABLED" : "DISABLED") + " for the whole server.", 0, (Sprite)null, false);
		}
		Log.LogInfo((object)"Sono rules received from the server.");
	}

	private static long GetServerPeerId()
	{
		if (ZRoutedRpc.instance == null || GetServerPeerIdMethod == null)
		{
			return 0L;
		}
		return (long)GetServerPeerIdMethod.Invoke(ZRoutedRpc.instance, null);
	}

	private static string WhyNotSleeping(ServerRules rules)
	{
		Players.Clear();
		List<ZDO> allCharacterZDOS = ZNet.instance.GetAllCharacterZDOS();
		for (int i = 0; i < allCharacterZDOS.Count; i++)
		{
			ZDO val = allCharacterZDOS[i];
			Players.Add(new SleeperState(val.GetOwner(), val.GetBool(ZDOVars.s_inBed, false), val.GetString(ZDOVars.s_playerName, "?"), val.GetBool(GhostIgnoreSleepHash, false)));
		}
		bool flag = SleepCore.EveryoneReady(Players, rules.PermissionAllowed, PermittedByPeer, Blockers, SleeperNames);
		bool num;
		if (!rules.NightRequired)
		{
			if (EnvMan.IsAfternoon())
			{
				goto IL_00b7;
			}
			num = !EnvMan.IsNight();
		}
		else
		{
			num = !EnvMan.IsNight();
		}
		if (num)
		{
			return "not night yet";
		}
		goto IL_00b7;
		IL_00b7:
		if (!flag)
		{
			if (SleeperNames.Count != 0)
			{
				return Blockers.Count + " player(s) awake without permission";
			}
			return "nobody in bed";
		}
		return "";
	}
}